MakerFLOSS_Troubleshooting/docs/superpowers/plans/2026-06-28-tappaas-side-publishing.md

279 lines
12 KiB
Markdown
Raw Normal View History

# 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://<svc>.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 = <contents of /etc/wireguard/wg1.key from Task 0>
# 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 <whoami-upstream-host>:<port>
}
http://forgejo.tappaas.makerfloss.eu {
bind 10.13.0.9
reverse_proxy <forgejo-upstream-host>:<port>
}
```
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 `<svc>.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 <caddy-local-ip>"
```
**Example — dnsmasq:**
```text
address=/tappaas.makerfloss.eu/<caddy-local-ip>
```
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 `<svc>.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.
</content>