RBAC: cluster-wide deployer-apps ClusterRole, drop per-service deployer Roles (zero per-service RBAC) #466
Labels
No labels
burndown-2026-06
coherence-core
consult
headless
interactive
P0
P1
P2
P3
P4
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
coilyco-flight-deck/infrastructure#466
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Every new service costs a hand-tuned RBAC block in
deploy/forgejo-runner-deploy.yml: a per-namespacedeployerRole(listing exactly which kinds that service deploys) plus aRoleBindingfor the sharedforgejo/deployerServiceAccount. Commitbe85c09is the tax made visible - node-stats-mcp grew a Service/ConfigMap/Ingress/Middleware, so its Role had to be widened by hand or CD half-applied and denied the rest (Forbidden on services/configmaps/ingresses/middlewares). Kai does not want to add an RBAC block for every new service.Decision (director surface session, 2026-07-04)
Go zero per-service RBAC via a cluster-wide grant, mirroring the pattern this repo already uses for the coarse grant.
deployer-namespacesis already aClusterRole+ClusterRoleBindinglettingforgejo/deployerget/create namespaces cluster-wide. Extend that same shape to app kinds.Implementation
ClusterRolenameddeployer-appsholding the union of the app-deploy kinds the existing per-namespace Roles grant, with the standardget,list,watch,create,update,patch,deleteverb set (pods stay read-onlyget,list,watch):services,configmaps,persistentvolumeclaims,serviceaccounts,pods(read-only)apps:deployments,replicasetsbatch:cronjobs,jobsnetworking.k8s.io:ingressestraefik.io:middlewaresexternal-secrets.io:externalsecretssecretsandrbac.authorization.k8s.io(roles/rolebindings) from the cluster-wide role. Cluster-wide Secret write and cluster-wide RBAC write are the two genuine escalations (read a kube-system secret or bind yourself more power). Keeping them out bounds the blast radius while still giving zero-RBAC for the read-only MCP fleet.ClusterRoleBindingnameddeployer-appsbindingforgejo/deployerto that ClusterRole, modeled byte-for-byte on the existingdeployer-namespacesClusterRoleBinding block.deployerRole+RoleBindingblocks now made redundant by the cluster-wide grant (atlas, node-stats-mcp, coilysiren-eco-gnome, authelia, repo-recall, and any other whose Role only lists kinds now indeployer-apps).deployerRole listssecretsorroles/rolebindings- from the current file that is at least coilysiren-eco-app and open-webui (both listsecrets,roles,rolebindings); check each namespace's block. Those keep exactly asecrets(+rbac)-scoped Role + RoleBinding until they migrate to ExternalSecrets. File a follow-up issue: "migrate eco-app/open-webui deployer off raw Secrets/RBAC to ExternalSecrets so their supplemental Roles can drop."deployer-namespacesClusterRole + ClusterRoleBinding untouched.Verify
forgejo/deployercreate the app kinds in an app namespace, and cannot touchsecretsinkube-system. If you can reach a cluster:kubectl auth can-i --as=system:serviceaccount:forgejo:deployer create services -n node-stats-mcp-> yes,... get secrets -n kube-system-> no.kubectl apply, as prior RBAC changes were applied by Kai per deploy#26). The engineer lands the manifest on main; the apply is Kai's.Refs
be85c09(widen node-stats Role by hand).deployer-namespacesClusterRole/ClusterRoleBinding indeploy/forgejo-runner-deploy.yml.Update
docs/FEATURES.mdif the RBAC model is inventoried there. Land on canonical Forgejo main.Provenance: director surface session, 2026-07-04.
🔒 Reserved by
ward agent --driver claude— containerengineer-claude-infrastructure-466on hostKAI-DESKTOP-TOWERis carrying this issue (reserved 2026-07-04T07:55:37Z). Concurrentward agentruns are blocked until it finishes or the reservation goes stale (2h0m0s TTL);--forceoverrides.Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).
— Claude (she/her), via
ward agentWARD-OUTCOME: done - deployer-apps ClusterRole + ClusterRoleBinding added, per-service Roles dropped (atlas/eco-app/open-webui keep secrets/RBAC-only supplementals); landed on main @
5fd4481, CI green. Livekubectl applyis Kai's operator step.The bulk of this was a satisfying delete: the giant per-namespace Role block collapsed into one cluster-wide grant plus three tiny supplementals, net -132 lines in the manifest. The one judgment call the issue flagged - 'check each namespace's block' - paid off: atlas also lists
secrets(its tailnet sidecarts-state), not just eco-app and open-webui, so it got a secrets-only supplemental too. Easy to have missed by trusting the 'at least eco-app and open-webui' shorthand.What fought back was entirely off to the side of the actual RBAC: the
code-commentshook rejects any YAML comment below the top header line, so my inline rationale had to move intodocs/cd-runner.md- which then blew the 4000-char doc-size cap and took a few rounds of trimming to fit. And thepylintpre-commit hook isalways_runand shells out touv, which couldn't provision Python 3.13 into the root-owned/opt/uv/python; pointingUV_PYTHON_INSTALL_DIRat a writable dir fixed it and pylint passed 10/10. Worth flagging that env breakage as a container-image rough edge - a naive run would read it as a lint regression and reach for--no-verify.Confidence is high on the manifest: it's the literal union of the old Roles, verb-for-verb, with the two real escalations (cluster-wide secrets + rbac) deliberately held namespaced. Couldn't run
kubectl auth can-i(no cluster reach from here), so the can-i checks in the issue are unverified - but the shapes mirror the existing deployer-namespaces grant byte-for-byte. Follow-up filed as #467 to migrate the three supplementals onto ExternalSecrets so the last namespaced RBAC can drop.