Git, CI/CD & GitOpsXC · CI Platform SecurityClusterAccess
Kubernetes access from CI — what cluster credentials do
What you'll learn
- Explain what a kubeconfig and a service-account token enable from a CI runner
- Verify cluster access with kubectl auth can-i before every privileged action
- Distinguish CI-direct cluster access from GitOps-mediated cluster access
- Recognise the failure modes of cluster-admin tokens versus namespace-scoped service accounts
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 Kubernetes credential on a CI runner is a production
credential: the namespace the token can read is the
namespace the cluster exposes, the workload it can apply
is the workload the cluster runs, and the secret it can
mount is the secret the pod consumes. A CI job that holds
a cluster-admin kubeconfig holds every secret in every
namespace. The discipline is to scope the credential
narrowly — namespace-scoped service-account tokens, not
cluster-wide kubeconfigs — and to verify the scope with
kubectl auth can-i before every privileged action.
What a cluster credential enables
A Kubernetes credential is a token plus a kubeconfig. The token authorises API calls; the kubeconfig names the cluster, the namespace, and the user. From a CI runner, the credential enables four categories of action:
flowchart TB
K["Cluster credential"]
K --> R1["Read"]
K --> R2["Write"]
K --> R3["Exec"]
K --> R4["Secrets"]
R1 --> N["Namespace contents"]
R2 --> M["Mutations"]
R3 --> D["Debug"]
R4 --> S["Sensitive data"]
The categories compound. A credential that can read secrets can read the database URL; a credential that can exec can read the pod’s environment; a credential that can write can replace the deployment with a hostile image. The blast radius is the union of every action the token can perform.
Verify cluster access before every action
The first step after acquiring a cluster credential is
to verify what the credential can do. Kubernetes
provides kubectl auth can-i:
kubectl auth can-i create deployments.apps --namespace=production
The output is yes or no. The CI engineer writes a
test that runs the verification before every privileged
action and fails the workflow on no. The verification
catches a wrong kubeconfig, a token scoped to the wrong
namespace, a RoleBinding that was deleted, and a leaked
credential that has been revoked.
CI-direct versus GitOps-mediated
There are two patterns for cluster access from CI, with different blast radii:
flowchart TB
subgraph DIRECT["CI-direct"]
CI1["CI job"] -->|"kubectl apply"| K1["Cluster API"]
end
subgraph GITOPS["GitOps-mediated"]
CI2["CI job"] -->|"push manifest"| R["Git repository"]
R -->|"reconciles"| K2["Cluster API"]
end
- CI-direct. The CI runs
kubectl apply. The cluster credential is on the runner; the blast radius is whatever the credential can reach. - GitOps-mediated. The CI commits a manifest; the GitOps controller (Argo CD, Flux) reconciles the cluster. The CI has no cluster credential; the blast radius is whatever a malicious commit can cause, bounded by the GitOps controller’s RBAC.
The GitOps-mediated pattern is structurally smaller. The CI cannot apply directly; the controller is the only writer. CI-direct access is necessary for one-off operations (debug exec, log streaming, emergency rollback) but should be the exception, not the rule.
Short-lived tokens for CI-direct access
When CI-direct access is necessary (debug, exec, log collection), the structural fix is short-lived tokens:
kubectl create token ci-debug --namespace=production \
--duration=10m --audience=api
The token’s lifetime is the operation’s lifetime. The
token expires when the operation completes; the runner
does not carry a credential between jobs. The
BoundServiceAccountToken projection in Kubernetes
1.21+ provides the same property for in-cluster
workloads.
Production discipline
- No cluster-admin tokens on CI runners. The token has the cluster’s blast radius; the runner is the wrong place for that.
- Namespace-scoped service accounts per workflow. Each workflow gets its own service account, its own RoleBinding, its own namespace.
kubectl auth can-ibefore every privileged action. Verification catches the wrong scope, the wrong namespace, the revoked token.- GitOps mediation as the default. CI writes to Git; the controller reconciles. CI-direct is the exception.
- Short-lived tokens for CI-direct access. Token lifetime is the operation’s lifetime.
Cross-course references
- Part XC-01 (What the CI platform can touch) maps the cluster surface in the wider blast radius.
- Part LII-01 (The Kubernetes CI discipline) covers the manifest-validation pattern GitOps mediation relies on.
- Part XXXVII (GitOps patterns) covers Argo CD and Flux reconciliation.
- Kubernetes for Production Sysadmins — Part LVI (DefenseInDepth) covers cluster-side defences.
Quiz
Knowledge check · 4 questions
Q1. A CI job applies a Deployment to the production namespace. What should the job verify before the apply, and why?
Q2. GitOps-mediated cluster access is structurally smaller than CI-direct cluster access because the CI never holds a cluster credential.
Q3. Explain why namespace-scoped service accounts are the structural ceiling for CI cluster access.
Q4. Diagnose a Kubernetes compromise that originated from a cluster-admin kubeconfig on a CI runner.
Team T's CI holds a kubeconfig with cluster-admin on the production EKS cluster, stored as a CI secret, never rotated. A fork PR opens; a malicious step reads the kubeconfig and exfiltrates it. The attacker uses the kubeconfig to list every Secret in every namespace and to exec into a production pod to read its environment, exfiltrating the production database URL and the payment-service signing key.
Passing score: 75%. Answers are checked in this browser.