Fix: AWS Access Denied — IAM Permission Errors and Policy Debugging
Quick Answer
How to fix AWS Access Denied errors — understanding IAM policies, using IAM policy simulator, fixing AssumeRole errors, resource-based policies, and SCPs blocking actions.
The Error
An AWS API call or CLI command returns an access denied error:
An error occurred (AccessDenied) when calling the PutObject operation:
User: arn:aws:iam::123456789012:user/deployer
is not authorized to perform: s3:PutObject
on resource: arn:aws:s3:::my-bucket/uploads/file.txtOr an IAM role assumption fails:
An error occurred (AccessDenied) when calling the AssumeRole operation:
User: arn:aws:iam::123456789012:user/ci-runner
is not authorized to perform: sts:AssumeRole
on resource: arn:aws:iam::999999999999:role/DeployRoleOr an EC2 instance/Lambda function can’t access other AWS services:
botocore.exceptions.ClientError: An error occurred (AccessDenied) when calling the
GetSecretValue operation: User: arn:aws:sts::123456789012:assumed-role/MyRole/MyFunction
is not authorized to perform: secretsmanager:GetSecretValueWhy This Happens
AWS uses a “default deny” security model — all actions are denied unless explicitly permitted. The full access decision requires checking:
- Identity-based policies — permissions attached to the user, role, or group making the request
- Resource-based policies — permissions on the resource itself (S3 bucket policy, KMS key policy, SQS queue policy)
- Permission boundaries — max permissions a role can have (overrides identity policies)
- Service control policies (SCPs) — org-level guardrails (AWS Organizations)
- Session policies — permissions passed when assuming a role
Any explicit Deny in any policy overrides all Allow statements. Missing a required permission in any layer causes AccessDenied.
Common specific causes:
- Wrong ARN in the policy — a typo in the resource ARN means the policy matches a different resource than intended
- Missing trailing
/*for S3 —s3:PutObjectonarn:aws:s3:::my-bucketonly grants access to the bucket itself, not objects inside it. Objects needarn:aws:s3:::my-bucket/* - Cross-account role assumption — the trust policy on the target role must explicitly allow the source account/user to assume it
- SCP blocking the action — an org-level SCP can block actions even if the IAM policy allows them
- Resource policy denies — an S3 bucket policy with
Effect: Denyoverrides anyAllowin the user’s identity policy - Condition key mismatch — a policy with conditions (MFA required, specific IP range, specific tag) fails silently when conditions aren’t met
Fix 1: Read the Error Message Carefully
The AccessDenied error contains the information needed to diagnose the issue:
User: arn:aws:iam::123456789012:user/deployer
is not authorized to perform: s3:PutObject
on resource: arn:aws:s3:::my-bucket/uploads/file.txtThis tells you:
- Who:
arn:aws:iam::123456789012:user/deployer— the IAM principal making the request - What:
s3:PutObject— the API action that was denied - Where:
arn:aws:s3:::my-bucket/uploads/file.txt— the specific resource
Check what policies are attached to this user:
# List policies attached to the user
aws iam list-attached-user-policies --user-name deployer
# List inline policies
aws iam list-user-policies --user-name deployer
# List groups the user belongs to (group policies also apply)
aws iam list-groups-for-user --user-name deployerFix 2: Use the IAM Policy Simulator
The IAM Policy Simulator lets you test whether a specific action would be allowed or denied without making the actual API call:
From the AWS Console:
- Go to IAM → Policy Simulator (or search “IAM Policy Simulator”)
- Select the user, role, or group to test
- Choose the service (e.g., S3) and action (e.g.,
PutObject) - Enter the resource ARN
- Click Run Simulation
From the CLI:
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:user/deployer \
--action-names s3:PutObject \
--resource-arns "arn:aws:s3:::my-bucket/uploads/file.txt"{
"EvaluationResults": [
{
"EvalActionName": "s3:PutObject",
"EvalDecision": "implicitDeny",
"MatchedStatements": [],
"MissingContextValues": []
}
]
}"implicitDeny" = no policy allows this action (add a policy). "explicitDeny" = a policy explicitly denies this action (find and remove the Deny).
Fix 3: Fix S3 Bucket and Object Permission Mistakes
S3 has two levels of ARNs — the bucket and the objects inside it. Most actions require different permissions for each:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket", // Needs bucket ARN
"s3:GetBucketLocation" // Needs bucket ARN
],
"Resource": "arn:aws:s3:::my-bucket"
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject", // Needs object ARN (bucket/*)
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::my-bucket/*" // /* is required for objects
}
]
}Common Mistake: Using
"Resource": "arn:aws:s3:::my-bucket"(without/*) fors3:PutObject. This matches the bucket itself but not any objects. The action is denied because no policy matchesarn:aws:s3:::my-bucket/uploads/file.txt.
S3 bucket policy blocking external access:
// Bucket policy — might be blocking your user's access
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
],
"Condition": {
"StringNotEquals": {
"aws:PrincipalAccount": "123456789012" // Only allows account 123...
}
}
}
]
}
// If your user is in a different account, this Deny overrides their AllowFix 4: Fix IAM Role Trust Policies for AssumeRole
When a user or service needs to assume an IAM role, the role’s trust policy must allow it:
// The role's trust policy (who can assume this role)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:user/ci-runner" // Specific user
},
"Action": "sts:AssumeRole"
}
]
}Common trust policy patterns:
// Allow an entire account to assume the role
{
"Principal": { "AWS": "arn:aws:iam::123456789012:root" }
}
// Allow a specific role (e.g., CI/CD system role)
{
"Principal": { "AWS": "arn:aws:iam::123456789012:role/GitHubActionsRole" }
}
// Allow EC2 instances to assume the role (for instance profiles)
{
"Principal": { "Service": "ec2.amazonaws.com" }
}
// Allow Lambda to assume the role (execution role)
{
"Principal": { "Service": "lambda.amazonaws.com" }
}
// Cross-account — allow account 999 to assume a role in account 123
{
"Principal": { "AWS": "arn:aws:iam::999999999999:root" }
}Also, the user/role assuming the role must have sts:AssumeRole permission:
// Policy on the user/role doing the assuming
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::999999999999:role/DeployRole"
}Both sides must allow the operation: the trust policy on the target role AND an sts:AssumeRole permission on the source principal.
Fix 5: Fix Permissions for EC2 and Lambda
EC2 instances and Lambda functions use instance profiles and execution roles for AWS API access. If these aren’t configured correctly, AWS SDK calls fail with AccessDenied:
Lambda — attach a role with the right permissions:
# Check the Lambda function's execution role
aws lambda get-function-configuration \
--function-name my-function \
--query 'Role'
# Returns: "arn:aws:iam::123456789012:role/MyLambdaRole"
# Check what policies are attached to that role
aws iam list-attached-role-policies --role-name MyLambdaRole
# Add a managed policy to the role
aws iam attach-role-policy \
--role-name MyLambdaRole \
--policy-arn arn:aws:iam::aws:policy/SecretsManagerReadWriteMinimal Lambda role for Secrets Manager access:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:my-secret-??????"
// The ?????? matches the 6-char suffix AWS adds to secret ARNs
}
]
}EC2 — attach an instance profile:
# Create a role with EC2 trust policy
aws iam create-role \
--role-name MyEC2Role \
--assume-role-policy-document file://ec2-trust-policy.json
# Attach permissions
aws iam attach-role-policy \
--role-name MyEC2Role \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
# Create instance profile and add role
aws iam create-instance-profile --instance-profile-name MyEC2Profile
aws iam add-role-to-instance-profile \
--instance-profile-name MyEC2Profile \
--role-name MyEC2Role
# Attach to running instance
aws ec2 associate-iam-instance-profile \
--instance-id i-1234567890abcdef0 \
--iam-instance-profile Name=MyEC2ProfileFix 6: Debug Deny from SCPs or Permission Boundaries
If IAM policies look correct but the error persists, a Service Control Policy (SCP) or Permission Boundary may be blocking the action:
# Check if SCPs apply to your account
aws organizations list-policies-for-target \
--target-id <ACCOUNT_ID> \
--filter SERVICE_CONTROL_POLICY
# Get the SCP document
aws organizations describe-policy --policy-id p-xxxxxxxxxxxxSCPs can’t be overridden by IAM policies — an SCP Deny blocks the action regardless of what any IAM policy says.
Permission boundaries — check if a role has a permission boundary:
aws iam get-role --role-name MyRole --query 'Role.PermissionsBoundary'A permission boundary limits the maximum permissions a role can have. If the boundary doesn’t include secretsmanager:GetSecretValue, the role can’t perform that action even if the role’s policy allows it.
Enable CloudTrail to see the exact denial reason:
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=PutObject \
--start-time 2026-03-21T00:00:00Z \
--end-time 2026-03-21T23:59:59Z \
--query 'Events[?contains(CloudTrailEvent, `AccessDenied`)]'CloudTrail logs include errorCode and errorMessage fields that show whether the denial came from an SCP, a permission boundary, or a resource policy.
Fix 7: Common Patterns for GitHub Actions and CI/CD
GitHub Actions deployments commonly hit IAM issues. The recommended approach uses OIDC (no long-lived credentials):
# .github/workflows/deploy.yml
permissions:
id-token: write # Required for OIDC
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
aws-region: us-east-1IAM role trust policy for GitHub Actions OIDC:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:*"
// Replace with your org/repo — limits which repos can assume this role
}
}
}
]
}Still Not Working?
Enable AWS CloudTrail if not already active — it logs all API calls including denied ones, showing exactly which policy caused the denial.
Check resource ARN wildcards — some services require exact ARNs, others support wildcards:
// DynamoDB — table and stream ARNs are separate
"Resource": [
"arn:aws:dynamodb:us-east-1:123456789012:table/MyTable",
"arn:aws:dynamodb:us-east-1:123456789012:table/MyTable/stream/*"
]
// KMS — must specify both the key and its alias
"Resource": [
"arn:aws:kms:us-east-1:123456789012:key/key-id",
"arn:aws:kms:us-east-1:123456789012:alias/my-key"
]Wait for IAM propagation — IAM changes take up to a few seconds to propagate globally. If you just attached a policy, wait 10–30 seconds and retry.
For related AWS issues, see Fix: GitHub Actions Permission Denied and Fix: AWS S3 Access Denied.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: AWS Lambda Layer Not Working — Module Not Found or Layer Not Applied
How to fix AWS Lambda Layer issues — directory structure, runtime compatibility, layer ARN configuration, dependency conflicts, size limits, and container image alternatives.
Fix: AWS SQS Not Working — Messages Not Received, Duplicate Processing, or DLQ Filling Up
How to fix AWS SQS issues — visibility timeout, message not delivered, duplicate messages, Dead Letter Queue configuration, FIFO queue ordering, and Lambda trigger problems.
Fix: AWS S3 CORS Error — Access to Fetch Blocked by CORS Policy
How to fix AWS S3 CORS errors — S3 bucket CORS configuration, pre-signed URL CORS, CloudFront CORS headers, OPTIONS preflight requests, and presigned POST uploads.
Fix: Kubernetes Secret Not Mounted — Pod Cannot Access Secret Values
How to fix Kubernetes Secrets not being mounted — namespace mismatches, RBAC permissions, volume mount configuration, environment variable injection, and secret decoding issues.