Skip to content

Confine the Kyverno background-controller Deployment grant to the umami namespaceΒ #3140

Closed

Description

@devantler

πŸ€– 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/RoleBinding in umami grants get, update, patch on umami-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.

Activity

  1. coderabbitai commented on Aug 14, 2026

    @coderabbitai
    Contributor
    πŸ”— 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 plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.

    • Create Plan

    πŸ§ͺ 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:

    issue_enrichment:
      auto_enrich:
        enabled: false

    πŸ’¬ Have feedback or questions? Drop into our discord!

  2. moved this to πŸ“₯ Backlog in 🌊 Project Boardon Aug 14, 2026
  3. added a commit that references this issue on Aug 26, 2026
  4. devantler commented on Aug 26, 2026

    @devantler
    ContributorAuthor

    πŸ€– Generated by the Agentic Engineer

    Already delivered β€” closing with the finding recorded

    Every acceptance criterion here is satisfied on main and verified live in prod. The premise needed
    correcting: this issue describes #2721 as narrowing the aggregated ClusterRole to
    resourceNames: [umami-umami-primary], and says the namespaced Role/RoleBinding was "the
    remaining 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 RoleBinding

    This 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:

    Criterion Result (read live, admin@prod)
    aggregated ClusterRole no longer grants Deployment write kyverno:background-controller:mutate-deployments β†’ NotFound. The only aggregated role left is mutate-longhorn-nodes (longhorn.io/nodes), and the kyverno:background-controller aggregate ClusterRole contains no apps/deployments rule at all
    Role/RoleBinding in umami both present since 2026-08-15T12:10:52Z, bound to ServiceAccount kyverno/kyverno-background-controller, pinned to resourceNames: [umami-umami-primary], verbs get, update, patch
    ClusterPolicy still admitted propagate-reloader-to-flagger-primary β†’ Ready=True, reason=Succeeded, generation=3
    annotation still propagates umami-umami-primary carries secret.reloader.stakater.com/reload: umami-db-rotated, matching its source Deployment

    So the aggregated ClusterRole is not required for mutateExisting admission. Worth recording for
    the 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 static namespace β€” which #2721 established when it made
    the 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 ClusterRole naming
    umami-umami-primary, so a blanket Deployment-write ClusterRole (no resourceNames) 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 create was originally proposed for.

  5. moved this from πŸ“₯ Backlog to βœ… Done in 🌊 Project Boardon Aug 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Fields

    No fields configured for Security.

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions