AWS IAM is the foundation of AWS security. It controls who can access what. Misconfigured IAM is the #1 cause of cloud security breaches. In this lesson, you'll learn users, groups, roles, policies, and the principle of least privilege—the skills every cloud engineer needs to secure AWS accounts.
1. Learning Objectives
By the end of this lesson, you will be able to:
- Create and manage IAM users and groups
- Write IAM policies using JSON (allow/deny, actions, resources)
- Implement least privilege access
- Use IAM roles for EC2 and cross-account access
- Enable MFA and password policies
- Audit IAM access with CloudTrail
2. Why This Matters
Real-world scenario: A developer accidentally deletes an S3 bucket with customer data. Without proper IAM, they have delete permissions. With least privilege, they only have read access. IAM is not just security—it's how you prevent accidents.
3. Core Concepts
IAM Components
IAM Components:
┌─────────────────────────────────────────────────────────────────┐
│ ROOT USER │
│ • Created with AWS account │
│ • Full permissions on everything │
│ • Only for initial setup and billing │
│ • NEVER use for daily tasks │
│ • Enable MFA immediately │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ IAM USERS │
│ • Individual people or applications │
│ • Each user has unique credentials │
│ • Should follow least privilege │
│ • Example: alice, bob, jenkins-deployer │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ IAM GROUPS │
│ • Collection of users │
│ • Assign policies to groups, not individual users │
│ • Example: Developers, Admins, ReadOnly │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ IAM ROLES │
│ • For AWS services (EC2, Lambda) │
│ • No long-term credentials (temporary) │
│ • Cross-account access │
│ • Example: EC2 role to read S3 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ IAM POLICIES │
│ • JSON documents defining permissions │
│ • Attached to users, groups, or roles │
│ • Allow or deny specific actions on specific resources │
└─────────────────────────────────────────────────────────────────┘
IAM components overview
IAM Policy Structure
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-bucket"
},
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-bucket/*"
},
{
"Effect": "Deny",
"Action": "s3:DeleteBucket",
"Resource": "arn:aws:s3:::my-bucket"
}
]
}
// Policy elements:
// Effect: Allow or Deny (Deny overrides Allow)
// Action: What API calls are allowed (wildcards supported)
// Resource: Which resources (ARNs - Amazon Resource Names)
// Condition: When policy applies (optional)
// Common ARN formats:
// arn:aws:s3:::bucket-name
// arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0
// arn:aws:iam::123456789012:user/username
// arn:aws:dynamodb:us-east-1:123456789012:table/table-name
IAM policy structure
AWS CLI Setup
# Install AWS CLI
# Linux
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
# macOS
brew install awscli
# Windows (choco)
choco install awscli
# Configure AWS CLI
aws configure
# AWS Access Key ID: AKIA...
# AWS Secret Access Key: ...
# Default region: us-east-1
# Default output format: json
# Create IAM user (using root only for initial setup)
aws iam create-user --user-name alice
# Create access key
aws iam create-access-key --user-name alice
# Create group
aws iam create-group --group-name Developers
# Attach policy to group
aws iam attach-group-policy \
--group-name Developers \
--policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
# Add user to group
aws iam add-user-to-group --user-name alice --group-name Developers
# List users
aws iam list-users
# Test credentials
export AWS_ACCESS_KEY_ID=AKIA...
export AWS_SECRET_ACCESS_KEY=...
aws s3 ls # Should work if permissions allow
AWS CLI installation and IAM commands
Common Policy Examples
// 1. Read-only access to S3 (least privilege)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetObject",
"s3:GetObjectVersion"
],
"Resource": [
"arn:aws:s3:::company-bucket",
"arn:aws:s3:::company-bucket/*"
]
}
]
}
// 2. Full EC2 access for a specific region
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ec2:*",
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:RequestedRegion": "us-east-1"
}
}
}
]
}
// 3. Restrict to specific hours (business hours only)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": "ec2:*",
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": false
}
}
}
]
}
// 4. Allow only tagged resources
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ec2:StartInstances",
"Resource": "arn:aws:ec2:*:*:instance/*",
"Condition": {
"StringEquals": {
"aws:ResourceTag/Environment": "production"
}
}
}
]
}
// 5. Deny delete actions (safety policy)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": [
"s3:DeleteBucket",
"rds:DeleteDBInstance",
"ec2:TerminateInstances"
],
"Resource": "*"
}
]
}
Common IAM policy examples
IAM Roles
// EC2 Role Policy (trust policy + permissions policy)
// 1. Trust Policy (who can assume this role)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
// 2. Permissions Policy (what the role can do)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::my-app-logs/*"
},
{
"Effect": "Allow",
"Action": "cloudwatch:PutMetricData",
"Resource": "*"
}
]
}
// Create role (CLI)
aws iam create-role \
--role-name EC2-S3-Access \
--assume-role-policy-document file://trust-policy.json
aws iam put-role-policy \
--role-name EC2-S3-Access \
--policy-name S3Access \
--policy-document file://permissions-policy.json
// Attach role to EC2 instance
aws ec2 associate-iam-instance-profile \
--instance-id i-1234567890abcdef0 \
--iam-instance-profile Name=EC2-S3-Access
// Cross-account role
// Account A (source) grants access to Account B
// In Account B (target):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:root"
},
"Action": "sts:AssumeRole"
}
]
}
// Assume role from CLI
aws sts assume-role \
--role-arn "arn:aws:iam::234567890123:role/CrossAccountRole" \
--role-session-name "SessionName"
IAM roles for EC2 and cross-account
Security Best Practices
# 1. Enable MFA for root account
aws iam create-virtual-mfa-device --virtual-mfa-device-name root-mfa
# 2. Set password policy
aws iam update-account-password-policy \
--minimum-password-length 14 \
--require-symbols \
--require-numbers \
--require-uppercase-characters \
--require-lowercase-characters \
--allow-users-to-change-password \
--max-password-age 90 \
--password-reuse-prevention 5
# 3. List unused users
aws iam list-users --query "Users[?PasswordLastUsed==null && CreateDate<='2024-01-01']"
# 4. Delete unused access keys
aws iam list-access-keys --user-name unused-user
aws iam delete-access-key --user-name unused-user --access-key-id AKIA...
# 5. Generate credential report
aws iam generate-credential-report
aws iam get-credential-report
# 6. Check for root activity
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=Username,AttributeValue=root \
--start-time "2025-01-01T00:00:00Z"
# 7. Audit policies (using IAM Access Analyzer)
aws accessanalyzer create-analyzer --analyzer-name my-analyzer --type ACCOUNT
# 8. Use IAM Roles Anywhere for workloads outside AWS
# Install the credential helper on on-prem servers
IAM security best practices
4. Complete Project: IAM Security Audit Tool
#!/usr/bin/env python3
"""
iam-audit.py - IAM security audit tool
"""
import boto3
import json
from datetime import datetime, timedelta
class IAMAudit:
def __init__(self):
self.iam = boto3.client('iam')
self.cloudtrail = boto3.client('cloudtrail')
def audit_users(self):
"""Audit all IAM users"""
print("=" * 60)
print("IAM USER AUDIT")
print("=" * 60)
users = self.iam.list_users()['Users']
for user in users:
username = user['UserName']
print(f"\n📋 User: {username}")
print(f" Created: {user['CreateDate']}")
print(f" Password last used: {user.get('PasswordLastUsed', 'Never')}")
# Check MFA
mfa = self.iam.list_mfa_devices(UserName=username)['MFADevices']
if mfa:
print(f" ✅ MFA: Enabled ({mfa[0]['SerialNumber']})")
else:
print(f" ❌ MFA: NOT ENABLED")
# Check access keys
keys = self.iam.list_access_keys(UserName=username)['AccessKeyMetadata']
print(f" Access keys: {len(keys)}")
for key in keys:
age = datetime.now().replace(tzinfo=None) - key['CreateDate'].replace(tzinfo=None)
print(f" - {key['AccessKeyId']}: Created {age.days} days ago")
# List attached policies
policies = self.iam.list_attached_user_policies(UserName=username)['AttachedPolicies']
if policies:
print(f" Attached policies: {[p['PolicyName'] for p in policies]}")
def audit_unused_users(self, days=90):
"""Find users inactive for X days"""
print("\n" + "=" * 60)
print(f"UNUSED USERS (inactive for > {days} days)")
print("=" * 60)
cutoff = datetime.now() - timedelta(days=days)
users = self.iam.list_users()['Users']
for user in users:
last_used = user.get('PasswordLastUsed')
if not last_used or last_used.replace(tzinfo=None) < cutoff:
print(f" ⚠️ {user['UserName']} - Last active: {last_used or 'Never'}")
def audit_roles(self):
"""Audit IAM roles"""
print("\n" + "=" * 60)
print("IAM ROLE AUDIT")
print("=" * 60)
roles = self.iam.list_roles()['Roles']
for role in roles:
if role['RoleName'].startswith('AWS'):
continue # Skip AWS service roles
print(f"\n🔐 Role: {role['RoleName']}")
print(f" Created: {role['CreateDate']}")
# Check trust policy
trust_policy = role['AssumeRolePolicyDocument']
principals = []
for statement in trust_policy['Statement']:
if 'Principal' in statement:
principals.append(statement['Principal'])
print(f" Trusted entities: {principals}")
def find_high_privilege_users(self):
"""Find users with admin access"""
print("\n" + "=" * 60)
print("HIGH PRIVILEGE USERS")
print("=" * 60)
users = self.iam.list_users()['Users']
admin_policies = ['AdministratorAccess', 'PowerUserAccess']
for user in users:
username = user['UserName']
policies = self.iam.list_attached_user_policies(UserName=username)['AttachedPolicies']
for policy in policies:
if policy['PolicyName'] in admin_policies:
print(f" ⚠️ {username} has {policy['PolicyName']}")
def generate_report(self):
"""Generate complete security report"""
print("\n" + "#" * 60)
print("AWS IAM SECURITY AUDIT REPORT")
print(f"Generated: {datetime.now().isoformat()}")
print("#" * 60)
self.audit_users()
self.audit_unused_users()
self.audit_roles()
self.find_high_privilege_users()
print("\n" + "=" * 60)
print("RECOMMENDATIONS")
print("=" * 60)
print("1. Enable MFA for all users")
print("2. Remove unused users and access keys")
print("3. Apply least privilege (avoid AdministratorAccess)")
print("4. Use roles instead of long-term keys")
print("5. Enable CloudTrail for audit logging")
print("6. Rotate access keys every 90 days")
if __name__ == "__main__":
audit = IAMAudit()
audit.generate_report()
Complete IAM security audit tool
5. Common Errors & Solutions
6. Summary Checklist
7. Next Steps
Next AWS lessons to generate:
- Lesson 9.2: EC2 (Compute)
- Lesson 9.3: S3 (Storage)
- Lesson 9.4: VPC (Networking)
- Lesson 9.5: RDS & DynamoDB (Databases)
- Lesson 9.6: Lambda & API Gateway (Serverless)
Azure Cloud lessons (separate request when ready):
- 10.1 Azure Fundamentals & IAM
- 10.2 Azure Virtual Machines
- 10.3 Azure Storage & Blob
- 10.4 Azure Networking (VNet)
- 10.5 Azure SQL & Cosmos DB
- 10.6 Azure Functions & App Services
Comments (0)
This is exactly what I needed! The initContainer approach solved our migration issues completely. Thanks for the detailed guide!
ReplyLeave a Comment