Transparent inspection with an AWS Gateway Load Balancer (GWLB)
A horizontally-scaled, highly-available inspection architecture. Workload traffic is steered to a GWLB endpoint that GENEVE-encapsulates each flow to a fleet of Enforza engines — one per AZ. Each engine decapsulates, applies policy, source-NATs to the internet, and returns replies through the tunnel. Deploy with Terraform or CloudFormation.
When to use GWLB
Section titled “When to use GWLB”- Transparent inspection — workloads keep egressing to a logical endpoint, with no per-instance route to a firewall ENI.
- Horizontal scale + HA — N engines in a GWLB target group; GWLB load-balances flows and removes unhealthy engines automatically.
- Multi-AZ affinity — with cross-zone off, each AZ’s traffic is inspected by that AZ’s engine — no cross-AZ data transfer, AZ-isolated blast radius.
If you only need one appliance or simple route-through NAT, prefer the single-gateway scenario instead. GWLB suits scale-out inspection and per-AZ isolation.
Architecture
Section titled “Architecture”Two VPCs. The spoke VPC holds workloads whose default route points at an AZ-local GWLB endpoint; the appliance VPC holds the GWLB and one engine per AZ. Flows are GENEVE-tunnelled to the engine in the same AZ — cross-zone off keeps each AZ’s traffic in-AZ.
Internet (0.0.0.0/0) │ IGW · SNAT Appliance VPC · 10.101.0.0/16 │ ┌───────────────────────────────────┴────────────────────────┐ │ AWS Gateway Load Balancer · GENEVE target group │ │ cross-zone = false │ │ Engine a Engine b Engine c │ │ EIP · gwlbtun EIP · gwlbtun EIP · gwlbtun │ │ efz-gwi0/gwo0 efz-gwi0/gwo0 efz-gwi0/gwo0 │ └───────────────────────────────┬────────────────────────────┘ GENEVE · UDP/6081 Spoke VPC · 10.100.0.0/16 │ ┌───────────────────────────────┴────────────────────────────┐ │ AZ a 10.100.1.0/24 AZ b 10.100.2.0/24 AZ c 10.100.3.0/24│ │ GWLBe a GWLBe b GWLBe c │ │ workload a workload b workload c │ │ workload-rt-a: workload-rt-b: workload-rt-c: │ │ 0.0.0.0/0 → GWLBe a 0.0.0.0/0 → GWLBe b 0.0.0.0/0 → GWLBe c │ └─────────────────────────────────────────────────────────────┘What it deploys
Section titled “What it deploys”- Appliance VPC (
10.101.0.0/16) with an IGW and one subnet per AZ. - One Enforza engine per AZ (3 total) on a Debian 12 AMI, each with an Elastic IP, source/dest check off, registered behind the GWLB.
- An AWS Gateway Load Balancer + GENEVE target group + a VPC endpoint service
(
cross_zone_lb = falsefor AZ affinity). - Spoke VPC (
10.100.0.0/16) with an IGW and, in each AZ, a workload subnet, a GWLB endpoint, and a sample workload whose default route points at its AZ-local GWLBe. - Optional
management_cidrs→/32bypass routes so you can SSH the workloads directly.
All resources tagged Project=enforza and Scenario=04-gwlb-deployment-key.
Prerequisites
Section titled “Prerequisites”- A fleet (multi-use) deployment key — one key registers all three engines.
- An EC2 key pair, and a region with three AZs.
- Terraform ≥ 1.5 (AWS provider
~> 5.0) or the AWS CLI, with credentials configured.
Get a fleet deployment key
Section titled “Get a fleet deployment key”Console (CCX): sidebar → Onboard Firewall → Deployment Keys → Generate
a fleet (multi-use) key — not single-use. Copy it (efz_xxxxxxxxxxxxxxxx); you’ll
pass the same key to all three engines.
The first-boot bootstrap
Section titled “The first-boot bootstrap”- Engines boot & bootstrap — each boots from the stock Debian 12 AMI;
cloud-initfetches the public installer and passes it the fleet key. - Engine + sidecar install — the installer sets up the Enforza engine and its
enforza-gwlbtunsidecar, and registers with the Enforza cloud using the fleet key. - Enable the GWLB connector — on each engine. The engines appear in the console.
On each firewall, enable the AWS GWLB connector. That starts the sidecar, brings up
the
efz-gwi0/efz-gwo0tunnels, and programs the GENEVE datapath. - Health checks go green — GWLB health checks (TCP, default port 9092) pass and GWLB starts forwarding flows to the engines.
The installer targets prod (https://dl.neon.efz.io/install.sh). The GWLB datapath
requires Debian 12 (bookworm) or Ubuntu 22.04+ — base_ami_id defaults to
official Debian 12 in eu-west-2. In other regions, pass your own base_ami_id.
Option A — Deploy with Terraform
Section titled “Option A — Deploy with Terraform”Work in the terraform/ directory and pass your fleet key, key pair and a resource
prefix:
cd terraformterraform initterraform apply \ -var deployment_key=efz_xxxxxxxxxxxxxxxx \ -var ssh_key_name=my-key \ -var resource_prefix=acme-gwlbThis builds the full 3-AZ topology — both VPCs, the GWLB and endpoint service, three
engines and three workloads. Optional overrides: -var base_ami_id=ami-xxxx (other
regions / pre-baked AMI), -var instance_type=c6i.xlarge,
-var management_cidrs='["203.0.113.4/32"]' (SSH bypass to the sample workloads), and
the CIDR vars (appliance_vpc_cidr, spoke_vpc_cidr).
Then enable the connector on each engine in the console (bootstrap step 3). Nothing inspects until all three are on.
Option B — Deploy with CloudFormation
Section titled “Option B — Deploy with CloudFormation”The template builds the identical 3-AZ topology. It creates no IAM resources, so
no --capabilities flag is needed:
aws cloudformation deploy \ --template-file cloudformation/template.yaml \ --stack-name enforza-gwlb-deployment-key \ --parameter-overrides \ DeploymentKey=efz_xxxxxxxxxxxxxxxx \ SshKeyName=my-key \ ResourcePrefix=acme-gwlbPrefer clicking? Upload template.yaml via CloudFormation → Create stack →
Upload a template file, then fill the same parameters in the form. Optional keys
mirror the Terraform vars: BaseAmiId, InstanceType, ApplianceVpcCidr,
SpokeVpcCidr, and ManagementCidrs=203.0.113.4/32 (SSH bypass; the first entry is
wired as a bypass route in each workload route table).
As with Terraform — enable the GWLB connector on each engine after the stack completes.
How traffic flows, and AZ affinity
Section titled “How traffic flows, and AZ affinity”- Workload → AZ-local GWLBe. A workload sends internet-bound traffic; its route
table sends
0.0.0.0/0to the AZ-local GWLB endpoint. - → same-AZ engine. The GWLBe GENEVE-encapsulates the flow (UDP/6081) to the GWLB, which forwards it to the engine in the same AZ (cross-zone off → AZ-sticky).
- Decap → policy → SNAT. The sidecar decapsulates onto
efz-gwi0; the engine evaluates the inner packet through its policy, then SNATs it to the engine’s Elastic IP and egresses via the appliance IGW. - Return, re-encapsulated. Return traffic hits the engine’s EIP, is un-NAT’d, and
a flow-tag route diverts it to
efz-gwo0, where the sidecar re-encapsulates the original GENEVE back via GWLB → GWLBe → the originating workload.
Hardening & source/dest check
Section titled “Hardening & source/dest check”- Host-firewall hardening. When the GWLB connector is enabled, the engine scopes
its inbound GENEVE (UDP/6081) and health-check (TCP/9092) accepts to the VPC CIDR
(auto-derived), not
0.0.0.0/0— so neither port is internet-reachable. - Source/destination check is off. GWLB delivers the real workload packets
(decapsulated from GENEVE) to the engine, whose source/destination is another host.
EC2’s source/destination check would drop those; disabling it
(
source_dest_check = false) is what lets the engine forward and NAT transit traffic. Mandatory for any inline inspection appliance.
Teardown
Section titled “Teardown”The engines deregister themselves on terminate. Remove any leftover firewall entries from the console afterwards.
# Terraformcd terraformterraform destroy -var deployment_key=efz_xxxxxxxxxxxxxxxx -var ssh_key_name=my-key
# CloudFormationaws cloudformation delete-stack --stack-name enforza-gwlb-deployment-key