Skip to content

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.

  • 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.

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 │
└─────────────────────────────────────────────────────────────┘
  • 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 = false for 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/32 bypass routes so you can SSH the workloads directly.

All resources tagged Project=enforza and Scenario=04-gwlb-deployment-key.

  • 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.

Console (CCX): sidebar → Onboard FirewallDeployment KeysGenerate a fleet (multi-use) keynot single-use. Copy it (efz_xxxxxxxxxxxxxxxx); you’ll pass the same key to all three engines.

  1. Engines boot & bootstrap — each boots from the stock Debian 12 AMI; cloud-init fetches the public installer and passes it the fleet key.
  2. Engine + sidecar install — the installer sets up the Enforza engine and its enforza-gwlbtun sidecar, and registers with the Enforza cloud using the fleet key.
  3. 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-gwo0 tunnels, and programs the GENEVE datapath.
  4. 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.

Work in the terraform/ directory and pass your fleet key, key pair and a resource prefix:

Terminal window
cd terraform
terraform init
terraform apply \
-var deployment_key=efz_xxxxxxxxxxxxxxxx \
-var ssh_key_name=my-key \
-var resource_prefix=acme-gwlb

This 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.

The template builds the identical 3-AZ topology. It creates no IAM resources, so no --capabilities flag is needed:

Terminal window
aws cloudformation deploy \
--template-file cloudformation/template.yaml \
--stack-name enforza-gwlb-deployment-key \
--parameter-overrides \
DeploymentKey=efz_xxxxxxxxxxxxxxxx \
SshKeyName=my-key \
ResourcePrefix=acme-gwlb

Prefer clicking? Upload template.yaml via CloudFormation → Create stackUpload 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.

  1. Workload → AZ-local GWLBe. A workload sends internet-bound traffic; its route table sends 0.0.0.0/0 to the AZ-local GWLB endpoint.
  2. → 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).
  3. 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.
  4. 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.
  • 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.

The engines deregister themselves on terminate. Remove any leftover firewall entries from the console afterwards.

Terminal window
# Terraform
cd terraform
terraform destroy -var deployment_key=efz_xxxxxxxxxxxxxxxx -var ssh_key_name=my-key
# CloudFormation
aws cloudformation delete-stack --stack-name enforza-gwlb-deployment-key

Enforza is a trading name of Synvu Limited, a company registered (15761962) in the United Kingdom. Registered office address: 71–75 Shelton Street, Covent Garden, London, WC2H 9JQ, United Kingdom.