defmodule GitGudWeb.RepoControlsAuditTest do
@moduledoc """
A repository's controls leave audit entries: branch protection, deploy
keys, webhooks (the repository's and the org's), registry tokens and
job-token grants. Never a key body, a token, a webhook secret or the
path of a webhook URL.
"""
use GitGudWeb.ConnCase, async: true
import Ecto.Query
import Phoenix.LiveViewTest
import GitGud.ForgeFixtures
alias GitGud.Audit.Entry
alias GitGud.Organizations
alias GitGud.Repositories.Storage
@key "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDIvCK0qd2v15O96oXcAYO3FpyDC2OYa3LxOoehl0HUx ci"
setup %{conn: conn} do
{owner, repo} = repository_fixture()
repo = GitGud.Repo.preload(repo, [:owner, :organization])
{:ok,
owner: owner,
repo: repo,
conn: log_in_user(conn, owner),
base: "/r/#{Storage.repo_handle(repo)}/#{repo.name}/settings"}
end
defp entries(repo),
do: GitGud.Repo.all(from e in Entry, where: e.repository_id == ^repo.id, order_by: e.id)
defp stored, do: GitGud.Repo.all(Entry) |> Enum.map(&Map.from_struct/1) |> inspect()
test "branch protection", %{conn: conn, repo: repo, base: base} do
{:ok, lv, _} = live(conn, base <> "/branches")
lv
|> form("#bp-form", %{
"branch_protection" => %{"pattern" => "release/*", "required_approvals" => "2"}
})
|> render_submit()
[rule] = GitGud.BranchProtections.list_rules(repo)
lv |> element("#delete-rule-#{rule.id}") |> render_click()
assert [added, deleted] = entries(repo)
assert {added.action, added.target_label} == {"branch_protection.created", "release/*"}
assert added.metadata["required_approvals"] == 2
assert {deleted.action, deleted.target_label} == {"branch_protection.deleted", "release/*"}
end
test "deploy keys, by title and fingerprint", %{conn: conn, repo: repo, base: base} do
{:ok, lv, _} = live(conn, base <> "/deploy-keys")
lv
|> form("#deploy-key-form", %{
"deploy_key" => %{"title" => "ci", "key" => @key, "read_write" => "true"}
})
|> render_submit()
[key] = GitGud.DeployKeys.list_keys(repo)
lv |> element("#delete-deploy-key-#{key.id}") |> render_click()
assert [added, removed] = entries(repo)
assert added.action == "deploy_key.added"
assert added.metadata == %{"fingerprint" => key.fingerprint, "read_write" => true}
assert {removed.action, removed.target_label} == {"deploy_key.removed", "ci"}
refute stored() =~ "AAAAC3NzaC1lZDI1NTE5AAAAIDIvCK0q"
end
describe "webhooks" do
test "a repository's: the host, never the path or the secret", %{
conn: conn,
repo: repo,
base: base
} do
{:ok, lv, _} = live(conn, base <> "/webhooks")
lv
|> form("#webhook-form", %{
"webhook" => %{"url" => "https://hooks.example/T000/B000/XYZTOKEN"}
})
|> render_submit()
[hook] = GitGud.Webhooks.list_webhooks(repo)
lv |> element("#toggle-webhook-#{hook.id}") |> render_click()
lv |> element("#toggle-webhook-#{hook.id}") |> render_click()
lv |> element("#delete-webhook-#{hook.id}") |> render_click()
assert ~w(webhook.created webhook.disabled webhook.enabled webhook.deleted) ==
Enum.map(entries(repo), & &1.action)
assert Enum.all?(entries(repo), &(&1.target_label == "hooks.example"))
refute stored() =~ "XYZTOKEN"
refute stored() =~ Base.encode64(hook.secret)
end
test "an org's", %{conn: conn, owner: owner} do
handle = "wh#{System.unique_integer([:positive])}"
{:ok, org} =
Organizations.create_organization(owner, %{"handle" => handle, "name" => handle})
{:ok, lv, _} = live(conn, ~p"/orgs/#{handle}/settings/webhooks")
lv
|> form("#webhook-form", %{"webhook" => %{"url" => "https://ci.example/hook/abc"}})
|> render_submit()
assert [%Entry{action: "webhook.created", organization_id: oid, target_label: host}] =
GitGud.Repo.all(from e in Entry, where: e.organization_id == ^org.id)
assert {oid, host} == {org.id, "ci.example"}
end
end
test "registry tokens, never the token", %{conn: conn, repo: repo, base: base} do
{:ok, lv, _} = live(conn, base <> "/registry-tokens")
html =
lv
|> form("form[phx-submit=create_token]", %{"name" => "argo-pull", "scopes" => ["pull"]})
|> render_submit()
[plaintext] = Regex.run(~r/ggrt_[A-Za-z0-9_-]+/, html)
[token] = GitGud.Packages.list_registry_tokens(repo)
render_click(lv, "delete_token", %{"id" => to_string(token.id)})
# Already gone: nothing more to record.
render_click(lv, "delete_token", %{"id" => to_string(token.id)})
assert [created, revoked] = entries(repo)
assert {created.target_label, created.metadata} == {"argo-pull", %{"scopes" => ["pull"]}}
assert revoked.action == "registry_token.revoked"
refute stored() =~ plaintext
end
test "job-token grants", %{conn: conn, repo: repo, base: base} do
{_, source} = repository_fixture()
source = GitGud.Repo.preload(source, [:owner, :organization])
label = "#{Storage.repo_handle(source)}/#{source.name}"
{:ok, lv, _} = live(conn, base <> "/job-tokens")
lv
|> form("form[phx-submit=create_grant]", %{"source" => label, "read" => "true"})
|> render_submit()
[grant] = GitGud.Workflows.JobTokenGrants.list(repo)
lv |> element("button[phx-value-id='#{grant.id}']") |> render_click()
assert [created, deleted] = entries(repo)
assert created.target_label == label
assert created.metadata == %{"read" => true, "dispatch" => false}
assert {deleted.action, deleted.target_label} == {"job_token_grant.deleted", label}
end
end
neiam
/gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
1
Log in to mark this repository.
5.6 KiB · text
History
b868907