Entra
Hybrid identity for the lab: on-prem Active Directory (ad.devhome.cloud) is synced
up to Microsoft Entra ID (devhome.cloud, the same tenant/custom domain as
Azure). This is the tenant-side foundation that
ArgoCD SSO, Rancher SSO,
Vault SSO, and vCenter's Entra federation
all build on. Entirely on the Microsoft Entra ID Free tier — nothing here needs P1/P2.
See On-Prem Active Directory for everything that happens on-prem (domain controller, security groups, GPOs, per-VM automation) — this page is specifically about the sync bridge up to Entra ID and the app registrations that consume the groups it carries up.
flowchart LR
subgraph OnPrem["On-prem (dc VM)"]
AD["Active Directory<br/>ad.devhome.cloud<br/>app-*-admins groups"]
end
subgraph Cloud["Microsoft Entra ID<br/>devhome.cloud"]
Sync["Cloud Sync agent"]
Entra["Entra ID directory<br/>(read-only synced groups)"]
end
AD -- "Password Hash Sync" --> Sync --> Entra
Entra -- OIDC --> Apps["ArgoCD / Rancher / Vault / vCenter<br/>see their SSO pages"]
Entra -- "My Apps tiles" --> MyApps["myapps.microsoft.com<br/>see My Apps"]
Microsoft Entra Cloud Sync
Chose Cloud Sync over the classic Entra Connect Sync: lightweight agent, no dedicated sync server/SQL Server, and it's fully supported running directly on the domain controller (no separate member server required) — reasonable tradeoffs for a single-DC homelab forest.
Authentication method is Password Hash Sync — free, no standing infrastructure (unlike Pass-through Authentication agents or ADFS federation servers).
Agent install and registration
cloud_sync_setup.ps1.tftpl (run by a null_resource in
terraform/vsphere/compute/ad-dc-post.tf, after the DC's post-promotion reboot)
does the whole thing non-interactively:
Add-KdsRootKey -EffectiveTime ((Get-Date).AddHours(-10))— gMSA creation needs a KDS root key; backdating it skips the ~10h AD replication-convergence wait that a normal effective time would otherwise impose.- Installs the agent silently (
ProvisioningAgentSetup.exe /quiet), fetched over SMB directly from the TrueNAS share rather than Terraform'sfileprovisioner — WinRM's file transfer is chunked/base64-encoded and is extremely slow for a binary this size. Connect-AADCloudSyncAzureADusing a dedicated break-glass account (see below) — this cmdlet takes a plain username/password with no MFA challenge support, so it needs an account excluded from that requirement.Add-AADCloudSyncGMSA, thenAdd-AADCloudSyncADDomain -DomainName "ad.devhome.cloud".
Add-AADCloudSyncADDomain fails from a WinRM session, every time
Add-AADCloudSyncADDomain talks to the agent over a local WCF named pipe
(net.pipe://localhost/AADConnectProvisioningAgent). Confirmed by direct testing:
this pipe is consistently unreachable from a terraform-driven WinRM remote-exec
session specifically — a 12-attempt retry loop with a 15s delay never once
succeeded — but the exact same Connect-AADCloudSyncAzureAD +
Add-AADCloudSyncADDomain pair succeeds immediately when run inside a Scheduled
Task (a different session context than a WinRM remote shell). The script creates
a one-shot Scheduled Task for just that step, runs it, polls for a result file, and
tears the task down — rather than trying to run it directly in the calling
session.
The sync configuration itself isn't scriptable
Per Microsoft's own CLI docs, there's no PowerShell/CLI path for creating the actual sync job (OU scope, attribute mapping, enabling Password Hash Sync, starting the sync) — that's portal-only, one-time: Entra admin center → Identity → Hybrid management → Microsoft Entra Connect → Cloud sync → New configuration, picking "Active Directory to Microsoft Entra ID" specifically (not any "Entra ID to AD"/group-writeback option — those need P1; basic AD→Entra sync doesn't).
A portal banner will claim P1 is required — it isn't, for this direction
The Configurations page shows a P1-license banner regardless of which configuration type you're about to create. It only actually applies to the reverse direction (Entra ID → AD / group writeback). Ignore it and proceed with "Active Directory to Microsoft Entra ID" — don't start the P1 trial it offers.
Entra Connect (extract-only) + Seamless SSO
Use case: without this, every domain-joined machine still has to hit an actual sign-in prompt for Entra-backed apps (ArgoCD, Rancher, Vault, vCenter, My Apps) even though the user already authenticated once at Windows logon. Seamless SSO closes that gap — a user signed into a domain-joined machine gets transparently signed into those apps via Kerberos against the on-prem DC, no separate prompt, no separate typed password. It's a UX improvement layered on top of Cloud Sync, not a replacement for it — Cloud Sync still owns directory sync (which users/groups exist, password hashes); Seamless SSO only affects how an already-synced user's browser session gets authenticated.
Getting there needs one PowerShell module (AzureADSSO.psd1) that only ships inside the
classic Entra Connect installer (AzureADConnect.msi) — but actually installing that
MSI would stand up a second, competing directory-sync engine alongside Cloud Sync, which
is explicitly not wanted here. connect_sync_setup.ps1.tftpl resolves that by extracting
the MSI's files with msiexec /a (Microsoft's own documented way to unpack an MSI
without running any install actions) — no Entra Connect Sync service ever gets installed
or started, just the one module file pulled out of the package.
What's still manual after that (needs an interactive Entra Global Admin sign-in with
MFA, no unattended path exists): New-AzureADSSOAuthenticationContext,
Enable-AzureADSSOForest, Enable-AzureADSSO -Enable $true — run from the same
extracted module, one-time per forest.
entra_sso_gpo.ps1.tftpl then rolls out the two client-side settings Microsoft's
Seamless SSO quickstart requires, as their own domain-root-linked GPO (User
Configuration, not Computer Configuration — per Microsoft's own instructions):
- Adds
autologon.microsoftazuread-sso.com(notmicrosoftonline.com— an easy domain to get wrong, and wrong silently fails SSO rather than erroring) to the Intranet zone's Site to Zone Assignment List. - Enables "Allow updates to status bar via script" for the Intranet zone — one of the two settings the quickstart calls for.
The dedicated Graph management app + break-glass admin
Terraform's azuread provider (in terraform/azure/) needs something to
authenticate as before it can create anything — that something can't be created by
Terraform itself. terraform/vault/scripts/bootstrap-entra-graph-app.ps1 is the
one manual, interactive bootstrap step:
- Creates an app registration + service principal, grants it
Application.ReadWrite.All,Group.ReadWrite.All,User.ReadWrite.All,RoleManagement.ReadWrite.Directory, andDomain.Read.All, and admin-consents all of them programmatically (the API equivalent of the portal's "Grant admin consent" button). - Idempotent — safe to re-run after adding a new permission to the list; detects the existing app by display name and only adds what's missing, without rotating the existing client secret.
- Requires the signed-in account to be at least a Privileged Role Administrator —
consenting
RoleManagement.ReadWrite.Directoryis itself a privileged action. - Pins
-TenantIdexplicitly. Without it,Connect-MgGraphuses the "common" multi-tenant endpoint, which resolved to an MSA (personal Microsoft account) identity instead of the Entra ID org identity on this particular tenant — a known quirk on individual/personal Azure subscriptions, where the signup account can be both a consumer MSA and a directory member. Directory APIs likeGet-MgServicePrincipalreject MSA auth outright.
Output feeds terraform/vault's entra_client_id/entra_client_secret vars, stored
at devhome/entra.
Separately, terraform/azure/entra.tf creates a break-glass admin
(admin@devhome.cloud, Global Administrator, force_password_change = true) —
needed because the day-to-day portal sign-in for this tenant is a Guest-type
identity (a personal Microsoft account invited in), and Entra ID hard-blocks Guest
users from configuring Cloud Sync regardless of what role they hold. Cloud-only by
necessity, not preference: it has to exist in Entra ID before Cloud Sync can be
configured, so it can't be an on-prem account waiting to be synced. Once
psharma@devhome.cloud is synced and granted admin roles for daily use, this account
doubles as the break-glass Global Admin Microsoft recommends every tenant keep anyway.
Terraform-managed Entra ID resources
All in terraform/azure/entra.tf, authenticated via the azuread provider
(devhome/entra):
| Resource | Purpose |
|---|---|
data.azuread_group.ad_argocd_admins / ad_rancher_admins / ad_vault_users / ad_vcenter_admins |
Read-only lookups of the AD-mastered groups Cloud Sync carries up — see On-Prem Active Directory for where these are actually created and have members. Entra ID rejects writes to synced-in groups, which is exactly why these are data sources, not resource blocks — the cloud-only azuread_group.argocd_admins/rancher_admins resources this page used to describe were removed in the 2026-07 consolidation onto this AD-mastered tier. |
azuread_application/azuread_service_principal — argocd, rancher, vault, vcenter |
OIDC app registrations. group_membership_claims = ["SecurityGroup"] on all four; app_role_assignment_required = true on argocd/rancher/vcenter so only assigned groups can sign in at all — Vault deliberately leaves this unset (any tenant user can complete OIDC login; real authorization happens inside Vault via Vault SSO's identity group alias) |
azuread_app_role_assignment — one per app |
Grants the matching AD-synced group the built-in default access role on that app's service principal. Two independent purposes: gates sign-in for argocd/rancher/vcenter, and — regardless of that gating — is what makes an app's tile actually appear on myapps.microsoft.com for that group's members. Confirmed 2026-07-25: Vault's tile didn't appear at all until this assignment was added, even though its service principal already existed. |
azuread_user.cloud_sync_agent |
The Cloud Sync break-glass account (svc-cloudsync@devhome.cloud), Hybrid Identity Administrator role |
azuread_user.break_glass_admin |
The portal break-glass account (admin@devhome.cloud), Global Administrator role |
vault_kv_secret_v2.entra_sso |
Publishes generated client secrets + AD-synced group object IDs (ad_*_admins_group_id/ad_vault_users_group_id) to Vault for downstream consumers — see below |
homepage_url and login_url matter, independent of OIDC working correctly
Confirmed 2026-07-25: vCenter's tile failed to launch from My Apps ("App launch
failed", generic Entra-side error) purely because azuread_application.vcenter's
web block had no homepage_url set — direct navigation to vcsa.devhome.cloud
worked fine the whole time, since that never goes through My Apps' launch path at
all. See My Apps for the full pattern
across all four apps.
Vault paths
| Path | Owner | Consumed by |
|---|---|---|
devhome/entra |
terraform/vault |
terraform/azure's azuread provider auth |
devhome/entra-sso |
terraform/azure/entra.tf |
ArgoCD, Rancher, Vault, vCenter's SCIM provisioning script, cloud_sync_setup.ps1.tftpl (hybrid admin creds) — see their respective pages for how each one consumes it |
devhome/entra-sso follows the same "stack writes back a Terraform-generated
credential for something else to consume" pattern as devhome/docs-site — see
Vault Consumers.
What's manual, permanently
- Cloud Sync's actual sync job (OU scope, Password Hash Sync toggle, start) — portal-only, no CLI path exists.
- The Cloud Sync agent installer — no stable direct-download URL; Microsoft gates it behind "Accept terms & download" in the admin center.
- The dedicated Graph app's initial creation — something has to authenticate as a
real human the very first time;
bootstrap-entra-graph-app.ps1automates everything after that one interactive sign-in.