AWS Identity and Access Management (IAM) is the control plane for who can access what in your AWS account. Every API call, console login, and service-to-service interaction is authorized through IAM. Getting IAM right is the single highest-impact security decision in AWS.

Core Concepts

Concept Description When to Use
User Long-lived identity for humans or legacy apps Individual developers (prefer SSO in production)
Group Collection of users sharing permissions Developers, Admins, ReadOnly
Role Temporary credentials via STS assume-role EC2, Lambda, cross-account, federated login
Policy JSON document defining Allow/Deny actions Attached to users, groups, or roles
Permission boundary Maximum permissions a role/user can receive Delegation scenarios

Policy Structure

IAM policies are JSON documents evaluated on every request:

  {
    "Version": "2012-10-17",
    "Statement": [{
        "Sid": "AllowS3ReadOnProjectBucket",
        "Effect": "Allow",
        "Action": [
            "s3:GetObject",
            "s3:ListBucket"
        ],
        "Resource": [
            "arn:aws:s3:::my-project-assets",
            "arn:aws:s3:::my-project-assets/*"
        ],
        "Condition": {
            "IpAddress": {
                "aws:SourceIp": "203.0.113.0/24"
            }
        }
    }]
}
  

Policy Evaluation Logic

  1. Explicit Deny always wins
  2. Explicit Allow grants access if no Deny matches
  3. Default is implicit Deny

Create Users and Groups (CLI)

  # Create a developers group
aws iam create-group --group-name Developers

# Attach a managed policy
aws iam attach-group-policy \
    --group-name Developers \
    --policy-arn arn:aws:iam::aws:policy/PowerUserAccess

# Create user and add to group
aws iam create-user --user-name jane-dev
aws iam add-user-to-group --user-name jane-dev --group-name Developers

# Create access keys (prefer SSO/roles in production)
aws iam create-access-key --user-name jane-dev
  

IAM Roles — The Preferred Pattern

Roles provide temporary credentials (15 min–12 hours). No long-lived keys to leak.

EC2 Instance Role

  # Trust policy — who can assume this role
cat > ec2-trust.json << 'EOF'
{
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Principal": {"Service": "ec2.amazonaws.com"},
        "Action": "sts:AssumeRole"
    }]
}
EOF

aws iam create-role \
    --role-name EC2-S3-ReadRole \
    --assume-role-policy-document file://ec2-trust.json

aws iam attach-role-policy \
    --role-name EC2-S3-ReadRole \
    --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

# Create instance profile and attach role
aws iam create-instance-profile --instance-profile-name EC2-S3-Profile
aws iam add-role-to-instance-profile \
    --instance-profile-name EC2-S3-Profile \
    --role-name EC2-S3-ReadRole
  

On the EC2 instance, the AWS SDK automatically picks up credentials from the instance metadata service (IMDSv2):

  import boto3
# No keys needed — uses instance role
s3 = boto3.client('s3')
objects = s3.list_objects_v2(Bucket='my-project-assets')
  

Lambda Execution Role

Every Lambda function needs an execution role:

  aws iam create-role \
    --role-name LambdaBasicExecution \
    --assume-role-policy-document '{
        "Version": "2012-10-17",
        "Statement": [{
            "Effect": "Allow",
            "Principal": {"Service": "lambda.amazonaws.com"},
            "Action": "sts:AssumeRole"
        }]
    }'

aws iam attach-role-policy \
    --role-name LambdaBasicExecution \
    --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
  

Least Privilege in Practice

Instead of AdministratorAccess, scope policies to specific actions:

  {
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Action": [
            "dynamodb:GetItem",
            "dynamodb:PutItem",
            "dynamodb:Query"
        ],
        "Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/Orders"
    }]
}
  

Use IAM Access Analyzer and policy simulator to validate:

  aws iam simulate-principal-policy \
    --policy-source-arn arn:aws:iam::123456789012:user/jane-dev \
    --action-names s3:ListBucket \
    --resource-arns arn:aws:s3:::my-bucket
  

MFA Enforcement

Require MFA for sensitive operations:

  {
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Deny",
        "Action": "*",
        "Resource": "*",
        "Condition": {
            "BoolIfExists": {
                "aws:MultiFactorAuthPresent": "false"
            }
        }
    }]
}
  

Attach this as a separate policy alongside Allow policies (Deny overrides Allow).

Cross-Account Access

Account A (workload) trusts Account B (CI/CD):

  {
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Principal": {"AWS": "arn:aws:iam::999999999999:root"},
        "Action": "sts:AssumeRole",
        "Condition": {
            "StringEquals": {
                "sts:ExternalId": "unique-external-id-here"
            }
        }
    }]
}
  

Real-World Scenario: Production IAM Layout

Identity Type Permissions
CI/CD pipeline Role in prod account Deploy to ECS, push to ECR only
Developer SSO → ReadOnly in prod, Full in dev No prod write access
Lambda functions Per-function execution roles DynamoDB table X only
EC2 app servers Instance role S3 bucket Y, Secrets Manager secret Z

AWS Managed vs Customer Managed Policies

Type Pros Cons
AWS Managed Maintained by AWS, quick setup Often too broad
Customer Managed Tailored, reusable, versioned You maintain them
Inline Tied to one user/role Hard to audit at scale

Prefer customer managed policies for production.

Common Mistakes

  1. AdministratorAccess everywhere — use scoped policies per workload
  2. Long-lived access keys on servers — use instance roles instead
  3. Wildcard resources ("Resource": "*") without conditions — scope to ARNs
  4. Ignoring service control policies (SCPs) in Organizations — IAM Allow can be blocked by SCP Deny
  5. Not enabling IAM Access Analyzer — free tool that finds overly permissive resources

Troubleshooting

Error Diagnosis Fix
AccessDenied Missing Allow or explicit Deny Check attached policies, SCPs, permission boundaries
Not authorized to perform sts:AssumeRole Trust policy mismatch Verify Principal ARN and ExternalId
Invalid principal in policy Typo in ARN Use IAM policy validator in console
Works in console, fails in CLI Different identity Compare ARNs with aws sts get-caller-identity

Best Practices Summary

  • Prefer roles over users for applications and services
  • Use IAM Identity Center (SSO) for human access in production
  • Enable MFA on all privileged accounts
  • Apply least privilege — start with zero, add permissions as needed
  • Rotate access keys every 90 days; better yet, eliminate them
  • Enable CloudTrail to audit IAM changes
  • Use permission boundaries when delegating IAM administration

Next: EC2 — Virtual Servers.