Skip to content

feat(gateway): make the gateway private, fronted by an ingress (ADR-0045)#246

Open
pedromvgomes wants to merge 1 commit into
mainfrom
feature/gateway-behind-ingress
Open

feat(gateway): make the gateway private, fronted by an ingress (ADR-0045)#246
pedromvgomes wants to merge 1 commit into
mainfrom
feature/gateway-behind-ingress

Conversation

@pedromvgomes

Copy link
Copy Markdown
Contributor

Summary

A north-south gateway is no longer its own public edge. It now requires an ingress: FK (same scope, singleton) that fronts it: the ingress terminates TLS, enforces the edge security tier (CrowdSec + rate limit), and reverse-proxies the gateway FQDN to a private gateway over the network, preserving the client IP. See ADR-0045.

Breaking change (the required ingress: field) → targets inforge v6.2.0.

What changed

  • Mandatory ingress: FKGatewaySpec, schemas/gateway.json, loader, and checkGateway (required + gateway-host/ingress-host same-network).
  • Two-server nginx render — a termination server on the ingress host (ACME + rate-limit → reverse-proxy, appends the real client to XFF) and a plain-HTTP routing server on the gateway host (set_real_ip_from the ingress + real_ip_header X-Forwarded-For to recover the client, then the derived routes → mesh egress). nginx.Render gained a gatewayRoutes slice; IngressProvider.Realize gained gatewayRoutes + gatewayIPs. Co-located = loopback; split = private IPs resolved in the apply (the routing server binds the gateway host's own private IP, never 0.0.0.0).
  • DerivationscrowdsecEdgeHosts is ingress-only (a gateway is never a public edge); gateway DNS + firewall 443/80 move to the ingress host; the split gateway host opens only GatewayHTTPPort (8442) privately; listed-service health relocates to the ingress host. checkGateway/context-builder reserve 8442 on the gateway host so a co-located backend cannot collide with the routing nginx.
  • No wardnet-cloud change — verified the routing server's real_ip recovery preserves the leftmost-X-Forwarded-For-from-loopback contract client_ip() already expects (crates/common/src/proxy_protocol.rs).

Docs

ADR-0045, AGENTS.md, and the website docs (gateway.md end-to-end incl. both mermaid diagrams, ingress.md, variables-yaml.md) all updated to the private-gateway model.

Test plan

  • go build ./..., go test -race ./..., golangci-lint run ./... — all clean.
  • New/updated unit tests for the two-server render, real_ip recovery, checkGateway (required FK + same-network), the 8442 collision guard, and the ingress-only CrowdSec/DNS/firewall/health derivations.
  • A prior multi-angle self-review's 5 findings are fixed in this branch (GatewayHTTPPort guard, private-IP bind, and doc/message cleanups).

Rollout note

Consumers add the ingress: FK to each gateway in lockstep with picking up v6.2.0. Phase 1 lands the model with gateways co-located with their ingresses (unchanged topology); the actual host-split is later a one-line host: change.

Merge Commit Message

feat(gateway): make the gateway private, fronted by an ingress (ADR-0045) [breaking: required ingress: FK, v6.2.0]

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown

bulwark

  • scan — no findings

  • coverage — go: 66.7% (baseline 66.3%); go patch: 84.2% (165/196 new lines; baseline 66.3%)

📦 Full bulwark output — complete scan and coverage logs.

…045)

A gateway is no longer its own public edge. It now requires an `ingress:` FK
(same scope, singleton) that fronts it: the ingress terminates TLS, enforces the
edge security tier (CrowdSec/rate-limit), and reverse-proxies the gateway FQDN to
a private gateway over the network, preserving the client IP.

Rendered as two servers: a TERMINATION server on the ingress host (ACME + rate
limit -> reverse-proxy to the gateway) and a plain-HTTP ROUTING server on the
gateway host (nginx.GatewayHTTPPort; set_real_ip_from the ingress + real_ip_header
X-Forwarded-For to recover the client, then the derived routes -> mesh egress).
Co-located = loopback; split = private IPs resolved in the provider apply.

Derivation changes: crowdsecEdgeHosts is ingress-hosts-only (a gateway is never a
public edge); the gateway FQDN A record + firewall 443/80 live on the ingress
host; the split gateway host opens only GatewayHTTPPort privately; listed-service
health relocates to the ingress host. nginx.Render gained a gatewayRoutes slice;
IngressProvider.Realize gained gatewayRoutes + gatewayIPs.

No wardnet-cloud change: the routing server's real_ip recovery preserves the
leftmost-XFF-from-loopback contract client_ip() expects.

Breaking change (required ingress: FK) -> targets inforge v6.2.0.

ADR-0045; AGENTS.md + website docs updated. go build/test -race/lint clean.
@pedromvgomes
pedromvgomes force-pushed the feature/gateway-behind-ingress branch from 7573ef5 to fd2def2 Compare July 23, 2026 20:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant