Skip to content

Vault SSO

Vault's UI/CLI login authenticates against Microsoft Entra ID via its native oidc auth method, same tenant/app-registration foundation as ArgoCD and Rancher SSO — see Entra for that shared setup, and On-Prem Active Directory for where app-vault-users itself is created and managed.

A deliberately different authorization model

Unlike ArgoCD/Rancher/vCenter, Vault's service principal does not set app_role_assignment_required — any tenant user can complete OIDC login and reach Vault's default policy. Real authorization happens inside Vault, via group-based policy assignment, not at the Entra sign-in gate:

# terraform/vault/entra-oidc.tf
resource "vault_jwt_auth_backend" "oidc" {
  path               = "oidc"
  type               = "oidc"
  oidc_discovery_url = "https://login.microsoftonline.com/${data.vault_kv_secret_v2.entra_sso.data["tenant_id"]}/v2.0"
  oidc_client_id     = data.vault_kv_secret_v2.entra_sso.data["vault_oidc_client_id"]
  oidc_client_secret = data.vault_kv_secret_v2.entra_sso.data["vault_oidc_client_secret"]
  default_role       = "default"
}

resource "vault_jwt_auth_backend_role" "oidc_default" {
  backend        = vault_jwt_auth_backend.oidc.path
  role_name      = "default"
  role_type      = "oidc"
  user_claim     = "email"
  groups_claim   = "groups"
  token_policies = ["default"]
  oidc_scopes    = ["openid", "profile", "email"]

  allowed_redirect_uris = [
    "https://vault.devhome.cloud:8200/ui/vault/auth/oidc/oidc/callback",
    "http://localhost:8250/oidc/callback", # vault login -method=oidc CLI
  ]
}

token_policies = ["default"] is what everyone gets just for logging in — Vault's built-in default policy, effectively nothing beyond the bare minimum. Real access comes from the identity group alias below.

Group-to-policy mapping via an external identity group

Members of app-vault-users (mastered in on-prem AD, synced up by Cloud Sync) get the devhome-read policy (defined in terraform/vsphere/compute/vault.tf — shared across this repo's ESO reader roles too) via a Vault external identity group:

resource "vault_identity_group" "vault_users" {
  name     = "app-vault-users"
  type     = "external"
  policies = ["devhome-read"]
}

resource "vault_identity_group_alias" "vault_users" {
  name           = data.vault_kv_secret_v2.entra_sso.data["ad_vault_users_group_id"]
  mount_accessor = vault_jwt_auth_backend.oidc.accessor
  canonical_id   = vault_identity_group.vault_users.id
}

The alias name is the group's Object ID, never its display name

Same gotcha as ArgoCD's RBAC mapping — Entra ID's groups claim in the ID token contains each group's Object ID, never app-vault-users the string. vault_identity_group_alias.name has to be the object ID (ad_vault_users_group_id from devhome/entra-sso), or Vault's identity engine never links a logged-in user to the group at all — they'd silently fall back to default with no policy, no error.

App registration and My Apps

azuread_application.vault + its service principal (terraform/azure/entra.tf): group_membership_claims = ["SecurityGroup"], preferred_single_sign_on_mode = "oidc", login_url/homepage_url both set to https://vault.devhome.cloud:8200. Client secret published to devhome/entra-sso as vault_oidc_client_id/vault_oidc_client_secret.

Vault's service principal existed but had no way to show up on My Apps

Confirmed 2026-07-25: applying azuread_service_principal.vault for the first time hit A resource with the ID "..." already exists — a service principal already existed in the tenant (likely auto-created alongside the application registration at some point), but it had never had login_url, owners, or preferred_single_sign_on_mode set, and had no azuread_app_role_assignment at all. Imported the existing SP into state, then applied those three attributes plus an assignment for app-vault-users — only then did Vault's tile appear. See My Apps for the general pattern this fits.