From 8a5966e3675d9453d009a27f7447c62d8b93e123 Mon Sep 17 00:00:00 2001 From: sjat Date: Sun, 28 Jun 2026 13:47:38 +0200 Subject: [PATCH] plan: TaPPaaS-side publishing (sendable, self-contained) Standalone plan for the TaPPaaS operator's Claude Code: WireGuard client (peer 10.13.0.9, split-tunnel), Caddy plain-HTTP backend on 10.13.0.9:80, firewall lock to 10.13.0.1, internal split-horizon DNS. Bakes in the verified VPS-side contract (hub endpoint/pubkey, preserved Host, *.tappaas wildcard, public DNS) and the key-exchange handshake. Flags the internal-TLS decision (internal CA vs Gandi DNS-01 vs no internal TLS). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../2026-06-28-tappaas-side-publishing.md | 278 ++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-28-tappaas-side-publishing.md diff --git a/docs/superpowers/plans/2026-06-28-tappaas-side-publishing.md b/docs/superpowers/plans/2026-06-28-tappaas-side-publishing.md new file mode 100644 index 0000000..c2325f5 --- /dev/null +++ b/docs/superpowers/plans/2026-06-28-tappaas-side-publishing.md @@ -0,0 +1,278 @@ +# TaPPaaS-side Publishing — Implementation Plan (for the TaPPaaS operator) + +> **For agentic workers (Claude Code on the TaPPaaS side):** this plan is +> self-contained. The VPS edge is **already built, deployed, and verified** — +> your job is the TaPPaaS/FLOSSFirewall end so the two meet. Every fact about +> the VPS side below is live and confirmed (2026-06-28). Work in the TaPPaaS +> infra/config repo (whatever holds the FLOSSFirewall WireGuard, Caddy, firewall +> and DNS config). Ask the operator when a value is environment-specific +> (upstream addresses, interface names, the local Caddy IP). + +**Goal:** Make `https://.tappaas.makerfloss.eu` serve real TaPPaaS services +from anywhere, and give internal clients a local (no-VPS-round-trip) path to the +same names (split-horizon DNS). + +**How it works (the pattern):** TLS for external clients terminates on the +**makerfloss VPS**. The VPS forwards every `*.tappaas.makerfloss.eu` request as +**plain HTTP, original Host preserved**, over a **WireGuard tunnel (`wg1`)** to +the FLOSSFirewall at **`10.13.0.9:80`**, where **Caddy** routes by Host to the +service. (This mirrors the already-running `mf01` setup.) + +--- + +## The contract — what the VPS already provides (live & verified) + +You are building the other end of these. Do **not** change them; rely on them. + +| Fact | Value | +|---|---| +| Public DNS (live) | `tappaas.makerfloss.eu` and `*.tappaas.makerfloss.eu` → **A `88.99.32.236`** | +| External TLS | Terminates at the VPS. Wildcard cert `*.tappaas.makerfloss.eu` already issued (Gandi DNS-01). **You do NOT need a public cert for the external path.** | +| What the VPS sends you | Plain **HTTP** to **`10.13.0.9:80`**, over `wg1`, with the **original `Host` header preserved** (so Caddy sees `whoami.tappaas.makerfloss.eu`, etc.) | +| Current state of that route | Returns **HTTP 502** today, because `10.13.0.9:80` isn't answering yet. Your work makes it answer → 200. | +| WireGuard hub (the VPS) | Endpoint **`makerfloss.eu:51820`** (UDP) · hub overlay IP **`10.13.0.1`** · overlay subnet **`10.13.0.0/24`** · **hub public key `mtx4bxTq5KvQmFPRubRBrVoWL6WDUrll9LWLhOMSlCQ=`** | +| Your assigned peer overlay IP | **`10.13.0.9/32`** | + +> A placeholder peer public key is currently registered on the VPS for +> `10.13.0.9`. It will be **replaced by yours** in the key-exchange step below — +> the tunnel won't complete a handshake until that swap happens. + +--- + +## What you will build (4 parts + 1 handshake) + +0. **Key exchange** (do first — it gates everything). +1. **WireGuard client** on the FLOSSFirewall (peer `10.13.0.9`, split-tunnel). +2. **Caddy plain-HTTP backend** on `10.13.0.9:80` (routes by Host, redirect OFF). +3. **Firewall** allowing `tcp/80` to the wg interface **only from `10.13.0.1`**. +4. **Internal split-horizon DNS** for `*.tappaas.makerfloss.eu`. + +--- + +## Task 0 — WireGuard key exchange (do this first) + +WireGuard needs a public-key swap; this is the one round-trip with the VPS owner +(sjat). Private keys never leave the FLOSSFirewall. + +**Steps (on the FLOSSFirewall, or in your WG GUI):** + +```bash +umask 077 +wg genkey | tee /etc/wireguard/wg1.key | wg pubkey | tee /etc/wireguard/wg1.pub +# Keep wg1.key secret (it goes only into your WG config, never committed). +cat /etc/wireguard/wg1.pub # <-- send THIS public key to sjat +``` + +**Send sjat:** the contents of `wg1.pub` (a 44-char base64 string ending `=`) and +confirm your peer IP is `10.13.0.9`. **sjat then** replaces the peer public key on +the VPS (`vault_wireguard_makerfloss_peers.flossfw.public_key`) and re-runs the +WireGuard server play. After that the handshake can complete. + +If your FLOSSFirewall is OPNsense/pfSense, generate the keypair in the WireGuard +GUI instead and copy out the public key the same way. + +--- + +## Task 1 — WireGuard client (peer `10.13.0.9`, split-tunnel) + +**Requirement:** an interface that holds `10.13.0.9/32`, peers with the VPS hub, +and routes **only** the overlay subnet — never default traffic (isolation: +nothing from TaPPaaS should egress via the VPS). + +**Example `wg-quick` config — `/etc/wireguard/wg1.conf`** (adapt to your stack): + +```ini +[Interface] +Address = 10.13.0.9/32 +PrivateKey = +# No DNS=, no default route — split tunnel. + +[Peer] +PublicKey = mtx4bxTq5KvQmFPRubRBrVoWL6WDUrll9LWLhOMSlCQ= +Endpoint = makerfloss.eu:51820 +AllowedIPs = 10.13.0.0/24 +PersistentKeepalive = 25 +``` + +Bring up and enable on boot: +```bash +wg-quick up wg1 +systemctl enable wg-quick@wg1 # systemd hosts +``` + +**OPNsense/pfSense equivalent:** local tunnel address `10.13.0.9/32`; peer +endpoint `makerfloss.eu:51820`; peer public key +`mtx4bxTq5KvQmFPRubRBrVoWL6WDUrll9LWLhOMSlCQ=`; allowed IPs `10.13.0.0/24`; +keepalive `25`. Do **not** set it as a gateway / default route. + +**Verify (after sjat has swapped your key):** +```bash +wg show wg1 # latest handshake is recent; transfer > 0 +ping -c3 10.13.0.1 # the VPS hub answers over the overlay +``` +If there is no handshake: confirm sjat swapped your public key; confirm UDP +`51820` egress to `makerfloss.eu` isn't blocked by an upstream firewall. + +--- + +## Task 2 — Caddy plain-HTTP backend on `10.13.0.9:80` + +**Requirement:** Caddy must serve **plain HTTP on `10.13.0.9:80`**, route by the +`Host` the VPS preserved, and **NOT redirect to HTTPS** on this listener (the VPS +already did TLS; a redirect here causes a loop — this is the one gotcha the `mf01` +setup hit). Your existing internal `:443` serving stays unchanged; you are only +**adding** the wg-bound HTTP backend. + +**Why `http://` in the site address:** in Caddy v2 an `http://` site address +disables Automatic HTTPS for that site and serves on port 80 — exactly the +no-cert, no-redirect behavior needed. `bind 10.13.0.9` pins it to the wg +interface. + +**Example Caddyfile — one block per published service** (adapt upstreams): + +```caddy +# --- VPS edge backend: plain HTTP on the wg interface only --- +# Repeat one block per service you want published publicly. + +http://whoami.tappaas.makerfloss.eu { + bind 10.13.0.9 + reverse_proxy : +} + +http://forgejo.tappaas.makerfloss.eu { + bind 10.13.0.9 + reverse_proxy : +} +``` + +Notes: +- **Exposure is opt-in here.** Only hostnames you give a block become reachable + publicly; the VPS forwards the whole wildcard, but unmatched hosts just get a + Caddy 404. Add a block to publish a service; remove it to unpublish. +- `bind 10.13.0.9` requires the `wg1` interface to exist before Caddy starts. + Order Caddy after `wg-quick@wg1`, **or** bind to `0.0.0.0` on `:80` and rely on + the Task 3 firewall to restrict it to `10.13.0.1` (simpler startup, firewall + does the isolation). +- Point `reverse_proxy` at the same upstreams your internal `:443` blocks already + use for these services. + +**Verify locally (on the FLOSSFirewall):** +```bash +curl -s -o /dev/null -w '%{http_code}\n' \ + -H 'Host: whoami.tappaas.makerfloss.eu' http://10.13.0.9:80/ +# Expect 200 (or whatever the service returns) — NOT a 3xx redirect. +``` + +--- + +## Task 3 — Firewall: lock `:80` to the VPS + +**Requirement:** on the wg interface, allow **`tcp/80` only from `10.13.0.1`** +(the VPS hub); block `:80` from anywhere else; ensure `:80` is not exposed on +other interfaces. This is the isolation guarantee — the plain-HTTP backend is +reachable solely by the VPS. + +**Example (nftables/iptables host firewall):** +```bash +iptables -A INPUT -i wg1 -p tcp --dport 80 -s 10.13.0.1 -j ACCEPT +iptables -A INPUT -i wg1 -p tcp --dport 80 -j DROP +``` + +**OPNsense/pfSense:** on the WireGuard interface, a pass rule — proto TCP, source +`10.13.0.1`, destination `(this firewall)` port `80`; below it a block rule for +TCP to port `80` on that interface. Confirm no other interface rule exposes `:80` +to untrusted networks. + +**Verify:** the local `curl` in Task 2 (sourced from the host itself) still works; +once end-to-end is up (Task 5), the request arrives from `10.13.0.1` and is +allowed. + +--- + +## Task 4 — Internal split-horizon DNS + +**Requirement:** internal clients (the TaPPaaS cluster now; the makerspace LAN +later) resolving `.tappaas.makerfloss.eu` should get **Caddy's local IP**, so +they reach Caddy directly and skip the VPS round-trip. External clients keep +getting the VPS (public DNS already does that). Configure this on the +FLOSSFirewall's resolver. + +**Example — unbound** (wildcard for the whole subdomain via `redirect`): +```text +local-zone: "tappaas.makerfloss.eu." redirect +local-data: "tappaas.makerfloss.eu. 300 IN A " +``` + +**Example — dnsmasq:** +```text +address=/tappaas.makerfloss.eu/ +``` + +Serve this view to the **cluster** now. (Makerspace-LAN coverage is a later phase +and depends on the OrangeMakers router — out of scope here.) + +> **Decision point — internal TLS.** Internal clients hitting Caddy directly will +> get **Caddy's** certificate for `.tappaas.makerfloss.eu`, not the VPS's. +> While the cluster is isolated, Caddy can obtain a publicly-trusted cert for +> these names only via **DNS-01** (which means putting a Gandi DNS-write +> credential on the FLOSSFirewall — the very thing the external-path design kept +> *off* the makerspace). Choose one, with sjat: +> 1. **Internal CA / `mkcert`** — issue an internal cert for `*.tappaas` and have +> cluster clients trust that CA. Keeps Gandi creds off-site. *(Recommended.)* +> 2. **Gandi DNS-01 on the FLOSSFirewall** — Caddy serves the real public +> wildcard internally too; reintroduces the credential in the makerspace. +> 3. **No internal TLS termination** — point internal DNS at the VPS as well +> (drop split-horizon); simplest, but every internal request round-trips the +> VPS. +> +> Tasks 1-3 do not depend on this; pick it before relying on the internal view. + +--- + +## Task 5 — End-to-end verification + +Run after Tasks 0-3 are in place (Task 4 is for the internal view). + +```bash +# 1) Tunnel handshake (on the FLOSSFirewall) +wg show wg1 # recent handshake, transfer > 0 +ping -c3 10.13.0.1 # hub reachable + +# 2) External end-to-end (from anywhere on the internet) +curl -s -o /dev/null -w '%{http_code}\n' https://whoami.tappaas.makerfloss.eu/ +# Expect 200 (was 502 before your backend existed). TLS is the VPS's +# *.tappaas wildcard and validates with no warning. + +# 3) Internal split-horizon (from a TaPPaaS cluster node, after Task 4) +dig +short whoami.tappaas.makerfloss.eu # → Caddy's LOCAL IP, not 88.99.32.236 +curl -sI https://whoami.tappaas.makerfloss.eu/ # served by Caddy directly +``` + +Report back to sjat once `(2)` returns 200 — that confirms the full path +(VPS TLS → wg1 → Caddy → service) is live. + +--- + +## Isolation requirements (must hold) + +- WireGuard is **split-tunnel** (`AllowedIPs = 10.13.0.0/24`, no default route) — + TaPPaaS never egresses through the VPS, and nothing on `wg1` reaches the wider + makerspace or the VPS owner's homelab. +- The plain-HTTP backend is reachable **only** from `10.13.0.1` (Task 3). +- The backend hop is plain HTTP but travels **inside** the WireGuard tunnel, so + it is encrypted on the wire. +- No public-DNS-write credential is required on the FLOSSFirewall for the + external path (the VPS owns that). Only the internal-TLS option 2 above would + reintroduce one — avoid it unless deliberately chosen. + +--- + +## Reference + +- Already-running sibling pattern (the template this mirrors): + `MakerFLOSS_Troubleshooting/runbooks/publishing-services-mf01.md`. +- VPS-side design + the changes that produced the contract above: + `MakerFLOSS_Troubleshooting/docs/superpowers/specs/2026-06-28-tappaas-vps-publishing-design.md` + and `.../plans/2026-06-28-tappaas-vps-publishing.md`. +- Questions about the VPS side / to do the key swap: contact sjat. +