Git, CI/CD & GitOpsLXXII · GitOps FoundationsFoundations
Pulled automatically — the controller reaches out to Git
What you'll learn
- Explain why a pull-based controller inverts the traditional CI/CD trust boundary
- Distinguish the pull model from the push model in terms of credentials, blast radius, and audit
- Identify what credentials a GitOps controller requires and what it does not
- Recognise the operational signals of a healthy and unhealthy pull loop
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
The third OpenGitOps principle - pulled automatically - is the one that changes the security model. In a traditional CI/CD pipeline, the runner authenticates to the cluster and pushes state. In a GitOps model, the controller inside the cluster authenticates to Git (or to a Git proxy) and pulls state. The direction is inverted; the trust boundary moves accordingly.
The contrast in one picture:
flowchart LR
subgraph Push["Push-based CD"]
CI1["CI runner"] -->|"cluster credentials"| K1["Cluster API"]
end
subgraph Pull["Pull-based GitOps"]
G["Git repository"]
C["Controller in cluster"] -->|"read-only token"| G
C -->|"in-cluster write"| K2["Cluster API"]
end
In the push model, the CI runner holds credentials capable of writing to the cluster. An attacker who compromises the CI runner, or a malicious CI job that exfiltrates the credentials, can write directly to production. In the pull model, the CI runner does not hold cluster credentials at all; the only thing it can do is commit to Git, and the controller inside the cluster decides what to apply based on what it reads from Git.
What the pull model buys
Three operational properties:
- Reduced blast radius for an external compromise. A compromised CI runner can push commits to Git, but it cannot push to the cluster directly. The controller must read the commit, observe the diff, and apply it - and the controller has its own admission controls, rate limits, and audit trail.
- No long-lived cluster credentials in CI. The CI runner
does not need a kubeconfig with
cluster-admin. The runner may not need any kubeconfig at all. The credentials exist only in the cluster’s controller pod, which the cluster itself protects. - Drift correction without redeploys. The controller is always running, not just at the end of a CI pipeline. A configuration drift introduced by any actor - manual edit, bug, attacker - is corrected by the next reconciliation tick.
What the pull model requires
The controller is a long-running process in the cluster. It needs:
- Read access to the Git repository. Either a deploy key,
a fine-grained personal access token, or a Git proxy (one
common pattern is
gitrepository-controllerin Argo CD or Flux). - Write access to the cluster API. Service account tokens, IRSA, Workload Identity, or a kubeconfig mounted into the controller pod.
- A reconciliation interval. The cadence at which the controller re-reads Git and re-applies. Three minutes is a common default; ten seconds is the lower bound.
flux get all
This is the Flux CLI command that lists every GitOps resource
the controller is reconciling - GitRepositories,
Kustomizations, HelmReleases, and the alerts or events
each one has produced.
Where the trust boundary actually sits
In the push model, the trust boundary is between CI and the cluster. The cluster must trust CI; CI must hold cluster credentials.
In the pull model, the trust boundary is between Git and the controller. The controller must trust Git; Git must hold a deploy key. The cluster’s write API is no longer reachable from outside the cluster’s network - it is reachable only from inside the cluster, by the controller pod, on the in-cluster network.
A practical consequence: a Kubernetes cluster running GitOps can run with the API server’s internet-facing endpoint disabled, because the controller does not need to be reached from outside - it reaches out. The cluster becomes a sealed system that pulls its configuration from a known source.
How often does the controller pull?
Three patterns:
- Polling. The controller wakes every N seconds and
git ls-remoteagainst the repo. Simple, robust, works against any Git host. Default in Argo CD and Flux. - Webhook. The Git host POSTs to the controller’s webhook endpoint when a ref is updated. Lower latency, requires the Git host to be reachable from the controller.
- Push reconciliation. Some controllers expose a CLI command to force a reconciliation; CI can call it after a merge. Useful for back-pressure on slow polling intervals.
The polling interval is the system’s reconciliation latency ceiling. A three-minute poll means a commit can take up to three minutes to be applied. A thirty-second poll means a commit is usually applied in seconds.
Production discipline
- The controller’s credentials are cluster-scoped. The controller needs write access to the cluster API; that access must be on a dedicated service account with a tight RBAC role. Cluster-admin is a placeholder, not a production setting.
- The repository’s deploy key is read-only. The controller never needs to write to the repository. A read-only deploy key (or a fine-grained token with read only) limits what a controller compromise can exfiltrate.
- The reconciliation interval matches the change cadence. A polling interval of 3 minutes is fine for hourly deploys; a polling interval of 30 seconds is required for hot-fixes that need to land within the hour.
Cross-course references
- Ansible for Production Sysadmins - Part XXXIX covers the pull-mode Ansible Tower / AWX model; the trust boundary is inverted in the same way.
- Terraform for Production Sysadmins - Parts XXVIII- XXX cover the operator-pattern Terraform run, which is the pull analogue for IaC.
- Kubernetes for Production Sysadmins - Parts on controllers cover the in-cluster loop pattern the GitOps controller relies on.
Quiz
Knowledge check · 4 questions
Q1. What is the primary security consequence of the GitOps pull model compared to a traditional CI/CD push model?
Q2. In a pull-based GitOps model, the CI runner must hold a kubeconfig with cluster-admin permissions to apply changes after a merge.
Q3. Name three operational properties the pull model buys over the push model.
Q4. Diagnose why a CI runner compromise did not produce a cluster compromise in a pull-based GitOps model.
Team T runs Flux in production. A malicious dependency in a CI job executes during a build, exfiltrates environment variables, and pivots. The CI runner's environment variables include `GITHUB_TOKEN` (a fine-grained personal access token with read-write to the manifests repository) and `KUBECONFIG` (an old token from a previous push-based pipeline, never rotated). Flux's controller pod is in the cluster, with read-only deploy keys for the same repository.
Passing score: 75%. Answers are checked in this browser.