Skip to content

Fix: AWS AccessDeniedException when calling an AWS service operation

FixDevs ·

Quick Answer

How to fix AWS AccessDeniedException caused by missing IAM permissions, explicit denies, SCPs, resource policies, permission boundaries, and misconfigured roles.

The Error

You call an AWS API and get:

An error occurred (AccessDeniedException) when calling the DescribeInstances operation:
User: arn:aws:iam::123456789012:user/myuser is not authorized to perform: ec2:DescribeInstances

Or variations like:

An error occurred (AccessDenied) when calling the PutObject operation: Access Denied
An error occurred (UnauthorizedAccess) when calling the AssumeRole operation:
User: arn:aws:sts::123456789012:assumed-role/my-role/session is not authorized to perform: sts:AssumeRole
botocore.exceptions.ClientError: An error occurred (AccessDeniedException) when calling the Invoke operation

AWS refused your request because the identity making the call does not have permission to perform the action on the target resource.

Why This Happens

AWS uses IAM (Identity and Access Management) to control who can do what. Every API call is evaluated against IAM policies. A request is allowed only if there is an explicit Allow and no explicit Deny.

The most common causes:

  • Missing IAM policy. Your user or role does not have a policy granting the required permission.
  • Explicit deny. A policy explicitly denies the action, overriding any allows.
  • Service Control Policy (SCP). An organization-level policy restricts the action for the entire account.
  • Resource-based policy. The target resource (S3 bucket, SQS queue, Lambda function) has a policy that blocks your identity.
  • Permission boundary. The IAM user or role has a permission boundary that limits its effective permissions.
  • Wrong identity. You are authenticated as a different user or role than you expect.
  • Cross-account access. You are trying to access a resource in a different AWS account without proper trust configuration.
  • Condition key mismatch. The policy allows the action but only under specific conditions (IP range, MFA, tags) that your request doesn’t meet.

Fix 1: Verify Your Identity

Before debugging policies, confirm who AWS thinks you are:

aws sts get-caller-identity

Output:

{
    "UserId": "AIDAEXAMPLEUSERID",
    "Account": "123456789012",
    "Arn": "arn:aws:iam::123456789012:user/myuser"
}

Check:

  • Is this the right user/role? You might be using the wrong AWS profile or credentials.
  • Is this the right account? You might be in a sandbox or staging account.
  • Is this an assumed role? If the ARN shows assumed-role, you are using temporary credentials from an assumed role, which may have different permissions than your IAM user.

If the identity is wrong, fix your credentials. See Fix: AWS Unable to locate credentials for troubleshooting credential configuration.

Pro Tip: Use named AWS profiles to avoid accidentally using the wrong credentials:

aws sts get-caller-identity --profile production
aws s3 ls --profile staging

Set AWS_PROFILE in your shell to change the default, rather than hardcoding credentials.

Fix 2: Check and Add Missing Permissions

Find out which permission you need. The error message usually tells you:

is not authorized to perform: ec2:DescribeInstances

This means you need the ec2:DescribeInstances permission. Add it to your IAM user or role.

Using the AWS Console:

  1. Go to IAM → Users (or Roles) → Select your identity
  2. Click “Add permissions” → “Attach policies directly”
  3. Search for the relevant managed policy (e.g., AmazonEC2ReadOnlyAccess)
  4. Attach it

Using the AWS CLI:

aws iam attach-user-policy \
  --user-name myuser \
  --policy-arn arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess

Using a custom inline policy:

aws iam put-user-policy \
  --user-name myuser \
  --policy-name EC2DescribeAccess \
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Action": "ec2:DescribeInstances",
      "Resource": "*"
    }]
  }'

Note: For production, prefer managed policies or shared custom policies over inline policies. Inline policies are harder to audit and manage at scale.

Fix 3: Check for Explicit Denies

An explicit Deny in any policy always wins over Allow. Check all policies attached to your identity:

aws iam list-attached-user-policies --user-name myuser
aws iam list-user-policies --user-name myuser
aws iam list-groups-for-user --user-name myuser

For roles:

aws iam list-attached-role-policies --role-name my-role
aws iam list-role-policies --role-name my-role

Download and inspect each policy. Look for "Effect": "Deny" statements:

{
  "Effect": "Deny",
  "Action": "ec2:*",
  "Resource": "*",
  "Condition": {
    "StringNotEquals": {
      "aws:RequestedRegion": "us-east-1"
    }
  }
}

This policy denies all EC2 actions outside us-east-1. Even if another policy allows ec2:DescribeInstances, the deny overrides it.

Common Mistake: Adding more Allow policies when an explicit Deny exists. The deny always wins. You must remove or modify the deny policy, not add more allows.

Fix 4: Use the IAM Policy Simulator

The IAM Policy Simulator evaluates your policies without making real API calls:

  1. Go to https://policysim.aws.amazon.com
  2. Select the user or role
  3. Choose the service and action
  4. Click “Run Simulation”

The simulator tells you whether the action is allowed or denied and which policy is responsible.

From the CLI:

aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:user/myuser \
  --action-names ec2:DescribeInstances

This shows which policies allow or deny the action and why.

Fix 5: Check Service Control Policies (SCPs)

If your AWS account is part of an AWS Organization, SCPs can restrict permissions at the account or OU (Organizational Unit) level. SCPs act as a guardrail — even if your IAM policy allows an action, the SCP can block it.

Check with your organization administrator. SCPs are managed at the organization level and are not visible to individual account users.

Common SCP restrictions:

  • Region restrictions: Only allow actions in specific regions
  • Service restrictions: Block access to certain services entirely
  • Root user restrictions: Prevent root from performing certain actions

Fix 6: Check Resource-Based Policies

Some AWS resources have their own policies that control access independently of IAM:

  • S3 bucket policies
  • SQS queue policies
  • SNS topic policies
  • Lambda function policies
  • KMS key policies

For S3 AccessDenied errors specifically, see Fix: AWS S3 Access Denied.

Check the resource policy:

aws s3api get-bucket-policy --bucket my-bucket

A bucket policy might explicitly deny access:

{
  "Effect": "Deny",
  "Principal": "*",
  "Action": "s3:GetObject",
  "Resource": "arn:aws:s3:::my-bucket/*",
  "Condition": {
    "StringNotEquals": {
      "aws:PrincipalOrgID": "o-12345example"
    }
  }
}

This denies access to anyone outside the organization, even if IAM policies allow it.

Fix 7: Check Permission Boundaries

Permission boundaries are IAM policies attached to users or roles that set the maximum permissions. Even if an attached policy allows an action, the permission boundary must also allow it.

Check if a permission boundary is set:

aws iam get-user --user-name myuser

Look for the PermissionsBoundary field in the output. If present, the effective permissions are the intersection of the attached policies and the permission boundary.

Fix 8: Fix Assume Role Issues

If the error happens when assuming a role, check the role’s trust policy:

aws iam get-role --role-name my-role --query 'Role.AssumeRolePolicyDocument'

The trust policy must explicitly allow your identity to assume the role:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "AWS": "arn:aws:iam::123456789012:user/myuser"
    },
    "Action": "sts:AssumeRole"
  }]
}

For cross-account role assumption, both sides need configuration:

  1. The role’s trust policy in the target account must trust the source account.
  2. The user/role in the source account must have sts:AssumeRole permission for the target role.

Fix 9: Debug with CloudTrail

CloudTrail logs every API call and its authorization result. Find the denied request:

aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=DescribeInstances \
  --max-results 5

Or search in CloudTrail Event History in the AWS Console. Filter by:

  • Event name: The API action
  • Error code: AccessDenied or UnauthorizedAccess
  • User name: Your identity

The CloudTrail event shows the exact ARN that made the request, the resource targeted, and the error. This is the definitive source for debugging access issues.

For SSL/TLS errors that prevent API calls from reaching AWS at all, see Fix: SSL certificate problem.

Still Not Working?

If you have checked everything above and still get AccessDeniedException:

Check VPC endpoints. If your service uses a VPC endpoint, the endpoint’s policy might restrict which IAM identities can use it. Check the endpoint policy in the VPC console.

Check condition keys. Some policies use conditions that aren’t obvious:

  • aws:SourceIp — only allows access from specific IPs
  • aws:MultiFactorAuthPresent — requires MFA
  • aws:PrincipalTag — requires specific tags on the identity
  • aws:RequestedRegion — restricts to specific regions
  • aws:CalledVia — restricts which service can make the call

Check for recently created policies. IAM policy changes can take a few seconds to propagate. If you just added a policy, wait 10-30 seconds and retry.

Check for service-linked roles. Some AWS services require service-linked roles that must be created separately. If a service can’t create the role automatically, you get AccessDeniedException.

Check Terraform state locks. If the AccessDeniedException happens during Terraform operations, it might be related to state lock issues rather than IAM.

Check the AWS service quotas. Some services return AccessDeniedException when you hit service quotas instead of a quota-specific error. Check Service Quotas in the AWS Console.

Use the --debug flag for detailed request information:

aws ec2 describe-instances --debug 2>&1 | tail -50

This shows the exact request, headers, and response, including any error details not shown in the standard output.

If network connectivity itself is the problem rather than permissions, see Fix: curl failed to connect for troubleshooting network issues to AWS endpoints.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles