Skip to content
ioob.dev
Go back

ArgoCD Part 7 — RBAC and SSO: Team-Level Access Control

· 6 min read
ArgoCD Series (7/8)
  1. ArgoCD Part 1 — What Is ArgoCD
  2. ArgoCD Part 2 — Installation and Initial Setup
  3. ArgoCD Part 3 — Registering an Application
  4. ArgoCD Part 4 — Kustomize and Helm
  5. ArgoCD Part 5 — Sync Strategies: Auto Sync, Self Heal, and Order Control
  6. ArgoCD Part 6 — Multi-Cluster and ApplicationSet
  7. ArgoCD Part 7 — RBAC and SSO: Team-Level Access Control
  8. ArgoCD Part 8 — Practical Patterns: App of Apps, CI Integration, Troubleshooting
Table of contents

Table of contents

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:

  1. SSO/Dex verifies “who this person is” (authentication)
  2. RBAC determines “what this person can do” (authorization)
  3. 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.

Part 8: Practical Patterns


Related Posts

Share this post on:

Comments

Loading comments...


Previous Post
ArgoCD Part 6 — Multi-Cluster and ApplicationSet
Next Post
ArgoCD Part 8 — Practical Patterns: App of Apps, CI Integration, Troubleshooting