Add site-to-site VPN solution (Crusoe ⇄ AWS/GCP, route-based IPsec + BGP) - #89
Open
smazigh wants to merge 20 commits into
Open
Add site-to-site VPN solution (Crusoe ⇄ AWS/GCP, route-based IPsec + BGP)#89smazigh wants to merge 20 commits into
smazigh wants to merge 20 commits into
Conversation
added 20 commits
July 23, 2026 11:46
- variables with strict validations (ASN ranges, PSK charset, tunnel uniqueness, mss<=mtu-40, no world-open SSH) - single/dual HA via tunnels_by_vm grouping with assignment preconditions - firewall product-expansion (one CIDR per Crusoe rule) - startup script renders swanctl/FRR/nftables/XFRM configs; healthcheck, journald retention, apt retry, SSH hardening - MSS clamp ordered before conntrack accept; FRR no network import-check; prefix-list filtering both directions
- gcp: HA VPN gateway, external gateway (single/dual redundancy), Cloud Router, 2 IKEv2 tunnels, BGP peers on /30s; input validations - aws: TGW with ECMP, CGW, S2S VPN pinned to IKEv2 AES256-GCM-16/SHA2-384/ DH20; optional VPC return routes to TGW - tests/render: renders swanctl/FRR/nftables templates from fixtures, 21 stanza assertions, exits non-zero on failure - params: example tfvars + full variable schema with GCP/AWS mappings
- scripts: verify, mtu, failover, rekey, security suites + shared lib (single-string remote commands, exported env for bash -c asserts, tool guards, explicit TUNNEL_A_IF_ID for failover) - healthcheck.sh standalone copy of the on-VM monitor - ci: fmt/validate/tflint/trivy config scan/render dry-run/gitleaks/ tracked-secrets guard (assumes dir is repo root) - docs: architecture, runbook (with observability + rotation), customer GCP/AWS guides, crypto profiles, IP planning, outgrowing-this - tests: phase runbook + tested-against matrix - apt retry loop in bootstrap; README finalized with quickstart and deliberate SPEC deviations
- render harness now covers all 7 templates (secrets, xfrm, handoff, full startup script) with bash -n on rendered shell; 34 assertions - TUNNEL_A_NAME defaults to tunnel-a in failover/rekey tests (matches docs) - matrix/docs wording: lock files gitignored, trivy is the CI scanner
… PASS) - crusoe module: firewall destination needs /32 suffix (API normalizes, caused perpetual plan drift); bootstrap now restarts strongswan/frr instead of enable --now (apt pre-starts them with stock config; frr never launched bgpd, stale vici socket broke swanctl --load-all) and retries the config load - scripts: pin VM-originated probe source to the VPC address (tunnel traffic otherwise sources from 169.254 and peers filter it); remove unsupported swanctl --rekey --timeout flag; failover asserts data-path (xfrm link) state instead of SA state since peers re-initiate instantly, and retries traffic across the BGP reconvergence window; fix literal \! escapes that broke negated asserts; TRANSFER_URL fallback for the MSS-clamp transfer proof; CALLER_ALLOWLISTED mode for security probes from an allow-listed source - record live run results in tests/matrix.md
customer-aws.md restructured into Path A (customer already runs AWS S2S VPN: create/re-point CGW + VPN connection via CLI or console, pin IKEv2 crypto, extract outside IPs/inside CIDRs/PSKs with describe-vpn-connections, route propagation checks) and Path B (greenfield via the optional terraform/aws module, unchanged). schema.md and README point existing deployments at Path A.
ip -o link show type xfrm yields 'ipsecNNN@parent'; the device name is the part before '@'. Strip it with cut -d@ -f1 so 'ip link show' resolves, and match 'state (UP|UNKNOWN)' (xfrm interfaces sit in UNKNOWN with the UP flag). Fresh redeploy validated the bootstrap fix: tunnels + BGP + data plane came up with zero manual intervention (verify.sh 7/7).
Documents the Crusoe VPC constraint (fabric drops foreign-dst frames; no VPC routes or src/dest-check toggle) and the proven workaround: vxlan overlay host->gateway + open gateway firewall for the overlay transport + SNAT to the gateway LAN IP. Validated live: CMK pod -> node vxlan -> gateway VM -> IPsec -> GCP, 0% loss + 20MB transfer. Compares with ipsec-tunnel-cmk.
New cluster_egress variable (default off) makes the gateway forward other Crusoe hosts' traffic despite the VPC fabric's dst-filtering: - gateway: vxlan-ceg hub (learning) + nftables overlay input/forward/MSS-clamp + runtime SNAT to the gateway LAN IP (set from the VM's own IP at boot via a persisted systemd unit) + Crusoe firewall rule for the vxlan transport - k8s/cluster-egress DaemonSet: per-node vxlan to the gateway, collision-free overlay IP from the node IP octets, routes remote CIDRs, overlay MSS clamp - render harness covers enabled + disabled nftables and the full bootstrap Validated live: terraform apply (gateway) + kubectl apply (DaemonSet) only -> CMK pod -> node vxlan -> gateway -> IPsec -> GCP, 0% loss + 20MB HTTP 200.
- cluster_egress flags: snat_mode (gateway|node), overlay_transport (vxlan; wireguard reserved), vxlan_id/port, overlay_cidr. snat_mode=node drops SNAT and advertises the overlay via BGP (per-node source identity). - Helm chart (k8s/cluster-egress) replaces the flat manifest; values fed from TF outputs; multi-gateway list with per-node hash spread. - Churn resilience: deterministic node MAC 02:00:a9:fe:<O3>:<O4> encodes the overlay IP; gateway neighbor-reconcile loop maps learned FDB -> permanent neigh so return traffic resolves with no ARP flood (learning vxlan hubs can't flood without multicast). New/removed/re-IP'd nodes self-heal, no per-node gateway config. - render harness covers snat_mode gateway+node branches. Static-validated (fmt/validate/render/helm lint). Multi-node live re-validation pending (local egress IP changed -> gateway SSH locked; needs gateway rebuild + GCP reapply).
- README: cluster/multi-VM egress section (resilience to node churn/IP changes, deterministic-MAC + gateway neigh-reconcile), doc-map entry, and SPEC §17 marked answered (Crusoe VPC drops foreign-dst; no route/ src-dest-check primitive — overlay is the sanctioned path). - CLAUDE.md: repo conventions, environment quirks (sandbox, 1Password signing, VM-replace-on-config-change, /32 SSH lockout), Crusoe platform facts, live status. - matrix: 2026-07-24 cluster-egress run — multi-node (2 nodes) + churn self-heal PASS; gateway auto-neigh for all 10 nodes. Multi-node + churn validated live.
Active IKEv1/weak-proposal probes from a non-peer source get no response regardless of IKE version, so they can't prove IKEv1 rejection on their own; the on-box 'version = 2 only' config assertion is authoritative. Verified live: ike-scan IKEv1/IKEv2/weak-aggressive all 0 handshakes (source-pinned).
Distinguishes live-validated (GCP single, cluster-egress incl. multi-node+ churn) from documented/module-validated (AWS, ha_mode=dual) and roadmap (WireGuard, per-pod identity).
ha_mode=dual applied clean (2 VMs, GCP two-interface, rules correct) but both VMs stayed non-functional 25+ min (no SSH, no IKE from peer), unchanged by reset — a platform issue in eu-iceland1-a; module is validate-clean and the single-VM path with identical bootstrap passed the same day.
ha_mode=dual: vm0/tunnel-a + vm1/tunnel-b, GCP TWO_IPS_REDUNDANCY. Each VM terminates its tunnel + BGP. VM-failure test: stopped vm1 mid-ping, connection survived on vm0 (16.7% loss over 90s = BGP reconvergence, then stable). Prior fresh pair was a transient Crusoe env glitch; clean re-apply worked immediately. README status + matrix updated.
- context/ (gitignored): methodology + learnings + the implementation SPEC and plan moved out of the shipped tree — internal build notes only. - docs: removed all SPEC cross-references and build/session narrative; docs read as neutral product documentation. cluster-egress doc reframed to a feature guide (flags, deploy, resilience, status/roadmap). matrix reads as a clean tested-against record. README 'Platform notes' replaces 'deviations from SPEC'. - code: stripped narrative/'Plan bug fix' comments from scripts; kept genuine constraint comments. No logic changes (render harness byte-identical). - CLAUDE.md trimmed to repo guidance + Crusoe platform facts. Static gate green: fmt, validate x3, render (45 checks), shellcheck, helm lint, ci yaml.
…lay) GitHub-native mermaid replaces/augments the ASCII: architecture.md topology + dual-VM HA; crusoe-cluster-egress.md overlay packet path. Validated via mermaid render.
smazigh
requested review from
chinmaybaikar,
datadoc24 and
youngjeong46
as code owners
August 13, 2026 14:44
chinmaybaikar
approved these changes
Aug 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Site-to-Site VPN — Crusoe ⇄ AWS/GCP (route-based IPsec + BGP)
Adds a new solution: a hardened, redundant, route-based IPsec (IKEv2) VPN with dynamic BGP routing that terminates on Crusoe and connects a Crusoe VPC to a customer's AWS or GCP VPC. The Crusoe side is fully automated with Terraform from a single params file; the customer side ships as optional Terraform plus documented runbooks.
New top-level directory:
site-to-site-vpn/. No existing files touched.What's included
terraform/crusoe/— the owned gateway module: 1 VM (ha_mode=single) or 2 (dual), static public IP(s), default-deny Crusoe firewall (UDP 500/4500 from peer IPs, SSH allow-listed), and a bashstartup_scriptthat renders strongSwan (swanctl, XFRM route-based tunnels), FRR (bgpdeBGP + prefix filters + ECMP), nftables, and kernel hardening. Idempotent.terraform/gcp/,terraform/aws/— optional customer-side modules (GCP HA VPN + Cloud Router; AWS S2S VPN on Transit Gateway). Existing AWS deployments are covered by a docs-only path.k8s/cluster-egress/— optional Helm chart: a vxlan overlay DaemonSet so CMK pods / other Crusoe VMs can egress through the gateway (works around the Crusoe VPC fabric only delivering own-destination frames).scripts/— live test suite (verify, mtu, failover, rekey, security, healthcheck) + shared lib.tests/render/— static template render harness (CI phase 0)..github/workflows/ci.yml— fmt, validate x3, tflint, trivy config scan, render dry-run, gitleaks, tracked-secrets guard.docs/— architecture (with mermaid diagrams), runbook (deploy -> verify -> troubleshoot -> rotate -> teardown), customer GCP/AWS guides, crypto profiles, IP planning, cluster-egress, outgrowing-this; plusparams/schema.md.Validation
Run live against Crusoe (dx-lab / seif-projects) and GCP (crusoe-solutions-engineering):
ha_mode=singleha_mode=dualSee
site-to-site-vpn/tests/matrix.mdfor the run record and pinned versions.Security
TF_VAR_tunnel_psks; CI enforces a secret-leak scan. PSK-in-state/instance-metadata exposure is documented with mitigations.Notes / limitations
Out of scope
Physical/partner interconnect, WireGuard to managed cloud VPN gateways, and provisioning of the customer's intra-cloud topology beyond the landing VPC.