From 6cfc3eb6422df6118ac9365c580fe6f04703198f Mon Sep 17 00:00:00 2001 From: Ian Furst Date: Wed, 19 Aug 2026 13:46:39 -0400 Subject: [PATCH] fix(rbac,test): add configmaps to rook-ceph role and verify NATS grants - Add `configmaps` to `rbac-rook-ceph-read.yaml` to prevent missing read permissions during cross-namespace KubeChangeSource lookups. - Add test case to `internal/broker/grants_test.go` to ensure `clank` holds necessary ephemeral consumer permissions for ledger rebuilds. - Update `TestEveryBrokerLoader_RequiresSealKeyNotJustClanks` to enforce seal key validation across all loaders. --- .../thump/templates/rbac-rook-ceph-read.yaml | 11 +++++++++++ internal/broker/grants_test.go | 19 +++++++++++++++++++ internal/config/config_test.go | 1 + 3 files changed, 31 insertions(+) diff --git a/deploy/chart/thump/templates/rbac-rook-ceph-read.yaml b/deploy/chart/thump/templates/rbac-rook-ceph-read.yaml index 40d2b619..0a5b857e 100644 --- a/deploy/chart/thump/templates/rbac-rook-ceph-read.yaml +++ b/deploy/chart/thump/templates/rbac-rook-ceph-read.yaml @@ -1,6 +1,14 @@ # Gated on domains.rookCeph.enabled: the rook-ceph namespace doesn't exist # on the dev cluster profile (docs/dev-environment.md) — see values.yaml's # domains comment. +# +# configmaps is required alongside pods/deployments/replicasets: the kube +# evidence tool's KubeChangeSource unions every subject namespace on every +# reasoning run, not just the namespace of the fingerprint being reasoned +# about (internal/evidence/kube_change.go), so any run on a rig with this +# domain enabled needs the grant regardless of which fingerprint fired. Same +# gap as rbac-acme-read.yaml, found live 2026-08-19 dead-lettering +# slo_burn:argocd via thump.detections. {{- if .Values.domains.rookCeph.enabled }} apiVersion: rbac.authorization.k8s.io/v1 kind: Role @@ -14,6 +22,9 @@ rules: - apiGroups: ["apps"] resources: ["deployments", "replicasets"] verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["configmaps"] + verbs: ["get", "list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/internal/broker/grants_test.go b/internal/broker/grants_test.go index 2b35ecb2..d1b94201 100644 --- a/internal/broker/grants_test.go +++ b/internal/broker/grants_test.go @@ -392,3 +392,22 @@ func TestNATSConfig_GrantsHissTheRebuildHoldsEphemeralConsumer(t *testing.T) { } } } + +func TestNATSConfig_GrantsClankTheRebuildLedgerEphemeralConsumers(t *testing.T) { + t.Parallel() + // rebuildLedger (internal/clank/rebuild.go) mints fresh ephemeral + // consumers across proposals, outcomes, and declines on every startup to + // replay the in-memory ledger and casebase. + users := parseNATSUsers(t, renderNATSConf(t)) + for _, want := range []string{ + "$JS.API.CONSUMER.CREATE.THUMP.*.thump.proposals", + "$JS.API.CONSUMER.CREATE.THUMP.*.thump.outcomes", + "$JS.API.CONSUMER.CREATE.THUMP.*.thump.declines", + "$JS.API.CONSUMER.MSG.NEXT.THUMP.*", + "$JS.ACK.THUMP.*.>", + } { + if !slices.Contains(users["clank@thump.svc"].Publish, want) { + t.Errorf("clank@thump.svc does not hold %q — rebuildLedger's ephemeral consumer replay cannot create/fetch/ack without it", want) + } + } +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 4e93d28f..f70a940b 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -789,6 +789,7 @@ func TestEveryBrokerLoader_RequiresSealKeyNotJustClanks(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { tc.env(t) + t.Setenv("THUMP_SEAL_KEY", "") if err := tc.load(true); err == nil || !strings.Contains(err.Error(), "THUMP_SEAL_KEY") { t.Error("every beat seals its WAL segments — hardening one loader and not the rest is a hole with a test in front of it") }