Argo CD has become the de facto GitOps controller for Kubernetes. It watches Git repositories and continuously reconciles cluster state to match declared manifests. That reconciliation loop is powerful — and dangerous if misconfigured. An attacker who compromises an Argo CD instance effectively controls every cluster it manages.
This guide covers the security configurations that matter for production Argo CD deployments.
The Argo CD Threat Model
Argo CD has privileged access to both your Git repositories (where it reads manifests) and your Kubernetes clusters (where it applies them). The primary threats are:
- Argo CD server compromise: An attacker gains access to the Argo CD UI or API and deploys malicious manifests.
- Git repository compromise: An attacker pushes malicious manifests to a watched repository, and Argo CD faithfully deploys them.
- RBAC bypass: Overly permissive Argo CD RBAC allows users to deploy to clusters or namespaces they should not access.
- Secret exposure: Argo CD stores repository credentials and cluster credentials. If these are exposed, the attacker has access to everything.
- Privilege escalation via Kubernetes RBAC: Argo CD's service account has broad Kubernetes permissions. An attacker who can create arbitrary manifests can escalate privileges.
RBAC Configuration
Argo CD's built-in RBAC is separate from Kubernetes RBAC. Configure both.
# argocd-rbac-cm ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
namespace: argocd
data:
policy.default: role:readonly
policy.csv: |
p, role:dev-team, applications, get, dev/*, allow
p, role:dev-team, applications, sync, dev/*, allow
p, role:ops-team, applications, *, */*, allow
p, role:security, applications, get, */*, allow
g, dev-group, role:dev-team
g, ops-group, role:ops-team
g, security-group, role:security
Key principles:
- Default to readonly: Set
policy.defaulttorole:readonly. Users without explicit grants can view but not modify. - Scope by project: Argo CD projects map to teams or environments. Restrict each role to specific projects.
- Separate sync and create permissions: A developer might need to sync (redeploy) an existing application but should not be able to create new ones targeting production clusters.
- Security team gets read access everywhere: Your security team needs visibility across all projects and clusters without write access.
SSO Integration
The built-in admin account should only be used for initial setup. Integrate with your identity provider immediately:
# argocd-cm ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
data:
url: https://argocd.example.com
dex.config: |
connectors:
- type: oidc
id: okta
name: Okta
config:
issuer: https://example.okta.com
clientID: $dex.okta.clientID
clientSecret: $dex.okta.clientSecret
insecureEnableGroups: true
scopes:
- openid
- profile
- email
- groups
After SSO is working:
- Disable the admin account:
admin.enabled: "false"inargocd-cm. - Map SSO groups to Argo CD RBAC roles.
- Enforce MFA at the identity provider level.
- Set short session timeouts.
Secret Management
Argo CD manages secrets for repository access and cluster credentials. Do not let it manage application secrets directly. Use one of these patterns:
Sealed Secrets
Encrypt secrets client-side. Only the cluster's Sealed Secrets controller can decrypt them:
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: database-credentials
namespace: production
spec:
encryptedData:
password: AgBy3i4OJSWK+PiTySYZZA9rO43...
The encrypted manifest is safe to commit to Git. Argo CD deploys it, and the Sealed Secrets controller decrypts it in-cluster.
External Secrets Operator
Pull secrets from external providers (AWS Secrets Manager, HashiCorp Vault, etc.) at deployment time:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-credentials
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets
kind: ClusterSecretStore
target:
name: database-credentials
data:
- secretKey: password
remoteRef:
key: /production/database/password
This keeps secrets entirely out of Git. Argo CD deploys the ExternalSecret resource, and the operator populates the actual Secret.
Project-Level Restrictions
Argo CD Projects define what an application can do. Use them to enforce boundaries:
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: production
namespace: argocd
spec:
description: Production applications
sourceRepos:
- 'https://github.com/example/production-manifests.git'
destinations:
- namespace: 'production-*'
server: 'https://production-cluster.example.com'
clusterResourceWhitelist:
- group: ''
kind: Namespace
namespaceResourceBlacklist:
- group: ''
kind: ResourceQuota
- group: ''
kind: LimitRange
roles:
- name: deployer
policies:
- p, proj:production:deployer, applications, sync, production/*, allow
Critical restrictions:
- sourceRepos: Limit which Git repositories can be used as manifest sources. Never allow
*. - destinations: Restrict which clusters and namespaces applications can deploy to.
- clusterResourceWhitelist: Control which cluster-scoped resources (namespaces, CRDs, ClusterRoles) applications can create. An unrestricted application could create ClusterRoleBindings granting itself admin access.
- namespaceResourceBlacklist: Prevent applications from modifying resource quotas or other administrative resources.
Network Security
Argo CD's server component exposes a gRPC/HTTPS API. Lock it down:
- Run the Argo CD server behind an ingress controller with TLS termination.
- Use NetworkPolicies to restrict which pods can communicate with Argo CD components.
- The repo-server (which clones Git repos) needs outbound access to your Git provider. Restrict all other outbound traffic.
- The application controller needs access to target clusters. Use network segmentation to limit this to specific cluster API endpoints.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: argocd-server-policy
namespace: argocd
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: argocd-server
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- port: 8080
Audit Logging
Enable Argo CD's audit logging and forward logs to your SIEM:
- Every sync operation (who triggered it, what changed).
- Application creation and deletion.
- RBAC changes.
- Repository and cluster credential modifications.
- Login attempts and session creation.
Argo CD emits Kubernetes events for application state changes. Capture these alongside the Argo CD server logs for a complete audit trail.
Webhook Security
If you use webhooks to trigger Argo CD syncs on Git push:
- Validate webhook signatures (GitHub, GitLab, and Bitbucket all support HMAC signatures).
- Restrict webhook endpoints to your Git provider's IP ranges.
- Rate-limit webhook processing to prevent denial of service.
How Safeguard.sh Helps
Safeguard.sh monitors your Argo CD configuration and the manifests it deploys for security issues. It scans Git repositories that Argo CD watches for misconfigurations, excessive permissions, and missing security controls in Kubernetes manifests. When combined with SBOM tracking, Safeguard.sh maps the complete path from source code through CI/CD to deployed containers — giving your security team full supply chain visibility across every cluster Argo CD manages.