Skip to content

Linuxcent

  • Linux Commands Cheat Sheet
  • Docker Tutorials
  • Kubernetes Tutorials
  • About Us
    • Privacy Policy
    • Disclaimer
    • Tutorial
    • Contact Us

AWS Identity Center

AWS IAM Deep Dive: Users, Groups, Roles, and Policies Explained

April 15, 2026April 14, 2026 by Vamshi Krishna Santhapuri

What Is Cloud IAM → Authentication vs Authorization → IAM Roles vs Policies → AWS IAM Deep Dive**


TL;DR

  • IAM users with long-lived access keys are legacy — use IAM Identity Center with federation; static keys are a security finding, not a feature
  • Roles issue temporary credentials via STS — the right identity model for every service (Lambda, EC2, ECS, CI/CD)
  • Every role has two required configs: trust policy (who can assume it) + permission policy (what it can do) — both must be correct
  • SCPs set the org-level ceiling; they cannot grant permissions and do not apply to the management account
  • Permissions boundaries set an identity-level ceiling — effective permissions are the intersection with identity-based policies, not the union
  • Cross-account trust without an ExternalId condition is vulnerable to the confused deputy attack — always include it with third-party trust
  • One role per service, never shared — a shared role’s blast radius is the union of what every consumer needs

The Big Picture

AWS IAM evaluates every API call through a specific chain. Understanding this chain is how you debug access issues and how you design guardrails that actually hold.

  AWS POLICY EVALUATION — every API call walks this chain top to bottom
  An explicit DENY at any step ends evaluation immediately.

         API call arrives
               │
               ▼
  ┌────────────────────────────┐
  │  Explicit DENY in any SCP? │── YES ──────────────────────────► DENIED
  └────────────────┬───────────┘     (cannot be overridden by anything)
                   │ NO
                   ▼
  ┌────────────────────────────┐
  │  SCP present with no ALLOW │── YES ──────────────────────────► DENIED
  └────────────────┬───────────┘
                   │ NO (or no SCP / management account)
                   ▼
  ┌────────────────────────────┐
  │  Explicit DENY in any      │── YES ──────────────────────────► DENIED
  │  identity or resource      │
  │  policy?                   │
  └────────────────┬───────────┘
                   │ NO
                   ▼
  ┌────────────────────────────┐
  │  Resource-based policy     │── YES (same-account principal) ──► ALLOWED*
  │  with ALLOW?               │
  └────────────────┬───────────┘   *unless denied above
                   │ NO
                   ▼
  ┌────────────────────────────┐
  │  Permissions boundary      │── YES, boundary has NO ALLOW ───► DENIED
  │  attached?                 │
  └────────────────┬───────────┘
                   │ NO boundary, or boundary ALLOWS
                   ▼
  ┌────────────────────────────┐
  │  Session policy attached   │── YES, session has NO ALLOW ────► DENIED
  │  (role assumption)?        │
  └────────────────┬───────────┘
                   │ NO session policy, or session ALLOWS
                   ▼
  ┌────────────────────────────┐
  │  Identity-based policy     │── YES ──────────────────────────► ALLOWED
  │  with ALLOW?               │
  └────────────────┬───────────┘
                   │ NO
                   ▼
                DENIED (default — nothing explicitly granted)

  Debugging AccessDenied: work bottom-up.
  Start with the identity-based policy. Then boundary. Then SCP.

Introduction

An AWS IAM deep dive reveals what most teams miss: the difference between an IAM model that works under deadline and one that survives scale, audits, and staff turnover. If you’ve read IAM roles vs policies and understand the three-layer stack, this is where it becomes specific to AWS — trust policies, SCPs, permissions boundaries, cross-account trust, and Identity Center.

In 2017 I was asked to help clean up an AWS account that had been running in production for two years. The team had built something real — a microservices application, a data pipeline, a CI/CD system. Competent engineers. But nobody had been specifically accountable for IAM.

When I pulled the configuration:

  • One IAM user with AdministratorAccess shared by the entire dev team. Password in a shared password manager. Access key three years old.
  • Six Lambda functions each carrying AWSLambdaFullAccess, AmazonS3FullAccess, and AmazonDynamoDBFullAccess — three broad managed policies each, instead of one custom policy with what each function actually needed.
  • A CI/CD pipeline role with iam:* on * because someone once needed to create a role during a deployment and found that the easiest path.
  • Three IAM users for contractors who had finished their engagements months earlier. Still active, access keys still valid.

None of this was malicious. All of it was the result of reaching for the broadest thing that works, under deadline, without a framework for IAM decisions.

AWS IAM is the most flexible cloud IAM system — and that flexibility is the problem. If you don’t know the full model, you default to broad grants because they’re easier to reason about. Broad things accumulate into exposure. This episode is the full model.


AWS IAM Identity Types: Users, Groups, and Roles Compared

IAM Users: Why Static Access Keys Are a Security Finding

An IAM user is a permanent identity with long-lived credentials: a password for console access, and optionally an access key pair. No expiry on the access key by default.

# Create a user
aws iam create-user --user-name alice

# Generate an access key — no expiry unless you set one
aws iam create-access-key --user-name alice

# Enforce MFA for console access
aws iam create-virtual-mfa-device \
  --virtual-mfa-device-name alice-mfa \
  --outfile /tmp/alice-mfa.png \
  --bootstrap-method QRCodePNG

aws iam enable-mfa-device \
  --user-name alice \
  --serial-number arn:aws:iam::123456789012:mfa/alice-mfa \
  --authentication-code1 123456 \
  --authentication-code2 654321

The access key exists the moment you create it. It survives team changes, org restructures, and offboarding unless someone explicitly deletes it. In every AWS account I’ve audited, access keys are where I find the oldest, most-forgotten credentials.

Current best practice: don’t create IAM users for human access. Use IAM Identity Center with federation. Static access keys are a finding, not a feature.

IAM groups — useful but limited

Groups are collections of users. Policies attached to a group apply to all members. Useful as a middle layer, but limited: you can’t add roles or services to a group, and if you’re moving toward Identity Center, groups in IAM become less relevant.

aws iam create-group --group-name Backend-Developers
aws iam attach-group-policy \
  --group-name Backend-Developers \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
aws iam add-user-to-group --group-name Backend-Developers --user-name alice

IAM Roles: How STS Temporary Credentials Work

A role is an identity without permanent credentials. It is assumed by entities — services, users, external systems — and STS issues temporary credentials. Those credentials expire. Nothing to rotate.

# Create a role that EC2 can assume
cat > ec2-trust-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "ec2.amazonaws.com" },
    "Action": "sts:AssumeRole"
  }]
}
EOF

aws iam create-role \
  --role-name AppServerRole \
  --assume-role-policy-document file://ec2-trust-policy.json

aws iam attach-role-policy \
  --role-name AppServerRole \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

# EC2 needs an instance profile to carry the role
aws iam create-instance-profile --instance-profile-name AppServerProfile
aws iam add-role-to-instance-profile \
  --instance-profile-name AppServerProfile \
  --role-name AppServerRole

# Launch with the profile
aws ec2 run-instances \
  --image-id ami-0abcdef1234567890 \
  --instance-type t3.micro \
  --iam-instance-profile Name=AppServerProfile

From inside the instance — no credential files, no configuration:

curl http://169.254.169.254/latest/meta-data/iam/security-credentials/AppServerRole
# Returns: AccessKeyId, SecretAccessKey, Token, Expiration
# AWS refreshes these before they expire. The application never sees a rotation event.

Lambda, ECS, and other services use different attachment mechanisms but the same model.


AWS IAM Policy Types: Managed, Inline, SCP, and Boundaries

Managed vs inline policies

┌────────────────────┬──────────────────────────────────┬──────────────────────────────────────┐
│ Type               │ Description                      │ Use when                             │
├────────────────────┼──────────────────────────────────┼──────────────────────────────────────┤
│ AWS Managed        │ Created by AWS, read-only        │ Quick prototyping; never production  │
│ Customer Managed   │ Created by you, reusable         │ Standard production permissions      │
│ Inline             │ Embedded in user/group/role      │ Explicit 1:1 non-transferable binding│
└────────────────────┴──────────────────────────────────┴──────────────────────────────────────┘

AWS Managed policies like AmazonS3FullAccess are convenient and dangerous for the same reason: broad by design, meant to cover every use case. For a Lambda that reads one specific bucket, AmazonS3FullAccess grants approximately 30 permissions you didn’t need.

# Create a customer managed policy — scoped to what the Lambda actually does
cat > lambda-reader-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "ReadSpecificBucket",
    "Effect": "Allow",
    "Action": ["s3:GetObject", "s3:ListBucket"],
    "Resource": [
      "arn:aws:s3:::app-data-prod",
      "arn:aws:s3:::app-data-prod/*"
    ]
  }]
}
EOF

aws iam create-policy \
  --policy-name LambdaS3ReadPolicy \
  --policy-document file://lambda-reader-policy.json

aws iam attach-role-policy \
  --role-name lambda-image-processor-role \
  --policy-arn arn:aws:iam::123456789012:policy/LambdaS3ReadPolicy

Service Control Policies — org-wide guardrails

SCPs attach to AWS Organization OUs or accounts. They define the maximum permissions any identity in that scope can have. They cannot grant — only restrict.

Two SCPs I apply to every account from day one:

// Region restriction — blast radius control
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Deny",
    "Action": "*",
    "Resource": "*",
    "Condition": {
      "StringNotEquals": {
        "aws:RequestedRegion": ["ap-south-1", "us-east-1", "eu-west-1"]
      }
    }
  }]
}
// Protect the audit trail — anti-forensics control
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Deny",
    "Action": [
      "cloudtrail:StopLogging",
      "cloudtrail:DeleteTrail",
      "cloudtrail:UpdateTrail"
    ],
    "Resource": "*"
  }]
}

The region restriction limits where compromised credentials can operate. The CloudTrail restriction means even an AdministratorAccess compromise cannot erase the audit trail. The attacker knows they’re being logged and cannot stop it. This is the authorization layer — understanding authentication vs authorization makes clear why SCPs operate at Gate 2, not Gate 1.

Permissions boundaries — identity-level ceilings

A permissions boundary sets the maximum permissions for a specific user or role. Effective permissions are the intersection of what the boundary allows and what identity-based policies grant.

Boundary allows:  s3:*, dynamodb:*
Identity policy:  s3:*, ec2:*
──────────────────────────────────
Effective:        s3:*             ← the intersection only
// Boundary: this role can use at most S3 and DynamoDB
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["s3:*", "dynamodb:*"],
    "Resource": "*"
  }]
}
aws iam put-role-permissions-boundary \
  --role-name DevTeamRole \
  --permissions-boundary arn:aws:iam::123456789012:policy/DevTeamBoundary

I use permissions boundaries for safe IAM delegation. When a dev team needs to create their own roles for their services, I give them iam:CreateRole and iam:AttachRolePolicy — but require any role they create to have a specific boundary. They can self-service IAM without accidentally creating a role more powerful than their team should have.


How AWS Cross-Account IAM Trust Works

AWS accounts are IAM isolation boundaries. An identity in Account A has zero access to Account B by default. Cross-account access requires explicit trust in both directions.

Account B creates a role with a trust policy naming Account A's identity.
Account A's identity has permission to call sts:AssumeRole on that role.
// Account B: trust policy on the cross-account role
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "AWS": "arn:aws:iam::ACCOUNT_A_ID:role/DeployPipelineRole"
    },
    "Action": "sts:AssumeRole",
    "Condition": {
      "StringEquals": { "sts:ExternalId": "unique-external-id-12345" }
    }
  }]
}
# Account A: the pipeline assumes the cross-account role
aws sts assume-role \
  --role-arn arn:aws:iam::ACCOUNT_B_ID:role/DeployTarget \
  --role-session-name pipeline-deploy \
  --external-id unique-external-id-12345

# Export the temporary credentials and operate in Account B
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...
aws s3 ls s3://account-b-bucket/

The ExternalId condition prevents the confused deputy attack. Without it, if you operate a service that assumes roles on behalf of customers, an attacker who knows your service’s ARN can trick it into assuming their customer’s role. The ExternalId is a shared secret proving the party requesting assumption is the one who established the trust. Always include it for third-party cross-account trust.


AWS IAM Identity Center: Federated Human Access Without Static Keys

IAM Identity Center (formerly AWS SSO) is the modern answer to “how do engineers access AWS accounts?” It federates an external IdP and maps your organization’s groups to Permission Sets.

  Okta / Google Workspace / Entra ID
    ↓ SAML 2.0 or OIDC
  IAM Identity Center
    ↓ Permission Sets (collections of policies)
  Account Assignments (group → permission set → account)
    ↓
  Temporary credentials in each target account (no long-lived keys)
# Configure CLI access via Identity Center
aws configure sso
# Prompts: SSO start URL, region, account, role

# Login — browser opens for IdP auth
aws sso login --profile prod-admin

# Use normally — credentials are temporary and auto-refreshed
aws s3 ls --profile prod-admin
aws ec2 describe-instances --profile prod-admin

When someone leaves the organization: disable them in your IdP. Their SSO session expires, their temporary credentials expire, access is gone. No access key hunting across 20 accounts.


AWS IAM Patterns for Production: What Survives Scale

One role per service — never share

Every Lambda, ECS task, and EC2 application gets its own role. Even two Lambdas doing similar things. The moment you share a role, its permissions are the union of what each consumer needs — and a compromise of one consumer exposes the full union.

# Dedicated execution role — specific to this function's actual needs
aws iam create-role \
  --role-name lambda-invoice-processor-role \
  --assume-role-policy-document \
  '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"},"Action":"sts:AssumeRole"}]}'

aws iam put-role-policy \
  --role-name lambda-invoice-processor-role \
  --policy-name InvoiceProcessorPolicy \
  --policy-document file://lambda-invoice-processor-policy.json

IAM escalation guardrail

Any role that isn’t an explicit IAM admin should have a guardrail blocking escalation actions:

{
  "Sid": "DenyIAMEscalation",
  "Effect": "Deny",
  "Action": [
    "iam:CreateUser", "iam:CreateRole", "iam:AttachRolePolicy",
    "iam:PutRolePolicy", "iam:PassRole", "iam:CreateAccessKey"
  ],
  "Resource": "*",
  "Condition": {
    "StringNotEquals": {
      "aws:PrincipalArn": "arn:aws:iam::123456789012:role/InfraAdminRole"
    }
  }
}

Even if a role gets over-permissioned, it cannot create users, escalate its own privileges, or pass roles to expand its access. Defense in depth against the privilege escalation paths covered in AWS IAM Privilege Escalation: How iam:PassRole Leads to Full Compromise.

Least privilege with tag conditions

{
  "Effect": "Allow",
  "Action": ["ec2:StartInstances", "ec2:StopInstances", "ec2:RebootInstances"],
  "Resource": "arn:aws:ec2:*:*:instance/*",
  "Condition": {
    "StringEquals": {
      "aws:ResourceTag/Environment": "dev",
      "aws:ResourceTag/Owner": "${aws:username}"
    }
  }
}

A developer can control EC2 instances in dev — specifically the ones tagged as theirs. Not prod. Not someone else’s instances. ABAC layered on a role, eliminating a class of privilege escalation through direct resource access.


⚠ Production Gotchas

╔══════════════════════════════════════════════════════════════════════╗
║  ⚠  GOTCHA 1 — SCPs don't apply to the management account          ║
║                                                                      ║
║  SCPs are applied to member accounts and OUs — not to the org      ║
║  management account. Guardrails you apply to member accounts do    ║
║  not protect the management account itself.                         ║
║                                                                      ║
║  Fix: lock down the management account separately. Use it only for  ║
║  billing and org management. Never run workloads in it.             ║
╚══════════════════════════════════════════════════════════════════════╝

╔══════════════════════════════════════════════════════════════════════╗
║  ⚠  GOTCHA 2 — Permissions boundary ≠ policy grant                 ║
║                                                                      ║
║  A boundary that allows s3:* does NOT grant S3 access. The         ║
║  boundary is a ceiling. Effective permissions are the intersection  ║
║  of boundary + identity policy. Both must explicitly Allow.         ║
║                                                                      ║
║  Fix: after setting a boundary, check effective permissions with:   ║
║  aws iam simulate-principal-policy                                  ║
╚══════════════════════════════════════════════════════════════════════╝

╔══════════════════════════════════════════════════════════════════════╗
║  ⚠  GOTCHA 3 — Cross-account trust without ExternalId              ║
║                                                                      ║
║  A trust policy that names any principal from Account A without     ║
║  ExternalId can be exploited if you operate a multi-tenant service. ║
║  An attacker can craft a request that tricks your service into      ║
║  assuming a victim's role (confused deputy).                        ║
║                                                                      ║
║  Fix: always add ExternalId condition to third-party trust policies.║
╚══════════════════════════════════════════════════════════════════════╝

╔══════════════════════════════════════════════════════════════════════╗
║  ⚠  GOTCHA 4 — iam:* on * in CI/CD role                           ║
║                                                                      ║
║  "The pipeline needs to create roles" is a legitimate requirement.  ║
║  Granting iam:* on * is not. It lets the pipeline create any role  ║
║  with any permissions — effectively full account access.            ║
║                                                                      ║
║  Fix: grant specific iam: actions, require all created roles to     ║
║  carry a permissions boundary. Delegate without escalating.         ║
╚══════════════════════════════════════════════════════════════════════╝

Quick Reference

┌───────────────────────────┬───────────────────────────────────────────────────────────┐
│ Term                      │ What it is                                                │
├───────────────────────────┼───────────────────────────────────────────────────────────┤
│ IAM User                  │ Permanent identity with long-lived credentials — legacy   │
│ IAM Role                  │ Assumable identity; STS issues temp creds — preferred     │
│ Trust policy              │ Who can assume this role (separate from permissions)      │
│ Instance profile          │ Container that attaches a role to an EC2 instance        │
│ AWS Managed policy        │ Broad, maintained by AWS — avoid in production           │
│ Customer Managed policy   │ You own it, you scope it — correct default               │
│ Inline policy             │ 1:1 binding, non-reusable — use only when intentional    │
│ SCP                       │ Org-level guardrail; constrains, does not grant          │
│ Permissions boundary      │ Identity-level ceiling; intersection with policy = effective│
│ Session policy            │ Restricts a specific role assumption session             │
│ ExternalId                │ Shared secret in cross-account trust — prevents confused deputy│
│ IAM Identity Center       │ Federated human access via SSO; no long-lived keys       │
│ Permission Set            │ Policy collection in Identity Center → becomes role in account│
└───────────────────────────┴───────────────────────────────────────────────────────────┘

Commands to know:
┌────────────────────────────────────────────────────────────────────────────────────────┐
│  # Simulate a policy before deploying — will this call succeed?                      │
│  aws iam simulate-principal-policy \                                                  │
│    --policy-source-arn arn:aws:iam::ACCOUNT:role/MyRole \                            │
│    --action-names s3:GetObject \                                                      │
│    --resource-arns arn:aws:s3:::my-bucket/*                                          │
│                                                                                        │
│  # Full IAM snapshot of the account — all users, roles, policies, groups            │
│  aws iam get-account-authorization-details --output json > iam-snapshot.json         │
│                                                                                        │
│  # Find unused permissions — what does this role actually call?                      │
│  aws iam generate-service-last-accessed-details \                                     │
│    --arn arn:aws:iam::ACCOUNT:role/MyRole                                            │
│  aws iam get-service-last-accessed-details --job-id JOB_ID                           │
│                                                                                        │
│  # List all access keys and their age                                                │
│  aws iam list-users --query 'Users[].UserName' --output text | \                    │
│    xargs -I{} aws iam list-access-keys --user-name {}                               │
│                                                                                        │
│  # Check effective permissions boundary on a role                                    │
│  aws iam get-role --role-name MyRole \                                               │
│    --query 'Role.PermissionsBoundary'                                                │
│                                                                                        │
│  # Assume a cross-account role                                                       │
│  aws sts assume-role \                                                                │
│    --role-arn arn:aws:iam::TARGET_ACCOUNT:role/CrossAccountRole \                   │
│    --role-session-name deploy-session \                                               │
│    --external-id your-external-id                                                    │
└────────────────────────────────────────────────────────────────────────────────────────┘

Framework Alignment

Framework Reference What It Covers Here
CISSP Domain 5 — Identity and Access Management AWS IAM is the most widely deployed cloud IAM system; this covers the full model
CISSP Domain 6 — Security Assessment and Testing Policy evaluation logic is the foundation for cloud security assessments
ISO 27001:2022 5.15 Access control Access control policy in AWS — SCPs, identity-based policies, resource-based policies
ISO 27001:2022 5.18 Access rights User and role provisioning, permission boundaries, Identity Center assignments
ISO 27001:2022 8.2 Privileged access rights IAM Identity Center, SCPs as org-level guardrails, least-privilege role design
SOC 2 CC6.1 AWS IAM is the primary technical control for CC6.1 in AWS-hosted environments
SOC 2 CC6.3 Identity Center with federation enables auditable access provisioning and removal
SOC 2 CC6.6 Cross-account trust relationships and ExternalId address third-party access controls

Key Takeaways

  • IAM users with static access keys are legacy for human access — use IAM Identity Center with federation; static keys are a persistent finding
  • Roles issue temporary credentials and are the right identity for every service — Lambda, EC2, ECS, CI/CD, cross-account
  • Trust policy controls who can assume a role; permission policy controls what the role can do — debug both when access fails
  • SCPs cap maximum permissions at org level and cannot be overridden — use them for region restriction and audit trail protection; they do not apply to the management account
  • Permissions boundaries cap at identity level — effective permissions are the intersection with identity-based policies, not the union
  • Cross-account trust without ExternalId is vulnerable to confused deputy — always include it with third-party trust
  • One role per service; share nothing — a shared role’s blast radius is the union of every consumer’s required permissions

What’s Next

EP05 moves to GCP IAM — a fundamentally different model where the resource hierarchy drives access inheritance. A misconfiguration at the folder level affects every project below it. We’ll cover why roles/editor keeps appearing in production audits and how to build a GCP IAM structure that composes correctly up the hierarchy.

Get the GCP IAM deep dive in your inbox when it publishes → https://linuxcent.com/subscribe

Next: GCP IAM Policy Inheritance: How the Resource Hierarchy Controls Access

Categories Cloud IAM Tags AWS, AWS IAM, AWS Identity Center, Cloud Security, Cross Account Access, IAM, IAM Roles, SCP Leave a comment

Recent Posts

  • AWS IAM Deep Dive: Users, Groups, Roles, and Policies Explained
  • IAM Roles vs Policies: How Cloud Authorization Actually Works
  • Authentication vs Authorization: AWS AccessDenied Explained
  • eBPF Program Types — What’s Actually Running on Your Nodes
  • What Is Cloud IAM — and Why Every API Call Depends on It
  • eBPF vs Kernel Modules: An Honest Comparison for K8s Engineers
  • BPF Verifier Explained: Why eBPF Is Safe for Production Kubernetes
  • What Is eBPF? A Plain-English Guide for Linux and Kubernetes Engineers
  • Cloud AMI Security Risks & How Custom OS Images Fix them and what’s wrong with defaults
  • EKS 1.33 Upgrade Blocker: Fixing Dead Nodes & NetworkManager on Rocky Linux
  • Supercharge Your Nginx Security: A Practical Guide to Enabling TLS 1.3 on Rocky Linux 9
Copyright © 2026 Linux Cent