skip to content

A team owns its own Argo CD AppProject and can commit anything to the repository that project allows. How can that still let them take over the whole cluster, and which AppProject fields prevent it?

level: seniorimportance: should knowfreq 50%

answer

  1. the committer's identity never reaches the API server
  2. the agent's credential is the real ceiling
  3. cluster-scoped kinds are the escalation surface
  4. a fence you can edit is not a fence

basics

~20 s

Argo CD applies manifests with its own cluster credentials, not the committer's, so a manifest that grants cluster-wide privileges is applied with full rights. The AppProject's destinations and cluster resource whitelist are what stop it, by refusing kinds and namespaces outside the tenant's fence.

solid answer

~50 s

The trap is that committing a manifest is a privilege escalation path. Argo CD's application controller holds a credential for each registered cluster — typically a very powerful one — and applies whatever the repository says using that credential. Nobody checks what the person who wrote the YAML could have done with their own Kubernetes identity, because the sync does not use it. So a tenant who can commit a ClusterRoleBinding, a mutating webhook, or a workload in `kube-system` gets it applied. The fences are on the AppProject: `destinations` restricts which cluster and which namespaces the project may target, and `clusterResourceWhitelist` restricts which cluster-scoped kinds may be applied at all — a project that names none permits none. `namespaceResourceBlacklist` handles the namespaced kinds you still want to withhold, such as ResourceQuota. Behind that, register the cluster with a narrowly scoped credential rather than a fleet-wide admin one, and keep `projects, update` in Argo CD RBAC away from tenants, since editing the project removes the fence.

code

yaml · 19 lines
yaml
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: team-a
  namespace: argocd
spec:
  sourceRepos:
    - https://github.com/acme/team-a-manifests.git
  destinations:
    - name: prod-eu
      namespace: team-a-web
  clusterResourceWhitelist: []
  namespaceResourceBlacklist:
    - group: ''
      kind: ResourceQuota
    - group: ''
      kind: LimitRange
    - group: networking.k8s.io
      kind: NetworkPolicy

go deeper

for a junior

Understand that Argo CD applies manifests with its own cluster access, so what lands in Git is what lands in the cluster regardless of who wrote it.

for a middle

Explain which AppProject fields do the fencing — destinations for cluster and namespace, clusterResourceWhitelist for cluster-scoped kinds — and give a concrete escalation manifest the fence would refuse.

for a senior

Demonstrate defence in depth: scope the registered cluster credential as well as the project, keep project edits out of tenant hands, and know how to audit whether the path was already used.

for a principal

Own where the tenancy boundary genuinely belongs — namespaces in one cluster versus separate clusters or separate Argo CD instances — and how much residual trust in the delivery agent your organisation is willing to carry.

## The mechanism the question turns on In a normal Kubernetes workflow, what you can create is bounded by your own identity: the API server authenticates you and RBAC decides. GitOps breaks that link on purpose. The delivery agent — here, Argo CD's application controller — reads manifests from Git and applies them with **its own** credential to the destination cluster. Git commit authorship is not a Kubernetes identity and is never presented to the API server. The consequence is direct: **whatever the agent can do, a committer can do**, if there is no fence in between. Argo CD registers each managed cluster as a Secret in its namespace carrying that cluster's credential, and `argocd cluster add` by default installs a service account bound to full cluster administration. A tenant who commits ```yaml apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: totally-normal subjects: - kind: User name: [email protected] roleRef: kind: ClusterRole name: cluster-admin apiGroup: rbac.authorization.k8s.io ``` into a path an Application syncs has just granted themselves cluster administration, and the audit log will show the deploy agent doing it. Variants are worse and quieter: a validating or mutating webhook configuration that intercepts every admission request, a DaemonSet with host mounts, a workload placed in `kube-system` where it inherits trust by convention. ## The fences, in order of usefulness **`clusterResourceWhitelist`.** Cluster-scoped kinds are where escalation lives — ClusterRole, ClusterRoleBinding, webhook configurations, CustomResourceDefinition, PriorityClass, and Namespace itself. An AppProject that lists nothing in `clusterResourceWhitelist` permits no cluster-scoped kind at all, which is the right posture for an application team. If a tenant legitimately needs one, whitelist that group and kind explicitly: ```yaml clusterResourceWhitelist: - group: '' kind: Namespace ``` **`destinations`.** Each entry pairs a cluster (by `server` URL or by `name`) with a `namespace`. Restricting the project to the team's namespaces stops the "deploy into kube-system" move and stops a manifest from reaching a cluster the team does not own. Watch for over-broad glob patterns here — a namespace pattern that is a bare `*` gives back everything you just took away. **`namespaceResourceBlacklist`.** Some namespaced kinds are platform policy rather than application config: ResourceQuota, LimitRange, NetworkPolicy. Blacklisting them keeps a tenant from raising its own quota or opening its own network policy while still letting it deploy freely. **`sourceRepos`.** Keeping the project pointed at repositories the tenant does not solely control lets you rely on the repository's review rules; a project allowing `*` means the fence is only as good as the least-protected repository anyone can reference. ## The credential behind the fence Project fields are enforced by Argo CD, so they are only as strong as Argo CD's integrity. Defence in depth means the destination credential itself should be scoped. `argocd cluster add` accepts a namespace restriction so the registered service account is granted rights only in named namespaces; on a genuinely hostile-tenant boundary, a separate cluster with its own credential beats a namespace split. Where the destination is the same cluster Argo CD runs in, remember that the controller can otherwise reach everything in it. Some Argo CD versions offer an opt-in sync **impersonation** feature, where the sync uses a service account named by the AppProject destination rather than the controller's own credential, which puts Kubernetes RBAC back in the path. It is off by default and has been staged in as an experimental capability, so treat it as an upgrade to evaluate, not as the baseline to assume in an interview answer. ## Who may edit the project Every fence above lives in an object. If the tenant can edit that object, the fence is decorative. In Argo CD RBAC the AppProject is the `projects` resource, and `update` on it must stay with the platform team; delegate inside the project through `spec.roles` instead. The same logic applies to the manifest repository holding your AppProject definitions if you manage Argo CD with Argo CD — that repository is a control-plane repository and needs review rules to match. ## How to answer it Say the mechanism first — the agent's credential, not the committer's, applies the manifest — then name `clusterResourceWhitelist` and `destinations` as the specific fields, then add the two things that make the fence real: a scoped cluster credential, and keeping project edits away from the tenant.

  • How would you detect that someone had already used this path before you put the fences in?
    Diff the cluster against what the repositories claim, and look for cluster-scoped objects Argo CD owns: resources carrying its tracking label or annotation with a group and kind no project should have needed. Then read the Kubernetes audit log for creates by the Argo CD service account outside tenant namespaces. Finally check the AppProject history for a widening edit that preceded the object's creation.
  • Does requiring signed commits on the manifest repository solve this?
    It narrows who can commit, not what a commit can do. Argo CD's AppProject supports requiring signed commits through `signatureKeys`, and it is a genuine control against an attacker without a trusted key. But an insider with a key still gets full application of whatever they write, so signature verification complements the destination and resource fences rather than replacing them.

saying these in an interview costs you the question

  • Assumes Kubernetes RBAC checks the person who wrote the manifest
  • Believes pull request review alone contains a hostile manifest
  • Thinks a namespaced project cannot create cluster-scoped objects by default
  • Registers every cluster with a fleet-wide admin credential
  • Lets tenants edit their own AppProject to unblock themselves

context