Skip to main content

IAM & Access Control

TSC mapping: CC5 (Control Activities), CC6.1 (Logical Access Security), CC6.2 (Access Provisioning), CC6.3 (Access Removal)

Identity is the primary control plane in Azure. SOC 2 auditors will review Entra ID sign-in and audit logs, Conditional Access policies, PIM role assignments, and RBAC policy exports. Get this layer right before anything else.


1. Global Administrator Account Hardening

Global Administrators have unrestricted access to the entire Entra ID tenant and Azure estate. They must be locked down and used only when necessary.

Enforce phishing-resistant MFA for Global Admins:

# List current Global Administrator assignments
az ad directory-role list --query "[?displayName=='Global Administrator'].id" -o tsv | \
xargs -I{} az ad directory-role member list --id {}

# Verify MFA registration status (requires Microsoft Graph)
az rest --method GET \
--uri "https://graph.microsoft.com/v1.0/reports/credentialUserRegistrationDetails" \
--query "value[?isMfaRegistered==false].userPrincipalName"

Best practices:

  • Use 2–5 Global Admin accounts maximum; all must be cloud-only (not synced from on-premises AD).
  • Assign Global Admin only when needed via PIM; remove the permanent assignment.
  • Enable Entra ID Privileged Identity Management for all privileged roles.

Reference: Entra ID admin role best practices →


2. Multi-Factor Authentication via Conditional Access

Enforce MFA for all users via a Conditional Access policy. Do not rely on per-user MFA settings — they are legacy and less auditable.

Create a baseline MFA policy (all users, all cloud apps):

# Requires az rest or Graph API — example using az rest
az rest --method POST \
--uri "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" \
--body '{
"displayName": "Require MFA for all users",
"state": "enabled",
"conditions": {
"users": { "includeUsers": ["All"], "excludeUsers": [] },
"applications": { "includeApplications": ["All"] }
},
"grantControls": {
"operator": "OR",
"builtInControls": ["mfa"]
}
}'

Audit MFA compliance:

# List users without MFA registered
az rest --method GET \
--uri "https://graph.microsoft.com/beta/reports/authenticationMethods/userRegistrationDetails" \
--query "value[?isMfaRegistered==false].[userPrincipalName,userType]" \
--output table

Key Conditional Access policies to implement:

PolicyConditionGrant control
Require MFA — all usersAll users, all appsMFA
Require compliant deviceAll users, all appsCompliant device OR MFA
Block legacy authenticationLegacy auth clientsBlock
Require phishing-resistant MFA for adminsDirectory rolesFIDO2 / certificate-based
Sign-in risk policySign-in risk ≥ MediumMFA
User risk policyUser risk ≥ HighMFA + password change

Reference: Conditional Access documentation →


3. Privileged Identity Management (PIM)

PIM enforces just-in-time (JIT) access to privileged Azure AD roles and Azure RBAC roles. Auditors expect privileged access to be time-limited, approved, and logged.

Enable PIM for the most sensitive roles:

# List eligible PIM role assignments for the current user
az rest --method GET \
--uri "https://graph.microsoft.com/v1.0/roleManagement/directory/roleEligibilitySchedules" \
--query "value[*].[roleDefinition.displayName,principal.userPrincipalName,scheduleInfo.expiration.type]"

Recommended PIM configuration:

RoleActivation durationApproval requiredMFA on activation
Global Administrator1 hourYes (2 approvers)Yes
Privileged Role Administrator2 hoursYesYes
Security Administrator4 hoursYesYes
Subscription Owner4 hoursYesYes
Contributor (production)8 hoursNoYes

Review PIM audit logs:

az rest --method GET \
--uri "https://graph.microsoft.com/v1.0/auditLogs/directoryAudits?\$filter=category eq 'RoleManagement'" \
--query "value[*].[activityDateTime,activityDisplayName,initiatedBy.user.userPrincipalName,targetResources[0].displayName]" \
--output table

Reference: PIM documentation →


4. Azure RBAC — Least Privilege

Auditors will sample Azure role assignments. Overly broad assignments (Owner at subscription scope, custom roles with * actions) are the most common SOC 2 finding.

Audit all Owner/Contributor assignments at subscription scope:

# List Owner assignments at subscription scope (flag all for review)
az role assignment list \
--role Owner \
--scope /subscriptions/<subscription-id> \
--query "[*].[principalName,principalType,roleDefinitionName,scope]" \
--output table

# List all role assignments across a subscription
az role assignment list \
--all \
--subscription <subscription-id> \
--query "[*].[principalName,principalType,roleDefinitionName,scope]" \
--output table

Assign least-privilege built-in roles:

# Assign Reader to a user at resource group scope (not subscription)
az role assignment create \
--assignee [email protected] \
--role Reader \
--scope /subscriptions/<sub>/resourceGroups/<rg>

# Remove an overly permissive Owner assignment
az role assignment delete \
--assignee [email protected] \
--role Owner \
--scope /subscriptions/<subscription-id>

Recommended role pattern:

PersonaRoleScope
All engineers (default)ReaderSubscription
DevelopersContributorDev/staging resource groups
SRE/OpsContributorProduction resource groups (via PIM)
Security teamSecurity ReaderSubscription
DBASQL DB ContributorDatabase resource groups
Break-glass adminOwnerSubscription (PIM, approval required)

Reference: Azure RBAC documentation →


5. Password Policy and Entra ID Password Protection

Enable Entra ID Password Protection to ban weak passwords and enforce a custom banned-password list.

# Enable Entra ID Password Protection (via portal or Graph)
az rest --method PATCH \
--uri "https://graph.microsoft.com/v1.0/domains/<domain>/federationConfiguration" \
--body '{"passwordNotificationWindowInDays": 14, "passwordValidityPeriodInDays": 90}'

Recommended settings:

  • Minimum length: 14 characters
  • Enable Entra ID Smart Lockout (default: 10 failed attempts)
  • Enable custom banned password list with company name variations
  • Enforce password expiry: 90 days for standard users (or use SSPR with passkeys)

Reference: Entra ID Password Protection →


6. Entra ID Access Reviews

SOC 2 CC6.3 requires evidence that access is removed when no longer needed. Entra ID Access Reviews provide automated, auditable quarterly access reviews.

Create a recurring access review for privileged roles:

# Create an access review for Global Administrator role
az rest --method POST \
--uri "https://graph.microsoft.com/v1.0/identityGovernance/accessReviews/definitions" \
--body '{
"displayName": "Quarterly Global Admin Review",
"descriptionForAdmins": "Quarterly review of Global Administrator assignments",
"scope": {
"query": "/roleManagement/directory/roleAssignments?$filter=roleDefinition/id eq '<role-id>'",
"queryType": "MicrosoftGraph"
},
"reviewers": [{"query": "/users/<reviewer-id>", "queryType": "MicrosoftGraph"}],
"settings": {
"mailNotificationsEnabled": true,
"reminderNotificationsEnabled": true,
"justificationRequiredOnApproval": true,
"defaultDecisionEnabled": true,
"defaultDecision": "Deny",
"instanceDurationInDays": 14,
"recurrence": {
"pattern": {"type": "absoluteMonthly", "interval": 3},
"range": {"type": "noEnd"}
},
"autoApplyDecisionsEnabled": true
}
}'

Review process:

  1. Schedule quarterly reviews for all privileged roles (Owner, Contributor, Security Admin).
  2. Reviewers confirm whether access is still required within 14 days.
  3. Unchallenged access is automatically revoked (defaultDecision: Deny).
  4. Export review results and retain for audit evidence.

Reference: Entra ID Access Reviews →


7. Service Principal and Workload Identity Management

Service principals are the Azure equivalent of IAM service accounts. Long-lived client secrets are a common audit finding.

Audit expiring and expired credentials:

# List all service principals with expiring client secrets
az ad app list --all \
--query "[*].{App:displayName, AppId:appId}" \
--output table

# List credentials for a specific app (check expiry dates)
az ad app credential list \
--id <app-id> \
--query "[*].[displayName,endDateTime,keyId]" \
--output table

Best practices:

  • Use Managed Identities instead of service principals wherever possible — no credential management required.
  • If a service principal is needed, use certificate credentials instead of client secrets.
  • Set a maximum client secret lifetime of 90 days; rotate before expiry.
  • Use Workload Identity Federation for CI/CD pipelines (GitHub Actions, Azure DevOps) — eliminates stored secrets entirely.
# Assign a managed identity to an Azure VM
az vm identity assign \
--name my-vm \
--resource-group my-rg \
--identities [system]

# Grant the VM's managed identity access to a Key Vault
az keyvault set-policy \
--name my-keyvault \
--object-id <vm-principal-id> \
--secret-permissions get list

Reference: Managed Identities → · Workload Identity Federation →


SOC 2 Evidence Checklist for IAM

Evidence itemHow to export
Entra ID user list with MFA statusGraph API: credentialUserRegistrationDetails
Conditional Access policy exportEntra portal → CA policies → Export JSON
PIM role assignment historyGraph API: privilegedAccess/azureResources/roleAssignments
Azure RBAC assignment reportaz role assignment list --all
Access Review resultsEntra portal → Identity Governance → Access Reviews → Export
Service principal credential expiry listaz ad app credential list per app
Entra ID audit log (role changes, user provisioning)Log Analytics workspace or Entra portal export
Password policy configurationEntra portal → Authentication Methods → Password Protection

Official references: