IAM policy denies API access for custom role despite correct permissions in policy document

We’ve created a custom IAM role for our automation service to access sensitive HR APIs through API Gateway. The role has an attached policy with explicit Allow statements, but API calls are getting AccessDenied errors. The same API calls work fine when using admin credentials.

Our IAM policy:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "execute-api:Invoke",
    "Resource": "arn:aws:execute-api:us-east-1:123456789012:abc123def/*"
  }]
}

The API Gateway has a resource policy that allows access from our VPC. The automation service runs on EC2 instances with this IAM role attached. CloudTrail shows the API calls are being made with the correct role, but they’re denied at the API Gateway level. We’ve verified the resource ARN matches our API Gateway ID. What could be overriding our explicit Allow policy?

Your resource ARN is too broad with the wildcard. You need to specify the stage and method. The format should be: arn:aws:execute-api:region:account:api-id/stage/method/resource. Also, check if there’s a resource policy on the API Gateway itself that’s denying access. Resource policies can override IAM policies even with explicit Allow statements. What does your API Gateway resource policy look like?

Let me provide a comprehensive solution addressing all three focus areas:

1. IAM Policy Resource ARNs: Your resource ARN wildcard is incorrect. API Gateway execute-api requires specific stage and path specifications:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "execute-api:Invoke",
      "Resource": [
        "arn:aws:execute-api:us-east-1:123456789012:abc123def/prod/GET/hr/*",
        "arn:aws:execute-api:us-east-1:123456789012:abc123def/prod/POST/hr/*",
        "arn:aws:execute-api:us-east-1:123456789012:abc123def/prod/*/hr/employees"
      ]
    }
  ]
}

Resource ARN format breakdown:

  • abc123def = API Gateway ID
  • prod = Stage name
  • GET/POST/* = HTTP method
  • /hr/* = Resource path

For broader access across all methods and paths in a stage:

"Resource": "arn:aws:execute-api:us-east-1:123456789012:abc123def/prod/*/*"

2. API Gateway Resource Policies: Your resource policy’s VPC condition is blocking IAM role access. Here’s the corrected policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": "execute-api:Invoke",
      "Resource": "arn:aws:execute-api:us-east-1:123456789012:abc123def/*",
      "Condition": {
        "StringEquals": {
          "aws:PrincipalArn": [
            "arn:aws:iam::123456789012:role/AutomationServiceRole"
          ]
        }
      }
    },
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": "execute-api:Invoke",
      "Resource": "arn:aws:execute-api:us-east-1:123456789012:abc123def/*",
      "Condition": {
        "StringEquals": {
          "aws:SourceVpce": "vpce-abc123"  // Use VPC Endpoint ID, not VPC ID
        }
      }
    }
  ]
}

Key differences:

  • Changed aws:SourceVpc to aws:SourceVpce (VPC Endpoint ID)
  • Added explicit principal ARN condition for IAM role access
  • Separated concerns: one statement for IAM roles, one for VPC endpoint access

3. Service Control Policy Overrides: Check for SCPs that might deny API Gateway access:

# List all SCPs attached to your account
aws organizations list-policies-for-target \
  --target-id 123456789012 \
  --filter SERVICE_CONTROL_POLICY

# Get SCP content
aws organizations describe-policy \
  --policy-id p-abc123

Common SCP that blocks API Gateway:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": "execute-api:Invoke",
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "aws:RequestedRegion": ["us-east-1", "us-west-2"]
        }
      }
    }
  ]
}

If you find a blocking SCP, work with your organization admin to add an exception for your automation role.

Complete Diagnostic Process:

Pseudocode for troubleshooting IAM denials:


// Pseudocode - IAM access denial diagnosis:
1. Check CloudTrail for denied API call with full context
2. Verify IAM role trust policy allows EC2 service principal
3. Test IAM policy simulator: aws iam simulate-principal-policy
4. Review API Gateway resource policy for conflicting conditions
5. Check for SCPs: aws organizations list-policies-for-target
6. Verify resource ARN format includes stage and method
7. Test with aws apigateway test-invoke-method for direct validation
8. Check for session policies if using AssumeRole
// See AWS IAM Policy Evaluation Logic documentation

Verification Steps:

  1. Test IAM policy with simulator:
aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:role/AutomationServiceRole \
  --action-names execute-api:Invoke \
  --resource-arns arn:aws:execute-api:us-east-1:123456789012:abc123def/prod/GET/hr/employees
  1. Verify EC2 instance profile:
# From EC2 instance
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
  1. Test API Gateway access with assumed role credentials:
aws sts get-caller-identity  # Verify you're using correct role
aws apigateway test-invoke-method \
  --rest-api-id abc123def \
  --resource-id xyz789 \
  --http-method GET \
  --path-with-query-string "/hr/employees"

Additional Considerations:

Permission Boundaries: Check if the role has a permissions boundary that restricts execute-api:

aws iam get-role --role-name AutomationServiceRole \
  --query 'Role.PermissionsBoundary'

Session Tags: If using session tags with AssumeRole, ensure they don’t conflict with resource policy conditions

VPC Endpoint Setup (if needed):

aws ec2 create-vpc-endpoint \
  --vpc-id vpc-abc123 \
  --service-name com.amazonaws.us-east-1.execute-api \
  --route-table-ids rtb-abc123

Final Working Configuration:

IAM Role Policy (attached to AutomationServiceRole):

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "execute-api:Invoke",
    "Resource": "arn:aws:execute-api:us-east-1:123456789012:abc123def/prod/*/*"
  }]
}

API Gateway Resource Policy:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"AWS": "arn:aws:iam::123456789012:role/AutomationServiceRole"},
    "Action": "execute-api:Invoke",
    "Resource": "arn:aws:execute-api:us-east-1:123456789012:abc123def/*"
  }]
}

The key issues in your configuration were:

  1. Resource ARN missing stage and method specification
  2. Resource policy using aws:SourceVpc condition which doesn’t apply to IAM role-based calls
  3. Potential SCP overrides not checked

With these corrections, your automation service should successfully invoke the HR APIs through API Gateway using the custom IAM role.

The VPC condition in your resource policy is the problem. When you call API Gateway from EC2 using an IAM role, the request goes through the public internet endpoint by default, not through a VPC endpoint. So aws:SourceVpc won’t match. You need to either: 1) Set up a VPC endpoint for API Gateway and route through it, or 2) Change your resource policy condition to check the IAM role ARN instead of source VPC. The latter is simpler if you don’t need VPC endpoint isolation.