Newer
Older
openstack-caracal-dc-dc / docs / changelog-20260716-review-sweep-phaseC2-D125.md

2026-07-16 -- D-125: Model B per-DC ISP egress (bridge-in, single-NAT) -- resolves OBS-3's design gap; egress efficacy is a deploy-time gate (present-only)

Context

Post-commit 5175365 (Stage-3 sweep C+D), the adversarial re-run + advisor confirmed OBS-3 as material once the operator ruled "DC0 and DC1 have their own ISP connections." Model B nesting had severed the per-DC simulated-ISP egress: modules/site-wan makes vr1-dc0-wan a libvirt NAT that egresses via ITS HOST's default route; under Model B that host is vvr1-dc0 (transit-only, East-West, SEC-010 FORWARD-dropped), so the inner NAT had nowhere to go. Operator ruled bridge-in (single-NAT) over the double-NAT alternative. Governing record: D-125 (cross-ref D-122/D-123). NO cloud mutation; authored present-only; both roots validate. NOT applied, NOT committed, NOT pushed.

What changed

New module opentofu/modules/wan-bridge/ (versions/variables/main.tf/outputs.tf). A libvirt forward = { mode = "bridge" } network bound to a pre-existing host bridge (bridge = { name = ... }), no subnet/NAT. Sibling to site-wan (chosen over a site-wan mode flag so every existing NAT user -- office1-wan, the new vr1-dc0-uplink -- stays literally untouched; noted vs. the option preview's "mode param" wording). Schema (bridge is an attribute-OBJECT, not a string) measured from the installed provider, not inferred. Covered by opentofu-validate S3 (standalone [PASS]).

OUTER root (opentofu/main.tf + variables.tf, vcloud).

  • New module "vr1_dc0_uplink" (site-wan NAT) = the SINGLE ISP NAT at vcloud level (egresses via vcloud's uplink). CIDR = 172.30.2.0/24 inlined -- the SAME /24 D-115 already assigned vr1-dc0's edge WAN (bridge-in is single-NAT; RULED, not HELD). [CORRECTED 2026-07-16: an earlier draft added a HELD `var.vr1_dc0_uplink_cidr` + a NetBox assignment; both removed -- D-125 adds no new NetBox object.]
  • vvr1_dc0 gains a 2nd NIC (order [transit, uplink] -- transit stays first so SEC-010/--transit-if keep keying on it). Netplan now declares the IP-less uplink port + the br-vr1-dc0-wan bridge (STP off), so the bridge exists before the inner apply. tofu validate Success.

INNER root (opentofu/vr1-dc0-substrate/main.tf). module "vr1_dc0_wan" switched site-wan (NAT, 172.30.2.0/24) -> wan-bridge bound to br-vr1-dc0-wan. OPNsense wan_network_name wiring unchanged (same output). The /24 and the OPNsense WAN .2 are UNCHANGED (same 172.30.2.0/24, D-115); only the libvirt realization moved to the vcloud NAT. tofu validate Success.

Bootstrap (scripts/site-headend-install.sh node-host mode).

  • New --uplink-if (default uplink) + --wan-bridge (default br-vr1-dc0-wan).
  • node_host_check: WAN-egress guard -- verifies the bridge exists AND the uplink is ENSLAVED (master <bridge>); same fail-open class as the SEC-010 transit-iface check. Prints the DEPLOY-TIME isolation gate (throwaway guest on the bridge must get a vcloud-ISP addr + ping out; FAIL => double-NAT fallback).
  • node_host_setup: SEC-010 rationale corrected (bridge-in has no inner NAT, so the old ip_forward forcing no longer applies); br_netfilter CONSTRAINT -- keep the FORWARD-drop interface-scoped, never global. Verifies the WAN bridge landed (does NOT create it; netplan owns L2). Harness 49 -> 54.

Records. D-125 (new); security-ledger SEC-010 (bridge-in + br_netfilter constraint + hardened --check); model-a-fallback-plan B->A delta item 8 + revert step + OPNsense WAN restore; session-ledger (commit 5175365 + OBS-3 RESOLVED-by-D-125 + new HELD gates); references/platform-traps.md 1g (bridge

  • br_netfilter + nested-foreign-MAC trap).

Verification

  • tofu validate OUTER = Success; INNER = Success; opentofu-validate S3 [PASS] modules/wan-bridge (+ site-wan still [PASS], untouched).
  • tests/site-headend-install = 54/54.
  • repo-lint = 0 fail (1 documented legacy WARN). All edited files ASCII/LF.

Deploy-time gates opened (NOT closed by this change)

  1. L2-NAT of foreign MAC across the nested bridge -- unprovable pre-apply (bridge-in's L4-boot-gate equivalent). Isolation-test first; FAIL => revert to double-NAT (D-125).
  2. Confirm the uplink/br-vr1-dc0-wan interface names on first boot (enp2s0-vs-ensX naming trap).

(No new HELD addressing: the /24 is the ruled 172.30.2.0/24 and the OPNsense WAN stays .2; D-125 adds no NetBox object. The only NetBox work for the DC substrate is D-124's transit /30 + rack IP.)

Revert (per item)

  • git rm -r opentofu/modules/wan-bridge; revert opentofu/main.tf (drop vr1_dc0_uplink + the 2nd NIC/netplan bridge); revert opentofu/vr1-dc0-substrate/main.tf vr1_dc0_wan back to site-wan NAT 172.30.2.0/24; revert the site-headend-install.sh --uplink-if/--wan-bridge block + harness; revert the doc records. (No tfvar to drop -- the /24 is an inlined ruled literal.) This reverts D-125's bridge-in to the (dead-egress) committed Model B state -- the full B->A revert is docs/model-a-fallback-plan.md.