Homeβ€Ί Blogβ€Ί AWS Architecture Series #45 β€” The allowlist that asks the client where it is going…
AWS Architecture AWS Architecture Series

AWS Architecture Series #45 β€” The allowlist that asks the client where it is going

A platform team fronts all outbound traffic with AWS Network Firewall in a central inspection VPC and allowlists the domains the workloads are permitted to reach. The rule group is correct, the policy is attached, and the control passes review. It is inspecting nothing, because domain filtering only looks at traffic from HOME_NET and HOME_NET defaults to the inspection VPC's own CIDR.

Verified against current vendor documentation on 7 September 2026. Pricing, limits and API behaviour were checked against the official docs on that date. Cloud services change fast — if you are reading this much later, treat the specifics as a starting point and re-check the linked sources.

Business Challenge

The previous post in this series ended on a control that faces outward: an endpoint policy limits which resources can be reached from a network. This one is about the other direction, and about a control that is much easier to deploy than it is to deploy correctly.

A platform team runs the hub-and-spoke topology: workload VPCs with no internet gateway, a central networking account, and all internet-bound traffic routed across a transit gateway into an inspection VPC where AWS Network Firewall sits in front of the NAT gateways. Security asks for egress control — not “can it reach the internet” but “what can it reach.” The team builds a domain list rule group with an Allow action and about forty entries: the package registries, the vendor APIs, the telemetry endpoints. Everything else is denied by construction, because for an allow list Network Firewall denies traffic of that protocol that does not match.

The rule group is well written. The policy is attached. The change passes review and the control is recorded as implemented. Three things are wrong with it, and only one of them is a configuration error.

1It is inspecting nothing

Domain list inspection is scoped by the HOME_NET variable, and the default value of HOME_NET is the CIDR range of the VPC where Network Firewall is deployed. Only traffic from that range is passed through domain list filtering. In a central inspection VPC the traffic does not originate there — it originates in the spokes and arrives over the transit gateway. AWS names this exact topology as the most common case requiring a manual setting.

Nothing fails. No rule errors, no alarm, no metric that reads zero in an obvious way. The firewall is passing traffic it was never asked to look at.

Fix

Set HOME_NET on the rule group to every spoke CIDR you intend to inspect, plus the inspection VPC's own range.

2The allowlist trusts the client

For HTTPS, Network Firewall reads the Server Name Indication field from the TLS handshake. For HTTP, it reads the Host header. Both are values the client writes. AWS is explicit that the service “doesn't pause connections to do out-of-band DNS lookups” and that it uses “the SNI or host header, not the IP addresses” when evaluating domain rules.

So a process that opens a socket to an arbitrary address and writes registry.npmjs.org into the SNI is allowed through, and one that reaches an approved domain while presenting no name at all is evaluated on the name it did not give. The allowlist constrains a declaration, not a destination.

Fix

AWS's own remedy: write separate IP address rules and use them in conjunction with the domain rules. Two assertions are harder to align than one.

3The field it depends on is optional and disappearing

SNI is readable because it is sent in the clear. TLS 1.3's Encrypted Client Hello removes that, and Network Firewall does not support it: when it cannot find an SNI in the client hello it closes the connection with an RST.

That is a safe failure, and it is worth being clear-eyed about what it means. The control does not degrade to “allow”; it degrades to “break.” Every client that adopts ECH stops working rather than stops being filtered, and the first symptom is a connectivity incident rather than a security one.

Fix

Know which of your egress destinations are moving to ECH before they do, and decide deliberately whether those flows are scoped out or blocked.

The pattern across all three

Each one is the same shape as the VPC endpoint mistake in #44: a control that is real, correctly configured in its own terms, and answering a narrower question than the review believed it was answering. “We allowlist egress by domain” is true. “Our workloads can only reach approved destinations” does not follow from it.

Architecture

Egress control is three separable decisions: where the inspection sits, what it matches on, and whether you are willing to decrypt. They are usually taken as one, which is why the result is usually a domain allowlist with an unexamined trust model.

Diagram: what a domain allowlist actually inspects. On the left, the field Network Firewall reads for each protocol: the TLS SNI for HTTPS and the HTTP Host header for HTTP, both written by the client, with no DNS lookup and no IP check. In the middle, the HOME_NET scope, which defaults to the inspection VPC CIDR so traffic arriving from spoke VPCs over a transit gateway is not inspected until HOME_NET is set manually. On the right, the escalation to TLS inspection and the traffic it drops: no server name in the client hello, Encrypted Client Hello, QUIC, StartTLS, WebSockets over HTTP/2, and non-TLS traffic inside the configured scope.
What a domain allowlist inspects, what scopes it, and what the decrypting alternative refuses to carry. The three columns are the three decisions, and the middle one is the one that silently defaults to inspecting nothing in a hub-and-spoke deployment.

What the rule group actually says

A domain list rule group carries a target list, the protocols to inspect, and a generated rules type. The wildcard form matters: a leading dot matches the domain and every subdomain, so .example.com covers example.com and abc.example.com alike, while an explicit name matches only itself.

rule-group.json
{
  "RuleVariables": {
    "IPSets": {
      "HOME_NET": {
        "Definition": [
          "10.0.0.0/16",
          "10.1.0.0/16",
          "192.0.2.0/24"
        ]
      }
    }
  },
  "RulesSource": {
    "RulesSourceList": {
      "Targets": [
        ".example.com",
        "www.example.org"
      ],
      "TargetTypes": [ "HTTP_HOST", "TLS_SNI" ],
      "GeneratedRulesType": "ALLOWLIST"
    }
  }
}

The HOME_NET block is the part that is absent from most first drafts, and its absence is invisible. The two 10.x ranges are the spoke VPCs whose traffic arrives over the transit gateway; 192.0.2.0/24 is the inspection VPC itself, which must stay in the list because setting the variable replaces the default rather than adding to it.

There is a second-order trap in the same mechanism. EXTERNAL_NET is always the negation of the policy's HOME_NET, and a rule group that sets its own HOME_NET without also setting EXTERNAL_NET inherits the policy's negation regardless. Set one and you can end up with a rule group whose idea of “inside” and “outside” do not describe the same boundary.

Where the inspection sits

1

Spoke VPC, no internet gateway

Default route points at the transit gateway. The workload cannot reach the internet except through the hub, which is what makes a single inspection point possible at all.

2

Inspection VPC, firewall endpoint per AZ

One endpoint in each Availability Zone, at $0.395 per endpoint-hour. Three AZs is roughly $865 a month before a byte is processed, and processing adds $0.065 per GB.

3

Domain rules, scoped by HOME_NET

The stateful engine reads the SNI or Host header of flows sourced from HOME_NET. Everything else passes the firewall without being evaluated against the domain list.

4

NAT gateway, then the internet

A fixed, auditable set of Elastic IPs. The firewall sits before this, so it sees the workload's source address rather than the translated one.

Why This Architecture Holds Up

A weak check in the right place still narrows the blast radius

Nothing above is an argument against domain filtering. An allowlist that can be defeated by a process deliberately writing a false SNI still stops the overwhelming majority of what egress control is for: a dependency that starts calling an analytics endpoint after an update, a container image that phones home, a misconfigured backup job writing to the wrong bucket over the public endpoint. None of those lie about their destination, because none of them are trying to.

The failure mode to avoid is not deploying it. The failure mode to avoid is recording it as a control against a deliberate adversary when it is a control against accident and drift.

The allow action is stricter than it looks

For an allow list, traffic of the specified protocol that does not match any entry is denied. That is the behaviour you want, and it has a sharp edge: AWS recommends against combining Reject or Alert domain rule groups with Allow ones under default action ordering, because the default drop that the allow group adds takes effect before the reject and alert rules do. A rule group added to improve visibility can silently stop being reached.

Pair the domain rules with IP rules, as AWS says to

The documentation does not present IP rules as an optimisation. It presents them as the mitigation for a manipulated SNI or Host header, to be used in conjunction with or in place of the domain rules. If your design has domain rules alone, it is missing the half that AWS names as the answer to the weakness in the other half.

Key Architecture Decisions

Decision Choice Reasoning
Inspection point Central inspection VPC in the networking account One place to reason about, one set of rules to maintain. The cost is that HOME_NET must be maintained alongside the spoke inventory.
HOME_NET Explicit list of spoke CIDRs plus the inspection VPC The default silently limits inspection to the inspection VPC's own range. Setting it replaces the default rather than extending it.
Match basis Domain rules and IP rules together Domain rules read a client-supplied field. IP rules read the destination the packet is actually going to. Neither alone is the control the review thinks it is.
Rule group mix Do not mix Alert or Reject groups with Allow groups Under default action ordering the Allow group's implicit drop wins first, so the other groups stop being evaluated.
Decryption TLS inspection scoped narrowly, not enabled globally It is the only way to see past the handshake, and it drops a specific and non-obvious list of traffic. Scope it to the flows that justify it.

What TLS inspection buys, and what it refuses to carry

If domain filtering is not enough, the escalation is a TLS inspection configuration: Network Firewall terminates the client's connection, decrypts, inspects against the stateful rules, and re-encrypts before forwarding. It needs certificates in AWS Certificate Manager and it works inbound, outbound, or both.

It also drops things, and the list is worth reading before rather than after enabling it:

Anything without a usable server name

If the client hello carries no server name, or the name does not match the SNI in the downstream server certificate, the traffic is dropped. Encrypted Client Hello falls here, closed with an RST.

QUIC and StartTLS

UDP-based transports such as QUIC are not supported, and neither is decryption of protocols that rely on StartTLS. A browser silently preferring QUIC is an application-level decision that removes your inspection.

Non-TLS traffic inside the scope

If port 80 is in the inspection scope and plain HTTP arrives on it, Network Firewall cannot identify it as TLS and drops it. The scope defines what must be TLS, not merely what may be.

Existing flows, on the day you enable it

Adding a TLS inspection configuration to a live firewall drops ongoing traffic that matches the scope. New connections are inspected; the ones already open are not drained.

Decryption narrows which rules still apply

Because the engine sees plaintext, TLS-based keywords no longer match — with the single exception of TLS.SNI, which still matches for in-scope traffic. Rules written against TLS attributes quietly stop firing for exactly the flows you decided were important enough to decrypt, while HTTP-keyword rules start working for the first time.

Closing Thought

Egress filtering has an unusual property among security controls: the version that is easy to deploy and the version that is hard to deploy look identical in an architecture diagram. Both show a firewall in an inspection VPC with an arrow to the internet. One of them is reading a field the client chose, from a source range it was never scoped to, and the other is not.

The three defects in this post are not really three. They are one question asked three times: what does this control actually observe? Not what does it intend to prevent — what byte, in what packet, does it read, and who wrote that byte. For a domain allowlist the answer is that the client wrote it, and AWS says so in the documentation rather than burying it.

That is the useful discipline to take from this one. When a control is recorded as implemented, write down the field it inspects next to it. If the field turns out to be supplied by the thing being controlled, the control is real but it is a control against mistakes, and the review should say so.

Next in this series

Networking & Security — DNS as the other egress path: why Route 53 Resolver DNS Firewall sits at a different layer than Network Firewall, what a query log shows that a flow log cannot, and the exfiltration channel that neither an endpoint policy nor a domain allowlist touches.

Comments

How was your experience?
Your feedback helps improve this site.
PoorExcellent