Skip to content

Commit 4bd4b11

Browse files
committed
feat(remote-signer): fix secret modification
1 parent f3bc655 commit 4bd4b11

2 files changed

Lines changed: 48 additions & 13 deletions

File tree

internal/embed/embed_crd_test.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,8 @@ func TestServiceOfferControllerSecretRBAC_Scoped(t *testing.T) {
875875

876876
readNames := map[string]bool{}
877877
deleteNames := map[string]bool{}
878+
updateNames := map[string]bool{}
879+
patchNames := map[string]bool{}
878880
var sawCreate bool
879881
for _, r := range rules {
880882
rm := r.(map[string]any)
@@ -908,9 +910,25 @@ func TestServiceOfferControllerSecretRBAC_Scoped(t *testing.T) {
908910
if verbs["delete"] {
909911
deleteNames[n] = true
910912
}
913+
if verbs["update"] {
914+
updateNames[n] = true
915+
}
916+
if verbs["patch"] {
917+
patchNames[n] = true
918+
}
911919
}
912-
if verbs["list"] || verbs["watch"] || verbs["update"] || verbs["patch"] {
913-
t.Error("serviceoffer-controller scoped secrets rule must not grant list/watch/update/patch — Secrets are create-only in the reconciler and all reads are by name")
920+
if verbs["list"] || verbs["watch"] {
921+
t.Error("serviceoffer-controller scoped secrets rule must not grant list/watch — all reads are by name")
922+
}
923+
// update/patch is allowed only on remote-signer-keystore, which the
924+
// reconciler updates via backfillSignerAuthToken to add the bearer
925+
// token key to keystores minted before signer auth existed.
926+
if verbs["update"] || verbs["patch"] {
927+
for n := range names {
928+
if n != "remote-signer-keystore" {
929+
t.Errorf("serviceoffer-controller must not grant secrets:update/patch on %s — only remote-signer-keystore is mutated (auth-token backfill)", n)
930+
}
931+
}
914932
}
915933
if names["litellm-secrets"] && verbs["delete"] {
916934
t.Error("serviceoffer-controller must not grant secrets:delete on litellm-secrets; the code only reads LITELLM_MASTER_KEY")
@@ -930,6 +948,16 @@ func TestServiceOfferControllerSecretRBAC_Scoped(t *testing.T) {
930948
t.Errorf("serviceoffer-controller must grant resourceName-scoped secrets:delete on %s for agent teardown", name)
931949
}
932950
}
951+
// backfillSignerAuthToken (agent_wallet.go) calls Update on the keystore
952+
// Secret to add the signer-auth bearer token to legacy keystores. Without
953+
// update + patch the Agent stays in Provisioning and every downstream
954+
// ServiceOffer condition blocks on WaitingForAgent.
955+
if !updateNames["remote-signer-keystore"] {
956+
t.Error("serviceoffer-controller must grant resourceName-scoped secrets:update on remote-signer-keystore for backfillSignerAuthToken")
957+
}
958+
if !patchNames["remote-signer-keystore"] {
959+
t.Error("serviceoffer-controller must grant resourceName-scoped secrets:patch on remote-signer-keystore for backfillSignerAuthToken")
960+
}
933961
if !sawCreate {
934962
t.Error("serviceoffer-controller must retain secrets:create for minting the per-agent API token + wallet keystore in dynamic namespaces")
935963
}

internal/embed/infrastructure/base/templates/x402.yaml

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -143,16 +143,19 @@ rules:
143143
# ServiceAccount + PVC are namespace-scoped child resources.
144144
#
145145
# Secrets: the reconciler touches exactly three, by name:
146-
# - litellm-secrets get (read LITELLM_MASTER_KEY, ns llm)
147-
# - hermes-api-server get/delete/create (mint the agent API token)
148-
# - remote-signer-keystore get/delete/create (mint the agent wallet keystore)
149-
# Secrets are create-only in the reconciler (isCreateOnlyKind): the API token
150-
# is preserved, not rotated, and the keystore is minted once and never
151-
# reshaped, so no `update`/`patch` verb is needed. `litellm-secrets` is
152-
# get-only; delete is confined to the two per-agent Secret names that teardown
153-
# owns. `create` is split into its own rule below: resourceNames cannot scope
154-
# create, and agent namespaces are minted dynamically, so create stays
155-
# namespace-wide. That is an integrity surface only.
146+
# - litellm-secrets get (read LITELLM_MASTER_KEY, ns llm)
147+
# - hermes-api-server get/delete/create (mint the agent API token)
148+
# - remote-signer-keystore get/update/patch/delete/create (mint + backfill auth token)
149+
# `hermes-api-server` is create-only: the token is preserved, not rotated.
150+
# `remote-signer-keystore` needs `update`/`patch` for a one-shot backfill
151+
# path (backfillSignerAuthToken in agent_wallet.go) that adds the
152+
# signer-auth bearer-token key to keystores minted before signer auth
153+
# existed. Idempotent: presence of the key (even empty) is a no-op, so
154+
# operator-rotated tokens are never clobbered. `litellm-secrets` is
155+
# get-only; delete is confined to the two per-agent Secret names that
156+
# teardown owns. `create` is split into its own rule below: resourceNames
157+
# cannot scope create, and agent namespaces are minted dynamically, so
158+
# create stays namespace-wide. That is an integrity surface only.
156159
- apiGroups: [""]
157160
resources: ["serviceaccounts"]
158161
verbs: ["create"]
@@ -173,8 +176,12 @@ rules:
173176
verbs: ["get"]
174177
- apiGroups: [""]
175178
resources: ["secrets"]
176-
resourceNames: ["hermes-api-server", "remote-signer-keystore"]
179+
resourceNames: ["hermes-api-server"]
177180
verbs: ["get", "delete"]
181+
- apiGroups: [""]
182+
resources: ["secrets"]
183+
resourceNames: ["remote-signer-keystore"]
184+
verbs: ["get", "update", "patch", "delete"]
178185
- apiGroups: [""]
179186
resources: ["secrets"]
180187
verbs: ["create"]

0 commit comments

Comments
 (0)