AWS IAM β
Identity & Access Management
The complete guide from first principles to professional mastery. Every API call, every service interaction, every resource access β IAM controls it all. Understand it deeply, secure everything.
β‘ IAM in 30 Seconds
- WHO: IAM defines every identity in AWS β humans (IAM Users), machines (IAM Roles), federated identities (SSO/OIDC).
- WHAT: Policies (JSON documents) define what actions are allowed or denied. Nothing is allowed by default.
- WHICH: Every policy targets specific AWS resources using ARNs β precise down to a single S3 object.
- HOW: IAM sits between every caller and every AWS service. Every API call is authenticated then authorized through IAM β no exceptions.
What is IAM
Imagine you just created an AWS account. You have one set of credentials β the root account. Your team grows: developers, data scientists, DevOps engineers, QA testers. You share the root credentials. One developer accidentally deletes a production database. Another downloads customer data to their laptop. There is no log of who did what. There is no way to restrict who can do what. You are one mistake away from catastrophe.
IAM exists to solve this exact problem. AWS Identity and Access Management gives you a way to create separate identities for every person and every service, grant each one exactly the permissions they need β nothing more β and audit every single action with a full trail.
Without IAM (The Chaos Scenario)
- Everyone shares root credentials β no individual accountability
- All-or-nothing access β intern can delete production data
- No audit trail β breach discovered weeks later, no idea who did it
- Shared access keys checked into code β leaked publicly on GitHub
- Services access everything β Lambda can read any S3 bucket
- No instant revocation β must change all creds to remove one person
With IAM (Controlled Access)
- Individual identities β every person and service has unique, traceable creds
- Least privilege β developers deploy but cannot delete production databases
- Full audit trail β CloudTrail logs every API call with identity, time & outcome
- Roles for services β Lambda can only access its specific DynamoDB table
- Instant revocation β disable one user in seconds, no others affected
- Conditions β access only from corporate IP, only with MFA active
IAM is the AWS service that answers two fundamental security questions for every single action taken in your AWS account:
IAM is not a firewall you can bypass. It is not an optional security layer. Every single AWS API call β Console, CLI, SDK, or service-to-service β passes through IAM. There is no path around it. This is by design: a single, unified security enforcement point for the entire cloud.
These two concepts are frequently conflated but represent completely different phases. IAM handles both, in strict sequence β authorization never runs if authentication fails.
| Dimension | Authentication (AuthN) | Authorization (AuthZ) |
|---|---|---|
| Core Question | "Who are you?" | "What can you do?" |
| IAM Checks | Credentials β password, access key, token signature | Policies β JSON permission documents |
| Sequence | Always first. Authorization won't run without it. | Runs only after identity is confirmed. |
| Success means | Identity proven β AWS knows who is making the request | Permission confirmed β identity is allowed |
| Failure HTTP Code | 401 Unauthorized | 403 Forbidden |
| Real-World Analogy | Showing your passport β proves who you are | Boarding pass β you're allowed on THIS specific flight |
| What makes it fail | Wrong password, expired key, invalid token signature | No Allow in any policy, or an explicit Deny exists |
You run aws s3 ls s3://my-bucket. If 401 β your AWS credentials are missing or misconfigured (~/.aws/credentials issue). If 403 β credentials are fine, but no IAM policy grants s3:ListBucket on that bucket. Different problems, completely different fixes.
Every IAM permission statement is built on four questions. Together, they define every access boundary in AWS:
WHO β The Principal
- IAM Users β individual human identities (permanent)
- IAM Roles β assumed identities (services, cross-account)
- IAM Groups β collections of users sharing policies
- Federated identities β SSO, SAML, OIDC, Web Identity
- AWS Services β EC2, Lambda, ECS using service roles
- Root account β unrestricted access (never use for daily work)
WHAT β The Action
s3:GetObjectβ read a specific file from S3ec2:StartInstancesβ start an EC2 instancelambda:InvokeFunctionβ call a Lambda functioniam:CreateUserβ create an IAM user (high sensitivity!)*β wildcard for all actions (use with extreme care)- Each service defines its own set of available actions
WHICH β The Resource (ARN)
arn:aws:s3:::my-bucket/*β all objects in one bucketarn:aws:ec2:us-east-1:123:instance/i-abcβ one instancearn:aws:dynamodb:*:*:table/ordersβ one DynamoDB table*β all resources (dangerous at account level)- ARN = Amazon Resource Name β globally unique identifier
WHEN/HOW β The Condition
aws:SourceIpβ only from specific IP/rangeaws:MultiFactorAuthPresentβ only if MFA is activeaws:RequestedRegionβ restrict to specific regionss3:prefixβ objects with specific path prefix onlyaws:CurrentTimeβ time-bounded access windows
The best mental model for IAM is a large corporate office building with an advanced security system. Every element maps precisely to an IAM concept:
The building β the AWS Account. Everything inside is your cloud infrastructure.
Building's master key β Root account. Opens every door. Kept in the CEO's safe. Never used daily.
ID badge / keycard β IAM credentials. Proves who you are at any door.
Access level on badge β IAM policies. Badge level 3 opens floors 1β3, not floor 7 (exec suite).
Visitor / contractor pass β IAM Role. Temporary. Returned when done. Cannot reuse it.
Department access group β IAM Group. All Finance employees share the same floor access. Change group β affects everyone.
Security guard log β CloudTrail. Every door entry: who, when, which door.
AWS Account β isolated environment containing all your AWS resources.
Root account β email + password at account creation. Unrestricted. Create β MFA β lockbox.
IAM User credentials β username+password (console) or access key+secret (CLI/SDK). Long-lived, tied to one individual.
IAM Policy β JSON document: Effect, Action, Resource, Condition. The exact rules that grant or deny access.
IAM Role β assumed identity returning temporary credentials (15 minβ12 hr). Preferred over long-lived user keys for services.
IAM Group β collection of users sharing policies. Not an identity β cannot authenticate directly. Cannot contain other groups.
CloudTrail β complete audit log of every IAM decision: authenticated, authorized, allowed, or denied.
Most AWS services are regional β an RDS database in us-east-1 doesn't exist in eu-west-1. IAM is fundamentally different: it is global.
Global Scope
- Create user "alice" once β works in ALL regions
- Policies apply globally across all services
- No concept of "deploy IAM to a region"
- Console shows no region selector for IAM
Eventual Consistency
- Changes propagate globally in seconds
- Not instantaneous across all regions
- Automation may need retry logic
- Relevant for high-velocity deployments
IAM is Free
- Zero cost for users, groups, roles, policies
- No charge for policy evaluation API calls
- Pay only for resources accessed, not IAM itself
- No reason to be stingy with permissions design
When a developer runs aws s3 ls s3://my-bucket, here is exactly what AWS does, in order:
- Request arrives at AWS endpoint (signed)The CLI signs the request using the configured credentials (access key ID + secret access key β HMAC-SHA256 / SigV4 signature). The signed request is sent to the S3 regional endpoint.
- IAM verifies the signature β AuthenticationAWS extracts the access key ID, finds the corresponding secret key in IAM, and re-derives the expected signature. Match β identity confirmed. No match β 401 Unauthorized. Stops here. Authorization never runs.
- IAM determines the principal's full contextWho is this principal? User, role, federated identity? Which account? What type of credentials (permanent, temporary)? What session policies? IAM collects all applicable policies: identity-based, resource-based, boundaries, SCPs.
- IAM evaluates all policies β AuthorizationIs there an explicit Deny anywhere? (If yes β 403, stop). Is there an explicit Allow in any applicable policy? (If yes β proceed). Neither? β Default Deny β 403. All conditions (IP, MFA, time, region) are also evaluated here.
- Decision rendered β Action executes or is blockedAllowed: S3 service executes
ListBucketand returns the response. Denied:AccessDenied (403)returned. The action never executes. CloudTrail logs the attempt and outcome either way.
AWS begins every authorization evaluation in a DENY state. It only changes to ALLOW if a policy explicitly grants it. You cannot "accidentally" allow something. "Not denied" β allowed. Silence = Deny. Access must be explicitly granted β it is never implied by absence of a deny statement.
| Property | Detail | Why It Matters |
|---|---|---|
| Global Service | Not region-specific. IAM entities work in all regions. | Exam Console shows no region. Created once, works everywhere. |
| Free | No charge for IAM itself. Unlimited users, roles, policies. | Exam No cost reason to avoid IAM best practices. |
| Default Deny | Every authorization starts as DENY. Must explicitly Allow. | Critical Silence = Deny. "Not denied" β allowed. |
| Eventual Consistency | Policy changes replicate globally in seconds (not instant). | Exam Automation may need retry logic immediately after changes. |
| Max IAM Users | 5,000 users per account | Exam Large orgs use federation instead of individual IAM users. |
| Max Groups | 300 groups per account | Organize by team, function, or access level. |
| Max Roles | 1,000 roles per account (soft limit) | One role per service/application for fine-grained control. |
| Access Keys per User | Max 2 active keys per IAM user | Design Two keys allow zero-downtime rotation: create new β update apps β delete old. |
| Temp Cred Duration | 15 minutes to 12 hours (role-dependent, STS) | Exam Short-lived, auto-expiring, no manual revocation needed. |
| MFA Types | Virtual (TOTP app), Hardware (U2F / Yubikey) | Always enable MFA on root. Recommended for all privileged users. |
- IAM = Global service β no region in console. One set of users/roles works everywhere, always.
- IAM = Free β no cost. Never a budget reason to avoid security best practices.
- Default Deny β every evaluation starts as DENY. Must have explicit Allow. "Not denied" β allowed. Silence = Deny.
- 401 vs 403 β 401 = bad credentials (AuthN fail). 403 = good credentials, no permission (AuthZ fail). Different fixes entirely.
- Root account β cannot be restricted by SCPs or permission boundaries. Unrestricted. Secure with MFA + lockbox. Never use daily.
- Every API call goes through IAM β console, CLI, SDK, CloudFormation, service-to-service. No exceptions, no bypass.
- IAM eventual consistency β changes can take seconds to minutes to propagate globally. Not instantaneous.
- AuthN before AuthZ β strict sequence. Authentication always runs first. If auth fails, authorization never runs.
- Users = maximum 5,000 per account. At scale, use IAM Identity Center + federation, not individual IAM users.
- IAM = Identity and Access Management. The universal security enforcement layer for every action in AWS.
- Two-phase security: Authentication ("who are you?" β credentials check) β Authorization ("what can you do?" β policy evaluation).
- Every API call passes through IAM β Console, CLI, SDK, service-to-service. No bypass, no exceptions, no shortcuts.
- Default Deny: nothing is allowed unless a policy explicitly grants Allow. "Silence = Deny" is the fundamental rule.
- Global service: IAM entities (users, groups, roles, policies) work across all AWS regions. Not region-specific. Free.
- Mental model: Corporate building security β badges (credentials), access levels (policies), visitor passes (roles), master key in safe (root).
- Four control dimensions: WHO (principal) Β· WHAT (action) Β· WHICH (resource) Β· WHEN/HOW (condition).
- 401 = bad credentials (authentication failure) Β· 403 = no permission (authorization failure). Critical debugging distinction.
- Least privilege principle: grant only what is needed, nothing more. IAM makes this enforceable at every level.
- Roles beat keys: temporary credentials (IAM Roles) are always preferred over long-lived access keys for services.
IAM is the single point of security enforcement for every action in AWS. Default deny. Explicit allow required. Without IAM, there is no cloud security β it IS the security. Master IAM, and everything else in AWS security becomes an extension of what you already understand.
Core Components: Users, Groups, Roles & Policies
IAM has four core entity types. Mastering how they relate β and when to use each β is the foundation of every AWS security design. The mental mistake most beginners make is treating Users and Roles as interchangeable. They are not.
IAM Users β Permanent Identities
- A long-lived identity tied to one specific person or application
- Has a fixed set of credentials: password (console) and/or access keys (CLI/SDK)
- Access keys never expire automatically β must be rotated manually
- Policies attached directly to user OR inherited through Groups
- Max 5,000 per account β not suitable for large orgs at scale
- Best for: individual developers, break-glass accounts, legacy apps that can't use roles
IAM Roles β Temporary Identities
- An identity that is assumed temporarily and returns short-lived credentials
- No password, no permanent access keys β credentials expire automatically
- Can be assumed by: EC2, Lambda, ECS, other services, users in same/other accounts, federated identities
- Preferred over Users for machine-to-machine access β no long-lived secrets to leak
- Has two policy types: Trust Policy (who can assume) + Permission Policy (what they can do)
- Best for: all AWS services, cross-account access, CI/CD, federation
IAM Groups β Organizing Users
- A collection of IAM Users that share the same policies
- Not an identity β a Group cannot authenticate or assume a role
- Makes management easy: add user to group β inherits all group policies instantly
- Groups can have multiple policies attached; users can be in multiple groups
- Cannot be nested β a group cannot contain other groups
- Best for: organizing by job function (Developers, DevOps, ReadOnly, Billing)
IAM Policies β Permission Documents
- JSON documents that define what is allowed or denied
- Attached to Users, Groups, or Roles β they have no effect alone
- Two main types: Identity-based (attached to identity) and Resource-based (attached to resource)
- Managed policies (reusable) vs Inline policies (embedded, one-to-one)
- Evaluated together β IAM checks all attached policies to make a single decision
- Deep dive: Chapter 03
The root account is created when you first sign up for AWS using an email + password. It has unrestricted, irrevocable access to everything in the account β it cannot be limited by any IAM policy, permission boundary, or SCP.
What Root CAN'T Be Restricted By
- IAM policies β root ignores all identity-based policies
- Permission boundaries β do not apply to root
- SCPs (Service Control Policies) β do not apply to the management account's root user
- There is no "deny root" policy β it literally cannot be locked down via IAM
Root-Only Actions (Must Use Root)
- Change account name, email, or root password
- Enable IAM billing access for non-root users
- Close the AWS account
- Restore IAM user permissions after accidental lockout
- Subscribe/unsubscribe from some AWS support plans
- Register as seller in the Reserved Instance Marketplace
Immediately after creating an AWS account: (1) Enable MFA on root. (2) Create an admin IAM user for daily work. (3) Store root credentials in a password manager. (4) Never use root for any daily operation β not even "just this once." A compromised root account = complete account takeover with no recovery path.
| Credential Type | Used For | Details | Best Practice |
|---|---|---|---|
| Username + Password | AWS Management Console login | Set by admin. User can change it. Password policy enforced account-wide. | Require MFA for all console users. Set minimum length + complexity. |
| Access Key ID + Secret | CLI, SDK, API calls (programmatic) | The secret is shown ONCE at creation. Max 2 keys per user. Never expire automatically. | Rotate every 90 days. Delete unused keys. Prefer roles over long-lived keys. |
| SSH Keys | AWS CodeCommit (Git over SSH) | Separate from other IAM credentials. Attached to user for CodeCommit access only. | Use HTTPS + credential helper instead. SSH keys are per-user. |
| Service-specific credentials | CodeCommit HTTPS, Keyspaces (Cassandra) | Auto-generated credentials for specific services. Not AWS-wide API keys. | Generate only when needed. Delete after use. |
IAM allows max 2 access keys per user precisely for rotation: (1) Create new key β (2) Update all apps/scripts to use new key β (3) Verify new key works β (4) Deactivate (not delete) old key β (5) Wait and confirm nothing broke β (6) Delete old key. Never delete first, replace second β that causes downtime.
Groups solve a management problem: when you have 50 developers and need to give all of them the same permissions, you don't attach 50 individual policies. You create a Developers group, attach one policy to it, and add all 50 users to it. Change the policy once β all 50 users get the update instantly.
Administrators β AdministratorAccess policy Β· Developers β dev service permissions Β· DevOps β deploy + infra permissions Β· ReadOnly β ViewOnlyAccess Β· Billing β billing console access onlyRoles are the most important IAM entity for modern AWS architecture. Any time a service, application, or external identity needs to act in AWS, it should use a role β not an IAM user with hardcoded keys.
A role has two policy layers that must both allow the interaction:
Trust Policy β "Who Can Assume Me?"
Defines which principal is allowed to call sts:AssumeRole on this role. Without trust, nobody can use the role.
- AWS services:
"Service": "ec2.amazonaws.com" - Another AWS account:
"AWS": "arn:aws:iam::123456789:root" - Federated identity:
"Federated": "cognito-identity.amazonaws.com" - Specific IAM user:
"AWS": "arn:aws:iam::123:user/alice" - This is a resource-based policy on the role itself
Permission Policy β "What Can This Role Do?"
Standard IAM policies defining what actions the role can perform. Evaluated after AssumeRole succeeds.
- Identical structure to user/group policies
- Can attach multiple managed + inline policies
- Defines the scope of temporary credential permissions
- Can also be limited further by session policies at assume time
- Best practice: scope tightly to only what the service needs
- Principal requests temporary credentialsAn EC2 instance (or AWS service, user, or external identity) calls
sts:AssumeRolewith the role ARN. If it's an EC2 instance with an instance profile, this happens automatically β the EC2 metadata service handles it transparently for the SDK. - STS validates the Trust PolicyAWS Security Token Service (STS) checks the role's Trust Policy. Is the requesting principal listed as a trusted entity? If not β denied. If yes β proceed. Conditions in the trust policy are also evaluated (e.g., require MFA, require specific ExternalId for cross-account).
- STS issues temporary credentialsSTS returns three values:
AccessKeyId,SecretAccessKey, andSessionToken. These expire after the configured duration (default 1 hour, max 12 hours for most roles). The caller must use all three together β the session token proves the credentials are temporary. - Caller uses temporary credentialsThe caller signs requests with the temporary creds. AWS IAM validates the session token and evaluates the role's permission policies. The original caller's identity is not used β they are now acting AS the role. All CloudTrail logs show the role ARN + the original principal (AssumedRoleUser).
- Credentials expire β automatically revokedWhen the TTL expires, the credentials become invalid. No manual cleanup needed. If the role's policies change, new sessions get new permissions. To revoke active sessions before expiry: use Revoke Sessions in the IAM console (adds an explicit deny policy with a time condition) or update the permission policy.
| Role Type | Trust Principal | Use Case | Exam Keyword |
|---|---|---|---|
| Service Role | AWS service (ec2, lambda, ecsβ¦) | EC2 accessing S3, Lambda writing to DynamoDB, ECS pulling from ECR | "EC2 instance profile", "execution role" |
| Cross-Account Role | Another AWS account ID | Production access from dev account, central logging account, shared services | "cross-account access", "ExternalId" |
| Federated Role | Identity provider (SAML, OIDC) | Corporate SSO, GitHub Actions CI/CD, Cognito web app login | "web identity federation", "SAML 2.0", "OIDC" |
| Service-Linked Role | Specific AWS service (managed by AWS) | Auto-created by services like EKS, RDS, ElastiCache β you don't manage trust | "AWSServiceRoleForβ¦", cannot delete if service uses it |
| Instance Profile | ec2.amazonaws.com | Container for a service role that gets attached to an EC2 instance. Not a separate IAM entity type β a wrapper that links a role to EC2. | "instance profile", "IMDS metadata" |
An EC2 instance cannot directly use an IAM Role. The role must be wrapped in an Instance Profile and the instance profile is attached to the EC2 instance. The AWS Console does this automatically, so most people never see the distinction. But the exam knows: iam:CreateInstanceProfile is a separate permission from iam:CreateRole. The SDK reads credentials from the EC2 Instance Metadata Service (IMDS) at http://169.254.169.254/latest/meta-data/iam/security-credentials/.
| Dimension | IAM User | IAM Role |
|---|---|---|
| Credential Type | Permanent (password, access keys) | Temporary (STS-issued, 15minβ12hr) |
| Expiry | Never expire β must manually rotate/delete | Automatically expire β self-cleaning |
| Leak Risk | High β if key leaks, attacker has persistent access | Low β leaked temp creds expire automatically |
| For AWS Services | β Anti-pattern β hardcoding keys in code | β Correct β instance profiles, execution roles |
| For CI/CD | β οΈ Avoid β storing keys in GitHub secrets | β Use OIDC role assumption (keyless) |
| For Humans | β OK for console access with MFA | β Better with IAM Identity Center + federation |
| Cross-Account | β Must share credentials across account boundary | β Assume role from other account β clean separation |
| Audit Trail | Logs show username | Logs show role ARN + original principal (AssumedRoleUser) |
| Best Practice | Use for: console admins, break-glass, legacy apps only | Use for: everything else β services, CI/CD, cross-account, federation |
- 4 IAM entities: Users (permanent people), Groups (user collections), Roles (temp identities), Policies (permission docs).
- Root account β unrestricted, cannot be IAM-restricted. Use only for account-level tasks. Lock with MFA + offsite storage.
- Users have two credential types: console password AND/OR programmatic access keys. Keys never auto-expire.
- Groups are NOT identities β they cannot log in, cannot assume roles. Only a tool for organizing users and sharing policies.
- Roles are the correct approach for services β EC2, Lambda, ECS, EKS all use roles. Never hardcode access keys in code.
- Roles have two policy layers: Trust Policy (who can assume) + Permission Policy (what they can do). Both must allow.
- STS issues temporary credentials (AccessKey + SecretKey + SessionToken). All three required. Expire automatically.
- Instance Profile β not a separate entity, it's a container that wraps a role for attachment to EC2. SDK reads auto from IMDS.
- Cross-account access β both accounts must trust each other: role's trust policy must allow the external account, and the user in the external account must have
sts:AssumeRolepermission. - Access key rotation β create new β update apps β verify β deactivate old β delete old. Max 2 keys per user for zero-downtime.
Roles over Users, always, for machine-to-machine access. Long-lived access keys are a security liability β temporary credentials expire by design. Build everything on roles and you eliminate an entire class of credential leak vulnerabilities.
- EC2 uses Instance Profile (not "attach a role directly"). One instance profile per EC2 at a time. Profile wraps a role.
- Groups cannot assume roles β only users, services, or other roles can assume roles.
- Cross-account role assumption β needs trust policy on the role (allows external account) AND permission on the caller (
sts:AssumeRole). - STS session = 3 values: AccessKeyId + SecretAccessKey + SessionToken. All three required to sign API calls.
- Service-Linked Role β AWS creates/manages it. Tied to specific service. Cannot delete while service uses it.
- Root-only actions β closing account, restoring IAM admin lockout, changing account email. No root = blocked.
- Users in multiple groups β inherits ALL policies from all groups. Policies are additive (except explicit Deny wins).
- Max access keys per user = 2 β designed for zero-downtime rotation, not for sharing with 2 apps.
Policies Deep Dive β The Language of Permissions
An IAM policy is a JSON document that defines permissions. It specifies what actions are allowed or denied, on what resources, under what conditions. Policies are the fundamental unit of authorization in AWS β without a policy granting access, nothing is allowed.
Policies are inert by themselves. A policy document sitting in IAM does nothing until it is attached to an identity (user, group, role) or a resource (S3 bucket, KMS key, Lambda function). A policy attached to a user defines what that user can do. A policy attached to an S3 bucket defines who can access that bucket β regardless of their identity policies.
A policy document contains a Version and a list of Statement objects. Each statement is one permission rule:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowS3ReadWithMFA",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-data-bucket",
"arn:aws:s3:::my-data-bucket/*"
],
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
},
"IpAddress": {
"aws:SourceIp": "203.0.113.0/24"
}
}
}
]
}
"2012-10-17". This is not the date you created the policy β it's the policy language version. Using an older version "2008-10-17" disables policy variables like ${aws:username}. Always use 2012-10-17."Allow" or "Deny". The only two options. An explicit Deny overrides any Allow β including from other policies. This is the most powerful type of statement in IAM.service:ApiName. Examples: s3:GetObject, ec2:StartInstances, iam:CreateUser. Use * or s3:* for wildcards. Actions are case-insensitive.*. For S3 buckets, you often need both the bucket ARN AND the bucket/* ARN together. Some actions are not resource-specific and require "Resource": "*" (e.g., iam:ListUsers).*).aws:SourceIp, aws:MultiFactorAuthPresent, aws:RequestedRegion, s3:prefix, aws:CurrentTime.Identity-Based Policies
- Attached to a User, Group, or Role
- Defines what that identity is allowed/denied to do
- Most common type β 90% of IAM policies are identity-based
- Can be Managed (reusable) or Inline (embedded)
- Example: Allow developer to
ec2:Describe*on all resources - No
Principalfield β it's implied by who it's attached to
Resource-Based Policies
- Attached to the AWS resource itself (S3 bucket, KMS key, Lambda, SQS, SNS)
- Always inline β cannot be managed or reused
- Has a
Principalfield β defines who the policy applies to - Allows cross-account access without role assumption (for supported services)
- Example: S3 bucket policy granting another account read access
- Evaluated in addition to identity-based policies, not instead
Service Control Policies (SCPs)
- AWS Organizations feature β apply to OUs or entire accounts
- Not standalone permissions β they restrict what identity-based policies can allow
- SCPs cannot grant permissions, only limit them
- Applied before identity-based policies β SCP must allow + identity must allow for access
- Do NOT apply to root user of management account
- Example: Prevent any action outside approved regions across all accounts
Permission Boundaries
- Set maximum permissions a user or role can ever have
- Identity MUST have permission in both boundary AND identity policy
- Intersection: boundary β© identity policy = effective permissions
- Used to delegate permission management safely (developers create roles but can't exceed boundary)
- Not a grant β does not give permissions by itself
- Example: Allow devs to create roles, but those roles can never exceed
S3FullAccess
Session Policies
- Passed inline during
sts:AssumeRoleorsts:GetFederationToken - Further restricts what the session can do (cannot expand role permissions)
- Intersection: session policy β© role policy = effective session permissions
- Useful: give a role broad permissions but restrict specific sessions to narrow tasks
- Example: CI/CD job assumes a deploy role but session policy limits to one Lambda only
IAM Access Control Tags (ABAC)
- Attribute-Based Access Control β use AWS tags in conditions
- Grant access based on matching tags between user and resource
- Example: dev can access any EC2 instance tagged
Env=devif their user also has that tag - Scales better than RBAC for large teams
- Key:
aws:ResourceTag/key,aws:PrincipalTag/key
| Dimension | AWS Managed Policy | Customer Managed Policy | Inline Policy |
|---|---|---|---|
| Created by | AWS β pre-built, maintained by AWS | You β custom JSON, your ownership | You β embedded in one identity |
| Reusability | Attach to multiple users/roles/groups | Attach to multiple identities | One-to-one β single identity only |
| Versioning | AWS updates automatically (no control) | You manage up to 5 versions | No versioning β edit in place |
| Visibility | Visible in all accounts | Visible in your account only | Part of the identity β not separate |
| Deletion | Can't delete β only detach | Deletes independently; detaches from all | Deleted when its identity is deleted |
| Exam keyword | "AWS managed" β AdministratorAccess, ReadOnlyAccess | "customer managed" β your org's policies | "inline" β strict 1:1 relationship |
| Best for | Common access patterns (Admin, PowerUser, ReadOnly) | Custom org-specific permissions at scale | Special case β strict 1:1 binding, unusual |
When a principal in Account A tries to access a resource in Account B, the interaction of policy types determines access. This is where most cross-account errors happen:
Identity policy OR resource policy is enough.
If Alice has an identity policy allowing s3:GetObject on bucket B, she can access it β even if bucket B has no bucket policy.
If bucket B's policy allows Alice, she can access it β even if Alice has no identity policy for S3.
Either side granting access = allowed (assuming no explicit deny anywhere).
BOTH identity policy AND resource policy must allow.
If Alice in Account A wants bucket B in Account B:
- Bucket B's policy must explicitly allow Account A's principal (or the whole account)
- AND Alice's identity policy in Account A must allow
s3:GetObject
One side alone is not enough. Both must explicitly allow. This prevents Account B from being accessed just because Account A's admin allows it.
Conditions are the most powerful (and most underused) part of IAM policies. They allow context-aware permissions β the same identity can have different effective access depending on how and where they connect.
| Condition Key | What it checks | Example Use |
|---|---|---|
aws:SourceIp | Client IP address of the request | Restrict S3 access to corporate network only |
aws:MultiFactorAuthPresent | Whether MFA was used for this session | Require MFA to delete critical resources |
aws:RequestedRegion | Region where the API call was made | Prevent creating resources outside approved regions |
aws:PrincipalTag/key | Tags on the requesting identity | ABAC: devs can only access their team's resources |
aws:ResourceTag/key | Tags on the target resource | ABAC: team members access only matching-tagged resources |
aws:CalledVia | Service that made the call on behalf of a principal | Allow only CloudFormation to call DynamoDB (not direct user) |
s3:prefix | S3 object key prefix | Restrict access to team/alice/ folder only |
iam:PassedToService | Service a role is being passed to | Prevent passing powerful roles to wrong services |
aws:SecureTransport | Whether HTTPS was used | Deny all S3 requests over HTTP (enforce encryption in transit) |
aws:SourceVpc / aws:SourceVpce | Request came from specific VPC or VPC Endpoint | S3 bucket accessible only from within your VPC |
NotAction and NotResource are powerful but dangerous keywords. They are often in exam questions because they behave counterintuitively.
NotAction β "Everything EXCEPT these actions"
Allow with NotAction = allow everything EXCEPT listed actions.
"Effect": "Allow", "NotAction": ["iam:*", "organizations:*"], "Resource": "*"- This allows ALL AWS actions EXCEPT IAM and Organizations
- Commonly used to grant full access except dangerous IAM/billing actions
- Exam trap: Deny with NotAction = deny everything EXCEPT listed actions (i.e., allow only those actions)
- Not the same as "deny these actions" β only affects what falls outside the NotAction list
NotResource β "Everything EXCEPT this resource"
Allow with NotResource = allow actions on everything EXCEPT listed resources.
"Effect": "Allow", "Action": "s3:*", "NotResource": "arn:aws:s3:::prod-bucket/*"- Allows all S3 actions on EVERY bucket EXCEPT prod-bucket
- Use when you want to broadly grant but protect specific sensitive resources
- Exam trap: This does NOT deny access to prod-bucket β it just doesn't grant S3:* there. A separate Allow for prod-bucket could still exist.
- To truly protect: add an explicit Deny for prod-bucket
These condition keys are available in every AWS policy regardless of service. They appear constantly on AWS certification exams.
| Key | What It Checks | Exam Scenario |
|---|---|---|
aws:SourceIp | Client IP address | "Restrict API access to corporate network" |
aws:MultiFactorAuthPresent | MFA active in session | "Require MFA to delete S3 objects" |
aws:RequestedRegion | Region of the API call | "Prevent resource creation outside us-east-1" |
aws:PrincipalOrgID | AWS Organization ID | "Only allow access from my organization" |
aws:PrincipalTag/<key> | Tags on the calling identity | "Developer can access only their team's resources" |
aws:ResourceTag/<key> | Tags on the target resource | "Only allow access to resources tagged Env=dev" |
aws:CurrentTime | Current timestamp | "Allow access only during business hours" |
aws:SourceVpc | VPC ID of the request | "S3 bucket accessible only from specific VPC" |
aws:SourceVpce | VPC endpoint ID | "Restrict access to traffic through a specific endpoint" |
aws:CalledVia | Service that made the call on behalf of principal | "Allow only if called via CloudFormation" |
When a question says "restrict access to onlyβ¦" β think Condition block + the appropriate global key.
SCPs are typically used as deny lists (deny specific actions). But the allow list strategy is more restrictive and is a common exam pattern:
SCP β Allow List (Whitelist)
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"s3:Get*",
"s3:List*",
"cloudwatch:Get*",
"cloudwatch:List*"
],
"Resource": "*"
}]
} With only this Allow statement in the SCP, all other actions are implicitly denied β even ec2:DescribeInstances. This locks accounts to an exact set of services.
Deny List (Default)
Start with AWS default FullAWSAccess SCP, then add explicit denies for what you want to block.
Use when: You want broad access with specific restrictions.
Allow List (Restrictive)
Remove the default SCP, then explicitly allow only the services you want.
Use when: You want strict control β sandbox or compliance-locked accounts.
"SCP restricts account to only read S3 and CloudWatch" β Allow list SCP, not deny list.
The most common real-world use case for permission boundaries:
Scenario: Safe IAM Delegation
You want junior developers to create IAM roles for their Lambda functions, but you don't want them to accidentally create admin roles.
Solution:
- Create a boundary policy that limits max permissions to
LambdaBasicExecution + S3ReadOnly - Give developers
iam:CreateRole+iam:AttachRolePolicyin their identity policy - Require them to attach the boundary when creating roles:
iam:CreateRolewith conditioniam:PermissionsBoundary = arn:aws:iam::...:policy/DevBoundary - Result: They can create roles, but no role they create can exceed the boundary ceiling
"Delegate IAM role creation safely" or "prevent privilege escalation" β Permission Boundary.
What It Does
- Test IAM policies before deploying them
- Enter user/role ARN, actions, and resources
- Shows allow/deny and explains WHY
- Tests against all attached policies + boundaries
When to Use
- Debugging "AccessDenied" errors
- Testing condition logic
- Understanding evaluation order
- Validating least-privilege policies before rollout
Access: https://policysim.aws.amazon.com β separate from the main AWS Console.
- Policy = JSON document with Version, Statement (array of: Sid, Effect, Action, Resource, Condition). Version is always
2012-10-17. - Effect is
AlloworDenyβ explicit Deny always wins over any Allow from any policy. - 6 policy types: Identity-based Β· Resource-based Β· SCPs Β· Permission Boundaries Β· Session Policies Β· ABAC tags.
- Identity-based β attached to user/group/role, no Principal field. Most common type.
- Resource-based β attached to resource, HAS Principal field, always inline, enables cross-account access without role assumption (for S3, KMS, Lambda, SQS, SNS).
- SCPs β Organizations-level, cannot grant permissions, only restrict. Do not apply to root of management account.
- Permission Boundaries β set maximum permissions ceiling. Identity MUST have the action in BOTH boundary AND identity policy (intersection).
- Managed vs Inline: AWS managed (AWS maintains), Customer managed (you own, reusable), Inline (1:1 binding, cannot reuse).
- Same-account: either identity OR resource policy can grant access. Cross-account: BOTH must allow.
- NotAction β allow everything EXCEPT listed actions. NotResource β allow actions on everything EXCEPT listed resources. High exam frequency.
- Conditions β context-aware rules. Multiple in same block = AND. Multiple values = OR. Key exam condition keys:
aws:MultiFactorAuthPresent,aws:SourceIp,aws:RequestedRegion.
Policies are not just Allow/Deny lists β they are contextual, layered, and intersecting. Add conditions to make permissions context-aware. Layer SCPs, boundaries, and identity policies to build defense-in-depth. An explicit Deny is absolute β it cannot be overridden by any Allow, anywhere.
- Version must be
2012-10-17β older version disables policy variables. Always use this. - Explicit Deny always wins β regardless of how many Allows exist in other policies. One Deny = blocked.
- Cross-account = both policies must allow β identity policy in requester's account + resource policy in target account.
- SCP cannot grant, only restrict β even AdministratorAccess user in an OU with restrictive SCP cannot exceed the SCP.
- Permission boundary β grant β it's a ceiling, not a floor. Identity policy still needs to explicitly allow.
- Resource-based policy has Principal field β identity-based does not. Resource-based = always inline.
- NotAction with Allow = allow everything EXCEPT listed. Very common exam scenario. Different from "Deny these actions."
- S3 bucket needs BOTH bucket ARN and bucket/* ARN β
s3:ListBucketapplies to the bucket,s3:GetObjectapplies to objects (bucket/*). - Condition evaluation: multiple conditions = AND. Multiple values in one condition = OR.
Roles & Trust Relationships
IAM Users solve human access. IAM Roles solve everything else: service-to-service access, temporary elevated access, federation, and cross-account operations. A role is an identity you become temporarily - not one you permanently own.
Role = Temporary Identity
- No permanent password and no long-lived access key pair
- Assumed via STS to get temporary credentials
- Credentials auto-expire (15 min to 12 hours)
- Used by AWS services, users, external IdPs, and other accounts
- Reduces key leakage risk dramatically
Trust Relationship = Entry Gate
- Defines who is allowed to assume the role
- Implemented as the role's trust policy
- Without trust, assume-role request is denied immediately
- Can restrict assumption with conditions (MFA, ExternalId, source account)
- Permission policy is checked only after trust is passed
A role can be used only when Trust Policy allows assumption AND Permission Policy allows the action. Trust decides who can become the role. Permission decides what that role can do after assumption.
| Component | Question it Answers | Where it Lives | Common Misunderstanding |
|---|---|---|---|
| Trust Policy | "Who can assume this role?" | On the role itself (resource-based policy) | People think this grants resource access. It does not. |
| Permission Policy | "What can this role do after assumption?" | Attached to role (identity-based policy) | People think this controls who can assume. It does not. |
| STS Session | "What temporary credentials are issued?" | Generated by STS on assume-role | People forget SessionToken is mandatory. |
| Session Policy (optional) | "Further restrict this one session?" | Passed at assume time | People assume it can expand role permissions. It cannot. |
Service Trust
- Principal: AWS service
- Example: EC2, Lambda, ECS, API Gateway
- Used by service roles and execution roles
- Most common pattern in production
Cross-Account Trust
- Principal: external AWS account or role ARN
- Use
ExternalIdto prevent confused deputy risk - Common for shared services and central security accounts
- Requires caller-side
sts:AssumeRolepermission too
Federation Trust
- Principal: SAML or OIDC provider
- Used by IAM Identity Center and CI/CD OIDC flows
- Session duration and audience restrictions are critical
- No long-lived IAM user keys required
- Caller requests role assumptionCaller invokes STS API such as
AssumeRole,AssumeRoleWithSAML, orAssumeRoleWithWebIdentity. - Trust policy evaluated firstIf caller principal and conditions do not match trust policy, request fails immediately and no credentials are issued.
- STS returns credential tripletSession credentials include
AccessKeyId,SecretAccessKey, andSessionToken, plus expiration timestamp. - APIs execute with role identityRequests are signed with temporary credentials. IAM evaluates role permissions (and other policy layers) for every API action.
- Session expires automaticallyAfter TTL, credentials are invalid by design. This is why roles are safer than long-lived user keys.
| Role Type | Trusted Principal | Typical Permissions | Real Scenario |
|---|---|---|---|
| EC2 Service Role | ec2.amazonaws.com | S3 read, Secrets Manager read, CloudWatch put logs | Web app on EC2 reads config secrets and writes app logs |
| Lambda Execution Role | lambda.amazonaws.com | DynamoDB write, SQS send, CloudWatch logs | Serverless API writes orders to DynamoDB |
| ECS Task Role | ecs-tasks.amazonaws.com | S3 read, KMS decrypt, X-Ray tracing | Containerized microservice accesses encrypted payloads |
| Cross-Account Ops Role | External account or specific role ARN | Deploy/update resources in target account | Central CI/CD account deploys to production account |
| Federated Workforce Role | SAML/OIDC provider | Console and API rights based on job function | Corporate SSO users assume admin/read-only roles |
Both sides must be configured correctly. Missing either side = AccessDenied.
| Side | Requirement | Example |
|---|---|---|
| Source Account (Caller) | Identity policy allowing sts:AssumeRole on the target role ARN | "Action": "sts:AssumeRole", "Resource": "arn:aws:iam::111111111111:role/TargetRole" |
| Target Account (Role Owner) | Trust policy allowing source account principal | "Principal": { "AWS": "arn:aws:iam::222222222222:root" } |
| Both (Optional) | Conditions: MFA, ExternalId, time, source IP | "Condition": { "StringEquals": { "sts:ExternalId": "abc123" } } |
Source Must Have
sts:AssumeRole permission pointing at the target role ARN
Target Must Trust
Trust policy listing the source account/role/user as Principal
Guardrails Apply
SCPs on both accounts can block the assumption even if policies allow it
"Cross-account access denied" β Check: (1) Caller has sts:AssumeRole, (2) Trust policy lists caller, (3) No SCP blocking on either side.
- Role = temporary identity, not permanent credentials. STS issues short-lived sessions.
- Trust policy controls who may assume a role. Permission policy controls what assumed role may do.
- Assume-role succeeds only when trust passes; then normal authorization evaluation starts.
- Service roles and execution roles are the standard pattern for EC2, Lambda, and other AWS services.
- Cross-account roles require both sides: caller permission to assume + target role trust allowing caller.
- Temporary credentials always include
SessionTokenand expire automatically. - Use conditions in trust policies (MFA, ExternalId, audience, source constraints) as security guardrails.
- Prefer roles over long-lived user access keys for services and CI/CD.
- Trust policy answers "who can assume"; permission policy answers "what can be done."
- EC2 uses instance profile to supply role credentials; Lambda uses execution role automatically.
- Cross-account assume role needs caller-side
sts:AssumeRoleand target-side trust allow. - ExternalId in trust policy mitigates confused deputy in third-party access scenarios.
- Session policies can only reduce role permissions, never expand them.
- Missing
SessionTokenwith temporary creds causes auth failures even with correct key/secret.
Permission Evaluation Logic
Every AWS API call goes through IAM's permission evaluation engine. Understanding this decision logic is the single most important skill for IAM mastery. The exam tests it heavily. Misunderstanding one step leads to accidental over-permissioning or mysterious access denials.
Explicit Deny ALWAYS wins. No combination of Allows from any policy type can override an explicit Deny statement anywhere. One Deny in any applicable policy = request denied, period.
When an API request arrives, IAM evaluates it through this exact sequence. The order matters:
- Step 1: Start with implicit DENYEvery request begins in DENY state. This is not a policy decision - it is the system default. Nothing is allowed until explicitly permitted. "Silence = Deny."
- Step 2: Evaluate all applicable policiesIAM collects all policies that apply: identity-based, resource-based, SCPs, permission boundaries, and session policies. All are evaluated together - not sequentially.
- Step 3: Check for explicit DENYIf ANY policy has an explicit Deny that matches the action, resource, and conditions - evaluation stops immediately. Result: DENIED. No further checks happen.
- Step 4: Check Organizations SCP (if applicable)If the account is in an AWS Organization, the SCP must allow the action. SCPs cannot grant access - they only filter. If SCP does not explicitly allow it, result: DENIED (implicit deny from SCP).
- Step 5: Check Permission Boundary (if set)If a permission boundary is attached to the identity, it must allow the action. Like SCPs, boundaries filter - they never grant. Not in boundary = DENIED.
- Step 6: Check Session Policy (if present)If session policies were passed during
AssumeRoleorGetFederationToken, the action must be in the session policy. If not = DENIED. - Step 7: Check for explicit ALLOWFinally, check identity-based and/or resource-based policies for an Allow. Same-account: either one can grant. Cross-account: both must grant. If no Allow found = DENIED (implicit deny).
Identity-based OR resource-based policy can grant.
- If Alice's identity policy allows
s3:GetObjecton bucket B, she can access it even if the bucket has no bucket policy. - If bucket B's policy allows Alice, she can access even without an identity policy.
- Either one granting = ALLOWED (assuming no explicit deny).
Union of permissions.
Both identity-based AND resource-based MUST allow.
- Account A caller needs identity policy granting the action.
- Account B resource needs resource policy allowing Account A.
- One side alone is NOT enough. Both must explicitly allow.
Intersection of permissions.
Exception: if caller uses a role in target account, only role permission policy applies (caller ID is now the role).
| Scenario | Effective Permissions Formula | Key Insight |
|---|---|---|
| User with policies only | Identity policies (union of all attached) | All Allow statements across all policies merge additively. |
| User with permission boundary | Identity policies INTERSECT boundary | Only actions in BOTH the boundary AND identity policy are allowed. |
| User in Organization | SCP INTERSECT identity policies | SCP is a ceiling applied to all non-root identities in the account. |
| Role session with session policy | Role policy INTERSECT session policy | Session further restricts. Cannot expand beyond role permissions. |
| Full stack (Org + Boundary + Session) | SCP INTERSECT Boundary INTERSECT Identity INTERSECT Session | Tightest constraint wins. All layers must pass. |
| Cross-account S3 access | (Source identity policy) INTERSECT (Bucket policy Principal allow) | Both accounts must independently grant. Neither alone is enough. |
Trap: "I have Allow so I'm good"
- An Allow in identity policy means nothing if SCP does not allow it.
- An Allow in identity means nothing if Boundary does not include it.
- An Allow is overridden by any explicit Deny anywhere in any policy.
- Allow is necessary but not sufficient without all layers passing.
Trap: "No Deny means allowed"
- Absence of Deny is NOT presence of Allow.
- If no policy has an explicit Allow for the action, it stays in default DENY.
- The system does not auto-allow if no deny exists. It auto-denies if no allow exists.
- "Not denied" does NOT mean "allowed."
Trap: "Deny on bucket policy blocks my admin"
- A Deny on a bucket policy blocks even AdministratorAccess users.
- Only root account user of the owning account can bypass resource policy deny.
- Be careful with broad Deny statements in resource policies.
- Resource-based Deny is as absolute as identity-based Deny.
Trap: "Cross-account just needs bucket policy"
- Bucket policy granting Account A is necessary but not sufficient.
- Account A must also have identity policy allowing
s3:GetObject. - Only exception: role assumption in the target account merges identity.
- Both sides must explicitly allow for cross-account resource access.
Use this table to instantly determine the final access decision for any scenario:
| Scenario | Explicit Deny? | SCP Allows? | Boundary Allows? | Session Policy? | Explicit Allow? | Final Decision |
|---|---|---|---|---|---|---|
| No policies attached | No | Yes | N/A | N/A | No | β DENIED |
| Identity allows | No | Yes | N/A | N/A | Yes | β ALLOWED |
| Deny + Allow both present | Yes | Yes | N/A | N/A | Yes | β DENIED |
| SCP blocks the action | No | No | N/A | N/A | Yes | β DENIED |
| Boundary blocks action | No | Yes | No | N/A | Yes | β DENIED |
| Session policy restricts | No | Yes | Yes | No | Yes | β DENIED |
| All layers pass | No | Yes | Yes | Yes | Yes | β ALLOWED |
A single "No" anywhere in any layer = DENIED. Access is allowed ONLY when every applicable layer says yes AND no explicit deny exists.
- Default state = DENY. Every request starts denied. Must be explicitly allowed.
- Explicit Deny is absolute. It overrides all Allows from all policy types. No recovery.
- Evaluation checks: Explicit Deny > SCP > Boundary > Session > Explicit Allow.
- All layers must pass. It is an intersection of SCP, boundary, session, and identity/resource policies.
- Same-account: identity OR resource policy can grant (union). Cross-account: both MUST grant (intersection).
- SCPs and boundaries cannot grant. They can only restrict what identity policies allow.
- No Deny does NOT mean allowed. Absence of Allow = implicit deny (the default).
- Resource-based explicit Deny blocks even admin users (only root bypasses).
IAM evaluation is a chain of filters: every filter can block, but only one check can grant. Build your mental model as: "denied by default, must pass ALL gates to get through." An explicit Deny at any stage is the end of the story.
- Explicit Deny always wins β this is the #1 tested concept in IAM.
- Cross-account: both sides must allow. Identity in source + resource in target.
- SCP does not apply to management account root. It applies to all other accounts.
- Permission boundary = ceiling, not a grant. Identity must still explicitly allow.
- "User got 403" β check: SCP, boundary, deny statements, then absent allow.
- Session policies restrict but cannot expand. Passed at AssumeRole time.
Security Best Practices
The most important security concept in IAM: grant only the minimum permissions required to perform a specific task. Nothing more. Start with zero access and add only what is needed. Never start with full access and try to remove.
Most security breaches are not from sophisticated attacks - they are from over-permissioned identities. An IAM user with AdministratorAccess whose credentials leak gives an attacker full account control. An IAM user with s3:GetObject on one bucket limits blast radius to that one bucket.
Root Account Protection
- Enable MFA on root immediately after account creation
- Never use root for daily operations - create admin IAM user or use IAM Identity Center
- Store root credentials in a hardware vault or offline password manager
- Delete root access keys if they exist
- Enable AWS CloudTrail to log any root usage
- Set billing alarm for unexpected charges (root compromise indicator)
Use Roles Over Users
- All AWS services should use roles, never hardcoded keys
- CI/CD pipelines should use OIDC federation or role assumption
- Cross-account access via role assumption, not shared credentials
- Temporary credentials expire automatically - built-in safety
- If you must use an IAM user, rotate access keys every 90 days maximum
- Never embed access keys in source code or configuration files
Policy Design Principles
- Start with zero permissions - add only what is needed
- Scope to specific resources (ARN), not
"Resource": "*" - Use conditions (IP, MFA, time, tags) to restrict context
- Use permission boundaries for delegated admin scenarios
- Prefer managed policies (version-controlled, reusable)
- Review and remove unused permissions periodically (Access Analyzer)
MFA Everywhere
- Root account: hardware MFA device (YubiKey or similar)
- All privileged users: virtual MFA at minimum
- Destructive operations: require MFA via policy condition
aws:MultiFactorAuthPresent: "true"in conditions- MFA for assuming sensitive cross-account roles
- SMS MFA is discouraged - use TOTP or hardware tokens
Common Anti-Patterns to Avoid
- Sharing IAM credentials between multiple team members
- Using
AdministratorAccessfor daily operations - Embedding access keys in application code, Dockerfiles, or Git repos
- Creating IAM users for services instead of roles
- Setting overly broad resource targets (
arn:aws:s3:::*) - Ignoring IAM Access Analyzer findings
Monitoring and Auditing
- AWS CloudTrail: log all IAM API calls (create, delete, attach policy)
- IAM Access Analyzer: identify resources shared externally
- IAM Credential Report: find unused users and keys
- Config Rules: enforce MFA, key rotation compliance
- GuardDuty: detect anomalous IAM activity (unusual API calls)
- Set alerts for root login, policy changes, and role creation
One of the most powerful least-privilege tools: generate a policy from actual usage instead of guessing what permissions are needed.
| Input | Output |
|---|---|
| CloudTrail logs (up to 90 days) | Least-privilege policy based on actual API calls made |
| IAM user/role ARN | Policy scoped to exactly what that identity used |
| Time range selection | Policies for specific audit windows (e.g., last 30 days) |
Real-World Example
Start a Lambda role with s3:* + dynamodb:* (overly broad). Run it in production for 30 days. Then use Access Analyzer to generate a policy containing only the S3 and DynamoDB actions actually invoked. Replace the broad policy with the generated one.
Result: Perfect least-privilege without guessing.
"Reduce permissions to only what is needed based on actual usage" β IAM Access Analyzer Policy Generation.
- Least privilege: start with zero, add only what is needed. Scope to specific resources and use conditions.
- Root account: MFA, no daily use, no access keys, store offline. Monitor with CloudTrail alerts.
- Roles over users: all services use roles. CI/CD uses OIDC. Cross-account uses role assumption. Never hardcode keys.
- MFA: hardware for root, virtual for all privileged users. Require MFA in policy conditions for destructive ops.
- Monitor: CloudTrail for audit trail, Access Analyzer for external access, GuardDuty for anomalies, Config for compliance.
- Access Analyzer: generate least-priv policies from usage, validate policy grammar, and detect stale access.
- Multi-account: Org SCPs as guardrails, IAM Identity Center for humans, separate security account for centralized audit.
- Rotation: access keys 90-day max. Use credential report to identify stale keys. Prefer roles to eliminate rotation need.
Security is not a one-time setup - it is continuous. Use automation: Access Analyzer for policy generation, Config for compliance checks, GuardDuty for threat detection. The goal is zero long-lived credentials, least-privilege policies, and complete audit visibility across every account.
- Root account: enable MFA, no access keys, only for account-level tasks (billing, close account).
- "EC2 needs S3 access" = use instance profile / role. Never access keys on the instance.
- "Audit who did what" = CloudTrail. "Find external access" = Access Analyzer.
- "Rotate credentials" = Credential Report + Config Rules. Automate with Lambda.
- "Prevent creating resources outside eu-west-1" = SCP with
aws:RequestedRegioncondition.
Advanced Topics β Federation, STS & Cross-Account
STS is the engine behind all temporary credentials in AWS. Every time a role is assumed, a federation token is generated, or a session is created, STS is the service doing it. Understanding STS APIs is essential for advanced IAM work.
| STS API | Who Uses It | What It Returns | Duration |
|---|---|---|---|
AssumeRole | IAM users, roles, or services within or across accounts | Temp creds (AccessKey + Secret + SessionToken) | 15 min to 12 hours (default 1 hr) |
AssumeRoleWithSAML | Corporate IdP users via SAML 2.0 federation | Temp creds mapped to federated identity | 15 min to 12 hours |
AssumeRoleWithWebIdentity | Web/mobile app users via OIDC providers (Google, GitHub, Cognito) | Temp creds for web identity | 15 min to 12 hours |
GetSessionToken | IAM user requesting MFA-elevated session | Temp creds with MFA context | 15 min to 36 hours |
GetFederationToken | IAM user creating a federated session for downstream use | Temp creds with optional session policy | 15 min to 36 hours |
GetCallerIdentity | Any caller (debugging) | Account ID, ARN, and UserId of the caller | N/A (read-only, no credentials issued) |
A role can assume another role (role chaining). However, the maximum session duration for chained roles is capped at 1 hour regardless of individual role settings. This is an exam-tested limit. Also, aws:SourceIdentity propagates through the chain for audit purposes.
Federation allows external identities to access AWS without creating IAM users. This is how enterprises with thousands of employees manage AWS access without 5,000 IAM users.
SAML 2.0 Federation
- Enterprise IdP (Active Directory, Okta, Azure AD) authenticates user
- IdP sends SAML assertion to AWS STS endpoint
- STS validates assertion and issues temporary credentials
- User assumes a role mapped to their SAML group/attribute
- No IAM user created - identity lives in the corporate directory
- Best for: large enterprises with existing IdP infrastructure
OIDC / Web Identity Federation
- User authenticates with OIDC provider (Google, GitHub, Cognito)
- App receives JWT token from the provider
- App calls
AssumeRoleWithWebIdentitywith the JWT - Trust policy validates audience and subject claims
- Best for: mobile apps, web apps, CI/CD (GitHub Actions OIDC)
- Cognito Identity Pools simplify this for mobile/web apps
IAM Identity Center (SSO)
- AWS-managed SSO service for multi-account access
- Connects to corporate IdP or uses built-in directory
- Permission sets map to IAM roles in target accounts
- Single sign-on portal for all AWS accounts and apps
- Replaces per-account IAM user management
- Best for: any org with multiple AWS accounts
Cognito (App Users)
- User Pools: managed user directory with sign-up/sign-in
- Identity Pools: exchange tokens for AWS temp credentials
- Supports social login (Google, Facebook, Apple)
- Maps authenticated/unauthenticated users to IAM roles
- Fine-grained access control via identity pool role mapping
- Best for: customer-facing apps needing AWS resource access
| Pattern | Mechanism | When to Use | Key Consideration |
|---|---|---|---|
| Role Assumption | Source account assumes role in target account via STS | Most common. CI/CD, admin access, service access. | Both sides must allow: trust policy + caller permission. |
| Resource-Based Policy | Target resource policy grants access to source account principal | S3 bucket sharing, KMS key sharing, Lambda invoke. | Source identity policy ALSO needed (cross-account intersection). |
| IAM Identity Center | Central SSO with permission sets across all accounts | Human access to multiple accounts from single login. | Creates roles in each account. Managed centrally. |
| AWS RAM (Resource Access Manager) | Share specific resources across accounts without policies | VPC subnets, Transit Gateways, License Manager configs. | Limited to supported resource types. |
When a third-party service (e.g., a monitoring SaaS) assumes a role in your account to access your resources, another customer of that SaaS could trick it into assuming YOUR role instead of theirs. The SaaS is the "deputy" being "confused" about which customer it is acting for. Solution: require ExternalId in the trust policy - a secret shared only between you and the legitimate third party.
A newer feature (2022) that extends IAM roles to workloads outside AWS β on-premises servers, Kubernetes clusters, IoT devices.
How It Works
- Uses X.509 certificates instead of access keys
- Register your Certificate Authority (CA) as a trust anchor
- On-prem workload presents certificate β gets temporary AWS credentials
- Credentials expire (configurable session duration)
Use Cases
- On-prem servers that need S3/DynamoDB access
- Hybrid Kubernetes clusters
- IoT devices calling AWS APIs
- Migration: replace long-lived access keys with cert-based auth
Workflow: Register CA β Create Trust Anchor β Create Profile (maps to role) β Workload presents cert β Gets temp creds via rolesanywhere:CreateSession
"On-premises server needs AWS access without IAM user or access keys" β IAM Roles Anywhere.
Not strictly IAM core, but frequently tested alongside IAM on AWS certification exams:
IAM DB Auth β Key Facts
- What: Authenticate to RDS (MySQL/PostgreSQL) using IAM credentials instead of database passwords
- How: Generate auth token with
aws rds generate-db-auth-tokenβ token valid for 15 minutes - Requires: RDS instance with IAM DB Auth enabled + IAM policy granting
rds-db:connect - Use case: No database password in application code; use IAM role on EC2/Lambda instead
- Limitation: Max ~200 connections/second (not for high-connection workloads)
- Benefit: Credentials rotate automatically; no SecretManager needed for DB passwords
"RDS authentication without storing passwords" or "Lambda accessing RDS securely" β IAM Database Authentication.
- STS is the engine behind all temporary credentials: AssumeRole, AssumeRoleWithSAML, AssumeRoleWithWebIdentity, GetSessionToken, GetFederationToken.
- Role chaining caps session at 1 hour maximum regardless of individual role settings.
- SAML 2.0 β enterprise federation via corporate IdP. User never has IAM credentials.
- OIDC β web identity federation for apps and CI/CD (GitHub Actions, Cognito).
- IAM Identity Center β AWS-managed SSO. Permission sets become roles. Best for multi-account human access.
- Cognito β User Pools for auth, Identity Pools for AWS credential exchange. App users pattern.
- Cross-account: role assumption (both sides allow) or resource-based policy (both sides allow).
- ExternalId in trust policy prevents confused deputy attacks from third-party services.
- Role chaining = 1 hour max session. Cannot extend via role settings.
- "Corporate SSO to AWS" = IAM Identity Center or SAML 2.0 federation.
- "Mobile app needs S3 access" = Cognito Identity Pools + IAM role.
- "GitHub Actions deploy to AWS" = OIDC federation, no stored secrets.
- "Third-party SaaS access" = cross-account role + ExternalId condition.
- GetCallerIdentity = debug "who am I?" No permissions needed to call it.
Architecture Patterns & Real-World Scenarios
lambda.amazonaws.com. Attach policy with sqs:ReceiveMessage, sqs:DeleteMessage, dynamodb:PutItem on the specific table ARN, and logs:CreateLogGroup / logs:PutLogEvents.AmazonDynamoDBFullAccess managed policy. Scope to table ARN and specific actions only. Use resource-based policy on SQS to allow Lambda invocation if using event source mapping.s3:prefix.aws:SecureTransport: false. Use S3 Block Public Access as safety net.sts:AssumeRole on a specific emergency role. Emergency role requires hardware MFA. Credentials stored in physical vault (not digital).- EC2: instance profile + service role. IMDSv2 enforced. No keys on disk.
- Lambda: execution role scoped to exact table/queue ARNs and actions needed.
- S3 mixed access: identity-based for same-account, bucket policy for cross-account, conditions for prefix isolation.
- CI/CD: OIDC federation (GitHub/GitLab). Cross-account deploy role with branch/repo conditions.
- Break-glass: separate security account, hardware MFA, alert on use, review after each incident.
- Multi-account strategy: Org SCPs as guardrails, Identity Center for humans, roles for services, security account for audit.
- Zero IAM users in production. All access via roles, SSO, or OIDC.
Production IAM is not about individual policies - it is about architecture. The right pattern eliminates entire classes of vulnerabilities. Use roles everywhere, federate humans, scope machines, guard with SCPs, and audit everything. The goal is zero long-lived credentials across all accounts.
- "EC2 needs S3 access without keys" = instance profile + service role.
- "Lambda writes to DynamoDB" = execution role with PutItem on specific table ARN.
- "Share S3 with another account" = bucket policy + caller identity policy (both required).
- "Deploy from GitHub without secrets" = OIDC provider + cross-account role.
- "Emergency access when SSO is down" = break-glass IAM user in security account + MFA.
- "Prevent any human from creating IAM users in prod" = SCP DenyIAMUserCreate on prod OU.