Skip to content
ProfessionalProNetwork FirewallTransit Gateway

Lab 19: The Firewall Rule That Only Guarded Itself

A central Network Firewall with a rule that drops plaintext HTTP. Security tested it and signed it off. From every spoke VPC the firewall exists to protect, HTTP goes straight through — and the firewall logged every one of those flows as allowed.

Debugging time
~45 min
Reading time
16 min
Reported by
Cloud Security
Tier
Professional
INC-1772SEV-2InvestigatingOpened 2026-09-29 09:15 UTC

Plaintext HTTP from spoke VPCs still reaches the internet after the egress block was signed off

Reported by Cloud Security

We rolled out the plaintext-HTTP egress block on the central firewall last week. The rule is a standard Suricata drop: $HOME_NET to $EXTERNAL_NET on port 80. I tested it myself from our test host before sign-off — curl http://… times out, HTTPS works, exactly as designed. The change ticket is closed.

This morning a platform engineer showed me a spoke workload fetching a package index over plain HTTP. It works first time, every time. I checked the firewall: state READY, policy attached, rule group referenced, my rule is in it, logging is on. Nothing has changed since I tested it.

I re-ran my test from the test host. Still blocked. So the firewall is enforcing the rule for me and not for them, and I cannot see what is different about their traffic.

What you are working with

One Transit Gateway, two VPCs, one Availability Zone, one firewall endpoint.

Starting state — a spoke behind a central inspection VPC
VPC spoke 10.190.0.0/16 AZ a contains workload 10.190.1.0/24, default route 0.0.0.0/0 → tgw, holding Workload. VPC inspection 10.192.0.0/16 Attached: Internet gateway. AZ a contains tgw attachment 10.192.0.0/24, default route 0.0.0.0/0 → firewall, holding no resources; firewall 10.192.1.0/24, default route 0.0.0.0/0 → natgw, holding Firewall endpoint; nat 10.192.2.0/24, default route 0.0.0.0/0 → igw, holding NAT Gateway; control 10.192.3.0/24, default route 0.0.0.0/0 → firewall, holding Control host.the internettransitgatewayspoke default →inspectionVPC spoke 10.190.0.0/16AZ aworkloadprivate10.190.1.0/240.0.0.0/0 → tgwWorkload10.190.1.50VPC inspection 10.192.0.0/16AZ atgw attachmentprivate10.192.0.0/240.0.0.0/0 → firewallno resourcesfirewallprivate10.192.1.0/240.0.0.0/0 → natgwFirewall endpointdrop tcp $HOME_NET →$EXTERNAL_NET 80natpublic10.192.2.0/240.0.0.0/0 → igwNAT Gatewaycontrolprivate10.192.3.0/240.0.0.0/0 → firewallControl host10.192.3.50 — the test hostInternet gateway

The spoke has no internet gateway of its own. Its only way out is the transit gateway, which delivers everything to the inspection VPC, where the attachment subnet hands it to the firewall endpoint and the firewall hands it to the NAT Gateway. The control host in the inspection VPC takes the same path from one hop later. Both hosts' HTTP crosses the same firewall endpoint and meets the same rule.

ResourceConfiguration
WorkloadSpoke VPC, 10.190.1.50, private subnet, default route → transit gateway
Control hostInspection VPC, 10.192.3.50, private subnet, default route → firewall endpoint
Transit gatewaySpoke route table: 0.0.0.0/0 → inspection attachment. Inspection table: spoke prefix propagated
FirewallREADY, one endpoint, one policy
Firewall policyStrict order, one stateful rule group, default action alert_established
Rule groupdrop tcp $HOME_NET any -> $EXTERNAL_NET 80 (flow:to_server; …)
LoggingAlert and flow logs to CloudWatch Logs
NAT GatewayInspection VPC public subnet; returns to the spoke and to the control subnet go back via the firewall

Both hosts reach the internet only through the firewall endpoint. The rule in the firewall is the one the ticket describes, and it is the only rule.

curl http://checkip.amazonaws.com from the spoke workload
  1. EC2

    Workload

    10.190.1.50 → :80

  2. GW

    Transit gateway

    spokes route table: 0.0.0.0/0 → inspection attachment

  3. SUBNET

    Attachment subnet

    0.0.0.0/0 → firewall endpoint

  4. FILTER

    Network Firewall

    stateful engine: one drop rule evaluated, no match, default action applied

  5. GW

    NAT Gateway → internet gateway

    source rewritten to the Elastic IP

  6. DEST

    checkip.amazonaws.com

    answers with the NAT address

Six hops and six passes. The fourth is the one that was supposed to fail. The firewall evaluated the rule, decided it did not apply, passed the flow under its default action, and wrote a log line saying so.

Scope and constraints

  • In scope: why the firewall drops HTTP from the control host and passes it from the spoke, when both flows cross the same endpoint and meet the same rule.
  • Out of scope: the transit gateway routing, the VPC route tables, the NAT Gateway, and the firewall's health. All correct, and the reproduction confirms each of them.
  • The reporter is right about everything they checked. The firewall is READY, the policy is attached, the rule is present and correctly written, and it genuinely blocks the test host. Confirm these early so you stop re-checking them.
  • Nothing is broken. Both hosts reach the internet. One of them is permitted to do something it should not be, and nothing alarms, because the firewall considers the outcome correct.
  • The fix adds a small amount of configuration to the policy and changes no rule.

Deploy the broken state

cd lab-19-network-firewall-home-net
terraform init
terraform apply

The firewall takes several minutes to reach READY, and the four route tables that target its endpoint cannot be created until it does, so budget ten to twelve minutes before anything is testable. Session Manager reaches both hosts through the firewall on 443, which the policy passes.