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)

IAM is the highest-evidence area for SOC 2 auditors. Auditors will pull IAM Credential Reports, review permission policies, verify MFA enforcement, and check access key ages. Get this layer right before anything else.


1. Root Account Hardening

The root account has unrestricted access to everything in the account. It must be locked down and effectively never used.

Enable MFA on root:

# Verify root MFA status
aws iam get-account-summary --query 'SummaryMap.AccountMFAEnabled'
# 1 = enabled, 0 = not enabled

Remove root access keys (required):

# Check if root has active access keys
aws iam get-account-summary --query 'SummaryMap.AccountAccessKeysPresent'
# Must be 0 — delete any root keys immediately via the console

AWS Config rules to enable:

RuleWhat it checks
root-account-mfa-enabledRoot MFA is active
iam-root-access-key-checkNo active root access keys exist

Reference: IAM best practices — root user →


2. MFA for All IAM Users

Every IAM user with AWS Console access must have MFA enabled. For programmatic-only users, enforce through policy.

Enforce MFA via an IAM policy condition (attach to all users/groups):

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyAllExceptMFASetup",
"Effect": "Deny",
"NotAction": [
"iam:CreateVirtualMFADevice",
"iam:EnableMFADevice",
"iam:GetUser",
"iam:ListMFADevices",
"iam:ListVirtualMFADevices",
"iam:ResyncMFADevice",
"sts:GetSessionToken"
],
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}

Audit current MFA status:

aws iam generate-credential-report
aws iam get-credential-report --query 'Content' --output text | base64 -d | \
awk -F, '{print $1, $4, $8}' | column -t
# Columns: user, password_enabled, mfa_active

AWS Config rules:

RuleWhat it checks
iam-user-mfa-enabledAll IAM users with passwords have MFA
mfa-enabled-for-iam-console-accessConsole-access users require MFA

Reference: Enabling MFA devices →


3. Password Policy

aws iam update-account-password-policy \
--minimum-password-length 14 \
--require-symbols \
--require-numbers \
--require-uppercase-characters \
--require-lowercase-characters \
--allow-users-to-change-password \
--max-password-age 90 \
--password-reuse-prevention 24 \
--hard-expiry

Reference: IAM password policy →


4. IAM Identity Center (Preferred for Human Access)

For SOC 2, the preferred pattern is zero long-lived IAM users for engineers. Use IAM Identity Center (formerly AWS SSO) to federate access from your identity provider (Okta, Azure AD, Google Workspace) and issue short-lived credentials via role assumption.

Why this satisfies CC6.2 and CC6.3 better than IAM users:

  • Provisioning and deprovisioning is automated via SCIM from your IdP.
  • Access is time-limited — sessions expire, no permanent credentials.
  • Multi-account access is centrally managed through Permission Sets.

Enable via AWS Organizations:

# Enable IAM Identity Center (done once per org)
aws sso-admin list-instances
# If empty, enable via the console: IAM Identity Center → Enable

Recommended Permission Sets for segregation of duties:

Permission SetManaged PolicyWho gets it
ReadOnlyAccessReadOnlyAccessAll engineers (default)
DeveloperAccessCustom — allow dev/staging, deny prodDevelopers
OpsAccessCustom — allow ops actions, no IAMSRE/Ops
SecurityAuditorSecurityAuditSecurity team
AdminAccessAdministratorAccessBreak-glass only, MFA required

Reference: IAM Identity Center → · SCIM provisioning →


5. IAM Access Key Management

Long-lived IAM access keys are a common audit finding. Keep them to a minimum and rotate them automatically.

Audit key age:

aws iam generate-credential-report
aws iam get-credential-report --query 'Content' --output text | base64 -d | \
awk -F, 'NR>1 {print $1, $9, $11, $14}' | column -t
# Columns: user, access_key_1_active, access_key_1_last_rotated, access_key_2_active

Disable keys older than 90 days:

aws iam update-access-key \
--access-key-id AKIAIOSFODNN7EXAMPLE \
--status Inactive \
--user-name <username>

AWS Config rules:

RuleWhat it checks
access-keys-rotatedKeys rotated within 90 days (configurable)
iam-user-unused-credentials-checkCredentials not used in 90 days
iam-no-inline-policy-checkNo inline policies on users, groups, or roles

Reference: Access key best practices → · Config rule: access-keys-rotated →


6. Least Privilege — IAM Policy Authoring

Auditors will sample IAM policies. Overly permissive policies (Action: "*", Resource: "*") are the most common SOC 2 finding.

Principles:

  • Use AWS managed policies only as a starting point — scope down with customer-managed policies.
  • Use Resource ARNs, not *, wherever possible.
  • Use Condition blocks to restrict by MFA, IP, VPC, or time.
  • Use permission boundaries to cap the maximum permissions a role can grant.

Example — scoped S3 policy for an application role:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::my-app-bucket-prod/*"
},
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-app-bucket-prod"
}
]
}

Use IAM Access Analyzer to surface overly permissive policies:

# Create an analyzer for the account
aws accessanalyzer create-analyzer \
--analyzer-name soc2-analyzer \
--type ACCOUNT

# List active findings (external access)
aws accessanalyzer list-findings \
--analyzer-arn arn:aws:access-analyzer:<region>:<account>:analyzer/soc2-analyzer \
--filter '{"status": {"eq": ["ACTIVE"]}}'

Reference: IAM Access Analyzer → · Permission boundaries →


7. Quarterly Access Reviews

SOC 2 CC6.3 requires evidence that access is removed when no longer needed. The standard way to evidence this is a documented quarterly access review.

Generate review data:

# Export IAM credential report (shows all users, key ages, last activity)
aws iam generate-credential-report && sleep 5
aws iam get-credential-report --query 'Content' --output text | base64 -d > iam-review-$(date +%Y-%m-%d).csv

# List all roles and their last used date
aws iam list-roles --query 'Roles[*].[RoleName,RoleLastUsed.LastUsedDate]' --output table

# List all policies attached to a user
aws iam list-attached-user-policies --user-name <username>

Review process:

  1. Export credential report and role last-used data.
  2. Flag any user not active in 90+ days and any role not assumed in 180+ days.
  3. Send to team leads for confirmation — did person/role still need access?
  4. Remove or disable access for any entry team leads cannot justify.
  5. Document the review outcome and retain for audit evidence.

SOC 2 Evidence Checklist for IAM

Evidence itemHow to export
IAM Credential Reportaws iam get-credential-report
Root MFA statusaws iam get-account-summary
Password policyaws iam get-account-password-policy
IAM Identity Center assignmentsExported from Identity Center console
AWS Config IAM rule compliance historyAWS Config console → Rules → Compliance timeline
IAM Access Analyzer findingsaws accessanalyzer list-findings
Quarterly access review recordsRetained internally (spreadsheet, ticketing system)

Official references: