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: