Repository navigation
Description
π€ Generated by the Agentic Engineer
Evidence
kyverno:background-controller:mutate-deployments is an aggregated ClusterRole, so it is bound cluster-wide. resourceNames selects names, not namespaces β Kubernetes RBAC only confines to a namespace through a Role + RoleBinding.
After platform#2721 the grant is get, update, patch on apps/Deployment with resourceNames: [umami-umami-primary]. The background controller can therefore still write any Deployment named umami-umami-primary in any namespace.
For context on how much #2721 already improved this β read live from prod, i.e. the state on main:
$ kubectl get clusterrole kyverno:background-controller:mutate-deployments -o yaml
rules:
- apiGroups: [apps]
resources: [deployments]
verbs: [get, list, watch, update, patch] # no resourceNames
So main grants blanket write on every Deployment in the cluster, and #2721 narrows that to a single name and drops list/watch. This issue is the remaining step, not a regression in that PR.
Why it matters
RBAC cannot restrict patch/update on a Deployment to metadata, so the grant implies full pod-template write on any matching object β the compromised-background-controller-token scenario the role's own comment names. #2721 also makes the policy static (match on umami/umami-umami, target umami/umami-umami-primary), so nothing needs the cross-namespace reach any more.
Proposed change
Replace the aggregated ClusterRole with a namespace-scoped Role in umami plus a RoleBinding for system:serviceaccount:kyverno:kyverno-background-controller.
The open question this issue exists to settle
Kyverno verifies the grant at policy admission, and the aggregated ClusterRole is currently what satisfies that check. It needs verifying that the check honours a RoleBinding rather than requiring the permission on the aggregated ClusterRole. If it does not, the policy is rejected at admission β and because the grant ships in infrastructure-controllers one layer ahead of the policy in infrastructure, that failure lands on the Flux layer rather than on a single app.
This is why it is a separate issue rather than a change folded into #2721: it cannot be proven from the manifests, and getting it wrong breaks a Flux layer in prod. Validate on the local test-bed cluster (ksail cluster create, opt the policy in) before shipping.
Note the check is only answerable now because #2721 makes the target namespace static; while it was templated ({{ request.object.metadata.namespace || request.namespace }}) Kyverno could not have resolved a namespace at admission time at all.
Acceptance criteria
- the aggregated ClusterRole no longer grants Deployment write
- a
Role/RoleBindinginumamigrantsget, update, patchonumami-umami-primary - the ClusterPolicy is still admitted, verified on a live cluster, and the annotation still propagates
- if admission does require the aggregated form, close this with that finding recorded β that is a useful outcome, not a failure
Size: small change, medium verification.
Metadata
Metadata
Assignees
Labels
Type
Fields
Projects
- StatusShow more project fieldsβ Done
Activity
coderabbitai commentedon Aug 14, 2026
π Related PRs
#2704 - fix(aws): constrain eks-ci KMS grant creation [merged]
#2985 - fix(k8s): gate deploy health on a serving CNPG cluster instead of a fully-ready one [merged]
#3037 - fix(cilium): refuse a zero-count gate release and correct the runbook order [merged]
#3069 - fix(coroot): grant the crossplane sync exporter the CRD read its discovery needs [merged]
#3109 - fix(deploy): avoid redundant GHCR node reboots [merged]
π Issue Planner
Check the box below or use the
@coderabbitai plancommand to generate an implementation plan and prompts that you can use with your favorite coding assistant.π§ͺ Issue enrichment is currently in open beta.
You can configure auto-planning by selecting labels in the issue_enrichment configuration.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:π¬ Have feedback or questions? Drop into our discord!
chore: agent-claim #3140 nonce=d4f549f7036cd120d760720aa05fd9f5
devantler commentedon Aug 26, 2026
Already delivered β closing with the finding recorded
Every acceptance criterion here is satisfied on
mainand verified live in prod. The premise neededcorrecting: this issue describes #2721 as narrowing the aggregated ClusterRole to
resourceNames: [umami-umami-primary], and says the namespacedRole/RoleBindingwas "theremaining step". #2721 as merged (2026-08-15, the day after this was filed) went further and shipped
the namespaced form directly β
k8s/bases/infrastructure/controllers/kyverno/role.yaml+role-binding.yaml. There is no aggregated Deployment-write ClusterRole left to remove.The open question is settled: Kyverno's admission check honours a
RoleBindingThis issue existed because that could not be proven from the manifests. It is now answered
empirically β the namespaced form has been the only grant in prod since 2026-08-15, and the policy is
admitted:
admin@prod)kyverno:background-controller:mutate-deploymentsβ NotFound. The only aggregated role left ismutate-longhorn-nodes(longhorn.io/nodes), and thekyverno:background-controlleraggregate ClusterRole contains noapps/deploymentsrule at allRole/RoleBindinginumami2026-08-15T12:10:52Z, bound toServiceAccount kyverno/kyverno-background-controller, pinned toresourceNames: [umami-umami-primary], verbsget, update, patchpropagate-reloader-to-flagger-primaryβReady=True,reason=Succeeded,generation=3umami-umami-primarycarriessecret.reloader.stakater.com/reload: umami-db-rotated, matching its source DeploymentSo the aggregated ClusterRole is not required for
mutateExistingadmission. Worth recording forthe next grant of this kind: upstream Kyverno's documentation only ever describes the aggregation
label, so the namespaced path is undocumented rather than unsupported. The condition that appears to
matter is that
targets[]carries a staticnamespaceβ which #2721 established when it madethe policy static β so a templated target namespace should not be assumed to behave the same way.
One thing this did surface
The CI guard protecting the narrowing had a fail-open: it only matched a
ClusterRolenamingumami-umami-primary, so a blanket Deployment-write ClusterRole (noresourceNames) passed it.Filed as #3390, fixed in #3391.
No live-cluster run was needed, and none was performed β the change has been in prod for 11 days, so
reading prod answered the question that
ksail cluster createwas originally proposed for.