furtka-gateway/traefik/dynamic/control-plane.yml.example

57 lines
3 KiB
Text
Raw Normal View History

feat: ship control-plane standalone, bring your own Traefik/wg-easy Restructure the deployment model: instead of bundling and owning its own Traefik + wg-easy, the control-plane now plugs into a Traefik and wg-easy the operator already runs (as sibling containers, or otherwise reachable). This avoids duplicating infra self-hosters commonly already have, and sidesteps having to run a second ACME/DNS setup entirely — the operator's own Traefik already has a working cert resolver. - New control_plane/wgeasy_v15.py: a wg-easy v15 REST client, verified live against a real running container (auth model, route shapes, and the new per-client PresharedKey all differ materially from v14's API). Threaded the preshared key through to the box side (furtka/wireguard.py in the furtka repo) so v15's handshake actually completes. - GATEWAY_CERT_RESOLVER is now a real env var instead of a GATEWAY_MODE- derived hardcoded "le", so it can mirror whatever resolver name (if any) the operator's own Traefik already has configured. - Root docker-compose.yaml/ops/deploy.sh: control-plane only now. deploy.sh asks how the operator's Traefik reaches it (same docker network, joined via `docker network connect`, or a separate host) instead of prompting for DNS-01 providers and port remaps single-parallel mode needed. - single-parallel mode is retired outright — nothing needs it once furtka-gateway doesn't own a second Traefik/wg-easy to remap ports for. - The old bundled stack (own Traefik+wg-easy, HTTP-01) moves to examples/starter-stack/ as the documented zero-infra on-ramp. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3jVwAqESRjPdW9P9nvfem
2026-09-19 13:34:29 +02:00
# Copy to traefik/dynamic/control-plane.yml (into whatever directory YOUR
# Traefik's file provider watches — see the root README's Prerequisites
# checklist) with two placeholders substituted:
#
# GATEWAY_CONTROL_PLANE_HOST a real hostname you control (e.g.
# gw.example.com), with its own A/AAAA record
# pointing at this host.
# GATEWAY_CONTROL_PLANE_TARGET where YOUR Traefik actually reaches the
# control-plane container. If it joined your
# Traefik's docker network (ops/deploy.sh
# does this via `docker network connect` —
# the primary, recommended path), this is
# `furtka-gateway-control-plane:8090` — the
# container's fixed name, which Docker's
# embedded DNS resolves on any network it's
# joined to (unlike the compose *service*
# name `control-plane`, which is only
# guaranteed to resolve on networks compose
# itself manages — not one joined after the
# fact via a bare `docker network connect`).
# If instead you exposed it on a separate
# host (GATEWAY_CONTROL_PLANE_BIND widened
# beyond 127.0.0.1 in docker-compose.yaml),
# this is that host's real reachable
# `host:port` — see ops/deploy.sh's warning
# about the access-control implication of
# that path.
Fix WireGuard peer routing and control-plane reachability Two gaps found while walking through a real single-tenant deployment scenario, both fixed and verified against the actual running stack (not just unit tests): - wg-easy moves to network_mode: host so Traefik can actually route to WireGuard peer addresses (wg-quick's route only existed inside wg-easy's own isolated network namespace before). This forced host-level sysctls (ops/host-sysctls.sh — Docker/runc rejects namespaced sysctls under host networking) and a host firewall rule (ops/firewall.sh) to keep wg-easy's now-unisolated admin API off the public interface. - control-plane gets a real Traefik route (traefik/dynamic/control-plane.yml.example) instead of being reachable only at 127.0.0.1:8090 — box registration carries a WireGuard private key and needs TLS, not bare loopback HTTP. Along the way, running the actual pinned wg-easy image (ghcr.io/wg-easy/wg-easy:14) surfaced that it's a different, older, Express-based codebase than what wg-easy's current docs/master branch describe: auth is a plain Authorization header per request (no session cookie), routes live under /api/wireguard/client, and its default WG_ALLOWED_IPS is full-tunnel (0.0.0.0/0) rather than the split-tunnel this design assumed. wgeasy.py and docker-compose.yaml are corrected accordingly, verified end-to-end against the real container. Also fixed: Docker Compose silently truncates a bcrypt hash's `$` characters when read from .env — documented the required $$ escaping.
2026-08-24 12:51:22 +02:00
#
# Without this file, the control-plane API is only reachable at
feat: ship control-plane standalone, bring your own Traefik/wg-easy Restructure the deployment model: instead of bundling and owning its own Traefik + wg-easy, the control-plane now plugs into a Traefik and wg-easy the operator already runs (as sibling containers, or otherwise reachable). This avoids duplicating infra self-hosters commonly already have, and sidesteps having to run a second ACME/DNS setup entirely — the operator's own Traefik already has a working cert resolver. - New control_plane/wgeasy_v15.py: a wg-easy v15 REST client, verified live against a real running container (auth model, route shapes, and the new per-client PresharedKey all differ materially from v14's API). Threaded the preshared key through to the box side (furtka/wireguard.py in the furtka repo) so v15's handshake actually completes. - GATEWAY_CERT_RESOLVER is now a real env var instead of a GATEWAY_MODE- derived hardcoded "le", so it can mirror whatever resolver name (if any) the operator's own Traefik already has configured. - Root docker-compose.yaml/ops/deploy.sh: control-plane only now. deploy.sh asks how the operator's Traefik reaches it (same docker network, joined via `docker network connect`, or a separate host) instead of prompting for DNS-01 providers and port remaps single-parallel mode needed. - single-parallel mode is retired outright — nothing needs it once furtka-gateway doesn't own a second Traefik/wg-easy to remap ports for. - The old bundled stack (own Traefik+wg-easy, HTTP-01) moves to examples/starter-stack/ as the documented zero-infra on-ramp. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3jVwAqESRjPdW9P9nvfem
2026-09-19 13:34:29 +02:00
# 127.0.0.1:8090 on this host itself — fine for a local health check,
# useless for a real remote Furtka box, which needs this over HTTPS
Fix WireGuard peer routing and control-plane reachability Two gaps found while walking through a real single-tenant deployment scenario, both fixed and verified against the actual running stack (not just unit tests): - wg-easy moves to network_mode: host so Traefik can actually route to WireGuard peer addresses (wg-quick's route only existed inside wg-easy's own isolated network namespace before). This forced host-level sysctls (ops/host-sysctls.sh — Docker/runc rejects namespaced sysctls under host networking) and a host firewall rule (ops/firewall.sh) to keep wg-easy's now-unisolated admin API off the public interface. - control-plane gets a real Traefik route (traefik/dynamic/control-plane.yml.example) instead of being reachable only at 127.0.0.1:8090 — box registration carries a WireGuard private key and needs TLS, not bare loopback HTTP. Along the way, running the actual pinned wg-easy image (ghcr.io/wg-easy/wg-easy:14) surfaced that it's a different, older, Express-based codebase than what wg-easy's current docs/master branch describe: auth is a plain Authorization header per request (no session cookie), routes live under /api/wireguard/client, and its default WG_ALLOWED_IPS is full-tunnel (0.0.0.0/0) rather than the split-tunnel this design assumed. wgeasy.py and docker-compose.yaml are corrected accordingly, verified end-to-end against the real container. Also fixed: Docker Compose silently truncates a bcrypt hash's `$` characters when read from .env — documented the required $$ escaping.
2026-08-24 12:51:22 +02:00
# anyway: the registration response carries a WireGuard private key in
# plaintext JSON, so that call must never go out over bare HTTP.
#
feat: ship control-plane standalone, bring your own Traefik/wg-easy Restructure the deployment model: instead of bundling and owning its own Traefik + wg-easy, the control-plane now plugs into a Traefik and wg-easy the operator already runs (as sibling containers, or otherwise reachable). This avoids duplicating infra self-hosters commonly already have, and sidesteps having to run a second ACME/DNS setup entirely — the operator's own Traefik already has a working cert resolver. - New control_plane/wgeasy_v15.py: a wg-easy v15 REST client, verified live against a real running container (auth model, route shapes, and the new per-client PresharedKey all differ materially from v14's API). Threaded the preshared key through to the box side (furtka/wireguard.py in the furtka repo) so v15's handshake actually completes. - GATEWAY_CERT_RESOLVER is now a real env var instead of a GATEWAY_MODE- derived hardcoded "le", so it can mirror whatever resolver name (if any) the operator's own Traefik already has configured. - Root docker-compose.yaml/ops/deploy.sh: control-plane only now. deploy.sh asks how the operator's Traefik reaches it (same docker network, joined via `docker network connect`, or a separate host) instead of prompting for DNS-01 providers and port remaps single-parallel mode needed. - single-parallel mode is retired outright — nothing needs it once furtka-gateway doesn't own a second Traefik/wg-easy to remap ports for. - The old bundled stack (own Traefik+wg-easy, HTTP-01) moves to examples/starter-stack/ as the documented zero-infra on-ramp. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3jVwAqESRjPdW9P9nvfem
2026-09-19 13:34:29 +02:00
# GATEWAY_MODE=single: keep the certResolver line below, set to whatever
# resolver name YOUR Traefik already has configured (blank/omit the `tls:`
# block entirely if your Traefik applies a default cert automatically —
# see GATEWAY_CERT_RESOLVER in .env.example, which controls this same
# choice for every *app* route furtka-gateway publishes; keep both
# consistent).
Fix WireGuard peer routing and control-plane reachability Two gaps found while walking through a real single-tenant deployment scenario, both fixed and verified against the actual running stack (not just unit tests): - wg-easy moves to network_mode: host so Traefik can actually route to WireGuard peer addresses (wg-quick's route only existed inside wg-easy's own isolated network namespace before). This forced host-level sysctls (ops/host-sysctls.sh — Docker/runc rejects namespaced sysctls under host networking) and a host firewall rule (ops/firewall.sh) to keep wg-easy's now-unisolated admin API off the public interface. - control-plane gets a real Traefik route (traefik/dynamic/control-plane.yml.example) instead of being reachable only at 127.0.0.1:8090 — box registration carries a WireGuard private key and needs TLS, not bare loopback HTTP. Along the way, running the actual pinned wg-easy image (ghcr.io/wg-easy/wg-easy:14) surfaced that it's a different, older, Express-based codebase than what wg-easy's current docs/master branch describe: auth is a plain Authorization header per request (no session cookie), routes live under /api/wireguard/client, and its default WG_ALLOWED_IPS is full-tunnel (0.0.0.0/0) rather than the split-tunnel this design assumed. wgeasy.py and docker-compose.yaml are corrected accordingly, verified end-to-end against the real container. Also fixed: Docker Compose silently truncates a bcrypt hash's `$` characters when read from .env — documented the required $$ escaping.
2026-08-24 12:51:22 +02:00
# GATEWAY_MODE=shared: delete the `tls:` block entirely — the shared
# wildcard defaultGeneratedCert (traefik/dynamic/default-cert.yml) already
# covers every hostname under GATEWAY_BASE_DOMAIN, this included.
http:
routers:
control-plane:
rule: "Host(`GATEWAY_CONTROL_PLANE_HOST`)"
entryPoints: [websecure]
service: control-plane
tls:
certResolver: le
services:
control-plane:
loadBalancer:
servers:
feat: ship control-plane standalone, bring your own Traefik/wg-easy Restructure the deployment model: instead of bundling and owning its own Traefik + wg-easy, the control-plane now plugs into a Traefik and wg-easy the operator already runs (as sibling containers, or otherwise reachable). This avoids duplicating infra self-hosters commonly already have, and sidesteps having to run a second ACME/DNS setup entirely — the operator's own Traefik already has a working cert resolver. - New control_plane/wgeasy_v15.py: a wg-easy v15 REST client, verified live against a real running container (auth model, route shapes, and the new per-client PresharedKey all differ materially from v14's API). Threaded the preshared key through to the box side (furtka/wireguard.py in the furtka repo) so v15's handshake actually completes. - GATEWAY_CERT_RESOLVER is now a real env var instead of a GATEWAY_MODE- derived hardcoded "le", so it can mirror whatever resolver name (if any) the operator's own Traefik already has configured. - Root docker-compose.yaml/ops/deploy.sh: control-plane only now. deploy.sh asks how the operator's Traefik reaches it (same docker network, joined via `docker network connect`, or a separate host) instead of prompting for DNS-01 providers and port remaps single-parallel mode needed. - single-parallel mode is retired outright — nothing needs it once furtka-gateway doesn't own a second Traefik/wg-easy to remap ports for. - The old bundled stack (own Traefik+wg-easy, HTTP-01) moves to examples/starter-stack/ as the documented zero-infra on-ramp. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3jVwAqESRjPdW9P9nvfem
2026-09-19 13:34:29 +02:00
- url: "http://GATEWAY_CONTROL_PLANE_TARGET"