AWS IAM Policy Mastery
A deep dive into crafting, managing, and auditing effective AWS IAM policies using policy-as-code principles.
📋 Prerequisites
- AWS account with appropriate permissions to manage IAM
- AWS CLI installed and configured
- Basic understanding of AWS IAM concepts (Users, Groups, Roles)
- Familiarity with JSON and command-line tools
- Read: What is Policy-as-Code?
🎯 What You'll Learn
- The core components of an IAM JSON policy document
- Differences between Identity-based and Resource-based policies
- How to use Service Control Policies (SCPs) for organizational guardrails
- Advanced policy logic with Conditions, Variables, and Permissions Boundaries
- Managing IAM policies as code with Terraform
- Auditing and validating policies with IAM Access Analyzer
- Debugging common "Access Denied" errors effectively
🏷️ Topics Covered
AWS IAM Policy Architecture
AWS Identity and Access Management (IAM) provides the foundation for security and access control in AWS. At its core is the IAM policy, a JSON document that explicitly defines permissions. Understanding how to write, apply, and manage these policies is the single most important security skill in the AWS ecosystem.
📝 IAM Policies
JSON documents that define permissions for an identity (user, group, role) or a resource.
🏢 Service Control Policies (SCPs)
A type of organization policy to manage permissions across multiple AWS accounts in AWS Organizations.
🔍 IAM Access Analyzer
A service that helps you identify resources shared with an external entity and validates policies against security best practices.
Anatomy of an IAM Policy
IAM policies are JSON documents composed of one or more statements. Each statement includes a set of elements that describe the permission.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "S3ReadAccessForSpecificBucket",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::example-bucket/*",
"arn:aws:s3:::example-bucket"
],
"Condition": {
"IpAddress": {
"aws:SourceIp": "203.0.113.0/24"
}
}
}
]
} - Version: Specifies the policy language version. Always use "2012-10-17".
- Statement: The main container for one or more individual permission statements.
- Sid (Statement ID): An optional identifier for the statement. Useful for organization.
- Effect: Specifies whether the statement results in an "Allow" or "Deny".
- Action: The specific API actions that are allowed or denied (e.g., `s3:GetObject`).
- Resource: The AWS resource(s) that the action applies to, identified by their ARN.
- Condition: Optional. Specifies circumstances under which the policy is in effect (e.g., source IP, time of day).
Core IAM Policy Types
IAM policies can be attached to identities or resources, and organizational policies can set broad boundaries. Understanding the differences is key to effective governance.
1. Identity-Based Policies
Attached directly to an IAM user, group, or role. They define what that identity can do.
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": "dynamodb:GetItem",
"Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/MyTable"
}
} 2. Resource-Based Policies
Attached to a resource, like an S3 bucket, SQS queue, or KMS key. They specify which principals are allowed to access that resource.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:root"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::example-bucket/*"
}
]
} 3. Service Control Policies (SCPs)
Used in AWS Organizations to set permission guardrails for all accounts in an OU or the entire organization. SCPs do not grant permissions; they only specify the maximum permissions an identity can have.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyLeavingOrganization",
"Effect": "Deny",
"Action": "organizations:LeaveOrganization",
"Resource": "*"
},
{
"Sid": "DenyUnapprovedServices",
"Effect": "Deny",
"Action": [
"rekognition:*",
"sagemaker:*"
],
"Resource": "*"
}
]
} Advanced Policy Techniques
Go beyond basic policies by using conditions, variables, and boundaries to implement true least privilege.
Conditional Logic
Condition keys allow for fine-grained control based on request context, such as tags, IP addresses, or time.
{
"Sid": "AllowEC2ActionsIfProjectTagMatches",
"Effect": "Allow",
"Action": "ec2:RunInstances",
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:PrincipalTag/project": "${aws:ResourceTag/project}"
}
}
} Permissions Boundaries
A permissions boundary is an advanced feature for using a managed policy to set the maximum permissions that an identity-based policy can grant to an IAM role. It acts as a safety net, not a permissions granter.
# The boundary policy defines the *maximum* permissions
aws iam create-policy \
--policy-name DeveloperBoundary \
--policy-document file://developer-boundary-policy.json
# When creating the role, attach the boundary
aws iam create-role \
--role-name DeveloperRole \
--assume-role-policy-document file://assume-role-policy.json \
--permissions-boundary arn:aws:iam::123456789012:policy/DeveloperBoundary
# Now attach the regular identity policy (its permissions are constrained by the boundary)
aws iam attach-role-policy \
--role-name DeveloperRole \
--policy-arn arn:aws:iam::123456789012:policy/DeveloperPermissions Managing IAM Policies as Code
For scalable and auditable governance, never click in the console. Define your IAM policies as code using tools like Terraform.
Terraform for IAM Policies
data "aws_iam_policy_document" "s3_read_only" {
statement {
sid = "ReadOnlyAccessToBucket"
effect = "Allow"
actions = [
"s3:GetObject",
"s3:ListBucket",
]
resources = [
"arn:aws:s3:::my-app-data-bucket",
"arn:aws:s3:::my-app-data-bucket/*",
]
condition {
test = "StringEquals"
variable = "aws:PrincipalTag/team"
values = ["data-science"]
}
}
}
resource "aws_iam_policy" "s3_read_only_policy" {
name = "S3ReadOnlyDataSciencePolicy"
path = "/application/"
description = "Read-only access to the data science S3 bucket"
policy = data.aws_iam_policy_document.s3_read_only.json
}
resource "aws_iam_role_policy_attachment" "s3_read_attachment" {
role = aws_iam_role.data_scientist.name
policy_arn = aws_iam_policy.s3_read_only_policy.arn
} CI/CD for Policy Validation
Integrate policy validation into your CI/CD pipelines to catch overly permissive or non-compliant policies before they are deployed.
GitHub Actions Workflow
This example uses the open-source cfn-lint for CloudFormation and AWS's own IAM Access Analyzer validation API.
name: AWS Policy Validation
on:
pull_request:
paths:
- 'policies/**.json'
- 'infra/**.tf'
jobs:
validate-iam-policies:
runs-on: ubuntu-latest
steps:
- name: Checkout code
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 Policies with IAM Access Analyzer
run: |
for policy_file in $(find policies -name '*.json'); do
echo "Validating $policy_file..."
aws accessanalyzer validate-policy \
--policy-type IDENTITY_POLICY \
--policy-document file://$policy_file \
--region us-east-1
if [ $? -ne 0 ]; then
echo "::error file=$policy_file::IAM Policy validation failed."
exit 1
fi
done
echo "All standalone policies are valid."
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Terraform Init & Plan
working-directory: ./infra
run: |
terraform init
terraform plan -out=tfplan
- name: Check Terraform Plan with OPA/Regula
uses: fugue/regula-action@v1
with:
input: infra/ Troubleshooting Common Issues
Debugging IAM is a critical skill. "Access Denied" messages are common, but they contain the information you need.
Common IAM Errors
❌ Implicit vs. Explicit Deny
Problem: A user has a policy allowing an action but still gets "Access Denied".
Solutions:
- Permissions in AWS are implicitly denied by default. An action must be explicitly allowed.
- An explicit deny in any applicable policy (Identity, Resource, SCP) always overrides an allow.
- Check for SCPs, Permissions Boundaries, and Session Policies that might be denying the action.
❌ Decoding "Access Denied" Messages
Problem: You receive an `sts:DecodeAuthorizationMessage` error.
Solution:
This error contains an encoded message with valuable debugging information. Use the AWS CLI to decode it:
aws sts decode-authorization-message --encoded-message YOUR_ENCODED_MESSAGE_STRING The output will show the exact policy statement that caused the denial.
❌ Using the IAM Policy Simulator
Problem: You need to test a policy before deploying it.
Solution:
The IAM Policy Simulator in the AWS Console is an invaluable tool. You can:
- Select a user, group, or role.
- Choose the API actions you want to test.
- Simulate the request against specific resources.
- See a clear "allowed" or "denied" result and which statement was responsible.
IAM Policy Best Practices
Start with Least Privilege
Grant only the permissions required to perform a task. Start with a minimal set of permissions and grant additional permissions as necessary.
Use IAM Roles
Use IAM roles for applications running on EC2, Lambda functions, and for cross-account access instead of long-lived access keys.
Leverage Conditions
Use condition keys to further restrict access based on source IP, MFA status, or resource tags.
Use Access Analyzer
Regularly run IAM Access Analyzer to identify unintended external access and to validate policies against best practices.
Policy as Code
Manage all IAM policies in a version control system (like Git) and deploy them using an IaC tool (like Terraform).
Regularly Review Permissions
Use services like IAM Access Advisor (last used timestamp) to remove unused permissions and roles.
Next Steps
🎉 Congratulations!
You have now mastered the core concepts of AWS IAM policy governance, including:
- ✅ The structure and syntax of IAM JSON policies.
- ✅ How to apply Identity, Resource, and Service Control Policies.
- ✅ Using advanced features like Conditions and Permissions Boundaries.
- ✅ Managing IAM policies as code with Terraform.
- ✅ Validating and auditing policies in a CI/CD pipeline.