swarm-secret-client: hives no longer read swarm/services/*
The hive policy rendered by policy::render granted every hive cert read on secret/data/swarm/services/*, which holds each swarm service's OIDC client secret. No reader of that tree authenticates with a hive cert: grafana, the otel collector and the store forwarder each read with their own leaf, and the bao UI's secret is read by the store's granter. render() now emits two stanzas: swarm/agents/* and the hive's own swarm/hives/<hive>/*. Kind::Service had no other caller and is removed. Docs and comments that described the grant as a decision are rewritten to the current state. Closes #4960
This commit is contained in:
parent
3bfdf0dfe0
commit
2505008b83
13 changed files with 82 additions and 136 deletions
|
|
@ -69,7 +69,7 @@ of the cell says how.
|
|||
| `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 | must be stated |
|
||||
| `swarm/hives/<hive>/matrix/sender-token` | `swarm-controller`, with the swarm's appservice token, for every hive in its directory in a five-minute pass | `swarm-controller` under its own certificate, before it decides whether to mint, and hive-c0re's `stored_sender_token()`, under the hive's own certificate | ✅ the controller's pass re-mints when the stored token is missing, unknown to the homeserver, or someone else's | ✅ hive-c0re's matrix sweep reads the store every run and overwrites its token file when the store's token differs |
|
||||
| `swarm/hives/<hive>/queue/agent` | authelia | `swarm-bao-queue-agent` on the hive's host, under its own per-hive certificate; no agent's policy reaches it | must be stated | 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 | must be stated |
|
||||
| `swarm/services/<clientId>/oidc/client` | authelia | that service's own reader, under its own certificate; the bao UI's by the store's granter | must be stated | must be stated |
|
||||
| _(not in the store)_ a hive's mTLS leaf | the store's own PKI, or an operator placing it by hand | its own client, off disk — the exception above, because it's what makes every other row's pull possible | must be stated | must be stated |
|
||||
|
||||
<!-- vale write-good.Passive = YES -->
|
||||
|
|
|
|||
|
|
@ -72,37 +72,24 @@ advertises a boundary it doesn't actually hold. A wide grant that says what it
|
|||
is beats a narrow one that only looks narrow.
|
||||
|
||||
What still holds: the grant is **read-only** (a hive can't write an agent's
|
||||
credential, so it can't hand itself an agent's identity), and it reaches three
|
||||
prefixes and nothing else in the store — every agent's credentials, the reader's
|
||||
_own_ entry under the hive namespace, which names the hive asking and so widens
|
||||
nothing between them, and every **swarm service's** OIDC client secret.
|
||||
credential, so it can't hand itself an agent's identity), and it reaches two
|
||||
prefixes and nothing else in the store — every agent's credentials, and the
|
||||
reader's _own_ entry under the hive namespace, which names the hive asking and
|
||||
so widens nothing between them.
|
||||
|
||||
That third prefix has the same shape of reason as the first, and the same honest
|
||||
cost. A swarm service (Grafana and the swarm collector are the two there today)
|
||||
registers **one** client for the whole swarm, so its credential's path names
|
||||
the service and never the host — and which hive runs a given service is a
|
||||
`deploy.*` fact, per-host by definition, so nothing swarm-wide exists to scope
|
||||
the grant to. The host running such a service has no store identity of its own
|
||||
either; it reads with the certificate of the hive it is. **Any hive can
|
||||
therefore read any swarm service's client secret**, which is worth what it
|
||||
buys: every swarm service gets its credential the same way from anywhere,
|
||||
instead of only where the identity provider happens to sit. Giving such a
|
||||
service its own store identity is what would remove this rather than re-scope
|
||||
it.
|
||||
A hive reads no **swarm service's** OIDC client secret. Every reader of
|
||||
`swarm/services/` presents a certificate that isn't a hive's: Grafana, the swarm
|
||||
collector and the store forwarder each fetch theirs with a leaf of their own,
|
||||
and the store's granter fetches the bao UI's.
|
||||
|
||||
**A tracked follow-up** narrows this, with the two candidate directions: scope
|
||||
the grant per hive (and pay for the re-emission), or give each agent container
|
||||
its own store identity so credentials never pass through a hive at all.
|
||||
|
||||
### Per-hive credentials live under the hive kind, not beside a service
|
||||
### Per-hive credentials live under the hive kind
|
||||
|
||||
`swarm_secret_client::policy` scopes the **hive** stanza narrowly (unlike the
|
||||
agent and service stanzas above) because a hive's own path names it, so
|
||||
scoping to the reader's own name costs nothing and drifts nowhere. That only
|
||||
holds if a credential that must be one-per-hive is actually stored under the
|
||||
hive kind.
|
||||
agent stanza above) because a hive's own path names it, so scoping to the
|
||||
reader's own name costs nothing and drifts nowhere. That only holds if a
|
||||
credential that must be one-per-hive is actually stored under the hive kind.
|
||||
**A credential that must be one-per-hive goes under `Kind::Hive`**; putting it
|
||||
under `Kind::Service` and expecting the grant to scope it — that's the mistake
|
||||
under the agent prefix and expecting the grant to scope it — that's the mistake
|
||||
this note exists to stop.
|
||||
|
||||
### Threat model: prompt injection → confused deputy
|
||||
|
|
|
|||
Loading…
Reference in a new issue