29.8 RBAC and SSO in ArgoCD
Right, let’s talk about locking this thing down. You’ve got your shiny ArgoCD instance running, syncing your entire universe of applications. It’s a thing of beauty. It’s also a terrifyingly powerful system that, if left open, would let an intern accidentally delete every production namespace from here to next Tuesday. We’re not about that life. We’re going to talk about giving people the exact amount of power they need, and not a bit more, using ArgoCD’s built-in Role-Based Access Control (RBAC) and then connecting it to your real identity provider (SSO) so you don’t have to manage a separate set of credentials. Because let’s be honest, you’d lose the password file.
The Lay of the RBAC Land
First, understand that ArgoCD’s RBAC is a two-part system: policies and roles. People get this backwards all the time, so pay attention.
- Policies are the raw, gritty permissions. They’re statements like
p, role:developer, applications, get, */*, allow. This is the “what.” Think of them as the individual levers and buttons in the power plant. - Roles are groups of those policies. They’re human-friendly names like
developerorreadonlythat you assign to people or groups. This is the “who gets to press which buttons.”
You define policies in the argocd-rbac-cm ConfigMap. This is where the magic, and also the foot-shooting, happens.
# argocd-rbac-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
namespace: argocd
data:
# The policy rule itself. Let's break this one down:
# p, <role/user/group>, <resource>, <action>, <object>, <effect>
policy.csv: |
p, role:readonly, applications, get, */*, allow
p, role:readonly, applications, sync, */*, deny
p, role:developer, applications, get, */*, allow
p, role:developer, applications, sync, my-namespace/*, allow
p, role:admin, applications, *, */*, allow
g, my-oidc-group:developers, role:developer
g, my-oidc-group:admins, role:admin
This example creates three roles. The readonly role can only view apps. The developer role can view all apps but can only sync apps in a specific namespace (my-namespace/*). The admin role can do anything to any app. The last two lines use g (for “group”) to assign our SSO groups (which we’ll set up next) to these roles. This is the golden path: manage access via groups, not individual users.
Hooking Up Your Identity Provider (SSO)
Managing users inside ArgoCD is a chore. You’re already using Okta, Google Workspace, or Azure AD—something real. Let’s make ArgoCD use that. This is configured in the argocd-cm ConfigMap. The specifics vary by provider, but the OIDC dance is mostly the same.
Here’s a generic OIDC example. You’ll need to plug in your own values, which you get from your admin console. The most common pitfall? Getting the issuer URL wrong. It’s almost always the base URL of your provider, not some deep auth endpoint.
# argocd-cm.yaml (snippet)
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
data:
url: https://argocd.my-company.com # Your ArgoCD external URL
oidc.config: |
name: Okta
issuer: https://my-company.okta.com
clientID: $oidc-okta-client-id # Your OAuth app's client ID
clientSecret: $oidc-okta-client-secret # Your OAuth app's client secret
requestedScopes: ["openid", "profile", "email", "groups"] # Request 'groups' scope!
After applying this, your ArgoCD login screen will get a fancy “Login with Okta” button. The critical part here is requestedScopes: ["groups"]. This tells your IdP to include group memberships in the token it sends back to ArgoCD. ArgoCD can then map those groups to the roles we defined in policy.csv using the g, my-oidc-group:developers, role:developer syntax.
The Power (and Peril) of Project Scoping
Application-level RBAC is good, but sometimes you need to go further. This is where Projects come in. A Project in ArgoCD is a way to isolate groups of applications, enforce source repositories, and control destinations (clusters/namespaces). It’s a fantastic way to create hard boundaries.
Imagine you have a “banking” team and a “retail” team. You can create Projects to ensure they can’t deploy to each other’s clusters.
# project-banking.yaml
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: banking
namespace: argocd
spec:
description: Banking Team Applications
sourceRepos:
- 'https://git.my-company.com/banking/*' # They can only use their repos
destinations:
- namespace: banking-* # They can only deploy to namespaces starting with 'banking-'
server: https://kubernetes.default.svc
- namespace: banking-prod-us
server: https://production-k8s.us-east-1.amazonaws.com
clusterResourceWhitelist:
- group: ''
kind: Namespace # Allow them to create namespaces, but that's it
Now, you can combine this with RBAC to give the role:banking-admin role permissions only on applications within the banking project. This is the ultimate form of isolation. The pitfall? Over-constraining yourself. If you forget to add a new cluster or repo to the project’s allow list, your syncs will fail with a frustratingly permissions-based error. It’s a “good problem to have” from a security perspective, but it’ll annoy you in the moment.
The Default Policy and Why You Should Nuke It
Out of the box, ArgoCD has a default policy that is, and I say this with love, utterly bonkers. It’s this:
p, role:admin, *, *, allow
This gives the admin role (which, remember, is often mapped to your local admin user) the ability to do literally anything. It’s the keys to the kingdom. For a real production setup, you need to replace this. Start by disabling this default wildcard rule and explicitly defining what even your admins can do. The goal is least privilege, even for yourself. It’s a pain, but it forces you to think about the access model, which is the entire point of this exercise.