Skip to content

ArgoCD SSO

ArgoCD authenticates against Microsoft Entra ID via OIDC instead of its own local user store. See Entra for the tenant-side setup (app registration, groups, Vault paths) this depends on, and On-Prem Active Directory for where app-argocd-admins itself is created and managed — it's an on-prem AD group synced up by Cloud Sync, not a cloud-only Entra group.

App registration

azuread_application.argocd (terraform/azure/entra.tf): group_membership_claims = ["SecurityGroup"], and app_role_assignment_required = true on its service principal so only members of app-argocd-admins can sign in at all. Client secret published to devhome/entra-sso in Vault. The matching azuread_app_role_assignment is also what makes ArgoCD's tile appear on My Apps for that group's members.

Four gotchas, in the order they surfaced

groups is not a valid Entra ID scope

AADSTS650053: ... asked for scope 'groups' that doesn't exist on the resource .... requestedScopes should be openid, profile, email only — the groups claim comes from the app registration's group_membership_claims setting, not from requesting a groups scope (that's an Okta/Auth0 convention that doesn't apply here).

Custom secret names need a specific label

AADSTS7000215: Invalid client secret provided. ArgoCD's argocd-cm resolves $<secretName>:<key> against any Secret in the namespace — but only if that Secret carries app.kubernetes.io/part-of: argocd. Without the label, the reference silently fails to resolve. Also: ArgoCD doesn't detect Secret content changes on its own — argocd-server needs a rollout restart after the secret is created/updated.

RBAC group mapping matches by object ID, not name

g, argocd-admins, role:admin in argocd-rbac-cm never matches anything — Entra ID's groups claim contains each group's Object ID, never its display name. Has to be g, <app-argocd-admins-object-id>, role:admin (object ID from data.azuread_group.ad_argocd_admins, or vault kv get -field=ad_argocd_admins_group_id devhome/entra-sso).

ArgoCD self-manages its own Helm release — a second, competing source of truth

terraform/vsphere/compute's helm_release.argocd_dke_mgmt (reading k8s/dke-mgmt/values/argocd-values.yaml) turned out to be dead weight: ArgoCD has been self-managing its own install via an Application object (k8s/dke-mgmt/argo-cd-app.yaml, syncPolicy.automated.selfHeal: true) with Helm values embedded inline in that manifest — a completely separate values source from what Terraform applies. All the OIDC/RBAC config had to be applied to argo-cd-app.yaml and picked up via kubectl apply -f (that Application isn't itself synced from git — it's a one-time-applied bootstrap manifest), not via Terraform, or ArgoCD's own self-heal reverted the change on its next sync cycle. The Terraform helm_release resource is a known cleanup item, not yet removed.

Where the config actually lives

argo-cd-app.yaml's inline spec.source.helm.values carries both pieces:

configs:
  cm:
    oidc.config: |
      name: Microsoft Entra ID
      issuer: https://login.microsoftonline.com/<tenant-id>/v2.0
      clientID: <argocd-app-client-id>
      clientSecret: $argocd-oidc-secret:oidc.entra.clientSecret
      requestedScopes:
        - openid
        - profile
        - email
  rbac:
    policy.csv: |
      g, <argocd-admins-object-id>, role:admin
    policy.default: role:readonly

Secret delivery

The OIDC client secret is delivered via an ExternalSecret (k8s/clusters/dke-mgmt/external-secrets.yaml, argocd-oidc-secret) — same ESO/Vault pattern as repo-devhome and cert-manager's secret-azuredns-config (see Vault Consumers) — rather than a Terraform-managed kubernetes_secret_v1, so it rotates automatically and doesn't require a second Terraform apply whenever the Entra client secret rotates. Remember the app.kubernetes.io/part-of: argocd label from the gotcha above — the ExternalSecret sets it via target.template.metadata.labels.

Group membership

Managed entirely on-prem now, not via a UPN-list Terraform variable — app-argocd-admins is one of the ad_security_groups in terraform/vsphere/compute/variables.tf, created by create_ad_groups.ps1.tftpl and synced up by Cloud Sync. Add or remove a member there (or directly in ADUC) and it carries up to Entra automatically — see On-Prem Active Directory.