--- id: RINFRA-ADR-0005 type: adr title: "k3s API is tunnel-only" status: accepted owner: railiance-infra revision: "accepted-1" last_reviewed: "2026-08-22" review_interval: 6m --- # 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 | ```bash 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.