railiance-infra/docs/adr/ADR-005-k3s-api-tunnel-only.md
codex 4d9e77c968
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Close RAIL-HO-WP-0009 declared-state gaps; leave live 6443 prune gated
Make the k3s API tunnel-only (ADR-005), stop declaring Flannel VXLAN
open to Anywhere, tag the base role so firewall can be scoped, and
schedule the Goss declared-vs-live check. CoulombCore sets ufw_manage
false so a converge cannot enable UFW there. T02 still needs operator
approval for make converge-firewall HOST=Railiance01.
2026-08-15 15:41:59 +02:00

2.1 KiB

ADR-005 — k3s API is tunnel-only

Status: Accepted Date: 2026-08-15 Deciders: implementation of RAIL-HO-WP-0009-T04 Workplans: RAIL-HO-WP-0009


Context

Operator addresses on this network rotate with the ISP lease. A public UFW allowlist for 6443/tcp is therefore a treadmill:

  • miss a rotation and kubectl breaks mid-session
  • leave the old grant standing and it becomes a grant to a stranger
  • hand-add the new address and the declaration drifts again

That cycle produced this workplan. The live allowlist drifted by hand during the session that was fixing allowlist drift, and again before the next session (89.244.90.248 appeared undeclared). On 2026-08-15 the workstation egress address was 85.132.220.102 — already on the revoked list as a "historic" operator address.

docs/deploy-stack.md already documents API access over the ops-bridge SSH tunnel for CoulombCore (k3s-api-coulombcore, local port 16443). The same tunnel already exists for Railiance01 (k3s-api-railiance01, local port 16444). SSH itself stays public, so the host remains recoverable.

Decision

The public k3s API allowlist is empty. Operator and agent kubectl access uses the ops-bridge tunnels:

Cluster Tunnel Local port Remote
CoulombCore k3s-api-coulombcore 16443 6443
Railiance01 k3s-api-railiance01 16444 6443
bridge up k3s-api-railiance01
# kubeconfig server: https://127.0.0.1:16444

Trade: every operator kubectl action depends on ops-bridge. That is accepted. A rotating public allowlist is the worse dependency.

Emergency break-glass remains SSH: ssh railiance01 -- sudo k3s kubectl …. Do not re-open 6443/tcp to Anywhere.

Consequences

  • k3s_api_allowed_sources stays [].
  • Former public grants live in k3s_api_revoked_sources so a firewall-tagged converge deletes them.
  • Goss asserts the 6443 allowlist size is exactly the declared length (zero) and that no revoked address remains.
  • Amending this ADR is required before adding any new public 6443 source.