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.