Identity · Runbook
Azure identity baseline for landing zones
A practical identity baseline for Azure landing zones: admin separation, PIM, Conditional Access, workload identities and secrets, written as a runbook.
- Effort: 1–2 days
- Risk: Medium
- Rollback: Partial
Applies to: Entra ID · PIM · RBAC
- Split admin identities from daily identities and enforce least privilege through PIM.
- Group-based RBAC + Conditional Access + logging is the bare minimum; portal role assignments do not scale.
- Day 0: create admin groups, PIM-eligible roles, CA baseline, break-glass. Day 30: convert noisy recommendations into policy, federate workload identities. Quarterly: review PIM activity, CA logs, exemptions.
- Secrets belong in Key Vault with private endpoints; prefer managed identities and OIDC for workloads.
- Treat the directory like code: Terraform for groups/assignments, Bicep/Policy for guardrails, KQL for validation.
- Access: Privileged Role Administrator in Entra ID for PIM configuration, and User Access Administrator (or Owner) at the management group scope for the Azure RBAC eligibility. These are two different control planes — you need rights on both.
- Licensing: PIM and risk-based Conditional Access require Entra ID P2. The rest of this baseline works on P1.
- Tooling: Terraform 1.7+ with
azurerm4.x or 5.x,azuread3.x and thehashicorp/timeprovider. The example below was checked against the azurerm 5.x and azuread 3.x docs. - Do this first: create and test the break-glass accounts before you enforce any Conditional Access policy. Locking yourself out is the classic failure here.
Why identity-first is the fastest path to “secure by default”
In Azure, identity is your control plane. If an attacker (or a rushed engineer) can get privileged identity access, all the networking controls and policy guardrails become speed bumps.
An identity baseline is not about locking people out. It’s about creating a predictable model where:
- platform admins can do platform work safely
- workload teams can ship without needing broad rights
- credentials stop living in pipelines and random scripts
- you can explain “who can do what” in 60 seconds
What good looks like
- Admin separation – every privileged user has a dedicated admin identity, MFA, and Conditional Access policies applied.
- Group-based RBAC with PIM – roles are assigned to Entra groups, not individuals, and are eligible (not permanent).
- Documented break-glass – two or more emergency accounts with phishing-resistant sign-in, monitored, validated at least every 90 days.
- Workload identity pattern – managed identities for Azure hosts, OIDC federation for CI/CD, Key Vault for secrets behind private endpoints.
- Policy + logging – Azure Policy enforcing RBAC hygiene, PIM activity logged to a central workspace, alerts hitting the SOC.
Baseline decision points
- Scope for admin roles – Tenant root vs management group, who owns what, and which roles exist (Platform, Security, Network, Workload).
- PIM configuration – Activation durations, approval requirements, justification text, and notifications.
- Conditional Access baseline – MFA requirements, trusted locations, device compliance expectations, and break-glass exclusions.
- Break-glass process – Who holds credentials, when they’re used, how they’re monitored/resets.
- Workload identity strategy – Managed identities vs service principals, when to use OIDC federation, and how to track secrets.
- Logging + alerting – Where PIM/Sign-in logs land, which alerts go to the SOC/platform, and how exemptions are tracked.
- Cost/trade-offs – Entra ID P1/P2 licensing coverage and training needs for engineers.
Signal vs noise
- Enable now: Separate admin accounts, PIM eligibility, CA baseline, break-glass monitoring, managed identities for new workloads.
- Enable at Day 30: Full OIDC federation for all CI/CD, automatic revocation of unused service principals, policy enforcement of MFA for workload admins.
- Probably never: Permanent Owner at tenant root, secrets scattered in pipelines, or “MFA optional” exceptions without expiry.
Phased rollout
- Day 0 baseline – Create admin groups, PIM assignments, CA policies, break-glass accounts, and Terraform modules to manage them.
- Day 30 hardening – Enable auditing policies, convert recurring identity recommendations into Azure Policy, federate major pipelines.
- Quarterly review – Use KQL to audit PIM activations, check for direct user role assignments, validate the break-glass accounts, and review exemptions.
Runbook: identity baseline for Azure landing zones
-
Create admin separation
Create separate admin accounts for anyone with privileged access.
Baseline:
- One “normal” user identity for daily work
- One “admin” identity for privileged actions (no email use, no browsing)
If you use admin workstations or hardened browser profiles, apply them to admin identities first.
-
Define your admin roles (small set, least privilege)
Start with the smallest useful set.
Typical roles:
- Platform Admin: owns management groups, policy, subscription vending
- Security Admin: owns Defender/Sentinel integration and security config
- Network Admin: owns connectivity patterns, DNS zones, firewall/vWAN config
Be clear which control plane you’re on. Managing management groups, policy and subscriptions is Azure RBAC, not an Entra directory role. Reach for Owner/Contributor at a management group scope, not Privileged Role Administrator. Directory roles are for managing the directory itself.
If you automate PIM, make the group eligible rather than permanently assigned:
pim-platform-admin.tf HCL# Role-assignable is only required for Entra directory roles, not Azure RBAC. # It's set here because it locks membership down to Privileged Role # Administrators (or delegated owners) for a group that holds Contributor at a # management group. It can't be changed after creation and there's a tenant # limit of 500, so decide per group. resource "azuread_group" "platform_admin" { display_name = "alz-platform-admin" security_enabled = true assignable_to_role = true } data "azurerm_management_group" "platform" { name = "mg-platform" } data "azurerm_role_definition" "contributor" { name = "Contributor" } # Pins the start time at first apply. timestamp() changes on every plan, and # start_date_time forces replacement, so it would recreate the assignment each run. resource "time_static" "platform_admin_start" {} # Eligible, not active. Members still activate through PIM, and the activation # requirements (MFA, justification, approval, max duration) live in the PIM # role settings rather than here. resource "azurerm_pim_eligible_role_assignment" "platform_admin" { scope = data.azurerm_management_group.platform.id role_definition_id = data.azurerm_role_definition.contributor.id principal_id = azuread_group.platform_admin.object_id schedule { start_date_time = time_static.platform_admin_start.rfc3339 expiration { duration_days = 365 } } justification = "Platform team standing eligibility, activated via PIM" }Avoid giving everyone Owner. Owner is convenient, but it’s also an incident generator, and it carries
Microsoft.Authorization/*, so an Owner can quietly grant themselves anything else. -
Enable PIM for privileged role elevation
Set a policy that privileged roles are:
- Eligible (not permanent)
- Time-limited activation
- Require MFA
- Require justification (and optionally approval for the highest roles)
Practical defaults:
- 1–4 hour activation for high privilege roles
- approval required for the most sensitive roles (your call)
-
Create and protect break-glass accounts
Break-glass accounts exist for a scenario where Conditional Access or MFA is misconfigured and admins get locked out.
Baseline (from Microsoft’s emergency access guidance):
- At least 2 cloud-only accounts on the
onmicrosoft.comdomain - Passkey (FIDO2) or certificate-based authentication, different from your normal admin method, with keys stored securely in separate locations
- Global Administrator assigned as permanent active in PIM, not eligible
- Excluded from Conditional Access policies that block or restrict sign-in (put them in one group and exclude the group)
- Monitor and alert on any sign-in
- Validate sign-in and admin tasks at least every 90 days
Document who can use them and what “emergency” means.
- At least 2 cloud-only accounts on the
-
Conditional Access baseline for admins
Keep this practical. You can add nuance later.
Admin baseline:
- Require MFA for admin identities
- Block legacy auth (if applicable)
- Restrict admin access to trusted locations/devices (if your org can)
- Enforce sign-in risk policies if using Identity Protection
The goal is to reduce credential replay and opportunistic admin compromise.
-
Standardise RBAC assignments at management group scope
Assign platform roles at the correct scope.
Recommended:
- Platform team roles assigned at Platform management group
- Workload team roles assigned at subscription scope or workload MG scope
If you assign high privilege at tenant root “just to make it work”, you will keep it forever.
-
Workload identity: prefer managed identity and federated credentials
For apps and automation:
- Prefer managed identities for Azure-hosted workloads
- Prefer federated credentials (OIDC) for GitHub Actions / CI pipelines where possible
- Avoid long-lived service principal secrets
This cuts the “credential in a pipeline” risk right down.
-
Secrets and keys: make them boring
If you must store secrets:
- Put them in Key Vault (or your chosen secret store)
- Control access via RBAC
- Use private connectivity if you’re operating in a private network model
- Rotate secrets on a schedule
The best secret is one you don’t need. The second best is one you can rotate without drama.
-
Logging and alerting for identity events
You should treat identity events as security signals.
Minimum:
- alert on privileged role activation
- alert on break-glass sign-in
- alert on risky sign-ins (if available)
- audit role assignment changes
If you don’t alert on privilege changes, you’re blind to the highest-risk activity.
Guardrails via Azure Policy
Enforce identity hygiene with policy initiatives. Use this Bicep skeleton to stop critical roles being assigned to individuals rather than groups. There’s no built-in definition for this, so the ID below is a custom definition you write and own. Azure Policy can’t see whether an assignment is PIM-eligible, so check PIM coverage with the queries below and PIM’s own access reviews instead.
targetScope = 'managementGroup'
// Resolve real definition IDs first, e.g.:
// az policy definition list --query "[?contains(displayName,'<search term>')].{name:displayName,id:id}" -o table
resource initiative 'Microsoft.Authorization/policySetDefinitions@2023-04-01' = {
name: 'identity-guardrails'
properties: {
displayName: 'Identity Guardrails'
description: 'Enforce group-based RBAC.'
policyType: 'Custom'
policyDefinitions: [
{
policyDefinitionReferenceId: 'deny-user-role-assignment'
// Custom definition: deployed at the management group, so its ID
// looks like /providers/Microsoft.Management/managementGroups/<mg>/providers/Microsoft.Authorization/policyDefinitions/<name>
policyDefinitionId: '<deny-user-role-assignment-id>'
}
]
}
}
resource assignment 'Microsoft.Authorization/policyAssignments@2023-04-01' = {
name: 'identity-guardrails'
properties: {
displayName: 'Identity Guardrails'
policyDefinitionId: initiative.id
}
}Validation checks
Use KQL to spot risky patterns. This lists directory role assignments made in the last 7 days where the principal is a user rather than a group. It needs Entra ID audit logs streaming into the workspace via a diagnostic setting.
AuditLogs
| where TimeGenerated > ago(7d)
| where Category == "RoleManagement"
| where OperationName has "Add member to role"
| mv-expand target = TargetResources
| where tostring(target.type) == "User"
| project TimeGenerated,
InitiatedBy = tostring(InitiatedBy.user.userPrincipalName),
TargetUser = tostring(target.userPrincipalName),
Changed = target.modifiedProperties
| order by TimeGenerated descFor Azure RBAC, the control plane that actually governs subscriptions, query the resource layer instead. This lists who created or changed role assignments in the last 7 days. It doesn’t tell you whether the assignee was a user or a group:
AzureActivity
| where TimeGenerated > ago(7d)
| where OperationNameValue =~ 'MICROSOFT.AUTHORIZATION/ROLEASSIGNMENTS/WRITE'
| where ActivityStatusValue =~ 'Success'
| project TimeGenerated, Caller, SubscriptionId, ResourceId = _ResourceId
| order by TimeGenerated descTo find the assignments that currently go straight to users, ask Azure Resource Graph, which records the principal type on each assignment:
authorizationresources
| where type =~ 'microsoft.authorization/roleassignments'
| where tostring(properties.principalType) =~ 'User'
| project principalId = tostring(properties.principalId),
roleDefinitionId = tostring(properties.roleDefinitionId),
scope = tostring(properties.scope)
| order by scope asc-
Admins use separate admin identities for privileged work.
-
Privileged roles are eligible and activated just-in-time via PIM.
-
Break-glass accounts exist, use passkeys or certificate-based auth, are monitored, and were validated in the last 90 days.
-
Conditional Access baseline is enforced for admin identities.
-
Workloads use managed identity or OIDC federation; secrets are not sitting in pipelines.
-
Role assignments are group-based and documented.
-
PIM activation logs are stored centrally and reviewed.
Common pitfalls
Uncontrolled sprawl. Use groups only and enforce via policy.
If nobody monitors activations, PIM is just extra clicks. Wire alerts to SOC/platform.
Permanent exclusions for “that one vendor” never get reviewed. Force expiry and document justification.
Service principals with passwords older than your tenancy keep popping up. Inventory and retire them.
Keys stored in someone’s drawer and never tested defeat the point. Validate at least every 90 days and log usage.
Rollback / back-out plan
- PIM misconfiguration – revert the Terraform module, redeploy previous settings, and communicate downtime for activations.
- Conditional Access outage – disable the offending policy via the emergency account, fix offline, reapply via source control.
- Policy guardrail issue – disable the assignment (not the definition), patch parameters, pilot in a test MG, then reapply.
- Workload identity rollback – keep legacy service principal credentials for a limited overlap while OIDC rollouts complete. Document the sunset date.
For every rollback, log who executed it, why, and when the change will be resubmitted. Identity hygiene is only real when it survives change windows.