From 9a5c160b39df2ddb3bf2023a82ecc7c0f1c86f14 Mon Sep 17 00:00:00 2001 From: oskar Date: Wed, 22 Jul 2026 17:29:11 +0200 Subject: [PATCH] =?UTF-8?q?fix(security):=20control-plane=20UI=20bind=20na?= =?UTF-8?q?=20Tailscale=20IP=20=E2=80=94=2018180=20z=20/action/mutate=20by?= =?UTF-8?q?l=20publicznie=20dostepny=20bez=20auth=20na=20VPS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- services/control-plane/docker-compose.yml | 13 ++++++++++++- services/control-plane/env.example | 13 +++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 services/control-plane/env.example diff --git a/services/control-plane/docker-compose.yml b/services/control-plane/docker-compose.yml index 446507d..0cde0ea 100644 --- a/services/control-plane/docker-compose.yml +++ b/services/control-plane/docker-compose.yml @@ -5,7 +5,18 @@ services: user: "1000:1000" command: python src/operator_ui.py 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: - /opt/homelab:/opt/homelab restart: unless-stopped diff --git a/services/control-plane/env.example b/services/control-plane/env.example new file mode 100644 index 0000000..53fe9c3 --- /dev/null +++ b/services/control-plane/env.example @@ -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