A new swarm service, `swarm-claude-login`, in its own container on the host
that sets `deploy.claude-login.enable` (defaulted on by `allSwarmServices`
where the host has the minter's store leaf, like the review classifier).
- swarm-ui: an agent card showing "login needed" gets a "log in" button.
Its dialog starts the login, shows the URL claude printed, and takes the
pasted code.
- swarm-controller: `POST/DELETE /api/agents/{name}/claude-login` and
`POST /api/agents/{name}/claude-login/code`, behind the swarm UI's
authelia-gated `/api/`, relay the steps to the minter as requests on
`$SWARM.claude-login.request`.
- swarm-claude-login: runs `claude auth login` over piped stdio with HOME
and CLAUDE_CONFIG_DIR in a per-login scratch dir, scrapes the first
https URL, writes the code to stdin, and stores the `.credentials.json`
at `swarm/agents/<agent>/claude-credentials` with a KV v2 write, a new
version over any earlier one. The scratch dir goes on every exit path; a
login waits 15 minutes for its code.
- hive-agent: while parked in `wait_for_login`, reads that leaf every 30 s
under the agent's own cert and installs it only when its KV version is
newer than the one recorded in `<harness>/claude-credentials-version`.
An equal or older version is never put back, because claude rotates the
refresh token and the stored copy is stale after the first refresh.
- swarm-secret-client: `claude_login` paths and `SecretStore::read_versioned`.
- Credentials: a `claude-login` store leaf minted by glue-bao-tls, a
`swarm-claude-login` policy (create/update on
`swarm/agents/+/claude-credentials`, read on its own queue client
secret), an authelia machine client, and a `--claude-login-client`
grant in the queue's auth-callout responder. The unit runs as a static
system user.
The hive's own web-UI login is unchanged.
Refs #5268
`write_policy` called `vaultrs::sys::policy::set`, which targets
`sys/policy/<name>` — the deprecated alias the store gates as a path of
its own. Every grant in this tree names `sys/policies/acl/hive-*`, so a
correctly-scoped controller was refused with a 403 and no hive read
policy has ever been written, on any deployment. The doc comment three
lines above the call already named the modern path; the code is what
moves to meet it.
vaultrs has no endpoint for that path (`grep policies/acl` over 0.8.0:
zero hits, against 8 for `sys/policy`), so this defines one over its own
endpoint machinery — which keeps the client's token header and `/v1`
prefix middleware rather than re-deriving them.
The alternative was to widen the grant to cover the legacy path. This
way needs no policy change at all: the deployed grant already permits
the write, so no store is re-bootstrapped and nothing is entrenched on
an alias upstream has deprecated.
Two tests pin the path and the body shape. The reason this survived
deployment is that nothing in the tree ever asserted either one.
Closes#4177.
mara ruled (a) on #3726: a thin workspace crate over `vaultrs` rather than
keeping bao access in nix and having each end trigger units. The HTTP is the
SDK's job; what this crate owns is the things the controller and a hive must
say *identically*, and which have no other home because neither end is senior
to the other.
Three such agreements:
`path::matrix_account` builds where a credential lives. It is fallible rather
than a `format!`, because both names reach it from elsewhere -- the agent name
from the topology, the account name from an agent's own config -- and a `/` or
`..` in either does not produce a malformed path, it produces a valid path to
a *different agent's* secret. The charset mirrors the KV bucket-name rule.
`Credential`'s `value` field is not a free choice: glue-matrix-bao-token.nix
reads the store with `bao kv get -field=value`, so the name is load-bearing
for a consumer no Rust test can reach. A test pins the serialised shape.
`client::Settings` reads BAO_ADDR / BAO_CLIENT_CERT / BAO_CLIENT_KEY /
BAO_CACERT explicitly instead of letting vaultrs fall through to its own
defaults, which look for VAULT_ADDR / VAULT_CLIENT_CERT / VAULT_CLIENT_KEY.
Every unit in this tree sets the BAO_ spellings, so the defaults would yield a
client with no identity at all -- surfacing as a TLS handshake failure, which
names neither the missing variable nor the reason.
The env read is split from the connect so every misconfiguration arm is
testable without a reachable store and without touching process-global env.
Dependency impact, measured against the lock at forge/main rather than assumed:
native-tls 0 -> 0, openssl-sys 0 -> 0, one reqwest (0.13.4) which vaultrs
shares, and 10 new crates that are all derive/proc-macro helpers.
Refs #3726