docs/swarm/credentials.md: mTLS cert is per-identity, not per-host

Two lines still framed the invariant as one file total: line 19 ('plus
exactly one file') and line 21 ('the only credential on disk') implied
a single mTLS cert page-wide, contradicting the earlier fix that made
line 26 say 'every host has at least one'. Agents and swarm-level
services each need their own identity certificate, so a host running
several holds several. Restate both as per-identity, keep the target
shape (store + certs on disk, nothing else) unchanged.

Also relax the touching-a-credential rule: moving a secret into bao
without resolving its renewal strategy in the same change is
acceptable while the migration to this shape is in progress, provided
a follow-up issue is opened to settle renewal.

Refs #4445
This commit is contained in:
atlas 2026-09-16 23:29:39 +02:00
commit 0c50913db9

View file

@ -16,14 +16,16 @@ 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 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 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, deletes it. The target isn't a shorter list of secret files. It's the store,
plus exactly one file. plus one file per identity.
**That one file is the mTLS client certificate, and it's the only credential **Those files are mTLS client certificates, one per identity, and they're
on disk.** It has to be a file, and the reason is the whole asymmetry: the the only credential on disk.** Each has to be a file, and the reason is the
certificate is what authenticates a principal to the store, so it's the one whole asymmetry: the certificate is what authenticates a principal to the
credential that can't be fetched from the store. Something has to exist on store, so it's the one credential that can't be fetched from the store.
disk before the first request, or there's nothing to make the request with. Something has to exist on disk before the first request, or there's nothing
Every host has at least one; its only power is to ask the store for the rest. 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: `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 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 — value."_ A literal in a nix expression lands in the nix store —
@ -68,3 +70,8 @@ A pull request that touches a credential can't:
- give a host or container an out-of-band credential that isn't the store - 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 mTLS leaf — one out-of-band credential per principal is the whole point of
the store 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.