IAM Policies
Define permissions using JSON policy documents
IAM policies are JSON documents that define permissions. They specify what actions are allowed or denied on which AWS resources, and under what conditions.
Policy Evaluation
AWS evaluates policies in this order: Explicit Deny → Organizations SCPs → Resource-based policies → Permission boundaries → Session policies → Identity-based policies. An explicit deny always wins.
Policy Structure
Every IAM policy follows this structure:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "UniqueStatementId",
"Effect": "Allow",
"Action": ["service:Action"],
"Resource": ["arn:aws:service:region:account:resource"],
"Condition": {
"ConditionOperator": {
"ConditionKey": "value"
}
}
}
]
}Policy Elements
Specifies whether the statement allows or denies access.
| Value | Description |
|---|---|
Allow | Grants the specified permissions |
Deny | Explicitly denies the specified permissions |
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "*"
}An explicit Deny always overrides any Allow. Use deny statements carefully.
Specifies the actions this statement covers. Format: service:action
{
"Action": [
"s3:GetObject", // Specific action
"s3:PutObject",
"s3:List*", // Wildcard prefix
"ec2:Describe*", // All Describe actions
"dynamodb:*" // All DynamoDB actions
]
}Use NotAction to match all actions except specified ones:
{
"Effect": "Deny",
"NotAction": [
"iam:ChangePassword",
"iam:GetUser"
],
"Resource": "*"
}Specifies the AWS resources the actions apply to. Uses ARN format.
{
"Resource": [
// Specific S3 object
"arn:aws:s3:::my-bucket/path/to/object.txt",
// All objects in bucket
"arn:aws:s3:::my-bucket/*",
// All buckets (for ListBuckets)
"*",
// Specific DynamoDB table
"arn:aws:dynamodb:us-east-1:123456789012:table/Users",
// All tables in region
"arn:aws:dynamodb:us-east-1:123456789012:table/*"
]
}Some actions (like s3:ListAllMyBuckets) don't support resource-level permissions and require "Resource": "*".
Optional conditions that must be met for the statement to apply.
{
"Condition": {
// String conditions
"StringEquals": {
"aws:RequestedRegion": "us-east-1"
},
// IP address conditions
"IpAddress": {
"aws:SourceIp": ["192.168.1.0/24", "10.0.0.0/8"]
},
// Date conditions
"DateGreaterThan": {
"aws:CurrentTime": "2024-01-01T00:00:00Z"
},
// Boolean conditions
"Bool": {
"aws:SecureTransport": "true"
},
// MFA requirement
"Bool": {
"aws:MultiFactorAuthPresent": "true"
}
}
}Specifies who is allowed or denied access. Only used in resource-based policies.
{
// Specific AWS account
"Principal": {
"AWS": "arn:aws:iam::123456789012:root"
},
// Specific IAM user
"Principal": {
"AWS": "arn:aws:iam::123456789012:user/developer"
},
// AWS service
"Principal": {
"Service": "lambda.amazonaws.com"
},
// Everyone (public)
"Principal": "*",
// Federated users
"Principal": {
"Federated": "arn:aws:iam::123456789012:saml-provider/ExampleProvider"
}
}Policy Types
Identity-Based Policies
Attached to IAM users, groups, or roles.
Pre-built policies created and maintained by AWS.
aws iam list-policies --scope AWS --query 'Policies[?starts_with(PolicyName, `Amazon`)].[PolicyName]' --output tableCommon policies:
AdministratorAccess- Full access to all servicesReadOnlyAccess- Read-only access to all servicesPowerUserAccess- Full access except IAMViewOnlyAccess- List and describe actions only
Custom policies you create and manage.
aws iam create-policy \
--policy-name S3ReadOnlyAccess \
--policy-document file://policy.json \
--description "Read-only access to S3"Benefits:
- Version control (up to 5 versions)
- Reusable across multiple entities
- Central management
Embedded directly in a user, group, or role.
aws iam put-user-policy \
--user-name developer \
--policy-name SpecificAccess \
--policy-document file://policy.jsonPrefer managed policies over inline policies. Inline policies are harder to manage and audit.
Resource-Based Policies
Attached to resources (S3 buckets, SQS queues, etc.). Include a Principal element.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCrossAccountAccess",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::987654321098:root"
},
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
]
}
]
}Common Policy Patterns
Least Privilege S3 Access
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListBucketContents",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-bucket",
"Condition": {
"StringLike": {
"s3:prefix": ["uploads/${aws:username}/*"]
}
}
},
{
"Sid": "ReadWriteUserFolder",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::my-bucket/uploads/${aws:username}/*"
}
]
}Require MFA for Sensitive Actions
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowWithMFA",
"Effect": "Allow",
"Action": [
"ec2:StopInstances",
"ec2:TerminateInstances"
],
"Resource": "*",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
}
}
}
]
}Restrict by IP Address
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyOutsideOffice",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"NotIpAddress": {
"aws:SourceIp": [
"192.168.1.0/24",
"10.0.0.0/8"
]
}
}
}
]
}Time-Based Access
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowDuringBusinessHours",
"Effect": "Allow",
"Action": "ec2:*",
"Resource": "*",
"Condition": {
"DateGreaterThan": {
"aws:CurrentTime": "2024-01-01T09:00:00Z"
},
"DateLessThan": {
"aws:CurrentTime": "2024-12-31T18:00:00Z"
}
}
}
]
}Policy Variables
Use dynamic values in policies:
| Variable | Description |
|---|---|
${aws:username} | IAM user name |
${aws:userid} | Unique user ID |
${aws:PrincipalTag/key} | Principal's tag value |
${aws:RequestTag/key} | Tag in the request |
${aws:ResourceTag/key} | Resource's tag value |
${aws:CurrentTime} | Current date and time |
${aws:SourceIp} | Requester's IP address |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:*"],
"Resource": [
"arn:aws:s3:::company-data/home/${aws:username}/*"
]
}
]
}Policy Validation
IAM Policy Simulator
Test policies without applying them:
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:user/developer \
--action-names s3:GetObject \
--resource-arns arn:aws:s3:::my-bucket/file.txtAccess Analyzer
Find resources shared with external entities:
aws accessanalyzer create-analyzer \
--analyzer-name policy-analyzer \
--type ACCOUNTPolicy Limits
| Limit | Value |
|---|---|
| Managed policies per account | 1,500 |
| Managed policy size | 6,144 characters |
| Inline policy size | 2,048 characters |
| Policies per user/role/group | 10 managed + unlimited inline |
| Policy versions | 5 |
Best Practices
Policy Best Practices
- Start with least privilege - Add permissions as needed
- Use managed policies - Easier to maintain and audit
- Test with Policy Simulator - Before applying to production
- Use conditions - Add constraints for extra security
- Avoid wildcards - Be specific about actions and resources
- Regular audits - Review policies periodically with Access Analyzer