- How to use this guide
- IAM Authentication + Resource Policy
- Lambda Authorizer + Resource Policy
- Amazon Cognito Authentication + Resource Policy
- POLICY EVALUATION TABLES
- TABLE A
- TABLE B
- POLICY EXAMPLES
- Allow a Single IAM User
- Allow an IAM Role
- Allow an account
- Deny all except an IAM User
- Allow a Cross-Account user
- Deny all except a specific Cross-account user/role
- Allow an IP Range
- Deny all except an IP Range
- Allow VPC
- Deny all except a VPC
- Allow VPC Endpoint
- Deny all except a VPC Endpoint
- Private API : Deny API traffic based on source vpc IP address or range
- aws:PrincipalAccount
- aws:PrincipalArn
Thi API Gateway Resource policy troubleshooting guide is based on How API Gateway resource policy affect authorization workflow – https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-authorization-flow.html
How to use this guide
1. Identify the Authorization workflow applicable for your Use case
2. Based on authorization workflow and If API is accessed from same account or cross account , refer the Table A or Table B (If needed)
3. Refer to the policy examples based on use case
API Gateway resource policy only
IAM Authentication + Resource Policy
Lambda Authorizer + Resource Policy
Amazon Cognito Authentication + Resource Policy
POLICY EVALUATION TABLES
TABLE A
SAME ACCOUNT : When access to an API Gateway API is controlled by an IAM policy (or a Lambda or Amazon Cognito user pools authorizer) and an API Gateway resource policy, both of which are in the same AWS account.
A few examples:
1. If IAM User/Role policy ALLOWS but In API Gateway resource policy an Explicit Allow could not be found then as per Row 2, access would be Allowed.
2. If IAM User/Role policy DENY but In API Gateway resource policy an Explicit Allow could not be found then as per Row 8, access would be Explicitly Denied.
{"Message": "User: arn:aws:iam::ACCOUNT_ID:user/USER_ID is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/dev/GET/ with an explicit deny"}
3. If IAM User/Role policy does not have an ALLOW/DENY and In API Gateway resource policy an ALLOW/DENY could not be found then as per Row 5, access would be Implicit Denied.
{"Message": "User: arn:aws:iam::ACCOUNT_ID:user/USER_ID is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/dev/GET/"}
Note: If the error does not specify about Explicity Deny, This would indicate an Implicit Deny and would be worth checking for Allow conditions exist or not for the resource.
IAM Entity(User/Role) (or Lambda or Amazon Cognito user pools authorizer) | API Gateway resource policy | Resulting behavior |
---|---|---|
Allow | Allow | Allow |
Allow | Neither Allow nor Deny | Allow |
Allow | Deny | Explicit Deny |
Neither Allow nor Deny | Allow | Allow |
Neither Allow nor Deny | Neither Allow nor Deny | Implicit Deny |
Neither Allow nor Deny | Deny | Explicit Deny |
Deny | Allow | Explicit Deny |
Deny | Neither Allow nor Deny | Explicit Deny |
Deny | Deny | Explicit Deny |
TABLE B
CROSS ACCOUNT : When access to an API Gateway API is controlled by an IAM policy (or a Lambda or Amazon Cognito user pools authorizer) and an API Gateway resource policy, which are in different AWS accounts.
IAM Entity(User/Role) (or Lambda or Amazon Cognito user pools authorizer) | API Gateway resource policy | Resulting behavior |
---|---|---|
Allow | Allow | Allow |
Allow | Neither Allow nor Deny | Implicit Deny |
Allow | Deny | Explicit Deny |
Neither Allow nor Deny | Allow | Implicit Deny |
Neither Allow nor Deny | Neither Allow nor Deny | Implicit Deny |
Neither Allow nor Deny | Deny | Explicit Deny |
Deny | Allow | Explicit Deny |
Deny | Neither Allow nor Deny | Explicit Deny |
Deny | Deny | Explicit Deny |
POLICY EXAMPLES
Allow a Single IAM User
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::ACCOUNT_ID:user/USER_ID"
},
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/*/*/*"
}
]
}
Allow an IAM Role
For assumed roles as well use, the Actual IAM role ARN not the STS assumed role ARN.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::ACCOUNT_ID:role/testrole"
},
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/*/*/*"
}
]
}
Allow an account
If you allow an Account in the Principal, All the IAM entities for that account would be allowed as long as the IAM entities(USER/ROLES) are having permissions to access
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": {
"AWS": "arn:aws:iam::ACCOUNT_ID:root"
},
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/*/*/*"
}
]
}
Deny all except an IAM User
As a best practice, you should include the ARNs for the account in your policy. Some services require the account ARN, although this is not required in all cases. Any existing policies without the required ARN will continue to work, but new policies that include these services must meet this requirement. IAM does not track these services, and therefore recommends that you always include the account ARN.
Therefore, When using NotPrincipal, Always specify root account ARN in the AWS principal along with specific IAM User/Roles.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"NotPrincipal": {
"AWS": [
"arn:aws:iam::ACCOUNT_ID:root",
"arn:aws:iam::ACCOUNT_ID:user/USER_ID"
]
},
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/*/*/*"
},
{
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/*/*/*"
}
]
}
Allow a Cross-Account user
Unlike NotPrincipal element, When using Principal JSON element It is not needed to specify the the ARNs for the account in your policy If an Explicit Allow is not found, It will result in Implicit Deny.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::ACCOUNT_ID:user/USER"
},
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/*/*/*"
}
]
}
Deny all except a specific Cross-account user/role
If an Explicit Allow is not found, It will result in Implicit Deny.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"NotPrincipal": {
"AWS": [
"arn:aws:iam::ACCOUNT_ID:root",
"arn:aws:iam::ACCOUNT_ID:user/burner"
]
},
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/*/*/*"
},
{
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/*/*/*"
}
]
}
Allow an IP Range
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/*/*/*",
"Condition" : {
"IpAddress": {
"aws:SourceIp": [ "{{sourceIpOrCIDRBlock}}", "{{sourceIpOrCIDRBlock}}" ]
}
}
}
}
Deny all except an IP Range
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/*/*/*",
"Condition" : {
"NotIpAddress": {
"aws:SourceIp": [ "{{sourceIpOrCIDRBlock}}", "{{sourceIpOrCIDRBlock}}" ]
}
}
},
{
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:REGION:API_ACCOUNT:API_ID/*/*/*"
}
]
}
Allow VPC
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/*/*/*",
"Condition": {
"StringNotEquals": {
"aws:sourceVpc": "{{vpcID}}"
}
}
}
]
}
Deny all except a VPC
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/*/*/*",
"Condition": {
"StringNotEquals": {
"aws:sourceVpc": "{{vpcID}}"
}
}
},
{
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/*/*/*"
}
]
}
Allow VPC Endpoint
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/*/*/*",
"Condition": {
"StringNotEquals": {
"aws:sourceVpce": "{{vpceID}}"
}
}
}
]
}
Deny all except a VPC Endpoint
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/*/*/*",
"Condition": {
"StringNotEquals": {
"aws:sourceVpce": "{{vpceID}}"
}
}
},
{
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/*/*/*"
}
]
}
Private API : Deny API traffic based on source vpc IP address or range
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": [
"arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/*/*/*"
]
},
{
"Effect": "Deny",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": [
"arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/*/*/*"
],
"Condition" : {
"IpAddress": {
"aws:VpcSourceIp": ["192.0.2.0/24", "198.51.100.0/24"]
}
}
}
]
}
aws:PrincipalAccount
This example policy would Deny all the requests If the requesting principal belongs to other account than specified in aws:PrincipalAccount
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": {
"AWS": "*"
},
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/*/*/*",
"Condition": {
"StringNotEquals": {
"aws:PrincipalAccount": "ACCOUNT_ID"
}
}
},
{
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:REGION:1234567890:API_ID/*/*/*"
}
]
}
Access Logs of Failed Request(Principal Account does not match) "authorizeError": "The client is not authorized to perform this operation.", "identityAccountIdIAM": "ACCOUNT_ID", "identityCallerIAM": "ACCESS_ID", "identityPrincipalOrgId": "o-xxxxxxxxxx", "IAMuserArn": "arn:aws:iam::ACCOUNT_ID:user/USER_ID", "ownerAccountId": "ACCOUNT_ID",
Access Logs of Passing Request (Principal Account matches) "authorizeError": "-", "identityAccountIdIAM": "ACCOUNT_ID", "identityCallerIAM": "ACCESS_ID", "identityPrincipalOrgId": "o-xxxxxxxxxx", "IAMuserArn": "arn:aws:iam::ACCOUNT_ID:user/USER_ID", "ownerAccountId": "ACCOUNT_ID",
aws:PrincipalArn
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": {
"AWS": "*"
},
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/*/*/*",
"Condition": {
"StringNotEquals": {
"aws:PrincipalArn": "arn:aws:iam::ACCOUNT_ID:user/burner"
}
}
},
{
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:REGION:ACCOUNT_ID:API_ID/*/*/*"
}
]
}
Access Logs of Failed Request(Principal Arn does not match) "authorizeError": "The client is not authorized to perform this operation.", "identityAccountIdIAM": "ACCOUNT_ID", "identityCallerIAM": "ACCESS_ID", "identityPrincipalOrgId": "o-XXXX", "IAMuserArn": "arn:aws:iam::ACCOUNT_ID:user/USER_ID", "ownerAccountId": "ACCOUNT_ID", Access Logs of Passing Request (Principal Arn matches) "authorizeError": "-", "identityAccountIdIAM": "ACCOUNT_ID", "identityCallerIAM": "ACCESS_ID", "identityPrincipalOrgId": "o-XXXXX", "IAMuserArn": "arn:aws:iam::ACCOUNT_ID:user/burner", "ownerAccountId": "ACCOUNT_ID",