diff --git a/docs/README.md b/docs/README.md index 1ae0fda8..3483f42a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -76,10 +76,6 @@ declarations. - **Where do agent snapshots go? How does the swarm's `btrfs receive` endpoint authenticate a pushing hive?** → [`networking/snapshot-store.md`](networking/snapshot-store.md). -- **Who mints each credential, who reads it, and how does it rotate — and - where's that shape headed?** → [`swarm/credentials.md`](swarm/credentials.md) - (current state, target state, and the progressive-enhancement rule); - [`swarm/secrets.md`](swarm/secrets.md) for where each file lives today. ## Scheduler, CI, observability diff --git a/docs/swarm/README.md b/docs/swarm/README.md index feea18bd..3d085e0f 100644 --- a/docs/swarm/README.md +++ b/docs/swarm/README.md @@ -104,12 +104,6 @@ Every credential the swarm holds, who mints it, where it must live, and which of the three topologies makes it the operator's job to place: [`secrets.md`](secrets.md). -Where that shape is **going** — the per-secret minter/reader/renewal -contract, the target of one mTLS identity per host and everything else -through the store, and the test a change has to pass to count as movement -toward it: [`credentials.md`](credentials.md). It supersedes `secrets.md` -when the migration completes. - ## Swarm UI The operator-only web surface on the swarm apex, why reaching it needs diff --git a/docs/swarm/credentials.md b/docs/swarm/credentials.md deleted file mode 100644 index 680848b6..00000000 --- a/docs/swarm/credentials.md +++ /dev/null @@ -1,77 +0,0 @@ -# Credentials: the target shape - -The swarm's credential store is bao. This page describes the shape every -credential is meant to have — who mints it, who reads it, and how it renews -— not what's on disk today. [`secrets.md`](secrets.md) remains the map of -the files that exist right now; this page replaces it, and `secrets.md` gets -deleted, once the swarm's credential path matches what's described below. - -**Public material is a value.** A certificate or a public nkey is handed to -every client that connects, so the store is a fine place for it. Nothing -below is about those. - -**No secret the store holds is ever written to disk.** That's the invariant, -and everything else in this page follows from it. A value pulled from the -store — bao — lives in the memory of the process that asked for it and -nowhere else: not in a state directory, not in a bind-mounted file, not in a -systemd credential, not in a rendered config, not for a moment before a unit -deletes it. The target isn't a shorter list of secret files. It's the store, -plus one file per identity. - -**Those files are mTLS client certificates, one per identity, and they're -the only credential on disk.** Each has to be a file, and the reason is the -whole asymmetry: the certificate is what authenticates a principal to the -store, so it's the one credential that can't be fetched from the store. -Something has to exist on disk before the first request, or there's nothing -to make the request with. Every identity — an agent, a swarm-level service — -needs one; a host running several holds several, and its only power is to -ask the store for the rest. -`swarm-bao.nix:529-533` states the rule for the nix option that carries it: -this is _"the credential an operator places by hand"_, and _"a path, never a -value."_ A literal in a nix expression lands in the nix store — -world-readable and permanent — so that option takes a path to the -certificate on disk, never the certificate's bytes. - -**The hive hands an agent an identity, never a secret.** An agent container -is passed an mTLS certificate from its hive and from then on authenticates -to the store under its own name, pulling what it needs when it needs it. No -process reads a secret on another principal's behalf: the principal that -needs a value is the principal that authenticates for it. - -Two per-agent credential files — the forge token and the github token — sit -outside this page: they're operator-supplied and never pass through the -store, so the table below doesn't govern them. - -**Per secret, the target specifies minter, reader, and renewal strategy.** -Those three are the contract, and the reader is a process pulling a store -path at runtime — not a path on disk, and not a unit whose job is to turn a -store value into a file. A renewal cell may never read `NONE`: state the -strategy for every credential, including the mTLS leaf. - -| store path | minter | reader — pulls at runtime, holds in memory | renewal | -| -------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | -------------- | -| `swarm/agents//matrix/` | `swarm-controller` | the agent container itself, under the certificate its hive passed in | must be stated | -| `swarm/hives//matrix/appservice-token` | one minter, on the authelia host | the hive process that presents the token to its homeserver, under the hive's own certificate | must be stated | -| `swarm/hives//queue/agent` | authelia | the agent container presenting the OIDC client to the swarm queue, under its own certificate | must be stated | -| `swarm/services//oidc/client` | authelia | the service process that presents the client secret, under the certificate of the host it runs on | must be stated | -| _(not in the store)_ the mTLS client leaf | the store's own PKI, or an operator placing it by hand | its own client, off disk — the sole exception above, because it's what makes every other row's pull possible | must be stated | - -## Touching a credential - -A pull request that touches a credential can't: - -- add a minter outside the swarm's existing mint path -- persist a store-provided secret to disk — a state directory, a bind mount, - a rendered config -- add a credential whose renewal strategy is `NONE` — state the strategy, - even if it's "operator reissues and restarts the reader" -- read a secret on another principal's behalf and hand it over — the - principal that needs the value authenticates for it -- give a host or container an out-of-band credential that isn't the store - mTLS leaf — one out-of-band credential per principal is the whole point of - the store - -While the swarm's credential path is still moving to this shape, a PR that -moves a secret into bao is allowed to leave its renewal strategy unresolved, -provided it opens a follow-up issue to settle renewal. That's a migration-era -allowance, not a standing exception to the renewal-strategy rule above.