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: