Skip to content
Networking7 min read

The CloudFront prefix list proves a request came from CloudFront, not from your CloudFront

A security group rule on the managed prefix list admits every CloudFront origin-facing server on earth, including the ones serving someone else's distribution pointed at your origin. AWS's own guidance puts a secret header first and the prefix list as an optional extra, the list costs 55 of your 60 rules, and VPC origins replace both with a service-managed security group that is scoped to your distributions and nothing else.

· Venerable Networks

The pattern is in every CloudFront hardening checklist: lock the origin's security group down to the CloudFront managed prefix list, so only CloudFront can reach it. The CloudFront documentation describes the control precisely, and the precision is the point:

The prefix list allows inbound traffic to your origin only from CloudFront origin-facing servers, which prevents non-CloudFront traffic from reaching your origin.

Non-CloudFront traffic. Not "traffic that is not from your distribution". A CloudFront distribution in any account, created by anyone, pointed at your origin's public hostname, sends requests from the same origin-facing servers. The prefix list admits them. What the rule proves is that the request passed through CloudFront's edge. It does not prove whose distribution it passed through.

Why AWS leads with a header

The guide to restricting access to Application Load Balancers does not start with the prefix list. Its three mitigations are:

Configure CloudFront to add a custom HTTP header to requests that it sends to the Application Load Balancer. Configure the Application Load Balancer to only forward requests that contain the custom HTTP header. Require HTTPS to improve the security of this solution.

The prefix list appears at the end, under a heading that begins with "(Optional)":

To further restrict access to your Application Load Balancer, you can configure the security group associated with the Application Load Balancer so that it only accept traffic from CloudFront when the service is using an AWS-managed prefix list. This prevents traffic that doesn't originate from CloudFront from reaching your Application Load Balancer at the network layer (layer 3) or transport layer (layer 4).

The two controls answer different questions. The header answers "is this my distribution": only your distribution has been configured to add it, and the load balancer's listener rule returns 403 to anything without it. The prefix list answers "is this CloudFront at all": it stops a scanner on the internet from connecting to the origin directly, before any HTTP is spoken. A distribution someone else created passes the second check and fails the first.

The header's weakness is also stated, and it is why the two are paired rather than either alone:

This use case relies on keeping the custom header name and value secret. If the header name and value are not secret, other HTTP clients could potentially include them in requests that they send directly to the Application Load Balancer.

A leaked header lets anyone on the internet impersonate your distribution at layer 7. The prefix list narrows "anyone on the internet" to "anyone with a CloudFront distribution", which is still anyone, but now they need the header too. The documentation's remedy for the header is operational: use HTTPS to the origin so the header is not visible in transit, and rotate it, with a four-step procedure for doing so without downtime.

The list costs 55 rules

The prefix list has a second property that the checklist rarely mentions. The CloudFront page flags it:

The CloudFront managed prefix list is unique in how it applies to Amazon VPC quotas.

And the AWS-managed prefix list documentation gives the number:

For example, the weight of a Amazon CloudFront managed prefix list is 55. Here's how the this affects your Amazon VPC quotas: Security groups – The default quota is 60 rules, leaving room for only 5 additional rules in a security group. … Route tables – The default quota is 50 routes, so you must request a quota increase before you can add the prefix list to a route table.

One rule referencing the list consumes 55 of the default 60. The group that had the prefix list added for hardening now accepts four more rules of any kind, and then refuses the fifth with an error that names neither the list nor the weight. Lab 22 is that incident, start to finish. The IPv6 list is a second 55, so a dual-stack origin that references both has exceeded the default quota before any other rule exists.

Next to the other AWS-managed lists, 55 is an outlier. S3 and DynamoDB weigh 1. Route 53 health checks weigh 25. VPC Lattice weighs 10. The CloudFront list is the only one that, on its own, nearly fills a security group, and the only one the documentation says cannot go into a route table without a quota increase.

VPC origins replace both controls with one

The prefix list and the header are workarounds for an origin that has a public address. CloudFront VPC origins remove the public address:

You can move your origins to private subnets with no public access, and you don't have to implement access control lists (ACLs) or other mechanisms to restrict access to your origins.

CloudFront reaches the origin through a service-managed network interface in your subnet, and the page offers two ways to let that traffic in. The first is the prefix list again. The second is different in kind:

Option 2: Allow traffic from the CloudFront service-managed security group (CloudFront-VPCOrigins-Service-SG). This can be done only after the VPC origin is created and the service-managed security group is created. This configuration is further restrictive as it restricts the traffic only to your CloudFront distributions.

"Only to your CloudFront distributions." That is the property the prefix list never had. The service-managed security group is attached to the interface CloudFront created for your VPC origin, so referencing it admits traffic from that interface and no other. Another account's distribution cannot reach a private-subnet origin at all, and it cannot borrow your service-managed group. A security group reference also counts as one rule regardless of size, so the 55-weight problem is gone with it.

The page carries its own constraints, and three of them are the kind that show up as a stalled rollout:

Internet gateway – You need to add an internet gateway to the VPC that has your VPC origin resources. The internet gateway is required to denote that the VPC can receive traffic from the internet. The internet gateway is not used for routing traffic to origins inside the subnet, and you don't need to update the routing policies.

An internet gateway that routes nothing, as a declaration. A VPC built without one, because the whole point was no public access, cannot host a VPC origin until one is attached, and attaching it changes no route.

Network Access Control Lists (NACLs) – Inbound NACL rules are not evaluated for traffic from CloudFront to your VPC origin. However, outbound NACL rules are evaluated on the return path and must allow traffic on ephemeral TCP ports (1024–65535) to 0.0.0.0/0 or to the CloudFront origin-facing IP ranges.

Asymmetric. The inbound network ACL is bypassed, so a restrictive inbound ACL does not block CloudFront. The outbound ACL is enforced, so a restrictive outbound ACL blocks the response. An origin that returns nothing to CloudFront while its security group is correct and its inbound ACL is irrelevant is an outbound ACL problem.

Wait for your VPC origin status to change to Deployed. This can take up to 15 minutes.

And the resource restrictions: no Gateway Load Balancers, no Network Load Balancers with TLS listeners, and a Network Load Balancer used as a VPC origin must have a security group attached, which NLBs created before security group support existed will not have.

Three controls, three claims

ControlWhat it proves about a requestCost
Managed prefix list in the security groupIt came from a CloudFront origin-facing server. Anyone's.55 rules of 60 per list
Secret origin header, checked by a listener ruleIt came from a distribution configured with the secret. Yours, until the secret leaks.One listener rule; rotation
VPC origin with the service-managed security groupIt came through the interface CloudFront created for your VPC origin. Yours.One rule; private subnet; internet gateway as declaration

The first two together are the documented answer for an internet-facing origin. The third is the documented answer for an origin that does not need to be internet-facing, which, if CloudFront is in front of it, is most of them.

What to do with it

Read "only from CloudFront" as "only from CloudFront's network". The prefix list is a layer-4 control over which addresses may connect. It has no notion of distribution. Anyone who can create a CloudFront distribution is inside the allowed set.

Pair the prefix list with the header, or do not rely on it for authorisation. AWS's own guide treats the header as the primary control and the list as the optional layer under it. The order matters.

Budget 55 rules before you add the list. On the default quota it leaves five. On a dual-stack origin it leaves none. Request the quota increase first, or expect Lab 22 in production.

Move origins to VPC origins where the Region supports it. The service-managed security group is the only one of the three controls that is scoped to your distributions by construction, it counts as one rule, and it removes the public address the other two were compensating for.

For VPC origins, check the outbound network ACL and the internet gateway. Inbound ACL rules are not evaluated; outbound rules are. The internet gateway must exist and does nothing.

This is the same distinction as the security group reference that stays correct when the web tier scales against the CIDR rule that does not: a reference to the thing itself versus a description of where the thing usually comes from. The prefix list is a description of CloudFront. The service-managed security group is a reference to yours.