TL;DR
Misconfigured IAM policies are responsible for 74% of cloud security breaches, according to the 2024 Verizon Data Breach Investigations Report. Overly permissive roles — granting more access than a service needs — create privilege escalation paths that attackers exploit. Testing IAM policies requires a systematic approach: verify every permission against the principle of least privilege, check for wildcard grants on sensitive actions, and test privilege escalation scenarios like the notorious “iam:CreateRole + iam:AttachRolePolicy” combination that allows any user to grant themselves admin access. According to the AWS Security Best Practices, teams with automated IAM policy testing catch 85% of permission misconfiguration in CI/CD before reaching production. Tools like AWS IAM Access Analyzer, Prowler, and Cloudsploit bring automated policy analysis, generating actionable findings with severity ratings. For teams using Terraform or CloudFormation, policy testing can be integrated into infrastructure PR review — catching overly permissive policies before they are ever deployed. This guide covers complete IAM policy testing strategy for AWS, Azure, and GCP: policy validation techniques, simulation-based testing, automated compliance scanning, and privilege escalation detection.
AI-Assisted Approaches
AI tools excel at analyzing complex IAM policies and generating comprehensive test suites.
Analyzing IAM policies for excessive permissions:
Analyze this AWS IAM policy for security issues:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:*"],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": ["iam:PassRole"],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": ["ec2:*"],
"Resource": "*",
"Condition": {
"StringEquals": {"ec2:Region": "us-east-1"}
}
}
]
}
Identify: Privilege escalation risks, overly broad permissions, missing
conditions, and provide a least-privilege alternative for a CI/CD pipeline role.
Generating IAM policy test cases:
Generate pytest test cases for validating AWS IAM policies using boto3:
1. Verify no policies allow iam:* or iam:PassRole to Resource "*"
2. Check that S3 policies require encryption conditions
3. Ensure no policies allow assume-role to external accounts without conditions
4. Validate that admin policies are only attached to break-glass roles
Include proper AWS session setup, policy document parsing, and clear assertions.
Creating Checkov custom checks:
Write a custom Checkov check in Python that validates:
1. IAM policies must not allow actions ending in "*" (e.g., s3:*, ec2:*)
2. All iam:PassRole statements must have explicit Resource ARNs
3. Policies with administrative actions must have MFA conditions
4. Cross-account trust policies must specify explicit account IDs
Include the check class, evaluation logic, and supported resource types.
When to Use Different Testing Approaches
Testing Strategy Decision Framework
| Test Type | Tool | When to Run | What It Catches |
|---|---|---|---|
| Grammar validation | IAM Access Analyzer | Pre-commit, CI | Syntax errors, deprecated elements |
| Best practices | Access Analyzer ValidatePolicy | CI pipeline | Overly permissive actions, missing conditions |
| Static analysis | Checkov, cfn-nag | Pre-commit, CI | Known bad patterns (Resource: “*”) |
| Policy simulation | IAM Policy Simulator | Pre-deploy | Actual permission behavior |
| Access analysis | Access Analyzer findings | Continuous | External access, unused permissions |
| Custom rules | OPA/Rego | CI pipeline | Organization-specific requirements |
Critical IAM Policy Checks
| Check ID | Description | Risk Level |
|---|---|---|
| CKV_AWS_1 | IAM policies should not allow full “*” administrative privileges | Critical |
| CKV_AWS_49 | IAM policies should not allow PassRole to “*” | Critical |
| CKV_AWS_40 | IAM policies should not allow privilege escalation | Critical |
| CKV_AWS_62 | IAM policies should not have empty SID | Low |
| CKV_AWS_109 | IAM policies should not allow credentials exposure | High |
| CKV_AWS_110 | IAM policies should not allow permissions management without constraints | High |
AWS IAM Policy Testing
IAM Access Analyzer Policy Validation
# Install the Terraform IAM Policy Validator
pip install tf-policy-validator
# Validate Terraform template
tf-policy-validator validate \
--template-path ./terraform \
--region us-east-1
# Run specific checks
tf-policy-validator validate \
--template-path ./terraform \
--region us-east-1 \
--check-type VALIDATE_POLICY \
--check-type CHECK_NO_NEW_ACCESS
GitHub Actions Integration
name: IAM Policy Validation
on:
pull_request:
paths:
- 'terraform/**/*.tf'
- 'cloudformation/**/*.yaml'
jobs:
validate-iam-policies:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Validate Terraform IAM Policies
uses: aws-actions/terraform-aws-iam-policy-validator@v1
with:
template-path: terraform/
region: us-east-1
check-no-new-access: true
check-access-not-granted: true
check-no-public-access: true
- name: Run Checkov
uses: bridgecrewio/checkov-action@v12
with:
directory: terraform/
check: CKV_AWS_1,CKV_AWS_40,CKV_AWS_49,CKV_AWS_109,CKV_AWS_110
framework: terraform
Checkov for IAM Policies
# Install Checkov
pip install checkov
# Scan for IAM-specific issues
checkov -d ./terraform --check CKV_AWS_1,CKV_AWS_40,CKV_AWS_49
# Output as JUnit for CI
checkov -d ./terraform --framework terraform \
--check CKV_AWS_1,CKV_AWS_40,CKV_AWS_49,CKV_AWS_109,CKV_AWS_110 \
--output junitxml > iam-checkov-results.xml
Custom Checkov Policy Check
# custom_checks/iam_no_passrole_star.py
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
from checkov.common.models.enums import CheckResult, CheckCategories
import json
class IAMNoPassRoleStar(BaseResourceCheck):
def __init__(self):
name = "Ensure IAM policies do not allow PassRole to all resources"
id = "CKV_CUSTOM_IAM_1"
supported_resources = ['aws_iam_policy', 'aws_iam_role_policy']
categories = [CheckCategories.IAM]
super().__init__(name=name, id=id, categories=categories,
supported_resources=supported_resources)
def scan_resource_conf(self, conf):
policy = conf.get('policy', [None])[0]
if not policy:
return CheckResult.UNKNOWN
# Handle both string and dict formats
if isinstance(policy, str):
try:
policy = json.loads(policy)
except json.JSONDecodeError:
return CheckResult.UNKNOWN
statements = policy.get('Statement', [])
for statement in statements:
if statement.get('Effect') != 'Allow':
continue
actions = statement.get('Action', [])
if isinstance(actions, str):
actions = [actions]
resources = statement.get('Resource', [])
if isinstance(resources, str):
resources = [resources]
# Check for PassRole with * resource
has_passrole = any('PassRole' in action for action in actions)
has_star_resource = '*' in resources
if has_passrole and has_star_resource:
return CheckResult.FAILED
return CheckResult.PASSED
check = IAMNoPassRoleStar()
Python Policy Testing with boto3
import boto3
import json
import pytest
class TestIAMPolicies:
@pytest.fixture
def iam_client(self):
return boto3.client('iam')
@pytest.fixture
def access_analyzer_client(self):
return boto3.client('accessanalyzer')
def test_no_admin_star_policies(self, iam_client):
"""Verify no policies grant full admin access."""
paginator = iam_client.get_paginator('list_policies')
for page in paginator.paginate(Scope='Local'):
for policy in page['Policies']:
version = iam_client.get_policy_version(
PolicyArn=policy['Arn'],
VersionId=policy['DefaultVersionId']
)
document = version['PolicyVersion']['Document']
statements = document.get('Statement', [])
for stmt in statements:
if stmt.get('Effect') != 'Allow':
continue
actions = stmt.get('Action', [])
if isinstance(actions, str):
actions = [actions]
resources = stmt.get('Resource', [])
if isinstance(resources, str):
resources = [resources]
# Fail if Action: "*" and Resource: "*"
assert not (
'*' in actions and '*' in resources
), f"Policy {policy['PolicyName']} has admin privileges"
def test_validate_policy_with_access_analyzer(self, access_analyzer_client):
"""Use Access Analyzer to validate policy."""
policy_document = {
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::my-bucket/*"
}]
}
response = access_analyzer_client.validate_policy(
policyDocument=json.dumps(policy_document),
policyType='IDENTITY_POLICY'
)
# Check for errors and security warnings
findings = response.get('findings', [])
errors = [f for f in findings if f['findingType'] == 'ERROR']
security_warnings = [f for f in findings
if f['findingType'] == 'SECURITY_WARNING']
assert len(errors) == 0, f"Policy validation errors: {errors}"
assert len(security_warnings) == 0, f"Security warnings: {security_warnings}"
IAM Policy Simulator Testing
def test_policy_denies_unauthorized_actions(iam_client):
"""Simulate policy to verify denied actions."""
# Test that developer role cannot access production
response = iam_client.simulate_principal_policy(
PolicySourceArn='arn:aws:iam::123456789012:role/DeveloperRole',
ActionNames=[
'ec2:TerminateInstances',
's3:DeleteBucket',
'iam:CreateUser'
],
ResourceArns=[
'arn:aws:ec2:us-east-1:123456789012:instance/*',
'arn:aws:s3:::production-*',
'arn:aws:iam::123456789012:user/*'
]
)
for result in response['EvaluationResults']:
assert result['EvalDecision'] == 'implicitDeny', \
f"Developer role should not have {result['EvalActionName']} permission"
def test_policy_allows_required_actions(iam_client):
"""Verify role has required permissions."""
response = iam_client.simulate_principal_policy(
PolicySourceArn='arn:aws:iam::123456789012:role/CICDRole',
ActionNames=[
's3:PutObject',
'ecr:PushImage',
'ecs:UpdateService'
],
ResourceArns=[
'arn:aws:s3:::deployment-artifacts/*',
'arn:aws:ecr:us-east-1:123456789012:repository/my-app',
'arn:aws:ecs:us-east-1:123456789012:service/my-cluster/my-service'
]
)
for result in response['EvaluationResults']:
assert result['EvalDecision'] == 'allowed', \
f"CICD role missing required permission: {result['EvalActionName']}"
Azure IAM Testing
Azure Policy for RBAC Validation
# List custom role definitions
az role definition list --custom-role-only true --output table
# Check role assignments
az role assignment list --scope /subscriptions/<sub-id> --output table
# Validate with Azure Policy
az policy assignment list --query "[?policyDefinitionId contains 'rbac']"
InSpec for Azure RBAC
control 'azure-no-owner-custom-roles' do
impact 1.0
title 'Custom roles should not have Owner-equivalent permissions'
azure_role_definitions.where(role_type: 'CustomRole').ids.each do |role_id|
describe azure_role_definition(name: role_id) do
its('permissions.first.actions') { should_not include '*' }
its('permissions.first.not_actions') { should_not be_empty }
end
end
end
control 'azure-no-subscription-owner-assignments' do
impact 1.0
title 'Owner role should not be assigned at subscription level'
describe azure_role_assignments.where(
scope: "/subscriptions/#{input('subscription_id')}",
role_name: 'Owner'
) do
its('count') { should be <= 2 } # Allow only break-glass accounts
end
end
GCP IAM Testing
GCP Policy Analyzer
# Analyze IAM policy for a project
gcloud policy-intelligence analyze-iam-policy \
--project=my-project \
--full-resource-name="//cloudresourcemanager.googleapis.com/projects/my-project"
# Check for overly permissive bindings
gcloud projects get-iam-policy my-project --format=json | \
jq '.bindings[] | select(.members[] | contains("allUsers") or contains("allAuthenticatedUsers"))'
Terraform Validation for GCP IAM
# variables.tf - Define allowed roles
variable "allowed_project_roles" {
type = list(string)
default = [
"roles/viewer",
"roles/editor",
# Explicitly list allowed roles
]
}
# Validation in resource
resource "google_project_iam_binding" "binding" {
project = var.project_id
role = var.role
members = var.members
lifecycle {
precondition {
condition = contains(var.allowed_project_roles, var.role)
error_message = "Role ${var.role} is not in the allowed roles list."
}
precondition {
condition = !contains(var.members, "allUsers") && !contains(var.members, "allAuthenticatedUsers")
error_message = "Public IAM bindings (allUsers/allAuthenticatedUsers) are not allowed."
}
}
}
CI/CD Pipeline Integration
Complete GitHub Actions Workflow
name: IAM Policy Security
on:
pull_request:
paths:
- 'terraform/iam/**'
- 'policies/**'
jobs:
static-analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Checkov IAM checks
uses: bridgecrewio/checkov-action@v12
with:
directory: terraform/iam/
check: CKV_AWS_1,CKV_AWS_40,CKV_AWS_49,CKV_AWS_109,CKV_AWS_110
output_format: sarif
output_file_path: checkov-iam.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: checkov-iam.sarif
access-analyzer:
runs-on: ubuntu-latest
needs: static-analysis
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: us-east-1
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: terraform init
working-directory: terraform/iam/
- name: Validate with Access Analyzer
uses: aws-actions/terraform-aws-iam-policy-validator@v1
with:
template-path: terraform/iam/
region: us-east-1
check-no-new-access: true
policy-simulation:
runs-on: ubuntu-latest
needs: access-analyzer
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: us-east-1
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: pip install boto3 pytest
- name: Run policy simulation tests
run: pytest tests/iam/ -v --tb=short
Measuring Success
| Metric | Before Testing | After Testing | How to Track |
|---|---|---|---|
| Overly permissive policies | Found in audits | 0 in production | Checkov reports |
| Privilege escalation paths | Unknown | 0 detected | Access Analyzer findings |
| Policy validation time | Manual review (hours) | Automated (minutes) | CI/CD metrics |
| Unused permissions | Accumulated over time | Quarterly cleanup | Access Analyzer |
Warning signs your IAM testing isn’t working:
- Checkov passing but Access Analyzer findings in production
- “Emergency” policy changes bypassing CI/CD
- Roles accumulating permissions over time
- Service accounts with admin privileges
Conclusion
Effective IAM policy testing requires multiple validation layers:
- Static analysis (Checkov) catches known bad patterns in code
- Grammar validation (Access Analyzer ValidatePolicy) ensures policy syntax and structure
- Policy simulation (IAM Policy Simulator) verifies actual permission behavior
- Continuous analysis (Access Analyzer findings) detects drift and unused permissions
The key insight: IAM policies are too critical for manual review alone. Automated testing in CI/CD pipelines catches issues before they reach production, while continuous monitoring detects policy drift over time.
Official Resources
“IAM policy testing is the security work that nobody wants to do but everybody needs. The good news is that tools like IAM Access Analyzer make it possible to catch overly permissive policies automatically — the hard part is building the culture where policy review is as normal as code review.” — Yuri Kan, Senior QA Lead
FAQ
What is IAM policy testing?
Validating that IAM policies enforce least privilege. Tests check no wildcard permissions on sensitive actions, no privilege escalation paths (iam:CreateRole + iam:AttachRolePolicy combinations), and all identities have only necessary permissions.
How do you test AWS IAM policies?
IAM Policy Simulator for interactive testing. aws iam simulate-principal-policy in CI/CD pipelines. IAM Access Analyzer for automated cross-account analysis. Parliament for syntax validation and anti-pattern detection in code.
What is the principle of least privilege in IAM testing?
Each identity has only permissions needed for its specific function. Test by enumerating all permissions, checking for wildcards on sensitive actions, and simulating privilege escalation paths like self-grant scenarios.
What tools automate IAM policy testing?
AWS: IAM Access Analyzer, Prowler, Cloudsploit. Azure: Policy Compliance. GCP: Config Validator. Cross-cloud: Steampipe. For IaC: Parliament (Python), cfn-policy-validator (CloudFormation).
See Also
- Policy as Code Testing - OPA and Sentinel for custom IAM rules
- Security Group Testing - Network-level access control testing
- Compliance Testing for IaC - Meeting regulatory IAM requirements
- Terraform Testing Strategies - Complete IaC testing pyramid
- AWS Infrastructure Testing - Broader AWS testing strategies
