Watch
0
0
Fork
You've already forked hyperhive
0

bao: let the UI's admins login write the review classifier's API key

The docs tell the operator to write
`secret/swarm/services/review-classifier/anthropic-api-key` by hand in
the bao UI, but the UI's OIDC login for authelia's `admins` group lands
on `swarm-operator-viewer`, which is list+read on `secret/metadata/*`
and nothing under `secret/data/`. bao refuses the write, so an admin
can't set the key and every classifier run fails with "no API key".

The viewer policy gains `create` and `update` on that one leaf, no
`read`: a UI session still never sees a secret value. The leaf is a
named binding, shared with the classifier's reader policy.

module-eval pins the viewer's two stanzas and that no `data/` grant
carries `read`. docs/swarm/secrets.md and services.md describe the
write.

Closes #5148
This commit is contained in:
atlas 2026-10-12 18:17:28 +02:00
commit 133d5578d2
4 changed files with 32 additions and 11 deletions

View file

@ -577,13 +577,18 @@ let
# classifier's reviews with it; no other principal's policy reaches it.
classifierForgeTokenLeaf = "swarm/services/review-classifier/forge/token";
# The review classifier's Anthropic API key. The operator writes it through
# the UI's OIDC login; the classifier reads it on every run.
classifierApiKeyLeaf = "swarm/services/review-classifier/anthropic-api-key";
# The UI's OIDC client secret. The publisher writes it; the granter reads it
# into the `oidc` auth method's config, which is the only copy bao uses.
baoUiClientLeaf = "swarm/services/${cfg.ui.oidc.clientId}/oidc/client";
# What an OIDC login through the UI gets: the key tree and each key's KV
# metadata, never a value — KV v2 serves values under `data/`, which no
# stanza here names. The UI needs no `sys/` grant on top:
# metadata, and a write on the one leaf the operator fills by hand. Never a
# value: KV v2 serves values under `data/`, and the one `data/` stanza has
# no `read`. The UI needs no `sys/` grant on top:
# `sys/internal/ui/mounts` lists any mount the token holds a capability
# under, and the rest it calls is in the `default` policy.
operatorViewerPolicyName = "swarm-operator-viewer";
@ -591,6 +596,9 @@ let
path "${credentialMountPath}/metadata/*" {
capabilities = ["list", "read"]
}
path "${credentialMountPath}/data/${classifierApiKeyLeaf}" {
capabilities = ["create", "update"]
}
'';
# authelia's operator group (`operatorGroup` in ./swarm-authelia.nix, the
@ -967,7 +975,7 @@ let
name = "swarm-review-classifier";
cn = baoDeploy.reviewClassifierCommonName;
policyText =
readStanza "${credentialMountPath}/data/swarm/services/review-classifier/anthropic-api-key"
readStanza "${credentialMountPath}/data/${classifierApiKeyLeaf}"
+ readStanza "${credentialMountPath}/data/swarm/services/${hyperhiveCfg.swarm.review-classifier.clientId}/oidc/client";
}
];