AWS for DevOps — IAM Policies
2.3 — IAM Policies
Policies are JSON documents that define what actions are allowed or denied on which AWS resources. Every permission in AWS is controlled by a policy.
Policy Structure
Every policy follows this structure:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowS3ReadAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-deploy-bucket",
"arn:aws:s3:::my-deploy-bucket/*"
]
}
]
}
| Field | Purpose | Values |
|---|---|---|
Version |
Policy language version | Always "2012-10-17" |
Statement |
Array of permission blocks | One or more statements |
Sid |
Statement ID (optional label) | Any descriptive string |
Effect |
Allow or deny | "Allow" or "Deny" |
Action |
API actions | "s3:GetObject", "ec2:*", etc. |
Resource |
What the action applies to | ARN or "*" |
Condition |
Optional — when the policy applies | IP range, time, MFA, etc. |
How Policy Evaluation Works
When an IAM principal makes an API call, AWS evaluates all applicable policies:
Request arrives
│
▼
Is there an explicit Deny?
│
YES ─┤── DENIED (always wins)
│
NO
▼
Is there an explicit Allow?
│
YES ─┤── ALLOWED
│
NO
▼
DENIED (implicit deny — default)
Key rule: An explicit Deny always beats an Allow. If no statement matches, the request is denied by default.
Types of Policies
| Type | Attached To | Use Case |
|---|---|---|
| AWS Managed | AWS creates and maintains | Common tasks (ReadOnlyAccess, S3FullAccess) |
| Customer Managed | You create and maintain | Custom permissions for your org |
| Inline | Embedded directly in a user/group/role | One-off exceptions (avoid if possible) |
| Resource-based | Attached to a resource (S3, SQS, Lambda) | Cross-account access |
| Permission Boundary | Sets max permissions for a user/role | Delegation without over-privilege |
| Service Control Policy (SCP) | Attached to an AWS Organization OU | Account-level guardrails |
Identity-Based vs Resource-Based Policies
Identity-based — attached to a user, group, or role:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/*"
}
]
}
Resource-based — attached to the resource itself (e.g., S3 bucket policy):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:role/CrossAccountRole"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/*"
}
]
}
The key difference: resource-based policies have a Principal field that says who is allowed.
Permission Boundaries
A permission boundary limits the maximum permissions a user or role can have, even if their identity policy allows more.
Identity Policy Permission Boundary
┌──────────────┐ ┌──────────────┐
│ s3:* │ │ s3:Get* │
│ ec2:* │ ∩ │ s3:List* │
│ iam:* │ │ s3:Put* │
└──────────────┘ └──────────────┘
│
▼
Effective Permissions:
s3:Get*, s3:List*, s3:Put*
(ec2 and iam are blocked)
This is useful when you allow developers to create their own roles but want to prevent them from granting themselves admin access.
Creating a Custom Policy (CLI)
# Create a policy from a JSON file
$ aws iam create-policy \
--policy-name DeployBucketReadOnly \
--policy-document file://deploy-bucket-policy.json
{
"Policy": {
"PolicyName": "DeployBucketReadOnly",
"Arn": "arn:aws:iam::123456789012:policy/DeployBucketReadOnly"
}
}
# Attach it to a role
$ aws iam attach-role-policy \
--role-name CodeBuildRole \
--policy-arn arn:aws:iam::123456789012:policy/DeployBucketReadOnly
Useful Condition Keys
Conditions let you add fine-grained control:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*",
"Condition": {
"IpAddress": {
"aws:SourceIp": "203.0.113.0/24"
}
}
}
]
}
| Condition Key | Use Case |
|---|---|
aws:SourceIp |
Restrict by IP range |
aws:MultiFactorAuthPresent |
Require MFA |
aws:RequestedRegion |
Restrict to specific regions |
aws:PrincipalTag/team |
ABAC — tag-based access |
s3:prefix |
Limit S3 access to specific prefixes |
Policy Simulator
Before attaching a policy, test it:
$ aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:user/alice \
--action-names s3:GetObject \
--resource-arns arn:aws:s3:::my-bucket/config.yaml
{
"EvaluationResults": [
{
"EvalActionName": "s3:GetObject",
"EvalDecision": "allowed"
}
]
}
You can also use the IAM Policy Simulator in the Console.
Real-World DevOps Policy Examples
CodeBuild role — build and push Docker images:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ECRPush",
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload"
],
"Resource": "*"
},
{
"Sid": "CloudWatchLogs",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:123456789012:log-group:/aws/codebuild/*"
}
]
}
Lambda execution role — read from DynamoDB:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:Query"
],
"Resource": "arn:aws:dynamodb:ap-south-1:123456789012:table/Orders"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
}
]
}
Common Mistakes
| Mistake | Why It Hurts | Fix |
|---|---|---|
Using "Resource": "*" everywhere |
Way too broad — violates least privilege | Specify exact ARNs |
Forgetting the /* on S3 bucket ARN |
Policy applies to bucket but not objects inside | Add both bucket and bucket/* |
| Not understanding Deny overrides Allow | Unexpected access blocks | Check for explicit Deny statements |
| Ignoring policy version date | Old version lacks features like variables | Always use "2012-10-17" |
Quick Reference
| Task | Command |
|---|---|
| Create policy | aws iam create-policy --policy-name NAME --policy-document file://policy.json |
| Attach policy to user | aws iam attach-user-policy --user-name NAME --policy-arn ARN |
| Attach policy to role | aws iam attach-role-policy --role-name NAME --policy-arn ARN |
| List attached policies | aws iam list-attached-role-policies --role-name NAME |
| Simulate a policy | aws iam simulate-principal-policy --policy-source-arn ARN --action-names ACTION |
| Get policy details | aws iam get-policy --policy-arn ARN |
Next → MFA & Best Practices