AWS IAM Best Practices: Complete Security Guide for Cloud Engineers (2026)
AWS Identity and Access Management (IAM) is the gatekeeper for everything in your AWS account. Get IAM wrong and you get security breaches, data leaks, and compliance failures. Get it right and you have a solid security foundation that scales with your organization.
This guide covers AWS IAM from the ground up, including production patterns that stop the most common cloud security vulnerabilities. IAM is heavily tested on every AWS certification (CCP, SAA, DevOps Professional, Security Specialty).
IAM Core Concepts
Users vs Roles vs Groups
IAM Users: Long-term identities with permanent credentials (username/password or access keys). Use for:
- Human engineers who need AWS Console or CLI access
- Legacy service accounts (prefer roles when possible)
IAM Roles: Temporary credentials for services or cross-account access. Use for:
- EC2 instances, Lambda functions, ECS tasks — anything running in AWS
- Cross-account access (production account assuming a role in staging)
- Federated access (SSO, Active Directory)
IAM Groups: Collections of users that share the same policies. Use for:
- Organizing users by function: Developers, Operations, Security
- Never nest groups inside groups (IAM doesn't support it)
The golden rule: Services should ALWAYS use IAM roles, never access keys. A Lambda function with an IAM role is secure. A Lambda function with hardcoded access keys in the environment is a breach waiting to happen.
Policies
A policy is a JSON document that defines permissions. The evaluation logic:
- Default DENY: nothing is allowed unless explicitly allowed
- Explicit ALLOW: permissions from attached policies
- Explicit DENY: overrides any ALLOW (even from another policy)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadOnlyS3",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-app-data",
"arn:aws:s3:::my-app-data/*"
]
}
]
}
Policy types:
- AWS Managed Policies: AWS creates and maintains (e.g.,
AmazonS3ReadOnlyAccess) - Customer Managed Policies: You create, can be shared across multiple identities
- Inline Policies: Directly attached to a single user/role/group — hard to manage at scale
- Resource-based Policies: Attached to the resource itself (S3 bucket policy, Lambda resource policy)
Principle of Least Privilege
The most important IAM principle: grant only the minimum permissions needed for a task.
Before (dangerous):
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
After (least privilege):
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::my-app-uploads/*"
}
How to implement:
- Start by denying everything (IAM default)
- Identify exactly what the service/user needs to do
- Grant only those specific actions on specific resources
- Review and tighten permissions quarterly using IAM Access Analyzer
IAM Roles for Services (IRSA / Task Role)
EC2 Instance Profile:
resource "aws_iam_role" "ec2_app" {
name = "ec2-app-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "ec2.amazonaws.com" }
}]
})
}
resource "aws_iam_role_policy" "ec2_app_s3" {
name = "s3-access"
role = aws_iam_role.ec2_app.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = ["s3:GetObject", "s3:PutObject"]
Resource = "arn:aws:s3:::my-bucket/*"
}]
})
}
resource "aws_iam_instance_profile" "ec2_app" {
name = "ec2-app-profile"
role = aws_iam_role.ec2_app.name
}
# Attach to EC2 instance
resource "aws_instance" "app" {
iam_instance_profile = aws_iam_instance_profile.ec2_app.name
# ... rest of config
}
Lambda Execution Role:
resource "aws_iam_role" "lambda_processor" {
name = "lambda-processor-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
}]
})
}
# Allow Lambda to write logs (required for CloudWatch)
resource "aws_iam_role_policy_attachment" "lambda_logs" {
role = aws_iam_role.lambda_processor.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
# Allow Lambda to read from SQS
resource "aws_iam_role_policy" "lambda_sqs" {
name = "sqs-access"
role = aws_iam_role.lambda_processor.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = ["sqs:ReceiveMessage", "sqs:DeleteMessage", "sqs:GetQueueAttributes"]
Resource = aws_sqs_queue.processor.arn
}]
})
}
IAM Conditions
Conditions add context-based access control — the most powerful IAM feature.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "arn:aws:s3:::my-bucket/*",
"Condition": {
"IpAddress": {
"aws:SourceIp": ["10.0.0.0/8", "203.0.113.0/24"]
},
"StringEquals": {
"aws:RequestedRegion": "us-east-1"
},
"Bool": {
"aws:MultiFactorAuthPresent": "true"
}
}
}
]
}
Common condition keys:
aws:SourceIp— restrict to specific IP ranges (office, VPN)aws:MultiFactorAuthPresent— require MFA for sensitive actionsaws:RequestedRegion— prevent resource creation outside approved regionss3:prefix— restrict S3 access to specific folder pathsec2:Region— region-specific EC2 controlsaws:CurrentTime— time-based access (business hours only)
Service Control Policies (SCPs)
For AWS Organizations, SCPs are the guard rails at the account level. They apply to EVERYONE including the root user.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyNonApprovedRegions",
"Effect": "Deny",
"NotAction": [
"cloudfront:*",
"iam:*",
"route53:*",
"support:*"
],
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": ["us-east-1", "us-east-2"]
}
}
},
{
"Sid": "PreventRootUserAccess",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringLike": {
"aws:PrincipalArn": "arn:aws:iam::*:root"
}
}
}
]
}
SCPs are the nuclear option — a Deny in an SCP cannot be overridden by any IAM policy in the account.
Cross-Account Access
When Account A needs to access resources in Account B:
In Account B (trusting account) — create the role:
resource "aws_iam_role" "cross_account_reader" {
name = "cross-account-reader"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = "sts:AssumeRole"
Principal = {
AWS = "arn:aws:iam::111111111111:role/deployment-role" # Account A role
}
Condition = {
StringEquals = {
"sts:ExternalId": "unique-external-id" # Extra security for 3rd party
}
}
}]
})
}
In Account A (trusting account) — grant permission to assume:
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::222222222222:role/cross-account-reader"
}
Assuming the role (CLI):
aws sts assume-role \
--role-arn "arn:aws:iam::222222222222:role/cross-account-reader" \
--role-session-name "my-session" \
--external-id "unique-external-id"
IAM Best Practices Checklist
□ Enable MFA on root account (non-negotiable)
□ Delete or disable root account access keys
□ Create individual IAM users — don't share credentials
□ Use IAM roles for all AWS services (never access keys)
□ Enable IAM Access Analyzer to find overly permissive policies
□ Set up AWS Organizations + SCPs for multi-account environments
□ Enable CloudTrail in all regions + central S3 bucket
□ Review IAM credentials report quarterly
□ Set IAM password policy: min 14 chars, rotation required
□ Use permission boundaries for developer self-service
□ Tag IAM roles with team, service, and environment
□ Test IAM policies using IAM Policy Simulator before deploying
IAM for the AWS Certifications
CCP (CLF-C02): Know the difference between users, roles, groups, and policies. Understand MFA and least privilege.
SAA-C03: Deep understanding of IAM roles for cross-account access, resource-based policies, ABAC (attribute-based access control), and conditions.
DevOps Professional (DOP-C02): Advanced topics like permission boundaries, IAM Access Analyzer, Organizations SCPs, and automating IAM with CloudFormation/Terraform.
Security Specialty (SCS-C02): The full picture — detective controls (CloudTrail, Config, GuardDuty), incident response, and fine-grained IAM conditions.
*CloudPath Academy covers IAM in depth across multiple phases — from the basics in Phase 1 to advanced cross-account security patterns in Phase 3's DevSecOps module. Every lab involves real IAM configuration, not just theory.*