Table of contents
- What Happens When Everyone Is Admin
- AppProject — Dividing Resource Boundaries
- RBAC — Defining Roles and Permissions
- SSO Integration with Dex
- Direct OIDC Integration
- Local User Management
- Access Control Design Principles
What Happens When Everyone Is Admin
When ArgoCD is first installed, a single admin account does everything. Configuration, app creation, syncing — all with one account. This isn’t much of a problem when the team is two or three people, but the story changes as the organization grows.
A backend team member might accidentally delete the frontend team’s Application, or an intern might hit Sync on production. If you don’t clearly define “who can do what and where,” the traceability and stability that are GitOps’ strengths start to crumble.
ArgoCD’s access control is built on three pillars. AppProject divides resource boundaries, RBAC defines role-based permissions, and SSO authenticates actual users. Let’s examine each one.
AppProject — Dividing Resource Boundaries
AppProject is a unit that logically groups Applications. The default default project can access all source repos and all target clusters — convenient, but it also means there are no restrictions.
Let’s create a team-specific project. We’ll restrict which source repos the backend team can access, which clusters and namespaces they can deploy to, and which resource types they can use.
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: backend-team
namespace: argocd
spec:
description: "Backend team project"
# Allowed source repositories
sourceRepos:
- "https://github.com/my-org/backend-*"
- "https://github.com/my-org/shared-libs.git"
# Allowed deployment targets
destinations:
- server: https://kubernetes.default.svc
namespace: backend-*
- server: https://10.0.1.100:6443
namespace: backend-*
# Allowed resource types for deployment
clusterResourceWhitelist:
- group: ""
kind: Namespace
namespaceResourceWhitelist:
- group: "apps"
kind: Deployment
- group: "apps"
kind: StatefulSet
- group: ""
kind: Service
- group: ""
kind: ConfigMap
- group: ""
kind: Secret
- group: "networking.k8s.io"
kind: Ingress
# Orphaned resource monitoring
orphanedResources:
warn: true
Wildcards can be used in sourceRepos, enabling pattern matching like backend-*. In destinations, namespaces also support wildcards, allowing only namespaces with the backend- prefix.
clusterResourceWhitelist and namespaceResourceWhitelist restrict which resource types can be deployed. Without these configured, the project can’t deploy any resources at all, so you need to explicitly allow the required resource types.
Conversely, if you want to block only specific resources, a blacklist approach is also available.
namespaceResourceBlacklist:
- group: ""
kind: ResourceQuota
- group: ""
kind: LimitRange
A frontend team project follows the same structure, just with different source repos and namespace ranges.
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: frontend-team
namespace: argocd
spec:
description: "Frontend team project"
sourceRepos:
- "https://github.com/my-org/frontend-*"
destinations:
- server: https://kubernetes.default.svc
namespace: frontend-*
clusterResourceWhitelist: []
namespaceResourceWhitelist:
- group: "apps"
kind: Deployment
- group: ""
kind: Service
- group: ""
kind: ConfigMap
- group: "networking.k8s.io"
kind: Ingress
The frontend team’s clusterResourceWhitelist is set to an empty array. If the team doesn’t need to create cluster-level resources like Namespaces, it’s safer to block them entirely.
RBAC — Defining Roles and Permissions
If AppProject defines “which resources can be accessed,” RBAC defines “who can perform which actions.” ArgoCD’s RBAC is Casbin-based and managed in the argocd-rbac-cm ConfigMap.
The basic format of an RBAC policy is:
p, <subject>, <resource>, <action>, <object>, <effect>
Subject is a role or user, resource is the ArgoCD resource type (applications, repositories, clusters, etc.), action is the operation to perform (get, create, update, delete, sync, override, action), object is the target scope, and effect is allow or deny.
An actual configuration makes it clearer.
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
namespace: argocd
data:
policy.default: role:readonly
policy.csv: |
# Backend team developer role
p, role:backend-dev, applications, get, backend-team/*, allow
p, role:backend-dev, applications, sync, backend-team/*, allow
p, role:backend-dev, applications, action/*, backend-team/*, allow
p, role:backend-dev, logs, get, backend-team/*, allow
# Backend team lead role (with create/delete permissions)
p, role:backend-lead, applications, *, backend-team/*, allow
p, role:backend-lead, repositories, get, *, allow
p, role:backend-lead, clusters, get, *, allow
p, role:backend-lead, logs, get, backend-team/*, allow
# Frontend team developer role
p, role:frontend-dev, applications, get, frontend-team/*, allow
p, role:frontend-dev, applications, sync, frontend-team/*, allow
p, role:frontend-dev, logs, get, frontend-team/*, allow
# Infra team (full admin)
p, role:infra-admin, *, *, *, allow
# Map roles to groups
g, backend-devs, role:backend-dev
g, backend-leads, role:backend-lead
g, frontend-devs, role:frontend-dev
g, infra-team, role:infra-admin
policy.default: role:readonly grants read-only permissions to users who aren’t explicitly assigned a role. Changing the default to role:'' (no permissions) makes management more strict.
The g lines connect groups to roles. Users belonging to the backend-devs group automatically have role:backend-dev permissions. These group names match the group information coming from the IdP during SSO integration.
In backend-team/*, backend-team is the AppProject name. The object field in RBAC follows the format <project>/<application>, so backend-team/* means all Applications in the backend-team project. To target a specific Application, use something like backend-team/my-api.
SSO Integration with Dex
Let’s look at the role Dex plays between GitHub OAuth and ArgoCD, and the steps from when a user clicks the login button to when a JWT token is created.
sequenceDiagram
participant U as User Browser
participant A as argocd-server
participant D as argocd-dex-server
participant GH as GitHub OAuth
U->>A: Access /login
A->>D: Redirect to /api/dex
D->>GH: OAuth authorize request
GH-->>U: Consent screen
U->>GH: Approve
GH-->>D: Deliver code (callback)
D->>GH: Exchange code + client secret for token
GH-->>D: access_token + user/team info
D->>D: Generate JWT (including groups claim)
D-->>A: Return JWT
A-->>U: Login complete (session cookie)
ArgoCD can manage local users on its own, but as the team grows, it becomes practical to integrate with an external authentication system. ArgoCD has Dex built in, allowing connection to various IdPs (Identity Providers).
Dex is a lightweight authentication service that acts as an OIDC (OpenID Connect) broker. It supports many authentication sources including GitHub, GitLab, Google, LDAP, and SAML.
Let’s set up SSO integration using GitHub OAuth. First, you need to create an OAuth App in GitHub. Set the Authorization callback URL to https://argocd.example.com/api/dex/callback.
Enter the Client ID and Secret from the OAuth App into the ArgoCD configuration.
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
data:
url: https://argocd.example.com
dex.config: |
connectors:
- type: github
id: github
name: GitHub
config:
clientID: $dex.github.clientID
clientSecret: $dex.github.clientSecret
orgs:
- name: my-org
teams:
- backend-devs
- backend-leads
- frontend-devs
- infra-team
The Client ID and Secret are not placed directly in the ConfigMap. By using the $dex.github.clientID format, ArgoCD retrieves the value from the corresponding key in the argocd-secret Secret.
kubectl -n argocd patch secret argocd-secret -p \
'{"stringData": {
"dex.github.clientID": "actual-Client-ID",
"dex.github.clientSecret": "actual-Client-Secret"
}}'
The orgs and teams configuration is the key part. It restricts login to users belonging to specific teams in the GitHub organization, and these team names match the group names in the RBAC policy. For example, a user belonging to the my-org/backend-devs team on GitHub automatically gets role:backend-dev permissions.
Direct OIDC Integration
You can also integrate OIDC directly without Dex. If your organization is already running an IdP like Keycloak, Okta, or Azure AD, this approach is more straightforward.
Using Keycloak as an example, create an ArgoCD client in Keycloak and configure the necessary information.
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
data:
url: https://argocd.example.com
oidc.config: |
name: Keycloak
issuer: https://keycloak.example.com/realms/my-realm
clientID: argocd
clientSecret: $oidc.keycloak.clientSecret
requestedScopes:
- openid
- profile
- email
- groups
Including groups in requestedScopes is important. The IdP needs to include the user’s group information in the JWT token for ArgoCD’s RBAC group-based permission matching to work.
On the Keycloak side, you need to add a groups mapper to the Client Scope so the group claim is included in the token. The group claim name may differ across IdPs, and ArgoCD can handle this mapping.
oidc.config: |
name: Keycloak
issuer: https://keycloak.example.com/realms/my-realm
clientID: argocd
clientSecret: $oidc.keycloak.clientSecret
requestedScopes:
- openid
- profile
- email
- groups
requestedIDTokenClaims:
groups:
essential: true
Local User Management
Before adopting SSO, or when service accounts for CI pipelines are needed, local users can be used.
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
data:
accounts.ci-bot: apiKey,login
accounts.ci-bot.enabled: "true"
apiKey grants permission to generate API tokens, and login grants UI login permission. Since a CI bot doesn’t need UI login, granting only apiKey is sufficient.
After creating the account, set the password and generate a token.
argocd account update-password --account ci-bot --new-password 'new-password'
argocd account generate-token --account ci-bot
RBAC policies can be applied to local users as well.
p, ci-bot, applications, sync, */*, allow
p, ci-bot, applications, get, */*, allow
It’s common practice to give CI bots only sync and read permissions, without create or delete permissions.
Access Control Design Principles
Summarizing what we’ve covered, ArgoCD’s access control works hierarchically:
- SSO/Dex verifies “who this person is” (authentication)
- RBAC determines “what this person can do” (authorization)
- AppProject restricts “where this Application can access” (resource boundaries)
A few principles to keep in mind when designing access control. First, follow the principle of least privilege. Set the default policy to role:readonly or an empty role, and explicitly grant only the required permissions. Second, manage by groups. Assigning roles directly to individual users means updating them every time someone changes — using IdP groups is much easier to manage. Finally, add an extra layer of restriction for production access. Consider granting sync permissions for production projects only to senior engineers or adding a manual approval process.
With RBAC and SSO properly configured, you can track who performed what action and prevent incidents caused by mistakes. In the next part, we’ll cover patterns frequently used in real-world ArgoCD operations: App of Apps, repository strategies, CI integration, and troubleshooting know-how.


Loading comments...