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.

Lab Overview & AWS IAM Fundamentals

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

Learning Objectives

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:

  1. Authentication: AWS verifies the principal's identity (credentials, signature)
  2. Request Context: AWS gathers information (principal, action, resource, conditions)
  3. Policy Evaluation: All applicable policies are evaluated
  4. Decision: Allow (explicit allow, no deny) or Deny (explicit deny or no allow)

Key Principle: Explicit DENY always wins. No policy = implicit DENY.

Core IAM Components

IAM Users

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 TypeAttached ToUse Case
Identity-basedUsers, Groups, RolesGrant permissions to principals
Resource-basedS3, KMS, SQS, Lambda, etc.Grant cross-account access, anonymous access
Permission BoundaryUsers, RolesSet maximum permissions (guardrails)
Service Control Policy (SCP)AWS Organizations OUs/AccountsOrganization-wide guardrails
Session PolicyRole sessions (AssumeRole)Scope down session permissions
Access Control List (ACL)S3, VPCLegacy, 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 minutes6 stepsAWS 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 minutes7 stepsAWS 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
11

Configure AWS CLI

Terminal
# Install AWS CLI v2 (if not installed) # macOS curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg" sudo installer -pkg AWSCLIV2.pkg -target / # Linux curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" unzip awscliv2.zip sudo ./aws/install # Windows: Download from https://aws.amazon.com/cli/ # Verify installation aws --version # Configure credentials for dev-bob aws configure # AWS Access Key ID: [Enter access key ID] # AWS Secret Access Key: [Enter secret access key] # Default region name: us-east-1 # Default output format: json # Verify configuration aws sts get-caller-identity # Should return: # { # "UserId": "AIDA...", # "Account": "123456789012", # "Arn": "arn:aws:iam::123456789012:user/dev-bob" # }
12

Manage Users via CLI

AWS CLI
# 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 minutes8 stepsConsole + 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
14

Understand Policy Structure

JSON Policy
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowS3ReadOnly", "Effect": "Allow", "Action": [ "s3:GetObject", "s3:GetObjectVersion", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::my-bucket", "arn:aws:s3:::my-bucket/*" ] }, { "Sid": "DenyDeleteBucket", "Effect": "Deny", "Action": "s3:DeleteBucket", "Resource": "*" } ] } # Key points: # - Sid: Statement ID (optional, for documentation) # - Multiple actions in array # - Resource ARN format: arn:aws:service:region:account:resource # - Wildcards: * matches anything # - Explicit Deny overrides any Allow
15

Create S3 Bucket for Testing

AWS CLI
# Create test S3 buckets (bucket names must be globally unique) ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) REGION="us-east-1" # Development bucket aws s3 mb s3://dev-data-${ACCOUNT_ID}-${REGION} # Production bucket aws s3 mb s3://prod-data-${ACCOUNT_ID}-${REGION} # Upload test files echo "Development test file" > dev-test.txt aws s3 cp dev-test.txt s3://dev-data-${ACCOUNT_ID}-${REGION}/ echo "Production test file" > prod-test.txt aws s3 cp prod-test.txt s3://prod-data-${ACCOUNT_ID}-${REGION}/ # Verify buckets aws s3 ls
16

Create Custom Policy - S3 Dev Only

AWS Console
Navigate to: IAM Policies Create policy Click "JSON" tab Paste this policy:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowListAllBuckets", "Effect": "Allow", "Action": "s3:ListAllMyBuckets", "Resource": "*" }, { "Sid": "AllowDevBucketAccess", "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::dev-data-*", "arn:aws:s3:::dev-data-*/*" ] }, { "Sid": "DenyProdBucketAccess", "Effect": "Deny", "Action": "s3:*", "Resource": [ "arn:aws:s3:::prod-data-*", "arn:aws:s3:::prod-data-*/*" ] } ] }
Click "Next" Policy details: - Policy name: S3-DevBuckets-ReadWrite - Description: Allows full access to dev buckets, denies prod access Click "Create policy"
17

Create Policy with Conditions

Conditions add context-aware restrictions to policies.

JSON Policy
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowEC2ActionsWithMFA", "Effect": "Allow", "Action": [ "ec2:StartInstances", "ec2:StopInstances", "ec2:RebootInstances" ], "Resource": "*", "Condition": { "Bool": { "aws:MultiFactorAuthPresent": "true" } } }, { "Sid": "AllowEC2FromOfficeIP", "Effect": "Allow", "Action": "ec2:TerminateInstances", "Resource": "*", "Condition": { "IpAddress": { "aws:SourceIp": "203.0.113.0/24" }, "Bool": { "aws:MultiFactorAuthPresent": "true" } } }, { "Sid": "AllowTaggedResourcesOnly", "Effect": "Allow", "Action": "ec2:*", "Resource": "*", "Condition": { "StringEquals": { "ec2:ResourceTag/Environment": "Development" } } } ] } # Common condition keys: # aws:MultiFactorAuthPresent - MFA was used # aws:SourceIp - Request IP address # aws:CurrentTime - Time-based access # aws:RequestedRegion - Region restriction # aws:PrincipalTag/* - Tags on the principal # ec2:ResourceTag/* - Tags on EC2 resources
18

Attach Policy to User

AWS Console
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 minutes7 stepsConsole + 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!
27

Grant User Permission to Assume Role

AWS CLI
# Create policy allowing sts:AssumeRole ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) cat > assume-role-policy.json << EOF { "Version": "2012-10-17", "Statement": [ { "Sid": "AllowAssumeAdminRole", "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "arn:aws:iam::${ACCOUNT_ID}:role/AdminElevated-Role" } ] } EOF aws iam create-policy \ --policy-name AllowAssumeAdminRole \ --policy-document file://assume-role-policy.json # Get policy ARN POLICY_ARN=$(aws iam list-policies \ --query "Policies[?PolicyName=='AllowAssumeAdminRole'].Arn" \ --output text) # Attach to dev-bob (or Developers group) aws iam attach-user-policy \ --user-name dev-bob \ --policy-arn $POLICY_ARN # dev-bob can now assume AdminElevated-Role (with MFA)
28

Assume Role via CLI

AWS CLI
# 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 minutes5 stepsConsole + 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.

JSON Policy
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowViewAccountInfo", "Effect": "Allow", "Action": [ "iam:GetAccountPasswordPolicy", "iam:ListVirtualMFADevices" ], "Resource": "*" }, { "Sid": "AllowManageOwnVirtualMFADevice", "Effect": "Allow", "Action": [ "iam:CreateVirtualMFADevice", "iam:DeleteVirtualMFADevice" ], "Resource": "arn:aws:iam::*:mfa/${aws:username}" }, { "Sid": "AllowManageOwnUserMFA", "Effect": "Allow", "Action": [ "iam:DeactivateMFADevice", "iam:EnableMFADevice", "iam:ListMFADevices", "iam:ResyncMFADevice" ], "Resource": "arn:aws:iam::*:user/${aws:username}" }, { "Sid": "DenyAllExceptListedIfNoMFA", "Effect": "Deny", "NotAction": [ "iam:CreateVirtualMFADevice", "iam:EnableMFADevice", "iam:GetUser", "iam:ListMFADevices", "iam:ListVirtualMFADevices", "iam:ResyncMFADevice", "sts:GetSessionToken" ], "Resource": "*", "Condition": { "BoolIfExists": { "aws:MultiFactorAuthPresent": "false" } } } ] }
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.
32

Require MFA for Sensitive Actions

JSON Policy
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowEC2ReadWithoutMFA", "Effect": "Allow", "Action": [ "ec2:Describe*" ], "Resource": "*" }, { "Sid": "DenyEC2WriteWithoutMFA", "Effect": "Deny", "Action": [ "ec2:TerminateInstances", "ec2:StopInstances", "ec2:CreateVpc", "ec2:DeleteVpc", "ec2:ModifyVpcAttribute" ], "Resource": "*", "Condition": { "BoolIfExists": { "aws:MultiFactorAuthPresent": "false" } } }, { "Sid": "DenyIAMWithoutMFA", "Effect": "Deny", "Action": [ "iam:CreateUser", "iam:DeleteUser", "iam:CreateRole", "iam:DeleteRole", "iam:AttachUserPolicy", "iam:DetachUserPolicy" ], "Resource": "*", "Condition": { "BoolIfExists": { "aws:MultiFactorAuthPresent": "false" } } } ] } # This policy allows read-only EC2 without MFA, # but requires MFA for destructive actions.
33

Use MFA with CLI

AWS CLI
# 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 minutes5 stepsConsole + 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)
  • Permission boundary (maximum allowed)
  • SCPs (organization guardrails)
  • Resource-based policies (on target resource)

If any of these denies, the action is denied.

34

Create Permission Boundary Policy

JSON Policy
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowedServices", "Effect": "Allow", "Action": [ "s3:*", "dynamodb:*", "lambda:*", "logs:*", "cloudwatch:*", "sns:*", "sqs:*", "events:*" ], "Resource": "*" }, { "Sid": "AllowIAMReadOnly", "Effect": "Allow", "Action": [ "iam:Get*", "iam:List*" ], "Resource": "*" }, { "Sid": "AllowIAMPassRole", "Effect": "Allow", "Action": "iam:PassRole", "Resource": "*", "Condition": { "StringEquals": { "iam:PassedToService": [ "lambda.amazonaws.com" ] } } }, { "Sid": "DenyIAMChanges", "Effect": "Deny", "Action": [ "iam:CreateUser", "iam:DeleteUser", "iam:CreateRole", "iam:DeleteRole", "iam:AttachUserPolicy", "iam:AttachRolePolicy", "iam:PutUserPermissionsBoundary", "iam:DeleteUserPermissionsBoundary", "iam:CreatePolicyVersion" ], "Resource": "*" }, { "Sid": "DenyOrganizations", "Effect": "Deny", "Action": "organizations:*", "Resource": "*" } ] }
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 minutes5 stepsConsole + 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 minutes5 stepsConsole + 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

  1. Account B (trusting): Creates role with trust policy allowing Account A
  2. Account A (trusted): User/role has permission to call sts:AssumeRole
  3. Account A principal: Calls AssumeRole on Account B's role
  4. STS: Validates trust policy and returns temporary credentials
  5. 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.

Skills Mastered

Enterprise Applicability

Cloud Security

Foundation for securing any AWS workload.

DevOps/SRE

CI/CD pipelines, service authentication, least privilege.

Compliance

SOC2, PCI-DSS, HIPAA IAM requirements.

Multi-Account

AWS Organizations, Control Tower, landing zones.

What's Next

Additional Resources

Official Documentation
Tools
Certifications
Learning