add external gitlab accounts: stored in bao, linked per agent, allowed in squid, usable with glab
This commit is contained in:
parent
3b8ab17240
commit
d6e983995d
2 changed files with 1322 additions and 51 deletions
|
|
@ -48,6 +48,8 @@ let
|
|||
fetchUnit = machine: machine.systemd.services.hive-agent-forge-token;
|
||||
proxyUnit = machine: machine.systemd.services.hive-agent-proxy-credential;
|
||||
accountsUnit = machine: machine.systemd.services.hive-agent-forge-accounts;
|
||||
gitlabUnit = machine: machine.systemd.services.hive-agent-gitlab-accounts;
|
||||
bootWait = import ./boot-wait.nix { inherit lib; };
|
||||
tokenFile = machine: machine.services.hyperhive.agent.forge.tokenFile;
|
||||
|
||||
proxyEnvEntry = "-/run/hive-agent-proxy-credential/proxy.env";
|
||||
|
|
@ -269,6 +271,108 @@ let
|
|||
name = "the account fetch re-runs every two minutes";
|
||||
ok = agentForgeBao.systemd.timers.hive-agent-forge-accounts.timerConfig.OnUnitInactiveSec == "2min";
|
||||
}
|
||||
{
|
||||
# ../agent-modules/gitlab-accounts.nix. The switch is the store address,
|
||||
# as for the forge accounts; `glab` and the credential helper come with it.
|
||||
name = "an agent with a store address fetches its GitLab accounts and has glab, and one without has neither";
|
||||
ok =
|
||||
let
|
||||
named = n: machine: lib.any (p: (p.name or "") == n) machine.environment.systemPackages;
|
||||
in
|
||||
agentForgeBao.systemd.services ? hive-agent-gitlab-accounts
|
||||
&& agentForgeBao.systemd.timers ? hive-agent-gitlab-accounts
|
||||
&& named "glab" agentForgeBao
|
||||
&& named "git-credential-hive-gitlab" agentForgeBao
|
||||
&& !(agentForgeNoBao.systemd.services ? hive-agent-gitlab-accounts)
|
||||
&& !(agentForgeNoBao.systemd.timers ? hive-agent-gitlab-accounts)
|
||||
&& !(named "glab" agentForgeNoBao)
|
||||
&& !(named "git-credential-hive-gitlab" agentForgeNoBao);
|
||||
}
|
||||
{
|
||||
# The real `glab` binary is only reachable through the wrapper that
|
||||
# points it at the generated configuration, and the nix half of
|
||||
# `swarm_secret_client::gitlab::{accounts_dir,account_path}` reads a
|
||||
# directory of its own, never the forge accounts'.
|
||||
name = "the GitLab fetch lists the agent's own gitlab directory, and glab is only the wrapper";
|
||||
ok =
|
||||
let
|
||||
name = agentForgeBao.services.hyperhive.agent.user.name;
|
||||
s = (gitlabUnit agentForgeBao).script;
|
||||
in
|
||||
lib.hasInfix "bao kv list -format=json secret/swarm/agents/${name}/gitlab >" s
|
||||
&& lib.hasInfix "path=\"secret/swarm/agents/${name}/gitlab/$label\"" s
|
||||
&& !(lib.hasInfix "agents/${name}/forge" s)
|
||||
&& !(lib.elem pkgs.glab agentForgeBao.environment.systemPackages);
|
||||
}
|
||||
{
|
||||
# glab's own multi-host schema: `hosts.<host>.token` beside the global
|
||||
# keys, written to `<state>/glab/config.yml`; and the include that makes
|
||||
# git read the per-host credential blocks.
|
||||
name = "the GitLab fetch writes glab's hosts-keyed config and git's per-host credential blocks";
|
||||
ok =
|
||||
let
|
||||
name = agentForgeBao.services.hyperhive.agent.user.name;
|
||||
s = (gitlabUnit agentForgeBao).script;
|
||||
in
|
||||
# The pattern `swarm_secret_client::gitlab::URL_PATTERN` states, which
|
||||
# the controller applies before it stores an account.
|
||||
lib.hasInfix "test(\"^https?://[A-Za-z0-9.-]+(:[0-9]+)?(/[A-Za-z0-9._~/-]*)?$\")" s
|
||||
&& lib.hasInfix "token: .value, api_protocol: .scheme" s
|
||||
&& lib.hasInfix "hosts: (" s
|
||||
&& lib.hasInfix "git_protocol: \"https\"" s
|
||||
&& lib.hasInfix "/agents/${name}/state/glab/config.yml" s
|
||||
&& lib.hasInfix "/agents/${name}/state/glab/gitconfig" s
|
||||
&& lib.hasInfix "[credential \\\"\\(.scheme)://\\(.host)\\\"]" s
|
||||
&&
|
||||
lib.hasInfix "path = /agents/${name}/state/glab/gitconfig"
|
||||
agentForgeBao.environment.etc."gitconfig".text;
|
||||
}
|
||||
{
|
||||
# glab refuses a config that is not `0600`, and the agent owns its state
|
||||
# dir. The tokens only pass through the unit's own `0700` directory.
|
||||
name = "the GitLab fetch runs as the agent with its own store identity, and keeps tokens out of variables";
|
||||
ok =
|
||||
let
|
||||
u = gitlabUnit agentForgeBao;
|
||||
name = agentForgeBao.services.hyperhive.agent.user.name;
|
||||
in
|
||||
u.serviceConfig.User == name
|
||||
&& u.serviceConfig.UMask == "0077"
|
||||
&& u.serviceConfig.RuntimeDirectoryMode == "0700"
|
||||
&& u.environment.BAO_CLIENT_CERT == "/run/hive-agent-bao-identity/current/cert"
|
||||
&& !(lib.hasInfix "echo \"$(bao" u.script)
|
||||
&& !(lib.hasInfix "token=\"$(" u.script);
|
||||
}
|
||||
{
|
||||
# The boot-wait rule: the fetch states a finite budget below the 90 s
|
||||
# manager default, fails fast, and retries as a fresh start job, so it
|
||||
# never holds a boot open. The control: a limit at the stated budget
|
||||
# does flag it, so the empty answer below is not a walk that misses it.
|
||||
name = "the GitLab fetch states a short budget, retries with a backoff, and stays out of a boot wait over it";
|
||||
ok =
|
||||
let
|
||||
u = gitlabUnit agentForgeBao;
|
||||
reaching =
|
||||
limit:
|
||||
map (r: r.key) (
|
||||
bootWait.overBudget {
|
||||
guest = agentForgeBao;
|
||||
inherit limit;
|
||||
}
|
||||
);
|
||||
in
|
||||
u.serviceConfig.TimeoutStartSec == 60
|
||||
&& u.serviceConfig.Restart == "on-failure"
|
||||
&& u.serviceConfig.RestartSec == 30
|
||||
&& u.startLimitBurst == 2880
|
||||
&& !(lib.elem "hive-agent-gitlab-accounts.service" (reaching 61))
|
||||
&& lib.elem "hive-agent-gitlab-accounts.service" (reaching 60);
|
||||
}
|
||||
{
|
||||
name = "the GitLab fetch re-runs every two minutes";
|
||||
ok =
|
||||
agentForgeBao.systemd.timers.hive-agent-gitlab-accounts.timerConfig.OnUnitInactiveSec == "2min";
|
||||
}
|
||||
{
|
||||
# ../agent-modules/egress-proxy.nix. Presence arm: the unit and its timer
|
||||
# exist, and the agent is told nothing until a credential is fetched.
|
||||
|
|
|
|||
|
|
@ -2379,15 +2379,15 @@ let
|
|||
&& !(lib.hasInfix "secret/metadata/*" s);
|
||||
}
|
||||
{
|
||||
# `linked_accounts` names an agent's matrix accounts and forge labels by
|
||||
# listing their two directories. bao checks a LIST against the directory
|
||||
# `linked_accounts` names an agent's matrix accounts, forge labels and GitLab labels by
|
||||
# listing their three directories. bao checks a LIST against the directory
|
||||
# with a trailing slash, so each stanza names that slashed path and
|
||||
# reaches that one directory: not the agent's other keys, not `agents/`
|
||||
# itself, not anything below. Pinned as whole
|
||||
# stanzas, and as the only metadata stanzas under `agents/` beside the
|
||||
# queue revocation and the account deletes, so a widened path or an added
|
||||
# capability fails.
|
||||
name = "the controller may list each agent's matrix and forge accounts, and nothing else under agents";
|
||||
name = "the controller may list each agent's matrix, forge and gitlab accounts, and nothing else under agents";
|
||||
ok =
|
||||
let
|
||||
s = baoGrantHere.systemd.services.swarm-bao-controller-policy.script;
|
||||
|
|
@ -2395,11 +2395,12 @@ let
|
|||
in
|
||||
lib.hasInfix "path \"secret/metadata/swarm/agents/+/matrix/\" {\n capabilities = [\"list\"]\n}" s
|
||||
&& lib.hasInfix "path \"secret/metadata/swarm/agents/+/forge/\" {\n capabilities = [\"list\"]\n}" s
|
||||
&& stanzas == 7;
|
||||
&& lib.hasInfix "path \"secret/metadata/swarm/agents/+/gitlab/\" {\n capabilities = [\"list\"]\n}" s
|
||||
&& stanzas == 9;
|
||||
}
|
||||
{
|
||||
# `linked_accounts` deletes one matrix account, forge account, GitHub
|
||||
# token or backend environment through `delete_all_versions`, which
|
||||
# `linked_accounts` deletes one matrix account, forge account, GitLab
|
||||
# account, GitHub token or backend environment through `delete_all_versions`, which
|
||||
# addresses its metadata path.
|
||||
# A trailing `+` is one segment, so each stanza reaches the accounts
|
||||
# directly in that directory and nothing deeper. Pinned as whole stanzas,
|
||||
|
|
@ -2411,6 +2412,7 @@ let
|
|||
in
|
||||
lib.hasInfix "path \"secret/metadata/swarm/agents/+/matrix/+\" {\n capabilities = [\"delete\"]\n}" s
|
||||
&& lib.hasInfix "path \"secret/metadata/swarm/agents/+/forge/+\" {\n capabilities = [\"delete\"]\n}" s
|
||||
&& lib.hasInfix "path \"secret/metadata/swarm/agents/+/gitlab/+\" {\n capabilities = [\"delete\"]\n}" s
|
||||
&& lib.hasInfix "path \"secret/metadata/swarm/agents/+/github-token\" {\n capabilities = [\"delete\"]\n}" s
|
||||
&& lib.hasInfix "path \"secret/metadata/swarm/agents/+/backend-env\" {\n capabilities = [\"delete\"]\n}" s;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue