Your application assumes an IAM role with an inline policy containing `"Effect": "Allow"` for `s3:GetObject` on a specific bucket. When the app tries to read an object, it gets `AccessDenied`. The CloudTrail log shows the request hit the role policy. Walk through the IAM policy evaluation logic — what else could be blocking access?
IAM policy evaluation follows a strict deny-by-default model. An explicit Allow is necessary but not sufficient. The evaluation order is: (1) Explicit Deny (immediate failure), (2) Organization SCP (Service Control Policies) — if any SCP denies the action, the request fails, (3) Resource-based policies (S3 bucket policy), (4) Permission boundary (if set on the role), (5) Session policies (if assuming a role with session policy), (6) Identity-based policy (your inline role policy). A request succeeds only if all applicable policies either allow or don't restrict the action, and no policy explicitly denies. In your case, check: (1) S3 bucket policy — does it explicitly deny the principal? (2) Organization SCPs — verify no SCP denies s3:GetObject, (3) Permission boundary — if the role has a boundary, it must allow s3:GetObject, (4) Resource-based conditions — check object tags, encryption, versioning. Run `aws iam simulate-principal-policy --policy-source-arn arn:aws:iam::ACCOUNT:role/YOUR_ROLE --action-names s3:GetObject --resource-arns arn:aws:s3:::bucket/key` to simulate evaluation and see where it fails.
Follow-up: You check the S3 bucket policy and find `"Principal": {"AWS": "*"}` with `"Effect": "Deny"` on all s3:* actions. Your role's ARN matches this Principal. How would you debug this in the console without changing the policy?
Your IAM user has an inline policy allowing `iam:CreateAccessKey`, but when they log into the AWS console and try to create an access key in IAM, the button is grayed out and they see no error. Where do you look first?
The button being grayed out suggests the permission check happens on the console side via the GetUser, ListUsers, or implicit ListAccessKeys API call. Start by: (1) Confirm the user is actually authenticated with the policy containing the Allow — run `aws iam get-user` from their credentials, (2) Check for permission boundaries — even if the inline policy allows the action, a permission boundary overrides it, (3) Look for SCPs — if the account is in an Organization, check if the SCP denies iam:CreateAccessKey or limits it to admin roles, (4) Verify the policy syntax is valid — use `aws iam simulate-principal-policy` with the exact policy document to confirm evaluation, (5) Check for resource conditions in the policy — IAM actions often have resource ARNs; if your policy specifies a resource that doesn't match the target, it fails silently in console, (6) Check session policies if the user is assuming a role. The console silently disables buttons when permissions aren't explicitly allowed, unlike CLI which throws an explicit error.
Follow-up: You simulate the policy and it says "allowed". The user's credentials work in CLI for other IAM calls. What's likely the issue with the console button?
Your Terraform plan includes a new S3 bucket with a bucket policy that allows an EC2 role to read objects. The apply succeeds, but EC2 instances with that role still get `AccessDenied` when reading. Your bucket policy looks correct. What's missing?
Resource-based policies (like S3 bucket policies) define what principals can do to the resource, but the principal's identity-based policy must also allow the action. The evaluation requires BOTH: (1) The bucket policy must allow the EC2 role's ARN (or a wildcard principal), (2) The EC2 role's trust policy must allow the EC2 service (ec2.amazonaws.com) to assume it — this is the AssumeRole permission, (3) The role's permission policy must allow s3:GetObject. Most commonly, the role's permission policy is missing. The bucket policy says "you (this role) can read", but the role itself doesn't say "I allow my attached policies to perform s3 actions". Fix: Add an inline policy or managed policy to the role with `"Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::bucket/*"`. Verify with `aws iam get-role-policy --role-name YOUR_ROLE --policy-name POLICY_NAME` and `aws iam list-role-policies --role-name YOUR_ROLE`.
Follow-up: Both policies are correct. You test with an IAM user in the same account instead of EC2, and it works. What's the difference?
Your Lambda function has a role with an inline policy allowing `dynamodb:GetItem`, but when invoked, the function gets `AccessDenied` on DynamoDB reads. CloudWatch logs show the error, but IAM simulate says it should work. Explain the discrepancy.
The discrepancy often points to: (1) Resource restrictions in the policy — the policy allows dynamodb:GetItem, but specifies a Resource ARN that doesn't match the table being queried. E.g., your policy has `"Resource": "arn:aws:dynamodb:us-west-2:ACCOUNT:table/OtherTable"` but the function queries `YourTable`. (2) Session policy — if the Lambda role is assumed via a role session (rare in Lambda), a session policy could restrict permissions further, (3) VPC endpoint policy — if the function accesses DynamoDB via a VPC endpoint, the endpoint policy might deny the principal, (4) Implicit trust — Lambda might be using credentials from a different role than you think. Check CloudTrail for the exact principal and role used in the `AccessDenied` event. Run `aws iam simulate-principal-policy --policy-source-arn arn:aws:iam::ACCOUNT:role/LAMBDA_ROLE --action-names dynamodb:GetItem --resource-arns arn:aws:dynamodb:REGION:ACCOUNT:table/YOUR_TABLE` to simulate with the exact resource. Also verify the table name, region, and account ID in the policy match reality.
Follow-up: The policy and resource ARN are correct. You check CloudTrail and see the assumed role is not the one you attached the policy to. What happened?
An IAM user in your organization has a policy that allows `ec2:TerminateInstances` on all resources. When they try to terminate an instance via CLI, they get `AccessDenied`. A second user in a different AWS account can terminate instances in a role that has only `"Effect": "Allow"` on `ec2:TerminateInstances`. Why the difference?
The first user is likely hitting an Organization SCP (Service Control Policy) that blocks ec2:TerminateInstances. SCPs are organization-level policies that act as a ceiling on permissions — they apply to all principals in the organization, even if their identity-based policies allow the action. The evaluation order is: Explicit Deny (in any policy) > Organization SCP > Permission boundary > Identity-based policy. If the SCP has `"Effect": "Deny"` on ec2:TerminateInstances, the user cannot terminate instances regardless of their inline/managed policies. The second user in a different account has no SCP, so their role policy allows the action. To fix: Ask your AWS organization administrator to review SCPs via `aws organizations list-policies --filter SERVICE_CONTROL_POLICY` or in the AWS console under Organizations > Policies > Service Control Policies. If you're the admin, verify the SCP doesn't deny ec2 actions or that it has exceptions for your team. Alternatively, request an exception or work with infrastructure to whitelist your use case.
Follow-up: The SCP allows ec2:TerminateInstances. You check CloudTrail and the assumed role is correct. What else?
Your compliance team runs a report using `aws iam get-user-policy` for all users and finds one user has a policy with `"Effect": "Allow"` on `*:*` (all actions, all resources). Leadership gets nervous. You need to explain why this user isn't a security issue — they should trust your IAM design. What's your argument?
A principal policy allowing `*:*` is not a security issue if constrained by: (1) Permission boundaries — a permission boundary acts as a maximum permission set; even if the identity-based policy allows `*:*`, the boundary restricts it. E.g., the boundary could allow only `s3:*` and `dynamodb:*`. The effective permissions are the intersection: `*:*` AND (s3:* OR dynamodb:*) = (s3:* OR dynamodb:*). Check `aws iam get-role-policy` for permission boundaries. (2) Organization SCPs — if the organization SCP denies everything except `s3:GetObject`, the `*:*` policy is neutered. (3) Resource-based policy denial — even if the user is allowed, the resource's bucket policy might deny them. (4) Session policies — if the principal is assumed via a role session with a session policy, that policy limits the effective permissions. (5) Conditions in the policy — policies can have Conditions like `"IpAddress": "10.0.0.0/8"` that restrict when permissions apply. To confirm this user is safe, run `aws iam get-role-policy --role-name USER_ROLE --policy-name POLICY_NAME` to see the full policy, check for boundaries with `aws iam get-role --role-name USER_ROLE`, and check SCPs with `aws organizations list-policies-for-target --target-id ACCOUNT_ID --filter SERVICE_CONTROL_POLICY`.
Follow-up: The user has no boundary, no SCP restrictions, and the policy is truly `*:*`. Why should your security team not immediately revoke it?
Your development team uses a shared IAM role for CI/CD deployments. The role has policies for EC2, Lambda, and S3. A junior engineer manually applied a new inline policy to the role that's very permissive (s3:*). You want to prevent this in the future without removing the shared role. What IAM mechanism do you use?
Use a permission boundary on the shared role. A permission boundary is a managed policy that sets the maximum permissions a principal can have. Even if you add an inline policy allowing s3:*, the boundary acts as a ceiling. Steps: (1) Create a managed policy that defines the maximum permissions your CI/CD role needs — e.g., allow only s3:GetObject, s3:PutObject on specific buckets, specific Lambda actions, etc. (2) Set this policy as the permission boundary on the role using `aws iam put-role-permissions-boundary --role-name shared-role --permissions-boundary arn:aws:iam::ACCOUNT:policy/CIPDMaxPolicy`. (3) Any inline or attached policy on the role is now limited by this boundary. If a junior engineer adds a permissive s3:* policy, the effective permissions are still limited to what the boundary allows. (4) To verify the boundary is set: `aws iam get-role --role-name shared-role` — look for `"PermissionsBoundary"`. To see effective permissions, simulate: `aws iam simulate-principal-policy --policy-source-arn arn:aws:iam::ACCOUNT:role/shared-role --action-names s3:* --resource-arns arn:aws:s3:::*`. This approach prevents privilege escalation while keeping the shared role flexible.
Follow-up: A developer adds an inline policy that grants `iam:PutUserPolicy` on all IAM users. The boundary blocks it, but the developer complains they need to update CI/CD service accounts. How do you resolve this?
Your organization uses cross-account access: an IAM role in Account A needs to read S3 buckets in Account B. You set up the role in A with a trust policy allowing Account B's role, and you set up Account B's S3 bucket policy allowing Account A's role. Testing works in the CLI with `aws sts assume-role`, but the application running on an EC2 instance in Account A still gets `AccessDenied`. What's missing?
The EC2 instance must be launched with an instance profile that has a role; that role must have a trust policy allowing the EC2 service, and a permission policy allowing `sts:AssumeRole` on the cross-account role in Account B. The setup is: (1) In Account A: Create role A with trust policy allowing ec2.amazonaws.com (so EC2 can assume it), (2) Attach a permission policy to role A allowing `sts:AssumeRole` on arn:aws:iam::ACCOUNT_B:role/RoleB, (3) Create an instance profile from role A and launch EC2 with it, (4) In Account B: Create role B (or use existing) with a trust policy allowing `arn:aws:iam::ACCOUNT_A:role/RoleA` to assume it, (5) Attach a permission policy to role B allowing `s3:GetObject` on the bucket, (6) Update the S3 bucket policy to allow `arn:aws:iam::ACCOUNT_B:role/RoleB`. From the EC2 instance, the application needs to: assume role A (via instance metadata), then assume role B using credentials from role A, then use role B's credentials to access S3. If any link is missing, the chain breaks. Verify each step with `aws iam get-role --role-name RoleA`, `aws iam get-role-policy --role-name RoleA --policy-name POLICY`, `aws sts assume-role --role-arn arn:aws:iam::ACCOUNT_B:role/RoleB --role-session-name test` (from EC2), and check CloudTrail for the exact error in the cross-account assume-role call.
Follow-up: You verify the trust policies and permission policies are correct. The EC2 instance can manually assume role B from the CLI, but the application still fails. What's the application code issue?