- table cells: cut to what the credential is, where it comes from and what the operator does - prose sections: cut the matrix-prefix, appservice-token and leaf-delivery sections; trimmed revocation and retry notes to their operator-visible effect - progressive enhancement section: removed (contributor rules) - intro: cut to the store, the on-disk certificate and the column meanings - docs index: drop the "progressive-enhancement rule" clause from the credentials.md entry in docs/README.md - matrix rows: one sentence saying the name `main` is reserved
96 lines
19 KiB
Markdown
96 lines
19 KiB
Markdown
# Credentials: the target shape
|
|
|
|
<!-- vale write-good.Passive = NO -->
|
|
|
|
The swarm's credential store is bao. This page lists each credential: who
|
|
mints it, who reads it, and how it renews. It describes the target, not what's
|
|
on disk today; [`secrets.md`](secrets.md) maps the files that exist right now.
|
|
|
|
<!-- vale write-good.Passive = YES -->
|
|
|
|
**Store secrets stay off disk.** A process pulls a value from the store at
|
|
runtime and holds it in memory only.
|
|
|
|
**The mTLS client certificate is the only credential on disk.** Each
|
|
identity (an agent, a hive, a swarm-level service) has one. It's what logs a
|
|
principal in to the store, so you place a hive's or service's certificate by
|
|
hand. The nix option for it takes a path to the certificate, never its bytes,
|
|
because a literal in a nix expression lands in the world-readable nix store.
|
|
A hive hands each agent its certificate; the agent pulls everything else
|
|
itself.
|
|
|
|
In the table, **automatic re-mint** is something replacing the stored value
|
|
without an operator. **Automatic re-pull** is the reader picking up a replaced
|
|
value without a restart. ✅ means it happens today, ❌ means it doesn't, and
|
|
the rest of the cell says how.
|
|
|
|
<!-- vale write-good.Passive = NO -->
|
|
|
|
| store path | minter | reader | automatic re-mint | automatic re-pull |
|
|
| ----------------------------------------------------------- | ------------------------------------------------------------------------------------ | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
| `swarm/agents/<agent>/matrix/main` | `swarm-controller`, at agent creation and every 5 minutes | the agent | ✅ when missing or rejected by the homeserver | ✅ the daemon restarts within 5 minutes of a rejection |
|
|
| `swarm/agents/<agent>/matrix/<account>` | `swarm-controller`, from a credential you hand it | the agent | must be stated | ✅ retried within 2 to 16 minutes after a rejection |
|
|
| `swarm/agents/<agent>/matrix-recovery/<account>` | `swarm-controller`, a random passphrase per account | the agent | ❌ never replaced: the account's key backup opens only with this passphrase | ❌ read when the account comes up |
|
|
| `swarm/controller/swarm-controller/matrix/appservice-token` | `swarm-matrix-ctl` in `hive-matrix`, once | `swarm-controller` | ❌ minted once | ✅ read every 5 minutes |
|
|
| `swarm/controller/swarm-controller/oidc/client` | authelia, first boot | `swarm-controller` | ❌ minted once; `swarm-secret-publish` republishes a re-mint | ✅ re-read when authelia refuses it |
|
|
| `swarm/controller/swarm-controller/forge/token` | the forge, once | `swarm-controller` | ❌ minted once; delete `/var/lib/forgejo/swarm-controller-token-name` in the forge container to mint a new one | ✅ re-read on a 401 |
|
|
| `swarm/controller/swarm-controller/grafana/token` | `swarm-bao-grafana-controller-token` on Grafana's host | `swarm-controller` | ✅ every 10 minutes, when Grafana refuses it or none is stored | ✅ re-read on a 401 |
|
|
| `swarm/agents/<agent>/bao-mtls` | the store's agent PKI mount (`deploy.bao.agentPkiMountPath`), at agent creation | `hive-c0re`, then the agent | ✅ re-issued every 5 minutes once past half its 90-day validity | ✅ the agent re-reads every 10 minutes; it renews only while its current leaf is valid |
|
|
| `swarm/agents/<agent>/queue` | `swarm-controller`, at agent creation | the agent; the queue, log and metrics auth services | ✅ re-minted at 45 days; skipped for `Destroyed` agents, whose path is deleted | ✅ read on every reconnect; an open connection keeps the old secret for up to an hour; the log and metrics stores honour a replaced secret for up to `deploy.victorialogs.authVerdictTtl` and `deploy.victoriametrics.agentAuthVerdictTtl` (60 s by default) |
|
|
| `swarm/agents/<agent>/forge-token` | `swarm-controller`, at agent creation and every 5 minutes | the agent | ✅ when missing or no longer matching the forge | ✅ every 10 minutes |
|
|
| `swarm/agents/<agent>/forge/<label>` | `swarm-controller`, when you link an external forge account in the swarm UI | the agent | ❌ your token; delete it and link the label again | ✅ every 2 minutes |
|
|
| `swarm/agents/<agent>/github-token` | `swarm-controller`, when you link a GitHub account in the swarm UI | the agent | ❌ your token; delete it and link again | ✅ every 2 minutes |
|
|
| `swarm/agents/<agent>/claude-credentials` | `swarm-claude-login`, when you log the agent in from its card; one version per login | the agent, while it waits for a login | ❌ log in again | ✅ every 30 seconds while waiting |
|
|
| `swarm/hives/<hive>/matrix/sender-token` | `swarm-controller`, every 5 minutes | `swarm-controller` and the hive | ✅ when missing or rejected by the homeserver | ✅ the hive re-reads on every sweep |
|
|
| `swarm/hives/<hive>/forge/token` | `swarm-controller`, every 5 minutes | `swarm-controller` and the hive | ✅ when missing or no longer matching the forge | ✅ read on every forge call |
|
|
| `swarm/hives/<hive>/queue/agent` | authelia | `swarm-bao-queue-agent` on the hive's host | must be stated | must be stated |
|
|
| `swarm/hives/<hive>/oidc/client` | authelia, first boot; `swarm-secret-publish` copies it in | the hive | ❌ minted once; `swarm-secret-publish` republishes a re-mint | ✅ re-read when authelia refuses it |
|
|
| `swarm/services/<clientId>/oidc/client` | authelia | that service | must be stated | must be stated |
|
|
| `swarm/services/ci-runner/registration-token` | `swarm-controller`, from the forge, at start and every 5 minutes | `swarm-bao-ci-runner` on the runner's host | ❌ the forge keeps one token until an admin resets it; the controller then writes the new one within 5 minutes | ✅ every 10 minutes; restarts the runner on a change |
|
|
| `swarm/services/<grafana client id>/metrics/bearer` | `swarm-bao-grafana-metrics-bearer` on Grafana's host, when none is stored | `swarm-metrics-auth` and `swarm-logs-grafana-auth` | ❌ delete it and restart `swarm-bao-grafana-metrics-bearer` | ✅ the auth services re-read within a minute; ❌ Grafana restarts when its copy changes |
|
|
| `swarm/services/review-classifier/anthropic-api-key` | you, in the store's UI, field `api_key` | `swarm-review-classifier` | ❌ write a new key | ✅ read on every run |
|
|
| `swarm/services/review-classifier/acp-provider` | you, in the store's UI, field `api_key` | `swarm-review-classifier` | ❌ write a new key | ✅ read on every run |
|
|
| `swarm/services/review-classifier/forge/token` | `swarm-controller`, at start and every 5 minutes | `swarm-controller` | ✅ when missing or no longer matching the forge | ✅ read on every review |
|
|
| `swarm/services/builder/forge/token` | `swarm-controller`, at start and every 5 minutes | `swarm-builder`, `swarm-controller` | ✅ when missing or no longer matching the forge | ✅ read on every build |
|
|
| `swarm/services/ci-metrics/forge/token` | `swarm-controller`, at start and every 5 minutes | `swarm-ci-metrics`, `swarm-controller` | ✅ when missing or no longer matching the forge | ✅ read on every pass |
|
|
| _(not in the store)_ a hive's mTLS leaf | the store's PKI, or you by hand | the hive itself, from disk | must be stated | must be stated |
|
|
|
|
<!-- vale write-good.Passive = YES -->
|
|
|
|
The name `main` belongs to the swarm's own homeserver account, which the
|
|
controller mints, so the controller rejects any account you link under it.
|
|
|
|
`swarm-controller` checks every forge token itself: it looks at every agent
|
|
that has a store identity at start and every five minutes. For any whose
|
|
stored token is missing or stale it creates the forge user if there isn't one,
|
|
then mints the token. To check one agent now:
|
|
|
|
```sh
|
|
swarmctl agent mint-forge-token <agent>
|
|
```
|
|
|
|
An agent without a store identity gets no swarm token and keeps using the
|
|
`forge-token` file in its state dir, if it has one; re-create it with
|
|
`swarmctl agent create` to give it one. The swarm leaves an agent it already
|
|
declares as it is. For an agent with no declaration, such as one only its hive
|
|
knows about, `agent create` declares it `paused`; set it `up` from its card in
|
|
the swarm UI.
|
|
|
|
⚠️ **Re-create existing agents before deploying a hive-side change that makes a
|
|
container require a credential it may not have.** A container whose store
|
|
identity is absent doesn't start, so create the identities first, setting any
|
|
newly paused agent `up` as above.
|
|
|
|
**Revoking an agent's queue credential.** Declaring an agent `destroyed` —
|
|
`PUT /api/hives/{hive}/agents/{agent}/state` — deletes
|
|
`swarm/agents/<agent>/queue` and every version it held. An open queue
|
|
connection closes within an hour. An agent recreated under the same name gets a
|
|
fresh secret. It leaves the agent's mTLS leaf alone. If the store refuses the
|
|
delete, `swarm-controller` logs an `error` naming the agent and the teardown
|
|
continues; declare the agent destroyed again to re-run the revocation.
|
|
|
|
A failed store login retries every 30 seconds for a day. When a later attempt
|
|
lands a changed value, the service that loaded it restarts. If the fetch unit
|
|
can't compare file times it logs `cannot compare the mtime of <path> … —
|
|
leaving <unit> as it is` at `err`, and the service keeps the old value until
|
|
the next timer tick.
|