The Advanced AWS IAM Guide (2025): STS, SCPs, Permission Boundaries, Identity Center & ABAC
By Srikanth Ch, Senior DevOps Engineer | thedevopstooling.com
Who This Guide Is For
- Senior DevOps & Cloud Engineers deepening their AWS security knowledge
- Candidates preparing for AWS Security Specialty or SAA-C03 exams
- Architects designing secure multi-account AWS organizations
- Anyone facing senior-level AWS IAM interview questions
Reading Time: ~20–25 minutes
Let me tell you something that took me years to understand: most AWS security breaches don’t happen because of missing firewalls or unpatched servers. They happen because someone misconfigured an IAM policy at 2 AM, copy-pasted a trust policy from Stack Overflow, or didn’t understand how STS token chains actually work.
I’ve interviewed dozens of candidates for senior DevOps and cloud security roles. The pattern is consistent — almost everyone can explain what an IAM policy is. But ask them about permission boundaries, session policies, or how cross-account role chaining breaks, and the room goes silent.
This guide covers the advanced AWS IAM concepts that are missing from most tutorials — and frequently tested in senior-level interviews.
Let’s get into it.
Table of Contents: Advanced AWS IAM
TL;DR — Advanced IAM in 10 Bullets
- Use STS everywhere — never long-lived access keys for humans or workloads.
- Restrict trust policies with
aws:PrincipalOrgIDandExternalId. - Use permission boundaries to cap what developer-created roles can do.
- SCPs are guardrails, not grants — they only restrict at the org level.
- Prefer Identity Center over IAM users for all human access.
- Apply session policies for runtime least-privilege scoping.
- Implement ABAC using role session tags for scalable access control.
- Use IAM Roles Anywhere for on-prem and multi-cloud workloads.
- Continuously clean up unused permissions with Access Analyzer.
- Monitor CloudTrail for credential compromise patterns.
Why Advanced IAM Matters in 2025
AWS has quietly been deprecating traditional IAM users. If you’re still creating long-lived access keys for developers, you’re operating on patterns from 2015.
Modern AWS security is built on three pillars:
Temporary credentials everywhere — STS tokens, not static keys.
Attribute-based access control (ABAC) — Tags determining permissions, not hardcoded ARNs.
Centralized identity management — IAM Identity Center replacing scattered IAM users across accounts.
Understanding these shifts isn’t optional anymore. It’s table stakes for any DevOps engineer working in enterprise environments.
1. AWS STS — Temporary Security Credentials Deep Dive
Here’s a story. A developer at a fintech company I consulted for had their laptop stolen. Their AWS access keys were in a .env file. The attacker had 72 hours of access before anyone noticed.
If they’d been using STS temporary credentials with a 1-hour expiry, the damage window would have been… 1 hour. Maybe less.
How STS Actually Works
AWS Security Token Service (STS) generates temporary credentials consisting of three components:
Access Key ID — temporary, starts with ASIA (not AKIA like permanent keys)
Secret Access Key — temporary secret
Session Token — the critical piece that makes it all work
These credentials have a built-in expiration. When they expire, they’re useless. No revocation needed. No rotation headaches.
STS vs IAM Users — Quick Comparison
| Feature | IAM Users (Static Keys) | STS Temporary Credentials |
|---|---|---|
| Lifetime | Long-lived (until rotated) | Short-lived (minutes–hours) |
| Rotation | Manual or automated scripts | Built-in via expiration |
| Blast radius | High if leaked | Limited by duration & policy |
| Revocation | Must delete/rotate key | Automatic at expiry |
| Best for | Legacy systems, edge cases | All human & workload access |
Inside an STS Token
Here’s something most tutorials skip: STS tokens aren’t just random strings. They carry encoded session context that AWS uses to validate every request.
┌─────────────────────────────────────────────────────────────────┐
│ STS Credential Components │
├─────────────────────────────────────────────────────────────────┤
│ Access Key ID: ASIA... (20 chars, always starts ASIA) │
│ Secret Key: 40 characters, base64-encoded │
│ Session Token: ~1000+ characters, contains: │
│ - Assumed role ARN │
│ - Session name │
│ - Expiration timestamp │
│ - Session tags (if any) │
│ - Source identity (if set) │
│ - Cryptographic signature │
└─────────────────────────────────────────────────────────────────┘
The session token is cryptographically signed by AWS. This is why STS tokens cannot be reused across sessions — each token is bound to a specific IAM session context, including the exact role ARN, session name, and timestamp. Tampering with any component invalidates the signature.
This is also why attackers who steal STS credentials always run GetCallerIdentity first. It’s a free API call that tells them exactly what identity they’ve compromised, the account they’re in, and the role ARN — all without triggering permission errors that might alert defenders.
The AssumeRole API — Token Generation Flow
When your application calls sts:AssumeRole, here’s what happens:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Your Code │────────▶│ AWS STS │────────▶│ Target Role │
│ (Principal) │ │ Service │ │ Trust Policy │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
│ 1. AssumeRole Request │ │
│ (RoleArn, SessionName) │ │
│──────────────────────────▶│ │
│ │ 2. Validate Trust Policy │
│ │──────────────────────────▶│
│ │ │
│ │ 3. Trust Policy Allows? │
│ │◀──────────────────────────│
│ │ │
│ 4. Return Temp Creds │ │
│◀──────────────────────────│ │
└───────┴───────────────────────────┴───────────────────────────┘
The trust policy on the target role is evaluated first. If it doesn’t explicitly allow your principal, the request fails immediately.
Cross-Account STS in CloudTrail
When debugging cross-account access issues, you’ll need to read CloudTrail logs from both accounts. Here’s what a successful cross-account AssumeRole looks like:
Source Account CloudTrail (Account A):
{
"eventName": "AssumeRole",
"eventSource": "sts.amazonaws.com",
"userIdentity": {
"type": "AssumedRole",
"arn": "arn:aws:sts::111111111111:assumed-role/SourceRole/session"
},
"requestParameters": {
"roleArn": "arn:aws:iam::222222222222:role/TargetRole",
"roleSessionName": "cross-account-session"
},
"responseElements": {
"credentials": {
"accessKeyId": "ASIAXXXXXXXXXXX",
"expiration": "2025-01-15T12:00:00Z"
}
}
}
Target Account CloudTrail (Account B):
The AssumeRole event also appears here, showing the external principal that assumed the role — critical for auditing who’s accessing your account.
AssumeRole vs AssumeRoleWithSAML
AssumeRole — For AWS principals (IAM users, roles, services) assuming another role.
AssumeRoleWithSAML — For federated users authenticated through a SAML 2.0 identity provider (Okta, Azure AD, ADFS).
The key difference? With SAML, there’s no AWS principal making the request. The identity provider vouches for the user, and AWS trusts that assertion.
Role Chaining — And When It Breaks
Role chaining is when Role A assumes Role B, which then assumes Role C.
User → Role A → Role B → Role C
Here’s what catches people: AWS limits role chains to a maximum of one hop for session duration inheritance. If Role A has a 12-hour max session, and Role B is assumed from Role A, Role B’s session is capped at 1 hour by default.
Also critical: You cannot chain more than one AssumeRole call in a single request. Each hop requires a separate API call with the credentials from the previous hop.
Duration Limits Reference
| Scenario | Default | Maximum |
|---|---|---|
| AssumeRole | 1 hour | 12 hours (configurable on role) |
| AssumeRoleWithSAML | 1 hour | 12 hours |
| AssumeRoleWithWebIdentity | 1 hour | 12 hours |
| Role chaining | 1 hour | 1 hour (hard limit) |
| Console session (federated) | 1 hour | 12 hours |
Overly Permissive Trust Policies
I’ve seen this trust policy in production more times than I’d like to admit:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "sts:AssumeRole"
}
]
}
This says: anyone in the world with an AWS account can assume this role. It’s the IAM equivalent of leaving your front door open with a sign saying “come in.”
Always specify exact account IDs, role ARNs, or use conditions like aws:PrincipalOrgID to restrict to your organization.
🎯 Interview Questions You’ll Get
- “How does cross-account AssumeRole evaluation work?”
- “What breaks in role chaining and why is there a 1-hour limit?”
- “How would you debug an ‘AccessDenied’ for AssumeRole in CloudTrail?”
- “Why do attackers call GetCallerIdentity first after stealing credentials?”
2. Permission Boundaries — The Ceiling on Permissions
Here’s the mental model that finally made permission boundaries click for me:
Identity policies say what you can do.
Permission boundaries say what you’re allowed to be able to do.
The boundary is a ceiling. Even if your identity policy grants s3:*, if your permission boundary only allows s3:GetObject, you can only get objects.
SCP vs Permission Boundary vs IAM Policy
This comparison is interview gold — memorize it:
| Control Type | Scope | Grants Permissions? | Typical Use |
|---|---|---|---|
| IAM Policy | Single identity or resource | ✅ Yes | App & user access control |
| Permission Boundary | Single user or role | ❌ No (only limits) | Cap what dev-created roles can do |
| SCP | Entire account or OU | ❌ No (only limits) | Org-wide guardrails & compliance |
Key insight: Permission boundaries and SCPs never grant permissions. They only restrict what identity policies can allow.
Real-World Use Case: Platform Teams
Imagine you’re the platform team. You want developers to create their own IAM roles for Lambda functions. But you don’t want them creating roles that can delete production databases.
You create a permission boundary policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*",
"dynamodb:*",
"logs:*",
"lambda:*"
],
"Resource": "*"
},
{
"Effect": "Deny",
"Action": [
"iam:*",
"organizations:*",
"rds:Delete*"
],
"Resource": "*"
}
]
}
Now, every role developers create must have this boundary attached. They can give themselves whatever permissions they want in their identity policy — but they’ll never exceed the boundary.
Automatic Boundary Attachment Pattern
Smart platform teams enforce boundaries automatically. Here’s the pattern:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCreateRoleOnlyWithBoundary",
"Effect": "Allow",
"Action": "iam:CreateRole",
"Resource": "*",
"Condition": {
"StringEquals": {
"iam:PermissionsBoundary": "arn:aws:iam::123456789012:policy/DeveloperBoundary"
}
}
}
]
}
This policy allows developers to create roles, but only if they attach the approved permission boundary. No boundary, no role creation.
How Permission Boundaries + SCPs Interact
This is a top interview question: “If a role has both a Permission Boundary and an SCP applies to the account, which wins?”
The answer: neither “wins” — they’re evaluated in sequence, and all must allow.
┌─────────────────────────────────────────────────────────────────┐
│ Permission Evaluation Order │
├─────────────────────────────────────────────────────────────────┤
│ 1. Explicit Deny anywhere? → DENY (game over) │
│ 2. SCP allows the action? → If no, DENY │
│ 3. Permission Boundary allows? → If no, DENY │
│ 4. Identity Policy allows? → If no, DENY │
│ 5. Resource Policy allows? (if any) → Final decision │
└─────────────────────────────────────────────────────────────────┘
Think of it as a series of gates. Your request must pass through every gate. If any gate is closed, you’re blocked — regardless of what other gates say.
Boundary Misconfiguration Attack Scenario
Here’s an attack pattern interviewers love to discuss:
The Setup: A developer has permission to create IAM roles with a boundary attached. But the policy doesn’t restrict which boundary they can use.
The Attack:
- Developer creates a new, overly permissive boundary policy
- Developer creates a role with their malicious boundary
- Developer assumes that role
- Now they have permissions beyond what the platform team intended
The Fix: Always restrict which boundaries can be attached:
{
"Condition": {
"StringEquals": {
"iam:PermissionsBoundary": [
"arn:aws:iam::123456789012:policy/ApprovedBoundaryA",
"arn:aws:iam::123456789012:policy/ApprovedBoundaryB"
]
}
}
}
🎯 Interview Questions You’ll Get
- “What’s the difference between a permission boundary and an SCP?”
- “If a boundary denies an action but the identity policy allows it, what happens?”
- “How would you prevent developers from creating overly permissive roles?”
🧪 Hands-On Lab Idea (15–20 minutes)
- Create a
DeveloperBoundarypolicy blockingiam:*andrds:Delete*- Create a role with an identity policy allowing
s3:*andrds:*- Attach the boundary to the role
- Try to delete an RDS instance → observe the deny in CloudTrail
- Remove the boundary and retry → observe the difference
3. IAM Roles Anywhere — Extending AWS IAM Beyond the Cloud
This is one of AWS’s most underrated features, and it’s becoming an interview favorite for senior positions.
IAM Roles Anywhere lets workloads outside AWS assume IAM roles using X.509 certificates. No access keys. No embedded credentials.
How It Works
Your on-premises server has an X.509 certificate issued by a Certificate Authority (CA) that AWS trusts (you configure this trust).
When the workload needs AWS access, it presents its certificate to AWS. AWS validates the certificate against your configured trust anchor, then issues temporary STS credentials.
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ On-Prem Server │────────▶│ IAM Roles │────────▶│ AWS Services │
│ (with X.509) │ │ Anywhere │ │ (S3, etc.) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
│ 1. Present Certificate │
│─────────────────────────▶│
│ │
│ 2. Validate against │
│ Trust Anchor │
│ │
│ 3. Return STS Creds │
│◀─────────────────────────│
Configuring Trust Anchors & Profiles
Setting up IAM Roles Anywhere requires two components:
Trust Anchor — Tells AWS which CA certificates to trust:
# Create trust anchor with your CA certificate
aws rolesanywhere create-trust-anchor \
--name "OnPremCA" \
--source "sourceType=CERTIFICATE_BUNDLE,sourceData={x509CertificateData=<CA-CERT-PEM>}" \
--enabled
Profile — Links the trust anchor to an IAM role and sets session duration:
aws rolesanywhere create-profile \
--name "OnPremServerProfile" \
--role-arns "arn:aws:iam::123456789012:role/OnPremAccessRole" \
--duration-seconds 3600 \
--enabled
The IAM role’s trust policy must allow the Roles Anywhere service:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "rolesanywhere.amazonaws.com"
},
"Action": [
"sts:AssumeRole",
"sts:SetSourceIdentity",
"sts:TagSession"
],
"Condition": {
"ArnEquals": {
"aws:SourceArn": "arn:aws:rolesanywhere:us-east-1:123456789012:trust-anchor/abc123"
}
}
}
]
}
Why Certificate-Based Auth Is More Secure
No secrets to steal — The private key never leaves the server. Even if an attacker gets the certificate, they can’t use it without the private key.
Automatic rotation — Certificates can be rotated by your PKI infrastructure without any AWS-side changes.
Revocation support — Compromise a certificate? Revoke it in your CA. AWS checks certificate status using CRL or OCSP.
Strong identity binding — Certificate subject names map to session tags, creating audit trails tied to specific workloads.
Use Cases
On-premises data centers — Backup scripts pushing to S3 without storing access keys.
IoT devices — Devices with embedded certificates accessing AWS IoT Core.
Kubernetes clusters — External clusters (not EKS) assuming roles for workloads.
Multi-cloud architectures — Workloads in GCP or Azure accessing AWS resources.
🎯 Interview Questions You’ll Get
- “How would you give an on-premises application access to S3 without storing AWS credentials?”
- “What’s the difference between IAM Roles Anywhere and cross-account roles?”
- “How do you handle certificate revocation with IAM Roles Anywhere?”
4. Cross-Account IAM & Trust Policies Deep Dive
Cross-account access is where IAM gets genuinely complex. And where most security incidents happen.
Anatomy of a Trust Policy
Every IAM role has a trust policy that answers one question: Who can assume this role?
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/CrossAccountRole"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "unique-secret-string"
}
}
}
]
}
The ExternalId Pattern — Preventing Confused Deputy
Here’s the attack scenario ExternalId prevents (called the confused deputy problem):
- You sign up for a third-party monitoring service
- They ask you to create a role they can assume
- An attacker uses the same service and points it at YOUR role ARN
- The service, acting as a “confused deputy,” assumes your role on behalf of the attacker
ExternalId fixes this. You generate a unique secret and configure it in both your trust policy and the third-party service. The attacker doesn’t know your ExternalId, so they can’t trick the service into assuming your role.
sts:SourceIdentity — Audit Trail for Role Chains
When a user assumes a role, you can require them to set a SourceIdentity. This identity propagates through role chains, giving you a consistent identifier for audit purposes.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789012:root"},
"Action": "sts:AssumeRole",
"Condition": {
"StringLike": {
"sts:SourceIdentity": "*@company.com"
}
}
}
]
}
Now every CloudTrail log shows which human initiated the role chain, even if they’re five hops deep.
Condition-Based Trust Narrowing
Smart organizations don’t just specify principal ARNs — they use conditions to add defense in depth:
Restrict to your AWS Organization:
{
"Condition": {
"StringEquals": {
"aws:PrincipalOrgID": "o-xxxxxxxxxx"
}
}
}
Restrict to specific roles:
{
"Condition": {
"ArnLike": {
"aws:PrincipalArn": "arn:aws:iam::*:role/Approved*"
}
}
}
How AWS Evaluates Cross-Account Access
This is a top interview question. For cross-account access, AWS evaluates permissions in both accounts:
┌──────────────────────────────────────────────────────────────────────────┐
│ Cross-Account Access Evaluation │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ ACCOUNT A (Source) ACCOUNT B (Target) │
│ ┌────────────────────┐ ┌────────────────────┐ │
│ │ Principal's │ │ Target Role's │ │
│ │ Identity Policy │──────────────▶│ Trust Policy │ │
│ │ allows AssumeRole? │ │ allows Principal? │ │
│ └────────────────────┘ └────────────────────┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌────────────────────┐ │
│ │ │ If both allow: │ │
│ │ │ STS issues creds │ │
│ │ └────────────────────┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌────────────────────┐ │
│ │ │ When using creds: │ │
│ │ │ Role's Identity │ │
│ │ │ Policy evaluated │ │
│ │ └────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────┘
Key insight: The source account’s principal needs sts:AssumeRole permission AND the target role’s trust policy must allow that principal. Both must agree.
Dangerous Trust Policy Patterns
Watch out for these in security audits:
Trusting all principals in an account:
"Principal": {"AWS": "arn:aws:iam::123456789012:root"}
This trusts every IAM principal in that account — any user, any role.
Trusting wildcard principals:
"Principal": {"AWS": "*"}
This trusts literally anyone with an AWS account. Never do this without strict conditions.
Missing conditions on third-party access:
Without ExternalId, you’re vulnerable to confused deputy attacks.
🎯 Interview Questions You’ll Get
- “What is the confused deputy problem and how does ExternalId solve it?”
- “How do you audit who assumed a role across a chain of 5 hops?”
- “What’s the difference between trusting an account root vs a specific role ARN?”
5. AWS Organizations SCPs — Organizational Guardrails
Service Control Policies (SCPs) are the most powerful tool in AWS security — and the most misunderstood.
What SCPs Do (And Don’t Do)
SCPs set the maximum permissions for an entire account or organizational unit. They’re guardrails, not grants.
SCPs never grant permissions. They only restrict what IAM policies in member accounts can allow.
Think of it this way:
- IAM policy says: “You can do X”
- SCP says: “This account is allowed to do X”
- Both must align for access to work
The Evaluation Flow
┌─────────────────────────────────────────────────────────┐
│ Request Made │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ SCP at Root Level allows? ───────────▶ If no, DENY │
└─────────────────────────────────────────────────────────┘
│ Yes
▼
┌─────────────────────────────────────────────────────────┐
│ SCP at OU Level allows? ─────────────▶ If no, DENY │
└─────────────────────────────────────────────────────────┘
│ Yes
▼
┌─────────────────────────────────────────────────────────┐
│ SCP at Account Level allows? ────────▶ If no, DENY │
└─────────────────────────────────────────────────────────┘
│ Yes
▼
┌─────────────────────────────────────────────────────────┐
│ IAM Policy Evaluation (boundaries, identity, etc.) │
└─────────────────────────────────────────────────────────┘
Essential SCP Examples
Deny IAM User Creation (force use of Identity Center):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": [
"iam:CreateUser",
"iam:CreateAccessKey"
],
"Resource": "*"
}
]
}
Region Restriction (data residency compliance):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": ["us-east-1", "us-west-2", "eu-west-1"]
}
}
}
]
}
Advanced Pattern: Allow-List for Approved Services
Instead of denying specific services, enterprise organizations often flip the model: allow only approved services, implicitly deny everything else.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:*",
"s3:*",
"dynamodb:*",
"lambda:*",
"rds:*",
"cloudwatch:*",
"logs:*",
"iam:Get*",
"iam:List*",
"sts:*"
],
"Resource": "*"
}
]
}
With this SCP, accounts can only use the listed services. Someone tries to spin up a SageMaker notebook or Redshift cluster? Denied at the organization level.
🎯 Interview Questions You’ll Get
- “Do SCPs grant or restrict permissions?”
- “Can the management account be affected by SCPs?”
- “How would you enforce that no AWS resources are created outside specific regions?”
6. IAM Identity Center — The Future of AWS Authentication
AWS is actively pushing organizations away from IAM users toward IAM Identity Center (formerly AWS SSO). If you’re building new infrastructure, this should be your default.
How Enterprise SSO Actually Works
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ User │─────▶│ IdP │─────▶│ Identity │─────▶│ AWS │
│ Browser │ │ (Okta) │ │ Center │ │ Console │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │ │ │
│ 1. Access Portal │ │ │
│───────────────────────────────────────▶│ │
│ │ │ │
│ 2. Redirect to IdP │ │ │
│◀───────────────────────────────────────│ │
│ │ │ │
│ 3. Authenticate │ │ │
│───────────────────▶│ │ │
│ │ │ │
│ 4. SAML Assertion │ │ │
│◀───────────────────│ │ │
│ │ │ │
│ 5. Present to Identity Center │ │
│───────────────────────────────────────▶│ │
│ │ │ │
│ 6. Issue STS Creds │ │ │
│◀───────────────────────────────────────│ │
│ │ │ │
│ 7. Access AWS with temp creds │ │
│────────────────────────────────────────────────────────────▶│
Permission Sets
Permission Sets are the Identity Center equivalent of IAM policies. You create them once, then assign them to users/groups for specific accounts.
A single permission set might include:
- An AWS managed policy (like
ReadOnlyAccess) - Custom inline policies
- A permission boundary
When assigned, Identity Center automatically creates an IAM role in each target account with the permission set’s policies attached.
SCIM Provisioning
System for Cross-domain Identity Management (SCIM) lets you sync users and groups from your identity provider directly to Identity Center. Add someone to the “DevOps” group in Okta, and they automatically get DevOps access in AWS.
This is how you achieve the holy grail: zero manual IAM user management in AWS.
Multi-Account Architecture Pattern
Here’s how enterprise organizations structure Identity Center:
┌─────────────────────────────────────────────────────────────────────────┐
│ AWS Organization │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ Management Acct │ ◀── Identity Center lives here │
│ │ (SSO Account) │ - Connected to Okta/Azure AD │
│ └─────────────────┘ - Permission Sets defined │
│ │ │
│ ┌────────┴────────┬─────────────────┬─────────────────┐ │
│ ▼ ▼ ▼ ▼ │
│ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │ Prod │ │ Stage │ │ Dev │ │ Shared │ │
│ │ OU │ │ OU │ │ OU │ │ Svcs │ │
│ └───────┘ └───────┘ └───────┘ └───────┘ │
│ │
│ Permission Set Assignments: │
│ ───────────────────────────────────────────────────────────────────── │
│ "Platform-Admins" → AdminAccess → All accounts │
│ "Developers" → PowerUser → Dev OU only │
│ "Developers" → ReadOnly → Stage OU │
│ "Security-Team" → SecurityAudit → All accounts │
└─────────────────────────────────────────────────────────────────────────┘
🎯 Interview Questions You’ll Get
- “Why should organizations use Identity Center instead of IAM users?”
- “How do permission sets map to IAM roles in target accounts?”
- “What is SCIM provisioning and why does it matter?”
7. Session Policies — Runtime Permission Restrictions
This is one of the most overlooked IAM features, and it’s incredibly powerful for implementing least privilege dynamically.
What Session Policies Do
When you call AssumeRole, you can pass an optional session policy. This policy further restricts the credentials you receive — beyond what the role’s identity policy allows.
Critical rule: A session policy can only reduce permissions. It cannot grant access that the role itself doesn’t already have.
import boto3
import json
sts = boto3.client('sts')
session_policy = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/specific-prefix/*"
}
]
}
response = sts.assume_role(
RoleArn="arn:aws:iam::123456789012:role/AdminRole",
RoleSessionName="restricted-session",
Policy=json.dumps(session_policy)
)
Even though AdminRole might have s3:* permissions, these credentials can only read from one specific prefix.
Session Policy Evaluation
┌─────────────────────────────────────────────────────────────────┐
│ Effective Permissions = │
│ Role Identity Policy ∩ Session Policy ∩ Permission Boundary │
│ (intersection of all three) │
└─────────────────────────────────────────────────────────────────┘
If the role allows s3:*, but the session policy only allows s3:GetObject, you can only get objects. The session policy acts as another ceiling.
Real-World Use Case: Multi-Tenant Isolation
You have a multi-tenant application. Each tenant’s data is in a specific S3 prefix. When a user logs in, your application assumes a role with a session policy that restricts access to only that tenant’s prefix.
def get_tenant_credentials(tenant_id):
session_policy = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": f"arn:aws:s3:::data-bucket/tenants/{tenant_id}/*"
}
]
}
return sts.assume_role(
RoleArn="arn:aws:iam::123456789012:role/DataAccessRole",
RoleSessionName=f"tenant-{tenant_id}",
Policy=json.dumps(session_policy)
)
One role. Dynamic permissions. Perfect isolation.
🎯 Interview Questions You’ll Get
- “Can a session policy grant permissions the role doesn’t have?”
- “How would you implement per-tenant isolation without creating per-tenant roles?”
- “What’s the size limit for session policies?”
8. Advanced IAM Condition Keys
Condition keys are where IAM policies become truly powerful. Here are the ones you need to know for production security work.
aws:PrincipalArn
Restrict who can perform actions based on their ARN pattern:
{
"Condition": {
"ArnLike": {
"aws:PrincipalArn": "arn:aws:iam::*:role/Admin*"
}
}
}
Use case: Only allow roles matching a naming convention to perform sensitive operations.
aws:SourceVpc and aws:SourceVpce
Restrict access to requests originating from specific VPCs or VPC endpoints:
{
"Condition": {
"StringEquals": {
"aws:SourceVpc": "vpc-abc123"
}
}
}
Use case: Ensure your S3 bucket can only be accessed from within your VPC, never from the public internet.
aws:SecureTransport
Enforce TLS for all requests:
{
"Effect": "Deny",
"Action": "s3:*",
"Resource": "*",
"Condition": {
"Bool": {
"aws:SecureTransport": "false"
}
}
}
s3:prefix and s3:delimiter
Control ListObjects results — critical for multi-tenant buckets:
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::shared-bucket",
"Condition": {
"StringLike": {
"s3:prefix": ["tenants/${aws:PrincipalTag/tenant-id}/*"]
}
}
}
This ensures users can only list objects under their tenant’s prefix.
kms:ViaService
Ensure KMS keys are only used through specific AWS services:
{
"Condition": {
"StringEquals": {
"kms:ViaService": "s3.us-east-1.amazonaws.com"
}
}
}
Use case: A KMS key that can only be used for S3 encryption, never directly via the KMS API. This prevents attackers from using stolen credentials to decrypt data outside of S3’s access controls.
(Coming soon: Deep Dive: AWS KMS for DevOps)
kms:EncryptionContext
Enforce that encryption operations include specific context:
{
"Condition": {
"StringEquals": {
"kms:EncryptionContext:department": "finance"
}
}
}
aws:PrincipalOrgID
Restrict access to principals within your AWS Organization:
{
"Condition": {
"StringEquals": {
"aws:PrincipalOrgID": "o-xxxxxxxxxx"
}
}
}
Much better than listing individual account IDs — works automatically as you add accounts to your organization.
9. IAM Access Analyzer — Automated Security Validation
Access Analyzer moved from “nice to have” to “essential” when AWS added policy generation capabilities.
Beyond Basic Analysis
Most people know Access Analyzer can find publicly accessible S3 buckets. But it does much more:
Policy Validation — Checks your IAM policies for errors, security warnings, and suggestions before deployment.
Policy Generation — Analyzes CloudTrail logs to generate least-privilege policies based on actual usage.
External Access Findings — Identifies resources shared outside your organization.
Unused Access Analysis — Identifies permissions granted but never used.
Unused Access Analysis
Access Analyzer can identify:
- Unused roles (roles that haven’t been assumed)
- Unused permissions (actions allowed but never invoked)
- Unused access keys
This is gold for least-privilege enforcement:
aws accessanalyzer list-findings \
--analyzer-arn "arn:aws:access-analyzer:us-east-1:123456789012:analyzer/MyAnalyzer" \
--filter '{"findingType": {"eq": ["UnusedPermission"]}}'
Full Policy Generation Workflow
Here’s the complete workflow for generating least-privilege policies from CloudTrail:
Step 1: Start policy generation
aws accessanalyzer start-policy-generation \
--policy-generation-details '{
"principalArn": "arn:aws:iam::123456789012:role/MyRole"
}' \
--cloud-trail-details '{
"trails": [{"cloudTrailArn": "arn:aws:cloudtrail:us-east-1:123456789012:trail/my-trail"}],
"startTime": "2024-01-01T00:00:00Z",
"endTime": "2024-12-31T23:59:59Z"
}'
Step 2: Retrieve the generated policy
aws accessanalyzer get-generated-policy \
--job-id "job-id-from-step-1" \
--include-resource-placeholders
The output is a ready-to-use IAM policy containing only the actions the role actually used during the analysis period.
CI/CD Integration
Integrate Access Analyzer into your deployment pipeline:
# .github/workflows/validate-iam.yml
- name: Validate IAM Policies
run: |
result=$(aws accessanalyzer validate-policy \
--policy-document file://policy.json \
--policy-type IDENTITY_POLICY \
--output json)
errors=$(echo "$result" | jq '[.findings[] | select(.findingType == "ERROR")] | length')
if [ "$errors" -gt 0 ]; then
echo "Policy validation failed"
exit 1
fi
🎯 Interview Questions You’ll Get
- “How do you validate IAM policies before deployment?”
- “How would you identify unused permissions in existing roles?”
- “Explain how Access Analyzer generates least-privilege policies.”
🧪 Hands-On Lab Idea (20 minutes)
- Create an IAM role with
s3:*permissions- Use the role to perform only
s3:GetObjectands3:ListBucketoperations- Wait 24 hours for CloudTrail to populate
- Run policy generation on that role
- Compare generated policy to original — see AWS automatically suggest least-privilege
10. Role Session Tags (ABAC) — The Future of Permissions
Attribute-Based Access Control (ABAC) is AWS’s answer to the scaling problem of traditional RBAC.
Instead of creating hundreds of policies for different user/resource combinations, you use tags as dynamic permission conditions.
How It Works
When assuming a role, you can pass session tags:
response = sts.assume_role(
RoleArn="arn:aws:iam::123456789012:role/DataAccessRole",
RoleSessionName="user-session",
Tags=[
{"Key": "department", "Value": "engineering"},
{"Key": "project", "Value": "project-alpha"},
{"Key": "cost-center", "Value": "CC-1234"}
]
)
Your policy then uses these tags for access control:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::data-bucket/*",
"Condition": {
"StringEquals": {
"s3:ExistingObjectTag/project": "${aws:PrincipalTag/project}"
}
}
}
]
}
Now users can only access objects tagged with their project. One policy. Infinite scale.
ABAC Design Pattern: Projects & Tenants
Here’s the end-to-end flow for ABAC in a multi-tenant environment:
┌─────────────────────────────────────────────────────────────────────────┐
│ ABAC Flow: User to Resource │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ IdP Group │───▶│ Identity │───▶│ Role with │ │
│ │ "Project-X" │ │ Center │ │ Session Tags│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │
│ │ User in group │ Tags: project=X │
│ │ │ tenant=123 │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ IAM Policy Condition │ │
│ │ "StringEquals": { │ │
│ │ "s3:ExistingObjectTag/project": "${aws:PrincipalTag/project}" │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ S3 Objects with Tags │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ file1.csv │ │ file2.csv │ │ file3.csv │ │ │
│ │ │ project=X ✓ │ │ project=Y ✗ │ │ project=X ✓ │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ Result: User can only access file1.csv and file3.csv │
└─────────────────────────────────────────────────────────────────────────┘
Session Tags for Cost Allocation
Session tags flow through to CloudTrail and can be used for cost allocation:
response = sts.assume_role(
RoleArn="arn:aws:iam::123456789012:role/WorkloadRole",
RoleSessionName="batch-job",
Tags=[
{"Key": "cost-center", "Value": "analytics-team"},
{"Key": "environment", "Value": "production"},
{"Key": "workload", "Value": "daily-etl"}
]
)
These tags appear in CloudTrail and can be used to:
- Track which team/project caused which API calls
- Allocate costs back to business units
- Identify anomalous spending patterns by workload
Tags + SCP for Tenant Isolation
Combine session tags with SCPs for rock-solid tenant isolation:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"Null": {
"aws:PrincipalTag/tenant-id": "true"
}
}
}
]
}
This SCP denies all actions if the principal doesn’t have a tenant-id tag set. Combined with ABAC policies, you get mandatory tenant isolation that can’t be bypassed.
🎯 Interview Questions You’ll Get
- “What is ABAC and how does it differ from RBAC?”
- “How would you implement project-based access control without creating per-project roles?”
- “How can session tags be used for cost allocation?”
Bonus Advanced Topics
Horizontal Privilege Escalation in IAM
Horizontal escalation happens when a user gains access to another user’s resources without gaining additional permissions. Watch for these patterns:
Pattern 1: PassRole + Compute Services
User can’t access S3 directly, but can create a Lambda with a role that can. They invoke the Lambda, which accesses S3 on their behalf.
Pattern 2: CreatePolicyVersion
User can’t assume admin, but can create a new version of a policy attached to their role, adding admin permissions.
Pattern 3: UpdateFunctionCode
User can update Lambda code, injecting code that exfiltrates credentials from the execution role.
Always audit these permission combinations:
iam:PassRole+ compute service permissionsiam:CreatePolicyVersionoriam:SetDefaultPolicyVersionlambda:UpdateFunctionCodeon functions with privileged roles
IAM PassRole — The Permission That Enables Escalation
iam:PassRole allows you to associate an IAM role with an AWS service. Combined with the right permissions, it enables privilege escalation.
Always restrict PassRole to:
- Specific role ARNs (not
*) - Specific services via
iam:PassedToService
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "arn:aws:iam::123456789012:role/LambdaExecutionRole",
"Condition": {
"StringEquals": {
"iam:PassedToService": "lambda.amazonaws.com"
}
}
}
IRSA Security Implications (EKS)
IAM Roles for Service Accounts lets Kubernetes pods assume IAM roles:
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-service-account
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/MyPodRole
Security considerations:
Pod Identity Theft — If an attacker compromises a pod, they get that pod’s IAM role. Ensure least-privilege on pod roles.
Service Account Token Projection — IRSA uses projected tokens. Set automountServiceAccountToken: false on pods that don’t need AWS access.
(Coming soon: Mastering IRSA on EKS)
AWS SigV4 — How Requests Are Signed
Every AWS API request is signed using Signature Version 4:
1. Create canonical request (method, URI, headers, payload)
2. Create string to sign (algorithm, timestamp, scope, hash)
3. Calculate signing key (derived from secret key + date)
4. Calculate signature (HMAC-SHA256)
5. Add signature to Authorization header
Security implications:
- Requests include timestamps; AWS rejects requests older than 15 minutes
- The signing key is date-scoped, limiting replay attack windows
- Credentials never appear in requests; only derived signatures
IAM Policy Simulator
The Policy Simulator lets you test policies without performing actions:
aws iam simulate-principal-policy \
--policy-source-arn "arn:aws:iam::123456789012:user/testuser" \
--action-names "s3:GetObject" \
--resource-arns "arn:aws:s3:::my-bucket/sensitive/*"
Use cases: debugging “access denied” errors, validating policies before deployment.
CloudTrail Detection Patterns
Monitor for these patterns indicating compromised credentials:
Reconnaissance:
GetCallerIdentity— First thing attackers runListBuckets,DescribeInstances— Enumerating targets
Credential Theft:
CreateAccessKey— Attacker creating persistent accessUpdateAssumeRolePolicy— Adding themselves to trust policies
Data Exfiltration:
GetObjectfrom unusual IPs- API calls from regions where you don’t operate
(Coming soon: AWS Logging & Monitoring for DevOps)
Common IAM Security Mistakes
Overly permissive trust policies — Using "Principal": {"AWS": "*"} without conditions.
Unused permissions accumulation — Roles that have grown permissions over years without cleanup.
Missing permission boundaries — Allowing developers to create roles without ceiling restrictions.
Static credentials in code — Still the #1 cause of AWS security incidents.
PassRole without restrictions — Allowing PassRole to any role or any service.
Missing ExternalId on third-party roles — Vulnerable to confused deputy attacks.
🧠 Reflection Prompts
Prompt 1: Look at your production AWS accounts right now. How many IAM users exist with console access? How many have active access keys older than 90 days? What would happen if one of those keys leaked tonight?
Prompt 2: Draw out the permission evaluation flow for your most critical production role. Include SCPs, permission boundaries, identity policies, and resource policies. Where are the gaps?
📝 Mini Quiz
Question: A developer has an IAM role with the following:
- Identity policy allowing
s3:*on all resources - Permission boundary allowing only
s3:GetObjectands3:ListBucket - SCP allowing all S3 actions
- Session policy allowing
s3:GetObjectonarn:aws:s3:::data-bucket/*
Can the developer run s3:ListBucket on data-bucket?
Click to reveal answer
No. Let’s trace the evaluation:
- ✅ SCP allows all S3 actions
- ✅ Permission boundary allows
s3:ListBucket - ✅ Identity policy allows
s3:* - ❌ Session policy only allows
s3:GetObject
The session policy is the limiting factor. Even though every other layer allows s3:ListBucket, the session policy restricts the session to only s3:GetObject. The effective permission is the intersection of all four layers.
❓ Frequently Asked Questions
What are advanced IAM concepts in AWS?
Advanced AWS IAM concepts include STS temporary credentials, Service Control Policies (SCPs), permission boundaries, IAM Identity Center, session policies, ABAC with session tags, IAM Roles Anywhere, and Access Analyzer. These go beyond basic IAM users and policies to enable enterprise-grade security at scale.
What is the difference between SCP and permission boundary in AWS?
SCPs apply at the organization level to entire accounts or OUs, while permission boundaries apply to individual IAM users or roles. Both only restrict permissions (never grant), but SCPs are managed centrally in AWS Organizations, whereas permission boundaries are attached per-identity. SCPs affect everyone in an account; boundaries affect only the attached principal.
How do session policies work with AssumeRole in AWS?
Session policies are optional policies passed during AssumeRole that further restrict the credentials returned. They can only reduce permissions — never grant more than the role’s identity policy allows. The effective permissions are the intersection of the role’s policy, the session policy, and any permission boundary.
How do I secure cross-account IAM roles in AWS?
Secure cross-account roles by: (1) using ExternalId in trust policies to prevent confused deputy attacks, (2) adding aws:PrincipalOrgID conditions to restrict to your organization, (3) specifying exact principal ARNs rather than account roots, and (4) enabling sts:SourceIdentity for audit trails across role chains.
What IAM topics are asked in AWS security interviews?
Senior-level AWS security interviews commonly test: STS and role chaining, SCP vs permission boundary differences, cross-account trust evaluation, ABAC with session tags, Access Analyzer for least-privilege, IRSA in EKS, PassRole security implications, and CloudTrail detection patterns for credential compromise.
Conclusion
Advanced AWS IAM isn’t about memorizing policy syntax. It’s about understanding how AWS evaluates permissions across multiple layers — SCPs, boundaries, identity policies, resource policies, and session policies — and designing systems that enforce least privilege at every layer.
The organizations that get breached in 2025 won’t be the ones without firewalls. They’ll be the ones with overly permissive trust policies, unused permissions that accumulated over years, and developers who never understood how role chaining actually works.
You now understand these advanced AWS IAM concepts. Use that knowledge.
Ready to master AWS security for real? Check out our Complete AWS IAM Security Course — featuring hands-on labs, interview preparation modules, and real-world architecture patterns from production environments.
Have AWS IAM interview questions or feedback? Drop a comment below or connect on LinkedIn. Let’s build secure infrastructure together.
Last updated: 2025 | thedevopstooling.com
