Securing ArgoCD from the outside in: SSO and OIDC authentication, project-based RBAC authorization, API and network security, and even image signature verification and policy enforcement with Kyverno.

In episode 22 we built full observability — and honest observability will reveal something uncomfortable: ArgoCD holds an enormous set of keys. It stores repo credentials, cluster credentials, and can apply any resource to any cluster. A hacked ArgoCD is a hacked cluster. In this episode we discuss security best practices, from authentication to supply chain security, to make sure those keys are only used by those entitled to them.
Why does this matter? ArgoCD is a single point of control — that's exactly its strength, and at the same time its biggest attack surface. Zero trust starts to apply here: give the smallest sufficient access, verify identity via SSO, and don't trust images blindly. Each layer we add in this episode raises the cost of attack, and in security, a high attack cost is the best defense.
The first step is answering "who are you?". ArgoCD provides several mechanisms.
Built-in users (admin and those created via argocd-cm) are fine for a lab, not for a team. For production, connect the company's identity provider:
| Method | Mechanism | Suitable for |
|---|---|---|
| Built-in | Username/password in argocd-cm | Lab, initial bootstrap |
| Dex (SSO) | Connector combination: OIDC, SAML, LDAP, GitHub, GitLab | Organizations with a mixed identity provider |
| Direct OIDC | Okta, Auth0, Google directly as oidc.config | Teams already fully on OIDC |
| SAML | Via the Dex connector | Enterprise companies |
| LDAP | Via the Dex connector | Traditional AD infrastructure |
SSO configuration is done in argocd-cm, and because ArgoCD itself is GitOps-managed, argocd-cm should be a manifest in the repo:
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
data:
url: https://argocd.org.dev
oidc.config: |
name: Okta
issuer: https://org.okta.com
clientID: <client-id>
clientSecret: $oidc.okta.clientSecret
requestedScopes: ["openid", "profile", "email", "groups"]
accounts.andri: login
accounts.andri.enabled: "true"The best practice key here: disable local login once SSO is running. Keep only an emergency admin account, store its password in a vault, and rotate it after use. A still-active local login is a backdoor that doesn't need SSO.
Authentication answers "who"; authorization answers "may do what". ArgoCD's RBAC model has three layers:
argocd-rbac-cm) — maps users/groups to roles.role:admin (full), role:readonly, role:ci.apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
namespace: argocd
data:
policy.csv: |
p, role:readonly, applications, get, */*, allow
p, role:readonly, clusters, get, *, allow
g, andri, role:readonly
g, dev-platform, role:admin
policy.default: role:readonlyThe important last line: policy.default: role:readonly. A default deny makes every user without a role read-only — destructive changes require an explicit role. This follows the least privilege principle: give minimal access, raise it when needed.
Warning
Be careful with global role:admin. That role can read all secrets and sync anything. For small teams it's practical, but as the organization grows, limit global admin to the platform team, and give developers access through project roles — for example an api-deployer role on the api project that can only sync applications in that project.
Project roles are the way to hand over part of the control without handing over everything. Create a role in the project:
argocd proj role add-role api api-deployer
argocd proj role add-policy api api-deployer \
--action sync --permission allow --object '*'
argocd proj role generate-token api api-deployerThe generated token is a JWT that CI can use for argocd app sync on the api project only — without ever holding admin credentials.
The ArgoCD API is an attack surface: it accepts credentials, tokens, and commands. Mandatory practices:
argocd tokens must have an exp — avoid eternal tokens. Rotate them when a team member leaves.admin. Create dedicated CI users, give them a minimal project role (sync only), and use that token in pipelines.argocd login argocd.org.dev --sso
argocd account generate-token --account ci-bot
argocd app sync api --auth-token <token>UI and API access must be encrypted end-to-end. Use an Ingress with certificates from cert-manager, and make sure argocd-server runs behind it with --server.insecure=false (the default). For Git repos:
| Method | Advantages | Risks |
|---|---|---|
| SSH key (deploy key) | Access to only one repo, cannot commit (read-only when configured) | Keys need management and rotation |
| HTTPS token (PAT) | Can be scope-limited and expiring | Token can leak through logs if not careful |
For private repos, a read-only SSH deploy key is the safest choice for ArgoCD: ArgoCD only needs to read. Store the key as a repository secret in ArgoCD (encrypted), and if you use CI that writes manifests, use a separate PAT with minimal scope and a short lifetime.
ArgoCD must reach Git and clusters, but there's no reason for other pods to reach ArgoCD. Apply a NetworkPolicy that restricts:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: argocd-server-allow-ingress
namespace: argocd
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: argocd-server
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: argocd-serverThe last layer: making sure what runs in the cluster is truly what the team intended.
With cosign, images are signed during CI. ArgoCD Image Updater (episode 16) or an admission controller can verify the signature before a pod is allowed to run. An example verification with Kyverno:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-image-signature
spec:
validationFailureAction: Enforce
rules:
- name: check-cosign
imageVerification:
authorities:
- name: org-cosign
keyless:
subject: "ci@org.dev"
issuer: "https://token.actions.githubusercontent.com"
match:
resources:
kinds:
- PodOPA/Gatekeeper and Kyverno block manifests that violate policy — for example images without tags, resources without limits, or hostPath. Combine with GitOps: policies run as admission webhooks when ArgoCD applies resources, so bad manifests are rejected from the start.
Scanning images in CI (Trivy, from episode 18) is the first defense; further scanning in the cluster with ArgoCD Image Updater + Cosign or a controller that periodically checks the image registry is the second layer. A healthy rule: CI fails on critical CVEs, the admission controller blocks unsigned images, and policy forces every image to have a digest.
This episode locked down ArgoCD from the outside in: SSO/OIDC/Dex authentication with local login disabled, layered RBAC with default deny and project roles, API security with expiring JWTs and CI service accounts, network security with TLS, read-only deploy keys, and NetworkPolicy, and supply chain security with cosign signatures, Kyverno, and vulnerability scanning.
The points you should take with you:
policy.default: readonly makes destructive changes an explicit choice.Good security produces one other equally important asset: evidence. In the next episode 24 we discuss compliance & audit — audit trails, SOC 2, HIPAA, and PCI-DSS requirements, reporting, and policy as code. See you in episode 24!