The minter parses `Request.agent` with `Ident::parse`, as the controller does, before it makes a scratch directory, runs `claude auth login` or touches the store, and answers `Failed` otherwise. The name becomes a directory name and a store path in the one service that writes every agent's credentials, so it is checked at this service's own boundary. The doc states that the `operatorsOnly` authelia rule, not the controller, keeps the routes to operators, and that a stale version record waits for a relogin. The env-strip comment says it is defence in depth. Refs #5268
82 lines
4.4 KiB
Markdown
82 lines
4.4 KiB
Markdown
# Claude login from the swarm UI
|
|
|
|
An operator logs an agent in to claude from the agent's card in the swarm UI.
|
|
No hive is in the path: the login runs in the swarm's `swarm-claude-login`
|
|
service, the credentials go to the agent's own leaf in the secret store, and the
|
|
agent's harness installs them from there.
|
|
|
|
Where it runs and how it's enabled: [`services.md`](services.md#claude-login-minter).
|
|
|
|
## What happens on a login
|
|
|
|
1. The agent publishes `needs_login` in its turn-state header, and its card
|
|
shows **login needed** with a **log in** button.
|
|
2. **log in** opens a dialog, which calls
|
|
`POST /api/agents/<agent>/claude-login`. swarm-controller relays the start as
|
|
a request on the queue subject `$SWARM.claude-login.request`.
|
|
3. The minter runs `claude auth login` with `HOME` and `CLAUDE_CONFIG_DIR` in a
|
|
fresh scratch directory, over plain stdio pipes, and answers with the first
|
|
`https://` URL it prints. The dialog shows that URL.
|
|
4. The operator opens the URL, signs in to the agent's own claude account, and
|
|
pastes the code the page shows into the dialog, which posts it to
|
|
`POST /api/agents/<agent>/claude-login/code`.
|
|
5. The minter writes the code to claude's stdin and waits for it to exit. It
|
|
stores the `.credentials.json` claude wrote at
|
|
`swarm/agents/<agent>/claude-credentials` (field `value`), as a new version
|
|
over any stored before, and removes the scratch directory.
|
|
6. The agent's harness, while it waits for a login, reads that leaf every 30
|
|
seconds under the agent's own certificate. When the stored version is newer
|
|
than the one it installed last, it writes the file to
|
|
`~/.claude/.credentials.json`, records the version, and resumes its turn
|
|
loop as it does after a login from the agent's web UI.
|
|
|
|
A started login waits 15 minutes for its code, then the minter stops it.
|
|
Starting another login for the same agent replaces the running one. Each step
|
|
has a minute to finish; a refused code fails the login, and the dialog shows
|
|
what claude printed.
|
|
|
|
## Why the harness takes only a newer version
|
|
|
|
`claude` rotates the refresh token in `.credentials.json` as it uses the
|
|
session, and the agent can't write the store. After the first refresh the
|
|
stored copy is stale. A harness that installed it whenever the bytes differed
|
|
would replace the agent's live session with a stale copy and log the agent
|
|
out. The harness therefore compares the store's KV version with the last
|
|
version it installed (`claude-credentials-version` under the harness dir):
|
|
|
|
- it installs a newer version, that is, a new login;
|
|
- it leaves an equal or older version alone, even when the agent needs a
|
|
login again, so it never puts back a session that has gone bad;
|
|
- it reads nothing while the agent has a session.
|
|
|
|
## Credentials
|
|
|
|
| Principal | Grant |
|
|
| -------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
| `swarm-claude-login`, in its container | `create`, `update` on `swarm/agents/+/claude-credentials`; `read` on `swarm/services/<minter client id>/oidc/client` |
|
|
| the agent, under its own certificate | `read` on its own `swarm/agents/<agent>/*`, which covers the leaf |
|
|
|
|
The minter has no read on the leaf and nothing else under an agent. Every
|
|
hive's policy reads `swarm/agents/*`, so every hive can read every agent's
|
|
claude session, as it can read the GitHub token.
|
|
|
|
The `operatorsOnly` authelia rule on the swarm domain, not the controller,
|
|
keeps these routes to operators: the controller checks only that the gateway
|
|
named a user. Without that rule any account authelia admits could store its
|
|
own claude session as any agent's, so no agent account may exist before the
|
|
rule reaches the swarm.
|
|
|
|
The minter refuses a step whose agent name isn't an identifier before it
|
|
makes a directory or runs `claude`.
|
|
|
|
## Caveat
|
|
|
|
The installed-version record lives under the harness dir and the credentials
|
|
file under `~/.claude`. If only the record survives a rebuild, the agent
|
|
ignores the stored version until the operator logs it in again, which writes a
|
|
newer one.
|
|
|
|
## What stays
|
|
|
|
The agent's own web UI login (`/login`) still works, unchanged. It goes away
|
|
once the operator has confirmed the swarm login works.
|