SOC 2 compliance audits are notoriously strict about identity and access management (IAM). In our experience working with 200+ organizations preparing for SOC 2, we've identified five IAM misconfigurations that cause 80% of compliance failures. Here's how to avoid them.
1. Overly Permissive IAM Policies
The most common mistake is using wildcard permissions (*) in IAM policies. SOC 2 requires the principle of least privilege, meaning users should only have access to resources they actually need.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::my-app-bucket/*"
}
]
}Terraform Fix:
resource "aws_iam_policy" "s3_app_access" {
name = "S3AppAccess"
description = "Least privilege S3 access for application"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:PutObject"
]
Resource = "arn:aws:s3:::${var.app_bucket_name}/*"
}
]
})
}2. Missing MFA for Console Access
SOC 2 CC6.3 requires multi-factor authentication for all administrative access. Many organizations enable MFA for root users but forget about IAM users with administrative privileges.
Terraform Fix:
# Create MFA policy
resource "aws_iam_policy" "mfa_required" {
name = "MFAPolicy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "DenyAllExceptListedIfNoMFA"
Effect = "Deny"
NotAction = [
"iam:CreateVirtualMFADevice",
"iam:EnableMFADevice",
"iam:GetUser",
"iam:ListMFADevices",
"iam:ListVirtualMFADevices",
"iam:ResyncMFADevice",
"sts:GetSessionToken"
]
Resource = "*"
Condition = {
BoolIfExists = {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
})
}
# Attach to admin users
resource "aws_iam_user_policy_attachment" "admin_mfa" {
for_each = var.admin_users
user = each.value
policy_arn = aws_iam_policy.mfa_required.arn
}3. Unused Access Keys and Credentials
SOC 2 requires regular review and rotation of access credentials. Unused access keys represent a significant security risk and compliance violation.
Detection Script:
#!/bin/bash
# Find unused access keys older than 90 days
aws iam list-users --query 'Users[].UserName' --output text | while read user; do
aws iam list-access-keys --user-name "$user" --query 'AccessKeyMetadata[?CreateDate<`date -d '''90 days ago''' --iso-8601`].AccessKeyId' --output text | while read key; do
if [ ! -z "$key" ]; then
echo "Unused access key found: $key for user: $user"
fi
done
doneTerraform Fix:
# CloudWatch rule to detect unused access keys
resource "aws_cloudwatch_event_rule" "unused_keys" {
name = "unused-access-keys"
description = "Detect unused access keys"
event_pattern = jsonencode({
source = ["aws.iam"]
detail-type = ["AWS API Call via CloudTrail"]
detail = {
eventName = ["ListAccessKeys"]
}
})
}
# Lambda function to check key usage
resource "aws_lambda_function" "check_unused_keys" {
filename = "unused_keys_checker.zip"
function_name = "check-unused-keys"
role = aws_iam_role.lambda_role.arn
handler = "index.handler"
runtime = "python3.9"
timeout = 300
}4. Cross-Account Access Without Proper Controls
Cross-account access is common in multi-account AWS environments, but SOC 2 requires proper documentation and monitoring of all cross-account relationships.
# Cross-account role with proper conditions
resource "aws_iam_role" "cross_account_role" {
name = "CrossAccountAccess"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${var.trusted_account_id}:root"
}
Action = "sts:AssumeRole"
Condition = {
StringEquals = {
"sts:ExternalId": var.external_id
}
DateGreaterThan = {
"aws:CurrentTime": "2024-01-01T00:00:00Z"
}
}
}
]
})
}
# CloudTrail to monitor cross-account access
resource "aws_cloudtrail" "cross_account_monitoring" {
name = "cross-account-monitoring"
s3_bucket_name = aws_s3_bucket.cloudtrail_logs.bucket
include_global_service_events = true
is_multi_region_trail = true
enable_logging = true
}5. Missing IAM Policy Documentation
SOC 2 requires comprehensive documentation of all access controls. Many organizations have undocumented IAM policies that auditors can't verify for compliance.
Documentation Template:
# IAM Policy Documentation Template ## Policy: S3AppAccess - **Purpose**: Allows application to read/write objects in app bucket - **SOC 2 Mapping**: CC6.1 (Logical Access Security) - **Risk Level**: Medium - **Review Frequency**: Quarterly - **Last Reviewed**: 2024-12-15 - **Next Review**: 2025-03-15 ### Permissions Granted: - s3:GetObject on arn:aws:s3:::my-app-bucket/* - s3:PutObject on arn:aws:s3:::my-app-bucket/* ### Business Justification: Application requires read/write access to store user-generated content and configuration files. ### Risk Assessment: Low risk - limited to specific bucket, no administrative privileges. ### Monitoring: - CloudTrail logs all S3 API calls - S3 access logs enabled - Monthly access review required
Audit Preparation Checklist
Next Steps
Start by auditing your current IAM configuration using AWS Access Analyzer and the scripts provided above. Focus on the five areas we've covered, and ensure each policy has clear documentation and business justification.
For teams preparing for SOC 2, consider implementing these fixes as part of your compliance sprint. The Terraform code provided can be adapted to your specific environment and integrated into your existing infrastructure-as-code workflow.