fix(security): control-plane UI bind na Tailscale IP — 18180 z /action/mutate byl publicznie dostepny bez auth na VPS

Co: operator-ui (18180:8080) bindowal na 0.0.0.0, wiec /action/mutate
(brak autoryzacji, moze przenosic akcje do "approved") byl osiagalny z
publicznego internetu. Fix stosuje istniejacy wzorzec repo
(TAILSCALE_BIND_IP env var, patrz fleet-prometheus/llm-gateway/gokapi) +
dual-bind jak w ollama (127.0.0.1 obok TAILSCALE_BIND_IP), bo node-agent
na VPS laczy sie z network_mode: host przez localhost:18180/summary.

Nie ruszono operator_ui.py / mutate_action — auth to osobny temat.
This commit is contained in:
oskar 2026-07-22 17:29:11 +02:00
parent 6626a5bf8f
commit 9a5c160b39
2 changed files with 25 additions and 1 deletions

View file

@ -5,7 +5,18 @@ services:
user: "1000:1000" user: "1000:1000"
command: python src/operator_ui.py command: python src/operator_ui.py
ports: ports:
- "18180:8080" # Loopback: node-agent on the VPS runs with network_mode: host and probes
# localhost:18180/summary directly (_check_control_plane_health in
# node_agent.py). Tailscale IP: runtime-materializer@PIHA and other mesh
# consumers reach this as http://100.95.58.48:18180. No 0.0.0.0 — this
# host previously had NO 0.0.0.0 exemption and /action/mutate has no auth,
# so a public bind lets anyone on the internet approve remediation
# actions. Same dual-bind pattern as services/ollama/docker-compose.yml.
# Requires .env (from env.example) next to this file at deploy — see
# services/fleet-prometheus/README.md for the "missing .env silently
# binds 0.0.0.0" gotcha that also applies here.
- "127.0.0.1:18180:8080"
- "${TAILSCALE_BIND_IP}:18180:8080"
volumes: volumes:
- /opt/homelab:/opt/homelab - /opt/homelab:/opt/homelab
restart: unless-stopped restart: unless-stopped

View file

@ -0,0 +1,13 @@
# Copy to .env next to docker-compose.yml (gitignored); docker compose picks
# it up automatically. Same convention as services/fleet-prometheus,
# services/llm-gateway, services/gokapi, services/ollama.
# Tailscale IP of the VPS node. The operator-ui port (18180) is published as
# ${TAILSCALE_BIND_IP}:18180:8080 so the mesh-facing bind is never 0.0.0.0.
# Verify when rebuilding the host: tailscale ip -4.
#
# WARNING (same footgun as fleet-prometheus, see its README): if .env is
# missing at deploy time, Compose resolves this to an empty string and
# SILENTLY binds to 0.0.0.0 instead of failing — always confirm with
# `docker compose config | grep host_ip` before `up -d`.
TAILSCALE_BIND_IP=100.95.58.48