IAM to VPC endpoints: the extra policy on the network path
A request through a VPC endpoint is checked by the endpoint's policy as well as IAM and resource policies. What each side expects, the mismatches that bite, a known-good configuration, the known-bad ones and their symptoms, and how to tell which side refused.
markdownpatternv4TGER-J63Astage: buildtrust: officialservice: iamservice: vpcinterface: cliinterface: iacVPC endpoint policygateway endpointinterface endpointPrivateLinkaws:SourceVpceaws:SourceIpVPC endpoint policy access deniedmodify-vpc-endpoint
IAM to VPC endpoints
When traffic to an AWS service goes through a VPC endpoint, the endpoint's policy is one more layer that must allow the request. It never grants anything: the caller's identity policy and any resource policy must still allow. With no policy set, AWS attaches a full-access default, so endpoint denials only start when someone narrows it.
What crosses the handoff
| Side | Emits / expects |
|---|---|
| Caller (IAM identity in the VPC) | A signed request for an action on a resource, routed through the endpoint because of VPC routing (gateway endpoints) or DNS (interface endpoints). It carries the caller's principal and, because it used the endpoint, the endpoint ID as aws:SourceVpce. |
| VPC endpoint | One JSON policy per interface or gateway endpoint for an AWS service. It must contain a Principal element and be at most 20,480 characters including white space. It is evaluated against the principal, action and resource of each request passing through. |
| Target resource (bucket, queue, secret) | Its own resource policy, which can require the endpoint by testing aws:SourceVpce, or the VPC by testing aws:SourceVpc. |
Endpoints for services that are not AWS services (another account's service, Marketplace) have no policy and full access. Not every AWS service supports endpoint policies; where unsupported, the endpoint allows everything.
Mismatches that bite
aws:SourceIpdisappears. It is absent when the request travels through a VPC endpoint. A bucket policy that allows by office or NAT IP range stops matching as soon as an endpoint is added: an Allow keyed on it no longer applies, and a Deny written with a negated IP operator now matches and denies.aws:SourceVpceappears only on the endpoint path. It is present only when the requester used a VPC endpoint. A resource policy that requires a specific endpoint therefore denies every request made without that endpoint, including requests from the console.- Account principals in a gateway endpoint policy. For gateway endpoints the
Principalmust be"*"; to limit who can use it, testaws:PrincipalArnin a condition. Writing"AWS": "<account-id>"grants only that account's root user, not the users and roles in it. - Wildcards and system keys.
*and?and numeric operators cannot be used with system-generated global keys such asaws:PrincipalAccountoraws:SourceVpcin an endpoint policy. A string wildcard needs at least six consecutive characters before or after it. An ARN's account field is either an account ID or a wildcard, never both. - Propagation. A changed endpoint policy can take a few minutes to take effect; test again before concluding a fix did not work.
- CloudTrail gaps. An access denial from an endpoint policy may not appear in CloudTrail when the endpoint belongs to a different account than the caller or the target role.
Permissions on each side
- Caller: its identity-based policy must allow the action on the resource, exactly as without the endpoint.
- Endpoint owner: whoever runs
aws ec2 modify-vpc-endpoint --vpc-endpoint-id <id> --policy-document file://endpoint-policy.jsonneeds permission to modify the endpoint in the VPC's account. - Resource owner: controls the resource policy, including any
aws:SourceVpce/aws:SourceVpcrequirement.
Known-good
A gateway endpoint that only one role may use, combined with a bucket that only accepts requests through that endpoint:
{
"Statement": [{
"Effect": "Allow",
"Principal": "*",
"Action": "*",
"Resource": "*",
"Condition": { "StringEquals": { "aws:PrincipalArn": "arn:aws:iam::<account-id>:role/<app-role>" } }
}]
}
On the bucket, a Deny for requests that did not come through vpce-<id>, tested with StringNotEquals on aws:SourceVpce, scoped so that it does not lock out the administrators who must manage the bucket from outside the VPC. Write that exception before applying the Deny.
Known-bad
| Configuration | Symptom |
|---|---|
| Endpoint policy narrowed to a list of actions that omits one the application uses | User: <arn> is not authorized to perform: <action> … because no VPC endpoint policy allows the <action> action |
| Endpoint policy with an explicit Deny on an action | … with an explicit deny in a VPC endpoint policy |
Gateway endpoint policy with "Principal": {"AWS": "<account-id>"} | Every role and user in that account is refused through the endpoint (only the account root user is granted) |
Bucket policy requiring aws:SourceVpce with no exception for administrators | Console and out-of-VPC access to the bucket is denied, including for administrators; the message names the resource-based policy, not the endpoint |
Bucket policy allowing by aws:SourceIp after an endpoint is added | Requests from inside the VPC are refused by the bucket policy, though nothing about the caller changed |
Which side refused?
- Message names a VPC endpoint policy → the endpoint. Read its policy with
aws ec2 describe-vpc-endpoints --vpc-endpoint-ids <id>(UNVERIFIED command form) and compare it with the action and principal. - Message names a resource-based policy → the target's own policy, usually an
aws:SourceVpce,aws:SourceVpcoraws:SourceIpcondition; compare the condition with the path the request took. - Message names an identity-based policy → the caller; the endpoint is not involved.
- No denial in CloudTrail and the endpoint is in another account → suspect the endpoint first.