Architecture

Three-account hub-and-spoke topology from jajera/tgw-policy-based-routing: Hub VPC + Transit Gateway, Spoke A client with a policy table, Spoke B listener on :80 / :443.

On this page


Lab topology

Region: ap-southeast-2. The Transit Gateway lives in the network account and is shared with sandbox and shared-services via AWS RAM.

VPC Account / profile CIDR Role
Hub VPC network 10.0.0.0/24 Only IGW/NAT; TGW; optional Hub forwarder :80
Spoke A VPC sandbox 10.0.1.0/24 Client instance; policy table on TGW attachment
Spoke B VPC shared-services 10.0.2.0/24 Listener :80 / :443; Direct route table association

Spokes have no Internet Gateway and no NAT — internet egress is Hub-only via TGW.

Lab topology: three-account hub-and-spoke with PBR Spoke A policy table selects Steer (TCP/443 via Hub) or Direct (:80 / ICMP to Spoke B). Hub owns IGW/NAT. Spoke A sandbox 10.0.1.0/24 Spoke B shared-svc 10.0.2.0/24 Hub network 10.0.0.0/24 IGW + NAT Transit Gateway Policy table Spoke A only Steer RT via Hub Direct RT catch-all Hub RT Rule 100 → Steer (TCP/443) · Rule 200 → Direct (*) TCP/443 assoc. :80 / ICMP

Diagram description: Spoke A (top left) enters a Transit Gateway policy table. Rule 100 (TCP/443) selects Steer → Hub (right). Rule 200 (catch-all) selects Direct → Spoke B (bottom left). Hub RT associates horizontally with the Hub attachment. Cluster title sits top-left; edge chips label flows so text does not sit on connectors.

What the lab proves

Goal How
Same destination IP, different paths Curl Spoke B on 443 vs 80
443 steered via Hub Hub VPC flow logs / TGW ENI show spoke-to-spoke :443
80 skips Hub No spoke-to-spoke :80 on the Hub TGW path
Hub-only internet Spokes reach 8.8.8.8 / example.com only via Hub NAT
Bidirectional reachability Ping/curl among Spoke A, Spoke B, and Hub forwarder

Destination-based vs attribute-based forwarding

A classic transit gateway route table forwards by destination prefix alone. Without PBR, Spoke A’s attachment uses one route table — TCP/80 and TCP/443 to the same Spoke B address take the same path.

A policy table sits in front of route tables for the associated attachment: rules match packet attributes, the first match selects a target route table, then that table does normal destination-CIDR lookup.

Destination-based vs attribute-based forwarding Classic route tables forward by destination IP only so equal destinations share a path. A policy table matches L3/L4 attributes first, selects a route table, then destination lookup runs — so :443 and :80 can diverge. Destination-based (route table) Packet in on attachment Route table dest IP only CIDR lookup Attachment / next hop :80 and :443 to same IP → same path Attribute-based (policy table) Packet in on attachment Policy table L3/L4 match Steer RT TCP/443 Direct RT :80 / ICMP Spoke B via Hub Spoke B direct First match selects a route table; then dest-CIDR lookup

Diagram description: Top lane — destination-only forwarding: packet → route table → next hop; :80 and :443 to the same IP share one path. Bottom lane — PBR: packet → policy table (L3/L4) forks to Steer (TCP/443 via Hub) or Direct (:80 / ICMP), then each route table looks up the next hop.

Aspect Route Table Policy Table
Match criteria Destination IP prefix Source/dest CIDR, protocol, ports
Action Forward to next-hop attachment Select a target route table
Association One per attachment One per attachment (mutually exclusive with route table)
This lab Spoke B → Direct; Hub → Hub RT Spoke A → policy table

An attachment associates with either a policy table or a route table — never both. Associating a policy table requires first disassociating any existing route table.

Policy rules (this lab)

Rule Match Target route table
100 Protocol TCP (6), destination port 443 Steer (via Hub hairpin)
200 Catch-all * Direct

Without catch-all rule 200, unmatched traffic — including :80 and ICMP — is dropped (implicit deny). The catch-all is required for a usable lab.

Path map (Spoke A → …)

Traffic Policy rule Route table Path
Spoke B :443 100 Steer Spoke A → Hub → Spoke B
Spoke B :80 / ICMP 200 Direct Spoke A → Spoke B
Hub forwarder :80 200 Direct Spoke A → Hub
Internet (non-443) 200 Direct Spoke A → Hub → NAT
Internet HTTPS 100 Steer Spoke A → Hub → NAT

Static routes (conceptual)

Route table Destination Next hop
Direct 10.0.2.0/24 Spoke B attachment
Direct 10.0.1.0/24 Spoke A attachment
Direct 0.0.0.0/0 Hub attachment
Steer 10.0.2.0/24 Hub attachment (hairpin)
Steer 10.0.1.0/24 Spoke A attachment
Steer 0.0.0.0/0 Hub attachment
Hub 10.0.1.0/24 / 10.0.2.0/24 Spoke attachments

NAT return path: the Hub public subnet route table must route spoke CIDRs (10.0.1.0/24, 10.0.2.0/24) → TGW. Without those routes, outbound SYN can leave via NAT but replies blackhole — SSM and internet curls time out. The lab adds those routes in tgw.tf.

Live IDs and IPs change every apply — refresh with terraform output in the lab repository. See Deploy and prove.

For AWS behaviour detail, see Transit gateway policy tables.

Next: Policy Tables →


Back to top

Walkthrough for jajera/tgw-policy-based-routing. AWS feature availability, limits, and behaviour can change — verify against current AWS documentation before relying on any claim here.

TGW PBR lab walkthrough