swarm-bao: give the store forwarder's OIDC reader its own bao identity
`swarm-bao-forwarder-oidc` fetches the store container's collector secret, one path, and was the last reader still logging in with `deploy.bao.clientCertFile`: the hive's own leaf, whose policy reads every agent's credentials, the hive's tree and every service's OIDC secret. The four-way split gave grafana's and the swarm collector's readers leaves of their own and left this one behind. It now holds `forwarder-oidc.pem`, minted by `swarm-bao-pki`, and logs in under the `swarm-forwarder-oidc` cert-auth role, whose policy reads `secret/data/swarm/services/<store forwarder client id>/oidc/client` and nothing else. The role is written by `swarm-bao-forwarder-oidc-policy` from the bootstrap token, which gains the two grants that unit calls, and the reader is ordered after it. The subject is reserved as a hive name. A store host whose pair is null is refused at eval rather than falling back to the hive's leaf. The hive's own role and `client.pem` are untouched; nothing is revoked.
This commit is contained in:
parent
46d0b46670
commit
92e1909caf
9 changed files with 229 additions and 47 deletions
|
|
@ -98,7 +98,7 @@ collector this unit never reaches.
|
|||
The **secret store's own** collector — the forwarder inside the `swarm-bao`
|
||||
container — needs a delivery step too, and it takes the same route with one
|
||||
principal of its own: `swarm-bao-forwarder-oidc.service` reads
|
||||
`swarm/services/<client-id>/oidc/client` under this host's certificate and
|
||||
`swarm/services/<client-id>/oidc/client` under a leaf of its own and
|
||||
lands it in the container's tree, where `LoadCredential` hands it to the
|
||||
collector. The client id is its own
|
||||
(`services.hyperhive.swarm.bao.otel.clientId`), registered by
|
||||
|
|
@ -284,8 +284,10 @@ fourth in both directions. It renders unconditionally, because the export it
|
|||
authenticates has no unauthenticated mode to degrade into — and it orders
|
||||
itself **after** `container@swarm-bao`, because the store it reads runs in the
|
||||
container it delivers into. Nothing circular sits behind that: the identity it
|
||||
logs in with is this host's static `swarm-bao-pki` leaf, not anything the store
|
||||
mints.
|
||||
logs in with is the static `forwarder-oidc.pem` leaf `swarm-bao-pki` signs, not
|
||||
anything the store mints. With no leaf it has no fallback, so `swarm-bao.nix`
|
||||
refuses the build and names `deploy.bao.forwarderOidcClientCertFile` /
|
||||
`forwarderOidcClientKeyFile`.
|
||||
|
||||
A service's secret is one value for the whole swarm rather than one per hive, so
|
||||
it lives under the `services` prefix, and a hive's read policy grants that prefix
|
||||
|
|
@ -295,9 +297,9 @@ there is nothing to scope the grant to. A hive's **own** leaf can therefore read
|
|||
every swarm service's client secret, and that's stated in
|
||||
`swarm-secret-client`'s `policy` module beside the grant itself.
|
||||
|
||||
⚠️ **The readers no longer present it.** Four units used to log in with
|
||||
⚠️ **The readers no longer present it.** Five units used to log in with
|
||||
`deploy.bao.clientCertFile`, which is the hive's own leaf, and bao identifies a
|
||||
principal by the subject of the certificate it presents — so four readers behind
|
||||
principal by the subject of the certificate it presents — so five readers behind
|
||||
one leaf were one principal holding the union of their needs. Each now holds a
|
||||
leaf of its own, and a policy naming only the path that unit reads. See
|
||||
[per-principal identities](#per-principal-identities) below.
|
||||
|
|
@ -326,31 +328,34 @@ else a hive needs does.
|
|||
### Per-principal identities
|
||||
|
||||
Bao matches a cert-auth role on the certificate's subject, so a certificate is an
|
||||
identity and sharing one merges the identities. These four units read one path
|
||||
identity and sharing one merges the identities. These five units read one path
|
||||
each and each holds a leaf, a role and a policy of its own:
|
||||
|
||||
| unit | option pair under `deploy.bao.` | reads |
|
||||
| ------------------------ | -------------------------------------------------------- | -------------------------------------------------- |
|
||||
| `swarm-bao-matrix-token` | `matrixTokenClientCertFile` / `matrixTokenClientKeyFile` | `swarm/hives/<hive>/matrix/appservice-token` |
|
||||
| `swarm-bao-queue-agent` | `queueAgentClientCertFile` / `queueAgentClientKeyFile` | `swarm/hives/<hive>/queue/agent` |
|
||||
| `swarm-bao-grafana-oidc` | `grafanaOidcClientCertFile` / `grafanaOidcClientKeyFile` | `swarm/services/<grafana client id>/oidc/client` |
|
||||
| `swarm-bao-otel-oidc` | `otelOidcClientCertFile` / `otelOidcClientKeyFile` | `swarm/services/<collector client id>/oidc/client` |
|
||||
| unit | option pair under `deploy.bao.` | reads |
|
||||
| -------------------------- | ------------------------------------------------------------ | -------------------------------------------------------- |
|
||||
| `swarm-bao-matrix-token` | `matrixTokenClientCertFile` / `matrixTokenClientKeyFile` | `swarm/hives/<hive>/matrix/appservice-token` |
|
||||
| `swarm-bao-queue-agent` | `queueAgentClientCertFile` / `queueAgentClientKeyFile` | `swarm/hives/<hive>/queue/agent` |
|
||||
| `swarm-bao-grafana-oidc` | `grafanaOidcClientCertFile` / `grafanaOidcClientKeyFile` | `swarm/services/<grafana client id>/oidc/client` |
|
||||
| `swarm-bao-otel-oidc` | `otelOidcClientCertFile` / `otelOidcClientKeyFile` | `swarm/services/<collector client id>/oidc/client` |
|
||||
| `swarm-bao-forwarder-oidc` | `forwarderOidcClientCertFile` / `forwarderOidcClientKeyFile` | `swarm/services/<store forwarder client id>/oidc/client` |
|
||||
|
||||
The first two exist **per hive**, because the path they read carries a hive
|
||||
name and every hive runs its own reader. Their subjects are
|
||||
`<deploy.bao.matrixTokenCommonNamePrefix>-<hive>` and
|
||||
`<deploy.bao.queueAgentCommonNamePrefix>-<hive>`; `swarm.nix` reserves both
|
||||
composed spellings as hive names, so nobody can name a hive into another hive's
|
||||
role. The other two read a path that names a swarm service rather than a hive, so
|
||||
one role each is enough and their subjects are the flat
|
||||
`deploy.bao.grafanaOidcCommonName` and `deploy.bao.otelOidcCommonName`.
|
||||
role. The other three read a path that names a swarm service rather than a hive,
|
||||
so one role each is enough and their subjects are the flat
|
||||
`deploy.bao.grafanaOidcCommonName`, `deploy.bao.otelOidcCommonName` and
|
||||
`deploy.bao.forwarderOidcCommonName`.
|
||||
|
||||
On a host that mints its own PKI, `glue-bao-tls.nix` signs all four and defaults
|
||||
all eight options, and there is nothing to do. Elsewhere you issue each leaf from
|
||||
On a host that mints its own PKI, `glue-bao-tls.nix` signs all five and defaults
|
||||
all ten options, and there is nothing to do. Elsewhere you issue each leaf from
|
||||
that CA out of band and name it here — one file per principal rather than one file
|
||||
shared by four, which is the whole of what this buys.
|
||||
shared by five, which is the whole of what this buys.
|
||||
|
||||
Forgetting one of the eight elsewhere isn't a quiet degrade. Three of the four
|
||||
On a host without the store, where the fifth never runs, forgetting one of the
|
||||
other eight isn't a quiet degrade. Three of the four
|
||||
readers used to render only where their leaf existed, so a hand-configured
|
||||
remote-store hive that named the hive's own `clientCertFile` and missed a
|
||||
principal's pair just lost that unit; `swarm-grafana.nix` was alone in refusing
|
||||
|
|
|
|||
Loading…
Reference in a new issue