LAB 10: AWS IAM Foundations - Identity & Access Management
Users, Groups, Roles, Policies, and AWS Security Best Practices
Difficulty: Intermediate
Duration: 6-8 hours
Modules: 8
Platform: AWS
📑 Table of Contents
AWS Identity and Access Management (IAM) is the foundation of security in AWS. It controls who can do what across your entire AWS environment. Unlike traditional identity systems, AWS IAM is deeply integrated with every AWS service—every API call is authenticated and authorized through IAM. In this lab, you'll build a complete IAM foundation: creating users, organizing them into groups, writing custom policies, configuring roles for cross-account access and AWS services, implementing MFA, and following AWS security best practices. These skills are essential for any cloud engineer, DevOps practitioner, or security professional working with AWS.
AWS IAM enables you to manage access to AWS services and resources securely. With IAM, you can create and manage AWS users, groups, and roles, and use permissions to allow and deny their access to AWS resources. IAM is a global service—users, groups, roles, and policies are not region-specific. Every request to AWS services goes through IAM for authentication (who are you?) and authorization (what can you do?). Understanding IAM is not optional—it's the prerequisite for everything else in AWS.
✅ Prerequisites
AWS Account: Free tier eligible (new accounts get 12 months free)
AWS CLI: Version 2.x installed on your local machine
Basic Terminal/Command Line: Familiarity with shell commands
Text Editor: For writing JSON policies (VS Code recommended)
MFA Device: Smartphone with authenticator app (Google Authenticator, Authy)
⚠️ AWS Free Tier Notice
This lab uses services within AWS Free Tier. However:
IAM is always free (no charges for users, groups, roles, policies)
Some services used for testing (EC2, S3) have free tier limits
Set up AWS Budgets to alert on unexpected charges
Clean up resources after completing the lab
What You Will Build
Secure Root Account: MFA-protected root with minimal use
IAM Users: Individual identities for your team
IAM Groups: Organized access by job function
Custom Policies: Fine-grained permissions in JSON
IAM Roles: For EC2 instances, Lambda, and cross-account
MFA Configuration: Multi-factor auth for all users
Permission Boundaries: Guardrails for delegated administration
Access Analyzer: Find unintended access and public resources
Learning Objectives
Secure the AWS root account with MFA and access keys disabled
Create and manage IAM users with console and programmatic access
Organize users into groups with attached policies
Write custom IAM policies using JSON policy language
Create IAM roles for AWS services and cross-account access
Implement MFA for enhanced security
Use permission boundaries to limit delegated permissions
Analyze IAM configurations with Access Analyzer
🏢 Enterprise Scenario: Cloud Migration at TechStartup
You're the Cloud Security Engineer at TechStartup, and the CTO has mandated AWS adoption:
"We're moving to AWS. I want proper identity controls from day one—no sharing credentials, no overprivileged users, and definitely no exposed access keys. Our dev team needs access to deploy, ops needs access to monitor, and finance needs billing access only. Set up IAM properly so we don't become the next breach headline."
Individual accountability with unique IAM users
Least privilege with job-function groups
MFA everywhere for all human access
Roles for applications instead of embedded keys
Audit trail with CloudTrail integration
🎯 Skills You Will Gain
IAM Administration
Manage users, groups, roles, and policies at enterprise scale.
Policy Authoring
Write custom JSON policies with conditions and resource constraints.
Role-Based Access
Configure roles for services, applications, and cross-account.
Security Hardening
Implement MFA, password policies, and access key rotation.
Compliance
Use Access Analyzer and IAM tools for security audits.
Multi-Account
Design cross-account access patterns for organizations.
📚 IAM Architecture & Core Concepts
Before diving into hands-on work, you must understand IAM's core components and how they interact. IAM follows a request-based model where every API call is evaluated against policies to determine if it's allowed or denied.
📖 IAM Request Evaluation Flow
When a principal (user, role, or service) makes a request to AWS:
Authentication: AWS verifies the principal's identity (credentials, signature)
Request Context: AWS gathers information (principal, action, resource, conditions)
Policy Evaluation: All applicable policies are evaluated
Decision: Allow (explicit allow, no deny) or Deny (explicit deny or no allow)
Individual identities for people or applications. Have long-term credentials (password, access keys). Should map 1:1 to humans.
👥 IAM Groups
Collections of users. Policies attached to groups apply to all members. Cannot be nested. Used for job-function access.
🎭 IAM Roles
Assumable identities with temporary credentials. Used by services (EC2, Lambda), applications, and cross-account access. No long-term credentials.
📋 IAM Policies
JSON documents defining permissions. Identity-based (attached to users/groups/roles) or Resource-based (attached to resources).
🔑 Access Keys
Long-term credentials for programmatic access. Consist of Access Key ID + Secret Access Key. Should be rotated regularly.
🛡️ MFA
Multi-factor authentication adds a second factor beyond password. Virtual (app), Hardware (YubiKey), or SMS (deprecated).
▼ AWS IAM REQUEST FLOW ▼
1
Principal Makes Request
User, Role, or AWS Service calls AWS API (e.g., s3:GetObject)
↓
2
Authentication
AWS verifies credentials (signature, session token, MFA if required)
↓
3
Policy Evaluation
Identity policies, Resource policies, Permission boundaries, SCPs all evaluated
↓
4
Authorization Decision
ALLOW (explicit allow, no deny) or DENY (explicit deny or no allow)
↓
5
Action Executed or Denied
Request proceeds to service or returns AccessDenied error
Policy Types Comparison
Policy Type
Attached To
Use Case
Identity-based
Users, Groups, Roles
Grant permissions to principals
Resource-based
S3, KMS, SQS, Lambda, etc.
Grant cross-account access, anonymous access
Permission Boundary
Users, Roles
Set maximum permissions (guardrails)
Service Control Policy (SCP)
AWS Organizations OUs/Accounts
Organization-wide guardrails
Session Policy
Role sessions (AssumeRole)
Scope down session permissions
Access Control List (ACL)
S3, VPC
Legacy, cross-account (use policies instead)
Device Badges Legend
AWS Consoleconsole.aws.amazon.com
AWS CLIaws commands
TerminalLocal shell
JSON PolicyPolicy documents
IAM UserUser context
🔐 Module 1: Account Setup & Root User Security
Module 1: Secure Your AWS Account Foundation
Lock down the root user and prepare your account for enterprise use.
⏱️ 30-45 minutes🎯 6 steps📍 AWS Console
The root user has unrestricted access to your entire AWS account—including billing, account closure, and IAM itself. If compromised, an attacker owns everything. AWS best practice is to secure the root user with MFA, delete its access keys, and use it only for tasks that require root. All daily work should be done with IAM users or roles.
🚨 Root User Security Is Critical
The root user can:
Close the AWS account entirely
Change account settings and billing
Create and delete any IAM user/role
Access any resource regardless of policies
Never use root for daily tasks. Never create access keys for root.
1
Sign In to AWS Console
AWS Console
Navigate to: https://console.aws.amazon.com
Sign in as Root user:
- Enter your email address (root account email)
- Enter password
- Complete any CAPTCHA
First time? Create account:
1. Go to https://aws.amazon.com
2. Click "Create an AWS Account"
3. Provide email, password, account name
4. Enter payment information (required)
5. Verify phone number
6. Select support plan (Basic - Free)
You'll land on the AWS Management Console
2
Enable MFA on Root User
This is the most critical security step. Do this immediately.
AWS Console
Navigate to: IAM Dashboard
(Search "IAM" in top search bar → Click IAM)
In the IAM Dashboard:
→ Look for "Security recommendations"
→ Click "Add MFA for root user"
OR:
Click your account name (top right) → Security credentials
→ Multi-factor authentication (MFA)
→ Click "Assign MFA device"
Configure MFA:
- Device name: root-mfa
- Select: Authenticator app (recommended)
→ Click "Next"
Show QR code:
- Open authenticator app (Google Authenticator, Authy)
- Scan QR code
- Enter two consecutive MFA codes
→ Click "Add MFA"
✅ Root user now requires MFA for console sign-in
3
Verify No Root Access Keys Exist
AWS Console
Navigate to: Account name → Security credentials
Scroll to: Access keys
Verify status:
- Should show: "No access keys"
- If access keys exist: DELETE THEM IMMEDIATELY
To delete (if present):
→ Click "Actions" → "Delete"
→ Confirm deletion
⚠️ Root access keys are a critical security risk!
Anyone with these keys has full account access.
Why delete?
- Root access keys bypass MFA
- Can't be constrained by IAM policies
- If leaked, account is fully compromised
4
Configure Account Alias
Create a friendly sign-in URL for IAM users.
AWS Console
Navigate to: IAM → Dashboard
Find: AWS Account section
→ Click "Create" next to Account Alias
Enter alias:
- Account alias: techstartup (or your company name)
- Must be globally unique across AWS
- Lowercase letters, numbers, hyphens only
→ Click "Create alias"
New sign-in URL for IAM users:
https://techstartup.signin.aws.amazon.com/console
This is easier to remember than:
https://123456789012.signin.aws.amazon.com/console
5
Configure Password Policy
AWS Console
Navigate to: IAM → Account settings
→ Click "Edit" next to Password policy
Configure strong password policy:
✓ Minimum password length: 14 characters
✓ Require at least one uppercase letter
✓ Require at least one lowercase letter
✓ Require at least one number
✓ Require at least one non-alphanumeric character
✓ Enable password expiration: 90 days
✓ Password expiration requires administrator reset: No
✓ Allow users to change their own password: Yes
✓ Prevent password reuse: 24 passwords remembered
→ Click "Save changes"
This ensures all IAM user passwords meet security standards.
6
Enable AWS CloudTrail
CloudTrail logs all API calls—essential for auditing IAM activity.
AWS Console
Navigate to: CloudTrail (search in top bar)
→ Click "Create trail"
Configure trail:
- Trail name: management-events-trail
- Storage location: Create new S3 bucket
- Bucket name: cloudtrail-logs-[account-id]-[region]
- Log file SSE-KMS encryption: Enabled (recommended)
- Log file validation: Enabled
Events:
- Event type: Management events
- API activity: Read and Write
→ Click "Next"
→ Review and click "Create trail"
CloudTrail now logs all IAM API calls:
- CreateUser, DeleteUser
- AttachPolicy, DetachPolicy
- CreateRole, AssumeRole
- Console sign-ins
✅ Module 1 Complete!
Your AWS account foundation is now secured. The root user has MFA, no access keys, and CloudTrail is logging. In Module 2, we'll create IAM users and groups.
👥 Module 2: IAM Users & Groups
Module 2: Create Users and Organize by Job Function
Build your identity structure with users and groups.
⏱️ 45-60 minutes🎯 7 steps📍 AWS Console + CLI
IAM users represent individual identities—typically one per person. Groups organize users by job function (Developers, Operations, Finance) and make permission management scalable. Instead of attaching policies to each user, attach to groups. Users inherit permissions from their group memberships.
7
Create Admin User
Create your first IAM user with administrator access.
AWS Console
Navigate to: IAM → Users → Create user
Step 1 - User details:
- User name: admin-alice
- ✓ Provide user access to AWS Management Console
- Console password: Custom password (or auto-generate)
- ✓ Users must create a new password at next sign-in
→ Click "Next"
Step 2 - Permissions:
→ Select "Attach policies directly"
→ Search and select: AdministratorAccess
→ Click "Next"
Step 3 - Review:
→ Review settings
→ Click "Create user"
Step 4 - Retrieve credentials:
→ Download .csv file (contains console password)
→ Or copy password to secure location
⚠️ This is the only time you see the password!
Sign out of root → Sign in as admin-alice
Use the account alias URL you created
8
Create IAM Groups
AWS Console
Navigate to: IAM → User groups → Create group
Create Developers group:
- Group name: Developers
- Attach policies:
→ Search: PowerUserAccess
→ Select: PowerUserAccess (full access except IAM)
→ Click "Create group"
Create Operations group:
→ Click "Create group"
- Group name: Operations
- Attach policies:
→ Search: ReadOnlyAccess
→ Select: ReadOnlyAccess
→ Also search: CloudWatchFullAccess
→ Select: CloudWatchFullAccess
→ Click "Create group"
Create Finance group:
→ Click "Create group"
- Group name: Finance
- Attach policies:
→ Search: Billing
→ Select: Billing (view billing)
→ Select: AWSBudgetsActionsWithAWSResourceControlAccess
→ Click "Create group"
You now have three job-function groups!
9
Create Additional Users
AWS Console
Navigate to: IAM → Users → Create user
Create Developer user:
- User name: dev-bob
- ✓ Console access
- Password: Auto-generate
- ✓ Require password reset
→ Next
Permissions:
→ Select "Add user to group"
→ Select: Developers
→ Click "Next" → "Create user"
→ Download credentials
Create Ops user:
- User name: ops-carol
- Add to group: Operations
→ Create user
Create Finance user:
- User name: finance-dave
- Add to group: Finance
→ Create user
Summary of users:
- admin-alice: AdministratorAccess (direct policy)
- dev-bob: PowerUserAccess (via Developers group)
- ops-carol: ReadOnlyAccess + CloudWatch (via Operations)
- finance-dave: Billing access (via Finance group)
10
Create Access Keys for CLI
Generate programmatic access credentials for dev-bob.
AWS Console
Navigate to: IAM → Users → dev-bob → Security credentials
Scroll to: Access keys
→ Click "Create access key"
Use case:
→ Select "Command Line Interface (CLI)"
→ ✓ I understand the above recommendation...
→ Click "Next"
Description tag (optional):
- Description: Dev workstation CLI access
→ Click "Create access key"
⚠️ SAVE THESE IMMEDIATELY:
- Access key ID: AKIA...
- Secret access key: (only shown once!)
→ Download .csv file
→ Click "Done"
Security notes:
- Never commit access keys to git
- Rotate keys every 90 days
- Use roles instead of keys when possible
# List all IAM users
aws iam list-users
# Get details for specific user
aws iam get-user --user-name dev-bob
# List groups for a user
aws iam list-groups-for-user --user-name dev-bob
# List users in a group
aws iam get-group --group-name Developers
# Create user via CLI
aws iam create-user --user-name dev-eve
# Add user to group
aws iam add-user-to-group \
--user-name dev-eve \
--group-name Developers
# Create login profile (console access)
aws iam create-login-profile \
--user-name dev-eve \
--password "TempP@ssw0rd123!" \
--password-reset-required
# List all policies attached to a group
aws iam list-attached-group-policies --group-name Developers
13
Test User Permissions
AWS Console
Sign out and sign in as different users to test:
Test dev-bob (Developers - PowerUserAccess):
✓ Can create EC2 instances
✓ Can create S3 buckets
✓ Can view CloudWatch
✗ Cannot create IAM users (IAM restricted)
✗ Cannot view billing
Test ops-carol (Operations - ReadOnly + CloudWatch):
✓ Can view EC2 instances
✓ Can create CloudWatch dashboards
✗ Cannot create/modify resources
✗ Cannot view billing
Test finance-dave (Finance - Billing):
✓ Can view billing dashboard
✓ Can view cost explorer
✗ Cannot view EC2, S3, or other services
✗ Cannot create any resources
This demonstrates job-function-based access control!
✅ Module 2 Complete!
You've created users organized into job-function groups. In Module 3, we'll write custom IAM policies.
📋 Module 3: IAM Policies Deep Dive
Module 3: Write Custom IAM Policies
Master the JSON policy language for fine-grained access control.
⏱️ 60-90 minutes🎯 8 steps📍 Console + CLI
IAM policies are JSON documents that define permissions. Understanding policy structure is essential for implementing least privilege. AWS managed policies are convenient but often overly permissive. Custom policies give you precise control over who can do what to which resources under what conditions.
📖 Policy Structure
Every IAM policy has this structure:
Version: Always "2012-10-17" (current version)
Statement: Array of permission statements
Effect: "Allow" or "Deny"
Action: AWS service actions (s3:GetObject, ec2:StartInstances)
Resource: ARN(s) of resources this applies to
Condition: (Optional) When this statement applies
Principal: (Resource policies only) Who this applies to
Navigate to: IAM → Users → dev-bob → Permissions
→ Click "Add permissions" → "Attach policies directly"
Search: S3-DevBuckets-ReadWrite
→ Select the policy
→ Click "Next" → "Add permissions"
dev-bob now has:
- PowerUserAccess (from Developers group)
- S3-DevBuckets-ReadWrite (direct attachment)
But S3-DevBuckets-ReadWrite includes an explicit DENY
for prod buckets, which overrides any Allow from PowerUserAccess!
19
Test Custom Policy
AWS CLI
# Ensure CLI is configured as dev-bob
aws sts get-caller-identity
# Get bucket names
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# Test: List dev bucket - should WORK
aws s3 ls s3://dev-data-${ACCOUNT_ID}-us-east-1/
# Test: Upload to dev bucket - should WORK
echo "New dev file" > new-dev.txt
aws s3 cp new-dev.txt s3://dev-data-${ACCOUNT_ID}-us-east-1/
# Test: List prod bucket - should FAIL (Access Denied)
aws s3 ls s3://prod-data-${ACCOUNT_ID}-us-east-1/
# Error: An error occurred (AccessDenied)
# Test: Upload to prod bucket - should FAIL
aws s3 cp new-dev.txt s3://prod-data-${ACCOUNT_ID}-us-east-1/
# Error: An error occurred (AccessDenied)
# The explicit Deny in our custom policy blocks prod access
# even though PowerUserAccess would normally allow it!
20
Create Policy via CLI
AWS CLI
# Create policy document file
cat > ec2-readonly-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EC2ReadOnly",
"Effect": "Allow",
"Action": [
"ec2:Describe*",
"ec2:Get*"
],
"Resource": "*"
}
]
}
EOF
# Create the policy
aws iam create-policy \
--policy-name EC2-ReadOnly-Custom \
--policy-document file://ec2-readonly-policy.json \
--description "Read-only access to EC2"
# Get policy ARN
POLICY_ARN=$(aws iam list-policies \
--query "Policies[?PolicyName=='EC2-ReadOnly-Custom'].Arn" \
--output text)
echo "Policy ARN: $POLICY_ARN"
# Attach to group
aws iam attach-group-policy \
--group-name Operations \
--policy-arn $POLICY_ARN
21
Use IAM Policy Simulator
AWS Console
Navigate to: IAM → Policy Simulator (left menu)
OR: https://policysim.aws.amazon.com/
Select user to test:
→ Users → dev-bob
Select service:
→ S3
Select actions to simulate:
→ GetObject
→ PutObject
→ DeleteBucket
Enter resource ARN:
→ arn:aws:s3:::dev-data-*/*
Run Simulation:
→ Click "Run Simulation"
Results show:
- GetObject: ✅ allowed (S3-DevBuckets-ReadWrite)
- PutObject: ✅ allowed (S3-DevBuckets-ReadWrite)
- DeleteBucket: ❌ denied (not in policy)
Now test with prod resource:
→ arn:aws:s3:::prod-data-*/*
Results:
- All actions: ❌ denied (explicit Deny)
Policy Simulator is invaluable for debugging permissions!
✅ Module 3 Complete!
You've mastered IAM policy structure and created custom policies. In Module 4, we'll explore IAM roles.
🎭 Module 4: IAM Roles & AssumeRole
Module 4: Create and Assume IAM Roles
Configure roles for services, applications, and cross-account access.
⏱️ 60-90 minutes🎯 7 steps📍 Console + CLI
IAM roles are the preferred way to grant permissions to AWS services and applications. Unlike users, roles don't have long-term credentials—they provide temporary security credentials via AWS STS (Security Token Service). Roles are assumed: an entity requests to assume a role, STS validates the request against the trust policy, and returns temporary credentials.
📖 Role Components
Trust Policy: Defines WHO can assume this role (Principal)
Permission Policy: Defines WHAT the role can do (Actions/Resources)
Session Duration: How long credentials are valid (1-12 hours)
Common role use cases:
EC2 instances accessing S3, DynamoDB, etc.
Lambda functions accessing AWS services
Cross-account access for users in another account
Federated users from external identity providers
22
Create EC2 Instance Role
Create a role that EC2 instances can assume to access S3.
AWS Console
Navigate to: IAM → Roles → Create role
Step 1 - Trusted entity:
→ Select "AWS service"
→ Use case: EC2
→ Click "Next"
Step 2 - Permissions:
→ Search: AmazonS3ReadOnlyAccess
→ Select: AmazonS3ReadOnlyAccess
→ Click "Next"
Step 3 - Name and review:
- Role name: EC2-S3ReadOnly-Role
- Description: Allows EC2 instances to read S3 buckets
→ Click "Create role"
The trust policy automatically allows EC2 to assume this role:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
23
Create Instance Profile
Instance profiles attach roles to EC2 instances. The console creates this automatically.
AWS CLI
# Verify instance profile was created (console does this automatically)
aws iam list-instance-profiles-for-role \
--role-name EC2-S3ReadOnly-Role
# If creating via CLI, you need to:
# 1. Create the role
# 2. Create instance profile
# 3. Add role to instance profile
# Example for CLI-only creation:
# aws iam create-instance-profile --instance-profile-name MyProfile
# aws iam add-role-to-instance-profile \
# --instance-profile-name MyProfile \
# --role-name MyRole
24
Launch EC2 with Role
AWS Console
Navigate to: EC2 → Instances → Launch instance
Configure:
- Name: test-instance-with-role
- AMI: Amazon Linux 2023 (free tier)
- Instance type: t2.micro (free tier)
- Key pair: Create new or select existing
- Network settings: Default VPC, allow SSH
Advanced details:
→ Expand "Advanced details"
→ IAM instance profile: Select "EC2-S3ReadOnly-Role"
→ Click "Launch instance"
Wait for instance to be "Running"
→ Connect via EC2 Instance Connect or SSH
25
Test Instance Role
Terminal
# Connect to EC2 instance (via Instance Connect or SSH)
# No credentials needed on the instance!
# AWS CLI is pre-installed on Amazon Linux
# Check the instance's identity
aws sts get-caller-identity
# Returns the ROLE ARN, not a user ARN:
# {
# "UserId": "AROA...:i-0123456789abcdef0",
# "Account": "123456789012",
# "Arn": "arn:aws:sts::123456789012:assumed-role/EC2-S3ReadOnly-Role/i-0123..."
# }
# Test S3 read access - should WORK
aws s3 ls
# Test S3 write - should FAIL (read-only policy)
aws s3 cp /etc/hostname s3://dev-data-${ACCOUNT_ID}-us-east-1/
# Error: upload failed (access denied)
# The instance gets temporary credentials automatically!
# Check the metadata service:
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Returns: EC2-S3ReadOnly-Role
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/EC2-S3ReadOnly-Role
# Returns temporary credentials (AccessKeyId, SecretAccessKey, Token)
26
Create Role for User to Assume
Create a role that IAM users can assume for elevated privileges.
AWS CLI
# Get account ID
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# Create trust policy allowing users in this account to assume
cat > trust-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::${ACCOUNT_ID}:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
}
}
}
]
}
EOF
# Create the role
aws iam create-role \
--role-name AdminElevated-Role \
--assume-role-policy-document file://trust-policy.json \
--description "Elevated admin access requiring MFA"
# Attach administrator policy
aws iam attach-role-policy \
--role-name AdminElevated-Role \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess
# This role can only be assumed with MFA!
# To assume role with MFA, user needs MFA configured first
# (We'll do this in Module 5, but here's the command)
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
MFA_SERIAL="arn:aws:iam::${ACCOUNT_ID}:mfa/dev-bob"
MFA_CODE="123456" # From authenticator app
# Assume the role
aws sts assume-role \
--role-arn "arn:aws:iam::${ACCOUNT_ID}:role/AdminElevated-Role" \
--role-session-name "admin-session" \
--serial-number $MFA_SERIAL \
--token-code $MFA_CODE
# Returns temporary credentials:
# {
# "Credentials": {
# "AccessKeyId": "ASIA...",
# "SecretAccessKey": "...",
# "SessionToken": "...",
# "Expiration": "2024-01-15T12:00:00Z"
# },
# "AssumedRoleUser": {
# "AssumedRoleId": "AROA...:admin-session",
# "Arn": "arn:aws:sts::123456789012:assumed-role/AdminElevated-Role/admin-session"
# }
# }
# Export credentials to use the role:
export AWS_ACCESS_KEY_ID="ASIA..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_SESSION_TOKEN="..."
# Now commands run as the assumed role
aws sts get-caller-identity
# Shows AdminElevated-Role
✅ Module 4 Complete!
You've created roles for EC2 and user assumption. In Module 5, we'll implement MFA.
🔐 Module 5: Multi-Factor Authentication (MFA)
Module 5: Implement MFA for Enhanced Security
Configure virtual MFA and enforce MFA via policies.
⏱️ 30-45 minutes🎯 5 steps📍 Console + CLI
MFA adds a second factor (something you have) beyond passwords (something you know). Even if credentials are compromised, attackers can't authenticate without the MFA device. AWS supports virtual MFA (apps like Google Authenticator), hardware MFA (YubiKey), and SMS (deprecated). Every human user should have MFA enabled.
29
Enable MFA for IAM User
AWS Console
Sign in as: dev-bob (or admin user)
Navigate to: IAM → Users → dev-bob → Security credentials
MFA section:
→ Click "Assign MFA device"
Configure device:
- Device name: dev-bob-mfa
- Select: Authenticator app
→ Click "Next"
Set up authenticator:
1. Open your authenticator app
2. Click "Show QR code"
3. Scan QR code with app
4. Enter two consecutive MFA codes (wait 30 sec between)
→ Click "Add MFA"
✅ MFA is now enabled for dev-bob
Test: Sign out and sign back in
- Enter username and password
- Prompted for MFA code
- Enter code from authenticator app
30
Enable MFA via CLI
AWS CLI
# Create virtual MFA device (returns QR code seed)
aws iam create-virtual-mfa-device \
--virtual-mfa-device-name ops-carol-mfa \
--outfile qr-code.png \
--bootstrap-method QRCodePNG
# The qr-code.png file contains the QR code to scan
# Or get the seed as base32 string:
aws iam create-virtual-mfa-device \
--virtual-mfa-device-name ops-carol-mfa \
--outfile seed.txt \
--bootstrap-method Base32StringSeed
# After scanning QR in authenticator, enable MFA:
# Replace with actual codes from your authenticator
aws iam enable-mfa-device \
--user-name ops-carol \
--serial-number arn:aws:iam::123456789012:mfa/ops-carol-mfa \
--authentication-code1 123456 \
--authentication-code2 789012
# List MFA devices for user
aws iam list-mfa-devices --user-name ops-carol
31
Create MFA Enforcement Policy
Force users to enable MFA before they can do anything else.
Create this policy as: EnforceMFA-SelfService
Attach to a group: MFARequired (create new group)
Add all users who must have MFA to this group.
Effect: Users can ONLY set up MFA until they have MFA enabled.
Once MFA is enabled, their other permissions take effect.
# CLI credentials by default don't include MFA
# To use MFA-protected APIs, get session token:
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
MFA_SERIAL="arn:aws:iam::${ACCOUNT_ID}:mfa/dev-bob"
# Get MFA code from your authenticator app
read -p "Enter MFA code: " MFA_CODE
# Get session token (valid up to 12 hours)
aws sts get-session-token \
--serial-number $MFA_SERIAL \
--token-code $MFA_CODE \
--duration-seconds 3600
# Export the returned credentials:
export AWS_ACCESS_KEY_ID="ASIA..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_SESSION_TOKEN="..."
# Now CLI calls include MFA context
# aws:MultiFactorAuthPresent is "true"
# Verify MFA is present:
aws sts get-caller-identity
# MFA-protected actions now work
# Pro tip: Use AWS CLI profiles for easier management
# Add to ~/.aws/config:
# [profile mfa]
# source_profile = default
# mfa_serial = arn:aws:iam::123456789012:mfa/dev-bob
✅ Module 5 Complete!
MFA is now enabled and enforced. In Module 6, we'll explore permission boundaries and SCPs.
🛡️ Module 6: Permission Boundaries & SCPs
Module 6: Implement Guardrails for Delegated Administration
Use permission boundaries and SCPs to limit maximum permissions.
⏱️ 45-60 minutes🎯 5 steps📍 Console + CLI
Permission boundaries set the maximum permissions an IAM entity (user or role) can have. Even if a user has AdministratorAccess, a permission boundary limits what they can actually do. This enables safe delegation—you can let developers create their own IAM roles, but boundary ensures they can't escalate to admin. SCPs (Service Control Policies) do the same at the AWS Organization level.
📋 Effective Permissions
Effective permissions = Intersection of:
Identity-based policies (what the principal can do)
Create this policy in IAM:
Name: DeveloperBoundary
Description: Permission boundary for developer users and roles
This boundary:
✓ Allows common developer services (S3, Lambda, etc.)
✓ Allows IAM read-only
✗ Denies creating IAM users/roles
✗ Denies modifying IAM policies
✗ Denies organization operations
35
Apply Permission Boundary to User
AWS CLI
# Get boundary policy ARN
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
BOUNDARY_ARN="arn:aws:iam::${ACCOUNT_ID}:policy/DeveloperBoundary"
# Apply permission boundary to user
aws iam put-user-permissions-boundary \
--user-name dev-bob \
--permissions-boundary $BOUNDARY_ARN
# Verify boundary is applied
aws iam get-user --user-name dev-bob
# Look for "PermissionsBoundary" in output
# Now even though dev-bob has PowerUserAccess,
# they cannot exceed what DeveloperBoundary allows!
36
Test Permission Boundary
AWS CLI
# Configure CLI as dev-bob
# (or switch to dev-bob's credentials)
# Test allowed action - should WORK
aws s3 ls
aws lambda list-functions
# Test denied action - should FAIL
aws iam create-user --user-name test-user
# Error: User: arn:aws:iam::123456789012:user/dev-bob
# is not authorized to perform: iam:CreateUser
# with an explicit deny in a permissions boundary
# Even though PowerUserAccess doesn't include IAM,
# let's test another blocked action:
aws organizations describe-organization
# Error: explicitly denied by permissions boundary
# The boundary is working as a guardrail!
37
Understanding SCPs (Organizations)
AWS Console
SCPs require AWS Organizations (multi-account setup).
Navigate to: AWS Organizations
(Only available to management account)
If you have Organizations:
→ Policies → Service control policies
Example SCP - Deny Root User Actions:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyRootUser",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringLike": {
"aws:PrincipalArn": "arn:aws:iam::*:root"
}
}
}
]
}
Example SCP - Deny Leaving Organization:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": "organizations:LeaveOrganization",
"Resource": "*"
}
]
}
Example SCP - Region Restriction:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"NotAction": [
"iam:*",
"organizations:*",
"support:*"
],
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": [
"us-east-1",
"us-west-2"
]
}
}
}
]
}
SCPs apply to entire OUs or accounts!
Even account administrators are bound by SCPs.
38
Delegated Admin with Boundaries
Enable developers to create roles, but bounded.
JSON Policy
# Policy that allows creating roles WITH boundary requirement
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CreateRoleWithBoundary",
"Effect": "Allow",
"Action": "iam:CreateRole",
"Resource": "arn:aws:iam::*:role/Dev-*",
"Condition": {
"StringEquals": {
"iam:PermissionsBoundary": "arn:aws:iam::123456789012:policy/DeveloperBoundary"
}
}
},
{
"Sid": "AttachPoliciesToBoundedRoles",
"Effect": "Allow",
"Action": [
"iam:AttachRolePolicy",
"iam:PutRolePolicy"
],
"Resource": "arn:aws:iam::*:role/Dev-*",
"Condition": {
"StringEquals": {
"iam:PermissionsBoundary": "arn:aws:iam::123456789012:policy/DeveloperBoundary"
}
}
},
{
"Sid": "PassBoundedRoles",
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::*:role/Dev-*",
"Condition": {
"StringEquals": {
"iam:PassedToService": "lambda.amazonaws.com"
}
}
}
]
}
# This allows developers to:
# ✓ Create roles named "Dev-*"
# ✓ Only if they apply DeveloperBoundary
# ✓ Pass those roles to Lambda
#
# Developers CANNOT create roles that exceed their own boundary!
✅ Module 6 Complete!
You've implemented permission boundaries for safe delegation. Module 7 covers IAM security tools.
🔍 Module 7: IAM Access Analyzer & Security Tools
Module 7: Analyze and Audit IAM Configurations
Use AWS security tools to find risky configurations and unused access.
⏱️ 45-60 minutes🎯 5 steps📍 Console + CLI
IAM Access Analyzer uses automated reasoning to identify resources shared with external entities. It can also validate policies against best practices and generate policies based on access activity. These tools are essential for maintaining least privilege and catching misconfigurations before they become breaches.
39
Enable IAM Access Analyzer
AWS Console
Navigate to: IAM → Access Analyzer (left menu)
→ Click "Create analyzer"
Configure:
- Name: account-analyzer
- Zone of trust: Current account
(Findings show access from OUTSIDE this account)
→ Click "Create analyzer"
Access Analyzer now monitors:
- S3 bucket policies and ACLs
- IAM role trust policies
- KMS key policies
- Lambda function policies
- SQS queue policies
- Secrets Manager secrets
Any resource accessible from outside your account
will generate a finding.
40
Review Access Analyzer Findings
AWS Console
Navigate to: IAM → Access Analyzer → Findings
Finding types:
- Active: Resources shared externally (investigate!)
- Archived: Reviewed and accepted
- Resolved: Fixed or resource deleted
For each finding:
- Resource: The shared resource ARN
- External principal: Who has access (account, public, etc.)
- Access level: Read, Write, Permissions
- Condition: Any conditions on access
Actions per finding:
- Archive: Mark as reviewed/acceptable
- Rescan: Check if still applicable
- View resource: Go to resource to fix
Create public S3 bucket to test (then fix it):
→ S3 → Create bucket → Block all public access: OFF
→ Upload a file
→ Make object public
→ Access Analyzer will flag this!
41
Validate Policies
AWS Console
Navigate to: IAM → Access Analyzer → Policy validation
Paste a policy to validate:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
}
]
}
→ Click "Validate policy"
Findings:
⚠️ WARNING: This policy allows all S3 actions
⚠️ SUGGESTION: Use specific actions instead of s3:*
⚠️ SECURITY: Allows s3:PutBucketPolicy (privilege escalation)
Improved policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
]
}
]
}
→ Validate again: No warnings!
42
Generate Policies from Activity
AWS Console
Navigate to: IAM → Roles → [Select a role]
→ Click "Generate policy"
Policy generation:
1. Analyzes CloudTrail logs (last 90 days)
2. Identifies actions the role actually used
3. Generates least-privilege policy
Requirements:
- CloudTrail must be enabled
- Needs sufficient activity history
- Takes time to analyze
After generation:
- Review generated policy
- Identifies unused permissions in current policy
- Can create new policy from activity
This helps right-size overly permissive roles!
43
IAM Credential Report
AWS CLI
# Generate credential report
aws iam generate-credential-report
# Wait for report (check status)
aws iam get-credential-report --query 'State'
# Download report
aws iam get-credential-report \
--query 'Content' \
--output text | base64 --decode > credential-report.csv
# View report
cat credential-report.csv
# The report includes:
# - user: Username
# - arn: User ARN
# - user_creation_time: When created
# - password_enabled: Has console access?
# - password_last_used: Last console login
# - password_last_changed: When password changed
# - mfa_active: MFA enabled?
# - access_key_1_active: First access key status
# - access_key_1_last_used_date: Last API call with key 1
# - access_key_2_active: Second access key status
# Use this to find:
# - Users without MFA
# - Unused access keys
# - Users who haven't logged in
# - Old credentials needing rotation
✅ Module 7 Complete!
You've used IAM security tools for auditing. Module 8 covers cross-account access patterns.
🔗 Module 8: Cross-Account Access Patterns
Module 8: Configure Access Between AWS Accounts
Implement secure cross-account role assumption patterns.
⏱️ 45-60 minutes🎯 5 steps📍 Console + CLI
In multi-account environments, users and services often need to access resources in other accounts. The secure pattern is cross-account role assumption: Account A creates a role that trusts Account B, and principals in Account B assume that role to get temporary credentials. This avoids sharing long-term credentials across account boundaries.
📖 Cross-Account Access Flow
Account B (trusting): Creates role with trust policy allowing Account A
Account A (trusted): User/role has permission to call sts:AssumeRole
Account A principal: Calls AssumeRole on Account B's role
STS: Validates trust policy and returns temporary credentials
Account A principal: Uses temporary credentials to access Account B resources
44
Create Cross-Account Role (Account B)
In the target account, create a role trusting the source account.
AWS CLI
# In Account B (the account with resources to access)
# Replace 111111111111 with Account A's ID
# Create trust policy
cat > cross-account-trust.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "UniqueSecretString123"
}
}
}
]
}
EOF
# Create the role
aws iam create-role \
--role-name CrossAccount-S3Access \
--assume-role-policy-document file://cross-account-trust.json \
--description "Allows Account A to access S3 in this account"
# Attach permissions (what the role can do in Account B)
aws iam attach-role-policy \
--role-name CrossAccount-S3Access \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
# Note the role ARN:
# arn:aws:iam::222222222222:role/CrossAccount-S3Access
45
Grant AssumeRole Permission (Account A)
AWS CLI
# In Account A (the account whose users need access)
# Replace 222222222222 with Account B's ID
cat > assume-cross-account.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::222222222222:role/CrossAccount-S3Access"
}
]
}
EOF
aws iam create-policy \
--policy-name AssumeAccountB-S3Role \
--policy-document file://assume-cross-account.json
# Attach to user or group
POLICY_ARN=$(aws iam list-policies \
--query "Policies[?PolicyName=='AssumeAccountB-S3Role'].Arn" \
--output text)
aws iam attach-user-policy \
--user-name dev-bob \
--policy-arn $POLICY_ARN
46
Assume Cross-Account Role
AWS CLI
# As dev-bob in Account A
# Assume the role in Account B
aws sts assume-role \
--role-arn arn:aws:iam::222222222222:role/CrossAccount-S3Access \
--role-session-name cross-account-session \
--external-id "UniqueSecretString123"
# Returns:
# {
# "Credentials": {
# "AccessKeyId": "ASIA...",
# "SecretAccessKey": "...",
# "SessionToken": "...",
# "Expiration": "2024-01-15T12:00:00Z"
# }
# }
# Export credentials
export AWS_ACCESS_KEY_ID="ASIA..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_SESSION_TOKEN="..."
# Verify you're now in Account B
aws sts get-caller-identity
# Shows Account B's account ID and the assumed role
# Access Account B's S3
aws s3 ls
# Lists buckets in Account B!
47
Configure CLI Profile for Cross-Account
Terminal
# Add cross-account profile to ~/.aws/config
cat >> ~/.aws/config << 'EOF'
[profile account-b]
role_arn = arn:aws:iam::222222222222:role/CrossAccount-S3Access
source_profile = default
external_id = UniqueSecretString123
role_session_name = cross-account-session
EOF
# Now use the profile easily
aws s3 ls --profile account-b
# CLI automatically handles AssumeRole!
# Much easier than manual credential export
# Can also add MFA requirement:
[profile account-b-mfa]
role_arn = arn:aws:iam::222222222222:role/CrossAccount-S3Access
source_profile = default
external_id = UniqueSecretString123
mfa_serial = arn:aws:iam::111111111111:mfa/dev-bob
# CLI will prompt for MFA code when using this profile
48
Resource-Based Cross-Account Access
Alternative pattern using resource policies instead of roles.
JSON Policy
# S3 bucket policy allowing cross-account access
# Applied to bucket in Account B
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCrossAccountRead",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:user/dev-bob"
},
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::shared-bucket-account-b",
"arn:aws:s3:::shared-bucket-account-b/*"
]
}
]
}
# With this bucket policy:
# - dev-bob can access directly (no AssumeRole needed)
# - Simpler but less flexible
# - Cannot use for services that don't support resource policies
# Role-based (AssumeRole) is preferred because:
# - Works with any AWS service
# - Easier to audit (CloudTrail shows role assumption)
# - Can add conditions (MFA, ExternalId)
# - Follows least privilege better
✅ Module 8 Complete!
You've implemented cross-account access patterns. Review the security section next.
🛡️ AWS IAM Security Vulnerabilities & Best Practices
IAM misconfigurations are among the most common causes of AWS security breaches. Understanding these vulnerabilities helps you design more secure architectures and avoid common pitfalls.
CRITICAL
Exposed Access Keys
Access keys committed to Git, in public S3, or in client-side code grant attackers full user permissions.
🛡️ Mitigation
Use IAM roles instead of access keys wherever possible
Use git-secrets to prevent commits containing keys
Enable AWS Secrets Manager rotation
Use AWS CloudTrail to detect key usage
Enable GuardDuty for anomaly detection
CRITICAL
Overly Permissive Policies
Policies with "Action": "*" or "Resource": "*" grant more access than needed.
🛡️ Mitigation
Follow least privilege—grant only needed permissions
Use IAM Access Analyzer policy validation
Review AWS managed policies before using
Generate policies from CloudTrail activity
HIGH
Missing MFA
Accounts without MFA can be compromised via password theft or phishing.
🛡️ Mitigation
Enable MFA for all human users
Require MFA for sensitive actions via policy conditions
Use hardware MFA for root and privileged users
Enforce MFA via SCPs at organization level
HIGH
Confused Deputy Problem
Services tricked into acting on behalf of unauthorized principals via role assumption.
🛡️ Mitigation
Use ExternalId condition for third-party roles
Restrict Principal in trust policies to specific ARNs
Use aws:SourceArn and aws:SourceAccount conditions
HIGH
Privilege Escalation Paths
Users with iam:PassRole + service create permissions can escalate privileges.
🛡️ Mitigation
Restrict iam:PassRole to specific role ARNs
Use permission boundaries for delegated admins
Audit policies for escalation paths (Prowler, ScoutSuite)
MEDIUM
Stale Credentials
Old, unused credentials that haven't been rotated or disabled.
🛡️ Mitigation
Review IAM credential report regularly
Enforce password rotation policy
Rotate access keys every 90 days
Disable unused users and keys
✅ AWS IAM Best Practices Checklist
✅ Secure root account: MFA enabled, no access keys, used only for account tasks
✅ Enable MFA for all human IAM users
✅ Use groups to assign permissions, not direct user policies
✅ Grant least privilege—start with minimum and add as needed
✅ Use IAM roles for applications and AWS services (not access keys)
✅ Use IAM roles for cross-account access (not sharing credentials)
✅ Rotate credentials regularly (90 days for access keys)
✅ Enable CloudTrail for API auditing in all regions
✅ Use IAM Access Analyzer to find external access
✅ Validate policies with IAM Access Analyzer before deployment
✅ Use permission boundaries for delegated administration
✅ Use SCPs for organization-wide guardrails
✅ Review IAM credential report monthly
✅ Use conditions to restrict access (IP, MFA, time, tags)
🎓 Key Takeaways
You've built a complete AWS IAM foundation from root account security through advanced topics like permission boundaries and cross-account access.