docs(backlog): zamknij dziurę operator_ui + remediację bez SSH, dopisz follow-upy z 2026-07-22/23
Zamknięte: publiczny bind operator_ui:18180 (9a5c160), remediacja floty bez SSH (2dac154, E2E potwierdzone), uprawnienia actions/ na PIHA. Aktywne (nowe): retry zepsutego JSON w approved/, uprawnienia actions/ niezweryfikowane poza PIHA, brak checka .env/TAILSCALE_BIND_IP w deploy-local.sh, brak autoryzacji w operator_ui.py, alert_only zapycha approval queue, crash-loop bez container_restart, brak Telegram yes/no dla pending, homeassistant5 Exited(0), node-agent repo-less na lustro. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
cbce8a1b48
commit
0744627517
183
docs/backlog.md
183
docs/backlog.md
|
|
@ -102,6 +102,127 @@ historia incydentów, out-of-band watchdog.
|
||||||
|
|
||||||
## Aktywne
|
## Aktywne
|
||||||
|
|
||||||
|
### Executor: zepsuty JSON w `approved/` retry'owany w nieskończoność co 10s zamiast trafić do `failed/`/`rejected/`
|
||||||
|
|
||||||
|
**Data**: 2026-07-23
|
||||||
|
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
|
||||||
|
**Problem**: podczas testów E2E remediacji bez SSH zepsuty JSON akcji w `approved/`
|
||||||
|
(przyczyna: podstawienie zmiennej lokalnej w cudzysłowie w zagnieżdżonym heredoc przez
|
||||||
|
ssh) powodował, że executor próbował go sparsować co cykl (10s), logując "Failed to
|
||||||
|
move ... to running: Expecting value" bez końca — plik nigdy nie trafiał do
|
||||||
|
`failed/`/`rejected/`.
|
||||||
|
**Fix**: executor powinien przenosić nieparsowalny JSON do `failed/` (albo dedykowanego
|
||||||
|
`malformed/`) po pierwszej próbie, nie zostawiać go do nieskończonego retry w `approved/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Uprawnienia `actions/` naprawione TYLKO na PIHA (ręcznie) — SOLARIA i lustro niezweryfikowane
|
||||||
|
|
||||||
|
**Data**: 2026-07-23
|
||||||
|
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
|
||||||
|
**Problem**: fix uprawnień `/opt/homelab/actions` (grupa `pi` + setgid, patrz Zamknięte)
|
||||||
|
zastosowany ręcznie tylko na PIHA. SOLARIA (oskar tam podobno uid 1000 — do sprawdzenia
|
||||||
|
czy problem w ogóle występuje) i lustro (repo-less, patrz osobny wpis niżej)
|
||||||
|
niezweryfikowane — remediacja na tych węzłach może paść identycznie przy pierwszej
|
||||||
|
próbie.
|
||||||
|
**Fix docelowy**: node-agent powinien sam tworzyć `dispatch/<node>/` z właściwymi
|
||||||
|
prawami (grupa współdzielona + setgid) przy starcie, żeby to nie wracało za każdym
|
||||||
|
razem jako ręczna interwencja — patrz też „Tech-debt: globalny porządek uid/gid" niżej.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `deploy-local.sh` (control-plane): brak twardego checka, że `.env` istnieje i `TAILSCALE_BIND_IP` jest ustawione
|
||||||
|
|
||||||
|
**Data**: 2026-07-22
|
||||||
|
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
|
||||||
|
**Problem**: przy fixie bindu `operator_ui.py:18180` odkryto footgun: bez `.env`,
|
||||||
|
`docker compose` tylko OSTRZEGA i po cichu wraca do bindu `0.0.0.0` zamiast failować —
|
||||||
|
dokładnie ten sam wzorzec błędu jak historyczny bug fleet-prometheus/deploy-node.sh
|
||||||
|
(brak `--env-file`, patrz Zamknięte, commit `686aca7`), tym razem w
|
||||||
|
`deploy-local.sh`/control-plane.
|
||||||
|
**Fix**: dodać twardy check w `deploy-local.sh` — `.env` musi istnieć i
|
||||||
|
`TAILSCALE_BIND_IP` musi być ustawiony, inaczej abort przed `docker compose up`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `operator_ui.py` nie ma ŻADNEJ autoryzacji
|
||||||
|
|
||||||
|
**Data**: 2026-07-22
|
||||||
|
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
|
||||||
|
**Problem**: po zamknięciu publicznego bindu 18180 (patrz Zamknięte, commit `9a5c160`)
|
||||||
|
`operator_ui.py` nadal przyjmuje `/action/mutate` (w tym przejście do `approved`) bez
|
||||||
|
żadnego uwierzytelnienia — chroni tylko granica Tailscale mesh, nie autoryzacja per
|
||||||
|
operator.
|
||||||
|
**Fix**: osobny temat — do zaprojektowania (token/basic auth/mTLS w mesh), poza
|
||||||
|
zakresem fixu bindu.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `alert_only` zapycha approval queue
|
||||||
|
|
||||||
|
**Data**: 2026-07-22
|
||||||
|
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
|
||||||
|
**Problem**: przy reconie stanu wyjściowego 16 z 18 pending actions to były
|
||||||
|
liveness-transitions typu `alert_only`, nie realne decyzje do klikania — operator musi
|
||||||
|
przewijać szum, żeby znaleźć akcje, które faktycznie wymagają Approve/Reject.
|
||||||
|
**Fix**: rozważyć osobny widok/filtr dla `alert_only` w operator UI, albo
|
||||||
|
auto-acknowledge bez wejścia do tej samej kolejki co `container_restart`/`redeploy`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Crash-loop nie generuje `container_restart` — gap detekcja→akcja
|
||||||
|
|
||||||
|
**Data**: 2026-07-22
|
||||||
|
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
|
||||||
|
**Problem**: `node_exporter` na PIHA miał 1048 restartów i event `[high]`, ale
|
||||||
|
supervisor nie wygenerował żadnej akcji `container_restart` — crash-loop widoczny w
|
||||||
|
evencie nie przekłada się na akcję remediacyjną.
|
||||||
|
**Fix**: zbadać routing supervisora dla crash-loop sygnałów (`disk_pressure` i
|
||||||
|
`containers_not_running` mają jasne mapowanie na akcje w CLAUDE.md — crash-loop
|
||||||
|
najwyraźniej nie).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Telegram yes/no dla pending actions brakuje
|
||||||
|
|
||||||
|
**Data**: 2026-07-22
|
||||||
|
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
|
||||||
|
**Problem**: brain-watchdog ma `send_telegram` (używane dla alertów Prometheus), ale
|
||||||
|
nic nie łączy go z `actions/pending` — operator musi wejść do operator UI, żeby
|
||||||
|
zatwierdzić/odrzucić akcję, zamiast dostać yes/no bezpośrednio na Telegramie.
|
||||||
|
**Fix**: rozszerzyć brain-watchdog (albo osobny konsument) o powiadomienie z
|
||||||
|
przyciskami approve/reject per pending action.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `homeassistant5` na PIHA: `Exited (0)`, nie podnosi się mimo `restart: unless-stopped`
|
||||||
|
|
||||||
|
**Data**: 2026-07-22
|
||||||
|
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
|
||||||
|
**Problem**: kontener `homeassistant5` (legacy HA "ken", patrz cutover
|
||||||
|
`docs/backlog.md` sekcja "Cutover HA ken") zaobserwowany w stanie `Exited (0)` — exit
|
||||||
|
code 0 = czyste zatrzymanie, więc Docker `restart: unless-stopped` świadomie go nie
|
||||||
|
podnosi (to nie crash). Do zweryfikowania, czy to zamierzone wygaszenie z cutoveru czy
|
||||||
|
coś zatrzymało kontener niezamierzenie.
|
||||||
|
**Fix**: sprawdzić czy to spójne z planem wygaszenia `homeassistant5` (patrz cutover
|
||||||
|
HA ken, krok 2 „Wygaszenie homeassistant5") — jeśli tak, brak akcji; jeśli nie,
|
||||||
|
zbadać czemu wyszedł z kodem 0.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### lustro: `node_agent.py` wymaga ręcznego wypchnięcia do `/opt/homelab/deploy/node-agent` (repo-less)
|
||||||
|
|
||||||
|
**Data**: 2026-07-22
|
||||||
|
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
|
||||||
|
**Problem**: węzeł lustro nie ma repo/gita — `node_agent.py` (z fixami remediacji bez
|
||||||
|
SSH, commit `2dac154`) musi być ręcznie skopiowany do
|
||||||
|
`/opt/homelab/deploy/node-agent`, żeby dotrzeć na ten węzeł. Ryzyko rozjazdu
|
||||||
|
repo↔runtime identyczne jak przy innych repo-less węzłach.
|
||||||
|
**Fix**: ustalić mechanizm dystrybucji kodu node-agenta na węzły repo-less (rsync przy
|
||||||
|
deployu / paczka artefaktu / inny kanał) zamiast ręcznego kopiowania.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### 🔴 npm@VPS panel admina :81 publicznie osiągalny z internetu
|
### 🔴 npm@VPS panel admina :81 publicznie osiągalny z internetu
|
||||||
|
|
||||||
**Data**: 2026-07-10
|
**Data**: 2026-07-10
|
||||||
|
|
@ -420,6 +541,68 @@ Długoterminowo: `agent.sh new` powinien odmawiać jeśli żądana gałąź jest
|
||||||
|
|
||||||
## Zamknięte
|
## Zamknięte
|
||||||
|
|
||||||
|
### Publiczny bind `operator_ui.py:18180` na VPS bez autoryzacji — NAPRAWIONE (2026-07-22, commit `9a5c160`)
|
||||||
|
|
||||||
|
**Data**: 2026-07-22
|
||||||
|
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
|
||||||
|
**Było**: `operator_ui.py` (port 18180) nasłuchiwał na `0.0.0.0` na publicznym VPS
|
||||||
|
(`135.181.153.108`) bez żadnej autoryzacji. `curl` z zewnątrz do `/actions` zwracał
|
||||||
|
HTTP 200. `do_POST /action/mutate` przenosi akcje między stanami WŁĄCZNIE z
|
||||||
|
`"approved"` — dowolna osoba z internetu mogła zatwierdzić akcję remediacyjną.
|
||||||
|
Jedyne co chroniło do tej pory: executor nie umiał jeszcze wykonać akcji (brak SSH,
|
||||||
|
patrz wpis niżej) — przypadek, nie zabezpieczenie.
|
||||||
|
**Naprawione**: dual-bind wg wzorca fleet-prometheus — `127.0.0.1:18180:8080` +
|
||||||
|
`${TAILSCALE_BIND_IP}:18180:8080`, nowy `services/control-plane/env.example`.
|
||||||
|
Zweryfikowane po deployu: publiczny IP → HTTP 000, Tailscale → HTTP 200. Konsumenty
|
||||||
|
(node-agent VPS, materializer PIHA) nietknięte.
|
||||||
|
**Footgun zapamiętany**: brak `.env` → `docker compose` tylko OSTRZEGA i po cichu
|
||||||
|
wraca do bindu `0.0.0.0`, nie failuje — patrz wpis „deploy-local.sh: brak twardego
|
||||||
|
checka .env" w Aktywnych.
|
||||||
|
**Pozostaje osobno**: `operator_ui.py` nadal bez żadnej autoryzacji (bind zamknięty
|
||||||
|
chroni przed internetem, nie przed kimkolwiek w mesh Tailscale) — patrz Aktywne.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Remediacja floty bez SSH: executor→node-agent pull przez rsync — ZROBIONE (2026-07-22, commit `2dac154`), E2E potwierdzone 2026-07-23
|
||||||
|
|
||||||
|
**Data**: 2026-07-22 (implementacja), 2026-07-23 (pierwszy udany cykl E2E)
|
||||||
|
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
|
||||||
|
**Było**: łańcuch supervisor→pending→approved→running działał, ale executor próbował
|
||||||
|
`ssh oskar@{node} docker restart {container}` — kontener control-plane nie ma klienta
|
||||||
|
ssh, klucza, ani rozwiązywalnych nazw węzłów. Wykonanie zawsze failowało.
|
||||||
|
**Decyzja architektoniczna**: bez SSH w executorze (kontener na publicznym VPS z
|
||||||
|
powłoką na całą flotę = zły blast radius). Kierunek PULL: executor zleca (zapis
|
||||||
|
`actions/dispatch/<node>/<action_id>.json`), node-agent na docelowym węźle wykonuje
|
||||||
|
lokalnie przez własny `docker.sock`, wynik wraca eventem `action_result` przez
|
||||||
|
istniejący rsync-push. VPS nigdy nie inicjuje połączenia do węzła.
|
||||||
|
**Zabezpieczenia**: walidacja `node == self.node_name`; whitelist typów akcji =
|
||||||
|
`{container_restart}`; guard przed restartem samego node-agenta; brak wykonywania
|
||||||
|
dowolnych poleceń z payloadu; idempotencja (ponowne zlecenie = no-op). 183 testy.
|
||||||
|
**Potwierdzone w boju (2026-07-23)**: cykl `test-e2e-b` — Executing → Dispatched →
|
||||||
|
Completed w 31 sekund, `node_exporter` na PIHA realnie zrestartowany (uptime 30h→
|
||||||
|
minuty), zero połączeń SSH. Pierwszy w historii systemu pełny cykl remediacji.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Uprawnienia `actions/` na PIHA (uid/gid oskar 1004 vs kontener 1000) — NAPRAWIONE ręcznie (2026-07-23, poza repo)
|
||||||
|
|
||||||
|
**Data**: 2026-07-23
|
||||||
|
**Źródło**: sesja 2026-07-22/23 (`docs/sessions/2026-07-23-control-plane-remediation-e2e.md`)
|
||||||
|
**Było**: pierwszy test E2E remediacji bez SSH padał — agent logował `[Errno 13]
|
||||||
|
Permission denied: /opt/homelab/actions/dispatch` co cykl. `/opt/homelab/actions`
|
||||||
|
było `oskar:oskar drwxr-xr-x` (utworzone w maju), a działający wzorzec to
|
||||||
|
`/opt/homelab/events` = `oskar:pi drwxrwsr-x` (grupa `pi`, zapis grupowy, setgid).
|
||||||
|
Ten sam motyw uid/gid (host oskar 1004 vs kontener 1000) uderzył już czwarty raz —
|
||||||
|
patrz sekcja „Tech-debt: globalny porządek uid/gid/uprawnień we flocie".
|
||||||
|
**Naprawione (ręcznie, tylko na PIHA)**: `chgrp -R pi` + `chmod -R g+w` + `chmod g+s`
|
||||||
|
na `/opt/homelab/actions`. Executor zachował się poprawnie podczas awarii: po 300s
|
||||||
|
timeoutu przeniósł akcję do `failed` z czytelnym powodem, nic nie zawisło.
|
||||||
|
**Pozostaje osobno**: fix zastosowany TYLKO na PIHA — SOLARIA i lustro
|
||||||
|
niezweryfikowane; docelowy fix systemowy to node-agent tworzący
|
||||||
|
`dispatch/<node>/` z właściwymi prawami przy starcie — patrz Aktywne.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Supervisor: pętla zamrożona ~24h, healthy ale nie tika — NAPRAWIONE (2026-07-16, commit `409b583`)
|
### Supervisor: pętla zamrożona ~24h, healthy ale nie tika — NAPRAWIONE (2026-07-16, commit `409b583`)
|
||||||
|
|
||||||
**Data**: 2026-07-16
|
**Data**: 2026-07-16
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue