Amazon IAM

A practical guide to identity, policies, roles, federation, MFA, and access security in AWS

Purpose: Use this document as a clear reference when designing permissions, creating roles, and debugging access in AWS.

1. What is IAM?

AWS Identity and Access Management (IAM) controls who can call AWS and what they are allowed to do. It is global (not per-region) and free to use.

Mental model: IAM answers three questions for every API call: 1) Who is asking? (user, role, or AWS service) 2) What do they want to do? (action, e.g. s3:GetObject) 3) On which resource? (e.g. arn:aws:s3:::my-bucket/*). If an explicit Deny matches anywhere, the request is denied. Otherwise an explicit Allow is needed. No allow = deny by default.
TermMeaning
PrincipalAn identity that can make requests: IAM user, IAM role, or an AWS service.
PermissionOne API action, such as ec2:StartInstances.
PolicyA JSON document that lists permissions (Allow/Deny on actions/resources).
ARNAmazon Resource Name — unique address of a resource, e.g. arn:aws:s3:::my-bucket.
Root accountThe account owner email; full access, should rarely be used.

2. How access works (the big picture)

Principal (user / role / service)
        |
        v
Policies attached to the principal  ----+
        |                               |
        v                               v
Service control policies (org)    Resource-based policies
        |                               |
        +---------------+---------------+
                        v
              Evaluation: Deny wins,
              else Allow needed
                        |
                        v
              AWS API call succeeds / fails
StyleWhere the policy livesExample
Identity-basedAttached to a user, group, or role"This role may read from bucket X."
Resource-basedAttached to the resource itself"Bucket X allows this account/role to write."

Most permissions are identity-based. Resource policies are common for S3 bucket policies, Lambda function policies, KMS key policies, and cross-account access.

3. IAM entities: users, groups, roles

EntityWhat it isWhen to use
IAM userA long-lived identity with a password (console) and/or access keys (CLI/API).Humans that need steady access; legacy cases. Prefer roles + SSO now.
IAM groupA collection of users that share the same policies. Groups cannot be principals in policies.Applying the same permissions to many users (e.g. Developers, Admins).
IAM roleA set of permissions that anyone/something can temporarily assume. Has a trust policy (who may assume) + permissions policy (what they can do).EC2/Lambda service access, cross-account access, SSO/federation, switching contexts.
Instance profileA wrapper that delivers an IAM role's credentials to an EC2 instance.Attaching a role to EC2 (created automatically when you attach a role in the console).

Root user vs IAM user

Root userIAM user / role
IdentityAccount root email + passwordNamed identity inside the account
PermissionsEverything (no restrictions)Only what policies allow
Recommended useFew account-level tasks onlyAll daily work
MFAStrongly requiredRequired by best practice
Golden rule: Do not use the root account for everyday work. Do not create long-lived IAM users with access keys when a role (via IAM Identity Center) will do.

Access methods compared

MethodCredentialTypical use
ConsoleUsername + password (+ MFA)Humans in the browser
AWS CLI / SDKAccess key ID + secret access keyScripts, CI/CD, local dev (prefer roles)
Assumed roleTemporary STS credentialsEC2, Lambda, cross-account, SSO
IAM Identity CenterSSO portal / AWS CLI SSOHumans, org-wide access

4. Policy types

Policy typeAttached toPurpose
AWS managed policyUsers, groups, rolesReady-made policies maintained by AWS (e.g. AmazonS3ReadOnlyAccess). Convenient but often broad.
Customer managed policyUsers, groups, rolesYour own reusable policy; versioned, editable — preferred for production.
Inline policyOne specific user/group/roleEmbedded in the entity; deleted with the entity. Use sparingly.
Permissions boundaryIAM entityA maximum permission limit. An entity can never exceed the boundary.
Service control policy (SCP)OU / account (AWS Organizations)Org-wide guardrail. Does not grant permissions by itself — it filters what member accounts can ever do.
Session policyPassed at assume-role timeExtra limit applied to the session; advanced role-assumption flows.
Note: SCPs limit, they do not grant. An identity still needs an Allow in its own policy; the SCP just removes what is outside the org guardrail.
Effective permissions =
    Identity policy  ∩  Permissions boundary  ∩  SCP  ∩  (resource policy allow)
    ...unless an explicit Deny applies anywhere → Deny.

5. Anatomy of a policy

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowReadFromOneBucket",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::example-bucket",
        "arn:aws:s3:::example-bucket/*"
      ],
      "Condition": {
        "StringEquals": {
          "aws:RequestedRegion": "us-east-1"
        }
      }
    }
  ]
}
FieldMeaning
VersionPolicy language version; use "2012-10-17" for all modern policies.
StatementArray of permission rules.
SidOptional label to identify a statement.
EffectAllow or Deny.
ActionAPI actions, e.g. s3:GetObject, or wildcards s3:*.
ResourceTarget ARNs. Use * for "all resources" carefully.
ConditionExtra tests: region, MFA, source IP, tags, time, etc.
PrincipalOnly in resource-based policies — who may use the resource.

Action wildcards

s3:*            all S3 actions          (too broad for production)
s3:Get*         all actions starting with Get
s3:GetObject    one precise action      (prefer this)

Conditions you will actually use

Condition keyExampleEffect
aws:MultiFactorAuthPresent"Bool": {"aws:MultiFactorAuthPresent": "true"}Require MFA for the action.
aws:SourceIp"IpAddress": {"aws:SourceIp": "203.0.113.0/24"}Restrict by caller IP.
aws:RequestedRegion"StringEquals": ...Restrict to specific regions.
aws:PrincipalTag/TeamTag-based access controlAllow only if the caller's role has a matching tag.

6. Policy evaluation logic

1. Start: request is denied by default (implicit deny).
2. Any explicit Deny anywhere (identity, resource, SCP, boundary)
   matching the request?  ---- YES ----> DENIED (final).
3. Any explicit Allow matching?  ------- NO ----> still denied (implicit deny).
4. Also check permissions boundaries and SCPs:
   the action must be allowed by identity policy
   AND not blocked by boundary/SCP.
5. ALLOW.
SituationResult
Identity Allow + no DenyAllowed
Identity Allow + explicit Deny on the same actionDenied (Deny wins)
No Allow anywhereDenied (implicit deny)
Allow, but action outside permissions boundaryDenied
Allow, but SCP blocks the action in the orgDenied
Resource policy grants Allow to this principalAllowed (can be enough even without identity Allow)
Tip: "Deny if not MFA" is implemented as an explicit Deny statement with a condition — explicit Deny beats any Allow.

Example — force MFA for dangerous actions:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyUnlessMFA",
      "Effect": "Deny",
      "Action": [
        "iam:Delete*",
        "iam:Create*",
        "iam:Attach*",
        "iam:Detach*"
      ],
      "Resource": "*",
      "Condition": {
        "BoolIfExists": {
          "aws:MultiFactorAuthPresent": "false"
        }
      }
    }
  ]
}

7. IAM roles in depth

A role is the most important IAM concept in practice. It has two policies:

PieceQuestion it answers
Trust policy (principal)Who may assume this role? (account, user, service, external account)
Permissions policyWhat can they do once assumed?
Caller ---- assume-role (STS) ----> Role
                                      |
                                      +--> Trust policy: is caller allowed in?
                                      +--> Permissions: what actions result?
                                      |
                                      v
                            Temporary credentials
                            (AccessKey, SecretKey, SessionToken)
                            expire (15 min – 12 hours)

Common role use cases

Use caseTrust policy principalNotes
EC2 instance roleec2.amazonaws.comDelivered via instance profile; safest way for apps on EC2.
Lambda execution rolelambda.amazonaws.comPermissions for logs, S3, DynamoDB, etc.
ECS task roleecs-tasks.amazonaws.comPer-task permissions, distinct from the execution role.
Cross-account accessThe other account/role ARNRequires a resource-based policy allowing the assume + matching identity policy.
Human access (SSO)IAM Identity Center permission setsPreferred over long-lived IAM users.
Automation / CIGitHub OIDC, Jenkins, etc.Federated assume-role; no static keys stored.

Example: EC2 instance role trust policy

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Service": "ec2.amazonaws.com" },
      "Action": "sts:AssumeRole"
    }
  ]
}

Example: minimal S3-read permissions policy

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject"],
      "Resource": "arn:aws:s3:::example-bucket/*"
    },
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": "arn:aws:s3:::example-bucket"
    }
  ]
}
Note: ListBucket targets the bucket ARN (no /*); GetObject targets object ARNs (with /*). Mixing these up is a classic bug.

Credentials on EC2 (instance metadata)

Applications on EC2 retrieve temporary credentials from instance metadata. Never bake access keys into AMIs or user data.

EC2 instance
    |
    v
Instance profile -> IAM role -> least-privilege policies -> AWS services

Require IMDSv2 (session tokens; blocks simple SSRF credential theft):

aws ec2 modify-instance-metadata-options \
  --instance-id i-0123456789abcdef0 \
  --http-tokens required \
  --http-endpoint enabled

Cross-account access (pattern)

Account A (trusted)                    Account B (owns the role)
Role: DataReader  <--- trust ---       Trust policy: allow Account A
Permissions: read S3 in Account B
  1. In Account B, create role DataReader with a trust policy allowing Account A.
  2. In Account A, the caller runs sts:AssumeRole on arn:aws:iam::B:role/DataReader.
  3. Temporary credentials for the role are used for subsequent calls.

8. Federation and IAM Identity Center

Federation = use an external identity (company IdP, Active Directory, Google, etc.) to get AWS access without long-lived IAM users.

OptionWhat it isWhen
IAM Identity CenterCentral portal: one login → many AWS accounts/permission sets. Integrates with IdPs (Okta, Azure AD, Google, AD).Recommended for humans in orgs.
SAML 2.0 federationDirect SAML integration with your IdP to assume roles.Custom integrations; often backs Identity Center.
CognitoPublic user sign-in for apps (social login, user pools).Application end-users, not AWS admin access.
Web identity (OIDC)Roles trusted by an OIDC provider (e.g. GitHub Actions).CI/CD pipelines without static keys.
Human -> IdP (Okta/AD/Google)
            |
            v
    IAM Identity Center
            |
            v
    Permission set (policy) assumed into Target AWS account

Permission sets are templates (e.g. AdministratorAccess, PowerUser, ReadOnly) applied to users/groups per account.

9. MFA and access security

ControlWhy
MFA on root + all IAM usersBlocks password-only takeover.
Virtual / hardware MFAAuthenticator app (virtual), hardware key, or FIDO2 security key.
No access keys for humansHumans should use Identity Center; keys are for machines and should rotate.
Password policyMin length, complexity, expiry, no reuse.
Condition: require MFAFor sensitive actions (billing, IAM changes).

Setting a strong password policy (CLI sketch)

aws iam update-account-password-policy \
  --minimum-length 14 \
  --require-symbols \
  --require-numbers \
  --require-uppercase-characters \
  --require-lowercase-characters \
  --max-password-age 90 \
  --password-reuse-prevention 24

Access keys hygiene

DoDon't
One key pair per application, per environmentShare keys between apps or people
Rotate regularly (90 days) or use rolesCommit keys to git
Store in Secrets Manager / Parameter StorePut keys in user data or AMIs
Delete unused keys immediatelyLeave departed employees' keys active
aws iam list-access-keys --user-name my-user
aws iam get-access-key-last-used --access-key-id AKIA...

10. Tools: CLI, simulator, Access Analyzer

Useful IAM CLI commands

# Who am I?
aws sts get-caller-identity

# List users, groups, roles, policies
aws iam list-users
aws iam list-groups-for-user --user-name alice
aws iam list-attached-role-policies --role-name app-role
aws iam list-policies --scope Local

# Inline policies
aws iam list-role-policies --role-name app-role
aws iam get-role-policy --role-name app-role --policy-name my-inline

# Attach / detach managed policy
aws iam attach-role-policy \
  --role-name app-role \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
aws iam detach-role-policy \
  --role-name app-role \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

# Assume a role and use it
aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/DataReader \
  --role-session-name my-session

IAM policy simulator

Web tool: IAM → Policy simulator (or aws iam simulate-principal-policy). Answers "would this identity be allowed to do X on resource Y?" without executing the action. Excellent for debugging before granting more permissions.

IAM Access Analyzer

Enable it in every region you use. Treat findings as a to-do list: every external share should be intentional.

CloudTrail for auditing

Every IAM API call (CreateUser, AttachRolePolicy, AssumeRole, …) appears in CloudTrail. Alert on:

11. Troubleshooting AccessDenied

SymptomLikely causeFix
AccessDenied on S3 GetObjectMissing object permission or wrong resource ARN (/* needed)Check both bucket and object statements.
AccessDenied on ListBucketStatement targets bucket/* instead of bucketSplit List vs Get resources.
Role cannot be assumedTrust policy does not include caller, or caller lacks sts:AssumeRoleFix trust policy + identity policy.
Works in console, fails in CLICLI uses different identity (keys vs SSO session)aws sts get-caller-identity first.
Region errorsPolicy has Condition on regionAlign aws:RequestedRegion.
EC2 app deniedInstance has no instance profile / wrong roleAttach profile; wait for credentials.
Works alone, fails with boundary/SCPAction outside boundary or blocked by org SCPCheck boundary and SCPs.
"Still denied" after adding AllowExplicit Deny exists elsewhereSearch all policies for Deny on that action.

Debugging order

aws sts get-caller-identity          # 1. Who am I?
aws iam simulate-principal-policy \  # 2. Would this identity pass?
  --policy-source-arn arn:aws:iam::123456789012:role/app-role \
  --action-names s3:GetObject \
  --resource-arns arn:aws:s3:::example-bucket/*

Then read the error code carefully: AccessDenied, AccessDeniedException, UnauthorizedOperation — each points at a different layer (identity policy vs resource policy vs service auth).

12. IAM best practices checklist

Remember: IAM is allow-by-default-deny. Start from zero permissions, add the minimum Allow needed, test with the Policy Simulator, and use explicit Deny + conditions for guardrails. Roles over users; temporary credentials over permanent keys.