skip to content

What is a VPC endpoint policy, how does it combine with the caller's IAM policy and the target resource's own policy, and what is it typically used for?

level: seniorimportance: should knowfreq 45%

answer

  1. a filter on the pipe, not a grant
  2. default is wide open
  3. every applicable policy must allow
  4. stops writes to someone else's bucket
  5. one condition key covers the whole organisation

basics

~20 s

A VPC endpoint policy is a JSON policy attached to the endpoint that limits which principals, actions and resources may pass through it. It can only restrict, never grant: a request must also be allowed by the caller's identity policy and the resource's own policy.

solid answer

~50 s

An endpoint policy is a filter on the pipe rather than a grant. It is attached to the VPC endpoint itself, and a request that traverses that endpoint must be permitted by it *in addition to* everything else — the caller's IAM identity policy and, where one exists, the target resource's policy such as an S3 bucket policy. All of them must allow, and any explicit deny anywhere wins. Critically it grants nothing: an endpoint policy naming a bucket does not give anyone access to that bucket. New endpoints get a default policy of full access, so leaving it alone is not a security control. Its real use is the data perimeter: on an S3 endpoint you scope it so only your own organisation's buckets are reachable, which stops an instance with valid credentials from copying data out to an attacker's bucket over the private path. The modern way to express that is a condition on `aws:ResourceOrgID` rather than listing bucket ARNs by hand.

code

json · 22 lines
json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowOwnOrgBucketsOnly",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": "*",
      "Condition": {
        "StringEquals": { "aws:ResourceOrgID": "o-exampleorgid" }
      }
    },
    {
      "Sid": "AllowKnownVendorRepositories",
      "Effect": "Allow",
      "Principal": "*",
      "Action": ["s3:GetObject"],
      "Resource": "arn:aws:s3:::example-vendor-repo/*"
    }
  ]
}

go deeper

for a junior

Know that a VPC endpoint can carry its own JSON policy limiting what may be called through it, and that this policy restricts rather than grants access.

for a middle

Explain that the request must be allowed by the identity policy, the resource policy and the endpoint policy together, with any explicit deny winning, and that a fresh endpoint ships with a full-access default policy.

for a senior

Show you have used it as a data-perimeter control: scoping an S3 endpoint with aws:ResourceOrgID to stop exfiltration to foreign buckets, and knowing it only binds traffic that actually uses the endpoint, so NAT egress must be closed too.

for a principal

Own the perimeter as a standard rather than a one-off policy. Decide how endpoint policies, resource-side aws:SourceVpce conditions and egress restrictions combine, how exceptions such as vendor repositories are approved, and how drift is detected across many accounts.

## Where it sits in the evaluation AWS authorises a request by collecting every policy that applies and requiring that at least one allow exists in each *relevant* category, with any explicit deny overriding everything. When a request travels through a VPC endpoint, the endpoint policy joins that set. So for an S3 `GetObject` arriving over an S3 gateway endpoint, three things all have to permit it: the calling principal's identity policy, the bucket policy if the bucket has one, and the endpoint policy. A deny in any of them ends the matter. The consequence people trip over is the direction of power. **An endpoint policy can only narrow.** Writing `"Resource": "arn:aws:s3:::finance-data/*"` with an `Allow` in an endpoint policy does not grant anything to anyone; it merely declares that requests for other resources will not be permitted to pass through this endpoint. If the instance role has no S3 permission, the call still fails. The second consequence is the default. A newly created endpoint carries a full-access policy: ```json { "Statement": [ { "Effect": "Allow", "Principal": "*", "Action": "*", "Resource": "*" } ] } ``` That is deliberately permissive so the endpoint does not break anything on creation, and it means an endpoint you never edited is providing zero restriction. ## What it is actually for The headline use case is the **data perimeter** — specifically, egress of data to resources you do not own. Consider a compromised or simply misconfigured workload in a private subnet with a legitimate S3 endpoint. Its role permits `s3:PutObject`. Nothing about the network stops it from writing to *someone else's* bucket, because S3 is one global service reached through the same endpoint. Scoping the endpoint policy so only your organisation's buckets are reachable closes that path. Historically this meant listing bucket ARNs, which does not scale. The organisation-aware condition keys make it tractable: ```json { "Statement": [{ "Effect": "Allow", "Principal": "*", "Action": "s3:*", "Resource": "*", "Condition": { "StringEquals": { "aws:ResourceOrgID": "o-exampleorgid" } } }] } ``` The mirror-image control uses `aws:PrincipalOrgID` to require that whoever comes through the endpoint belongs to your organisation, which matters more for endpoints that can be reached from beyond a single VPC. A second, narrower use is blast-radius reduction inside an account: an endpoint dedicated to one workload's subnets can be scoped to just that workload's buckets or secrets, so a credential leak in that tier cannot reach the rest. ## Practical limits and gotchas Endpoint policies apply to *requests that go through the endpoint*, and nothing else. A workload that still has an internet route can reach the service publicly and is entirely unaffected by the endpoint policy — the control is only meaningful if the private path is the only path, which is why perimeter designs remove or restrict NAT egress at the same time. Not every service supports endpoint policies on its interface endpoint, and the actions and condition keys that can be used vary by service, so a policy that is syntactically valid may not do what you expect for a less common service. Verify against the specific service rather than assuming parity with S3. Another trap is over-scoping and forgetting the shared dependencies. Locking an S3 endpoint to your own buckets breaks legitimate reads of AWS-owned or vendor-owned buckets — package repositories, Amazon Linux and other OS repository mirrors, and some service artefacts all live in buckets outside your organisation. Teams discover this when instances stop being able to install packages. The fix is a deliberate allow-list of those known third-party sources alongside the organisation condition. Finally, keep it distinct from neighbouring controls. The endpoint's **security group** decides which hosts may open a connection to an interface endpoint; the **endpoint policy** decides which API calls may pass. And a resource policy on the target — a bucket policy conditioned on `aws:SourceVpce` — is the complementary control that says "this data may only be reached through that endpoint". Endpoint policy restricts what leaves through the pipe; the resource-side condition restricts which pipe may reach the data. Mature designs use both, because each covers the direction the other cannot.

  • You scope an S3 endpoint policy to your organisation and instances can no longer install OS packages. Why?
    Package repository mirrors for the distribution live in AWS-owned or vendor-owned S3 buckets that are outside your organisation, so the aws:ResourceOrgID condition excludes them and those reads are blocked at the endpoint. You add a deliberate allow for the specific known repository bucket ARNs alongside the organisation condition.
  • How does an endpoint policy differ from a bucket policy conditioned on aws:SourceVpce?
    They face opposite directions. The endpoint policy restricts what may leave your network through that pipe — which buckets your workloads can reach. The bucket policy condition restricts which pipe may reach the data — that the bucket accepts requests only via that endpoint. Neither implies the other, so a full data perimeter uses both.
  • Does an endpoint policy protect a workload that also has a NAT route to the internet?
    No. The policy only evaluates requests that actually traverse the endpoint. A workload with internet egress can call the service's public endpoint and bypass it entirely. That is why endpoint-based perimeters are paired with removing or tightly restricting NAT egress, otherwise the control is advisory at best.

saying these in an interview costs you the question

  • Thinks an endpoint policy grants permissions to callers
  • Assumes a new endpoint is restrictive by default
  • Confuses it with the endpoint's security group
  • Believes it blocks traffic that still has internet egress
  • Lists bucket ARNs by hand instead of an org condition

context