DevDocsDev Docs
IAM

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:

policy-structure.json
{
  "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.

ValueDescription
AllowGrants the specified permissions
DenyExplicitly denies the specified permissions
Allow Effect
{
  "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 Examples
{
  "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:

NotAction Example
{
  "Effect": "Deny",
  "NotAction": [
    "iam:ChangePassword",
    "iam:GetUser"
  ],
  "Resource": "*"
}

Specifies the AWS resources the actions apply to. Uses ARN format.

Resource Examples
{
  "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 Examples
{
  "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.

Principal Examples
{
  // 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.

List AWS Managed Policies
aws iam list-policies --scope AWS --query 'Policies[?starts_with(PolicyName, `Amazon`)].[PolicyName]' --output table

Common policies:

  • AdministratorAccess - Full access to all services
  • ReadOnlyAccess - Read-only access to all services
  • PowerUserAccess - Full access except IAM
  • ViewOnlyAccess - List and describe actions only

Custom policies you create and manage.

Create Customer Managed Policy
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.

Create Inline Policy
aws iam put-user-policy \
  --user-name developer \
  --policy-name SpecificAccess \
  --policy-document file://policy.json

Prefer 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.

S3 Bucket Policy
{
  "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

s3-least-privilege.json
{
  "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

require-mfa.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowWithMFA",
      "Effect": "Allow",
      "Action": [
        "ec2:StopInstances",
        "ec2:TerminateInstances"
      ],
      "Resource": "*",
      "Condition": {
        "Bool": {
          "aws:MultiFactorAuthPresent": "true"
        }
      }
    }
  ]
}

Restrict by IP Address

ip-restriction.json
{
  "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

time-based.json
{
  "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:

VariableDescription
${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
Policy with Variables
{
  "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:

Simulate Policy
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.txt

Access Analyzer

Find resources shared with external entities:

Create Analyzer
aws accessanalyzer create-analyzer \
  --analyzer-name policy-analyzer \
  --type ACCOUNT

Policy Limits

LimitValue
Managed policies per account1,500
Managed policy size6,144 characters
Inline policy size2,048 characters
Policies per user/role/group10 managed + unlimited inline
Policy versions5

Best Practices

Policy Best Practices

  1. Start with least privilege - Add permissions as needed
  2. Use managed policies - Easier to maintain and audit
  3. Test with Policy Simulator - Before applying to production
  4. Use conditions - Add constraints for extra security
  5. Avoid wildcards - Be specific about actions and resources
  6. Regular audits - Review policies periodically with Access Analyzer

Next Steps

On this page