Git, CI/CD & GitOpsCV · GitOps Anti-PatternsControllerPrivilege
Cluster-admin everywhere — the controller holds the keys to the cluster
What you'll learn
- Verify the effective permissions of a controller ServiceAccount with kubectl auth can-i
- Identify the three blast-radius multipliers in a cluster-admin controller binding
- Design a scoped ServiceAccount per AppProject with the verbs each resource actually needs
- Distinguish impersonation in the controller from impersonation in the applied manifests
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
A GitOps controller is the single identity that writes to
every namespace the platform manages. When that identity is
cluster-admin, the blast radius of any mistake it makes is
the whole cluster. A typo in a chart that contains a wide
RBAC manifest, a Helm hook that escalates a ServiceAccount,
or a templating bug that emits * for a verb is a single
apply that grants a binding the rest of the cluster
inherits.
The cluster-admin binding in plain text
The Argo CD default install creates a ClusterRoleBinding that
grants the application controller’s ServiceAccount the
cluster-admin ClusterRole. Flux ships similar bindings.
Both work because the controllers manage many namespaces and
many resource kinds; both are also the reason an unintended
manifest becomes an unintended cluster-wide permission.
flowchart LR
A["App manifest with RBAC"] --> B["Controller applies"]
B --> C["ClusterRoleBinding created"]
C --> D["Cluster-wide permission granted"]
D --> E["Lateral movement to every namespace"]
D --> F["Identity impersonation at will"]
D --> G["Secrets readable across the cluster"]
The verification step most teams skip:
kubectl auth can-i --list --as=system:serviceaccount:argocd:argocd-application-controller
The output lists every Resource, every Verb, and every API group the controller is permitted to touch.
The scoped ServiceAccount per project
The replacement is a ServiceAccount per project, with a RoleBinding (not a ClusterRoleBinding) bound to a Role that names only the resources and verbs the manifests in that project actually use.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: prod-deployer
namespace: prod-platform
rules:
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["services", "configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
The Role names four resource kinds and six verbs. The
controller bound to this Role cannot create a
ClusterRoleBinding, cannot read a Secret outside
prod-platform, and cannot impersonate another identity. The
blast radius is the namespace.
The three blast-radius multipliers
Three properties of a cluster-admin controller make every mistake worse than the same mistake would be for a human:
- Cross-namespace reach. A wide RBAC manifest in a chart becomes a binding the rest of the cluster inherits.
- Impersonation. A controller that may bind cluster roles can impersonate other identities to read Secrets.
- Continuous reconciliation. A controller reapplies the mistake every reconcile.
A scoped ServiceAccount removes all three.
Production discipline
- Run
kubectl auth can-i --list --as=...on every controller in every cluster. - One ServiceAccount per project, bound to a Role.
- CI fails any chart that contains a ClusterRole or a ClusterRoleBinding.
Cross-course references
- This course, Part LXXXIII (GitOpsRBAC) - Argo CD and Flux RBAC models, AppProject scoping, multi-tenancy.
- Kubernetes for Production Sysadmins, Part LVIII (RBAC) - Role, RoleBinding, and ClusterRoleBinding in depth.
Quiz
Knowledge check · 4 questions
Q1. Which kubectl command shows the effective permissions of the Argo CD application controller ServiceAccount?
Q2. A controller that holds cluster-admin only applies manifests that engineers have reviewed, so the blast radius is bounded by review.
Q3. Name the three blast-radius multipliers a cluster-admin controller adds to every manifest error.
Q4. Diagnose a controller-privilege incident and recommend the architecture that prevents it.
A team runs Argo CD with the default cluster-admin binding on the application controller. A developer copies a Helm chart from a public tutorial; the chart contains a ClusterRole granting `*` on `*`. The chart is merged; the controller reconciles; the ClusterRoleBinding is created; every ServiceAccount in the cluster is now cluster-admin reachable.
Passing score: 75%. Answers are checked in this browser.