Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c50913db9 | ||
|
|
596e8f006f | ||
|
|
972b80d7b8 | ||
|
|
cc77db0e7e | ||
|
|
940e11ecda | ||
|
|
f1e5fd5592 | ||
|
|
ae388c48a7 | ||
|
|
b2c1378fd3 | ||
|
|
52fabe7096 | ||
|
|
937289383c |
3 changed files with 87 additions and 0 deletions
|
|
@ -76,6 +76,10 @@ 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
|
||||
|
||||
|
|
|
|||
|
|
@ -104,6 +104,12 @@ 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
|
||||
|
|
|
|||
77
docs/swarm/credentials.md
Normal file
77
docs/swarm/credentials.md
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# 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/<agent>/matrix/<account>` | `swarm-controller` | the agent container itself, under the certificate its hive passed in | must be stated |
|
||||
| `swarm/hives/<hive>/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/<hive>/queue/agent` | authelia | the agent container presenting the OIDC client to the swarm queue, under its own certificate | must be stated |
|
||||
| `swarm/services/<clientId>/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.
|
||||
Loading…
Reference in a new issue