docs(kb-site): przekaz ACCESS_TOKEN generatorowi w procedurze publikacji
Poprzedni commit nauczyl gen_pages.py dopisywac token do linkow, ale nic go nie podawalo — publikacja poszlaby stara sciezka i dalaby build z golymi linkami, czyli stan sprzed fiksa. kb-site nie ma skryptu deployu: generator wolany jest wylacznie recznie z runbooka (kroki 2 i 7), wiec to tam token musi wejsc. Zrodlo tokenu: /opt/homelab/config/kb-site/.env na wezle GENERUJACYM (SATURN/SOLARIA), nie na PIHA. To swiadome odstepstwo od konwencji config/<serwis>/ z CLAUDE.md — plik trzyma zwykle sekrety wezla, ktory serwis uruchamia, a ten token jest potrzebny tam, gdzie serwis sie generuje. Kontener nginx dalej nie ma zadnej konfiguracji ani sekretow; odnotowane w service.yaml i env.example, zeby nikt nie szukal .env na PIHA. Token idzie zmienna srodowiskowa (set -a; . plik; set +a), nie flaga --access-token: argument z linii polecen laduje w historii shella i jest widoczny w ps dla kazdego uzytkownika wezla. Lancuch publikacji z kroku 7 dostal dwa nowe ogniwa przed scp: test -n "$ACCESS_TOKEN" (pusty token = build nieklikalny) oraz grep -q 'key=' w index.html (token byl, ale nie dojechal do generatora). Oba zatrzymuja publikacje tak samo jak --check. Krok 6 weryfikuje teraz wlasciwa rzecz: wyciaga href ze spisu i pobiera GO, zamiast recznie sklejac URL — czyli testuje to, co faktycznie bylo zepsute. Doszedl tez negatywny test bramki (bez tokenu ma NIE byc 200). Tabela problemow: "index sie otwiera, ale klikniecie daje 403" (build bez tokenu) i "403 takze z tokenem" (rotacja tokenu w NPM rozjechana z plikiem — stary build zostaje z martwym tokenem w kazdym linku). kb/services/kb-site.md (public) — sekcja Access: token siedzi teraz w tresci kazdej serwowanej strony, wiec jedna zapisana strona wydaje go w calosci. Model zagrozen bez zmian (URL wejsciowy zawsze go niosl), ale warto, zeby dokument mowil to wprost obok zdania "to obscurity, not access control". Test: sekwencje z krokow 2 i 7 przepuszczone na symulowanym pliku tokenu (prod /opt/homelab nietkniety) — token obecny: Token: TAK, 4x key= w index.html, lancuch dochodzi do tar; token pusty: staje na pierwszym ogniwie, brak tgz; build bez tokenu przy ustawionej zmiennej: staje na grep, brak tgz. check_okf.py exit 0, gen_pages --check exit 0, service.yaml parsuje sie. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f5c6f3b651
commit
bb3792d219
|
|
@ -28,6 +28,15 @@ a vhost musi już odpowiadać na porcie 80, zanim zamówisz cert.
|
|||
przekierowany na PIHA — tak samo jak dla `vikunja.okit.pl`.
|
||||
- `scripts/npm/.env` wypełniony poświadczeniami (patrz `kb/runbooks/npm-api.md`).
|
||||
- Strefa `okit.pl` w Cloudflare (patrz `kb/phases/okit-cloudflare.md`).
|
||||
- **Token bramki dostępny na węźle, z którego generujesz** (SATURN/SOLARIA — nie
|
||||
na PIHA): plik `/opt/homelab/config/kb-site/.env` z jedną linią
|
||||
`ACCESS_TOKEN=<token>`, `chmod 600`. To wyjątek od konwencji z CLAUDE.md:
|
||||
`config/<serwis>/` trzyma zwykle sekrety węzła, który serwis *uruchamia*, a
|
||||
ten token jest potrzebny tam, gdzie serwis się *generuje*. Kontener nginx na
|
||||
PIHA nadal nie ma żadnej konfiguracji ani sekretów.
|
||||
|
||||
Wartość to ten sam token, co w advanced config proxy hosta w NPM. Żyje
|
||||
wyłącznie w bazie NPM i w tym pliku — **nigdy w repo**.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -57,11 +66,44 @@ docker volume ls | grep kb-site # kb-site_kb-site_content
|
|||
Generujemy w checkoucie repo, na węźle z którego pracujesz — nie na PIHA.
|
||||
|
||||
```bash
|
||||
set -a; . /opt/homelab/config/kb-site/.env; set +a # ACCESS_TOKEN
|
||||
test -n "$ACCESS_TOKEN" || echo "BRAK TOKENU — nie publikuj tego builda"
|
||||
|
||||
python3 scripts/kb/gen_pages.py --base-url https://kb-e2a24af3.okit.pl
|
||||
python3 scripts/kb/gen_pages.py --check
|
||||
echo $? # 0 = czysto, 1 = trafienia
|
||||
```
|
||||
|
||||
### Token w linkach
|
||||
|
||||
Generator czyta `ACCESS_TOKEN` ze środowiska i dopisuje `?key=<token>` do
|
||||
**każdego linku wewnętrznego** (karty na spisie, linki między dokumentami,
|
||||
powrót „All documents"). Bez tego bramka NPM odbija 403 przy każdym kliknięciu
|
||||
i działa wyłącznie ręcznie sklejony URL — strona jest wtedy technicznie żywa,
|
||||
ale nie da się po niej chodzić.
|
||||
|
||||
Token podajemy zmienną środowiskową, nie flagą `--access-token`: argument
|
||||
z linii poleceń ląduje w historii shella i jest widoczny w `ps` dla każdego
|
||||
użytkownika węzła. Flaga istnieje do jednorazowych generacji testowych.
|
||||
|
||||
Sanity check tuż po generacji — `Token: TAK` w wyjściu generatora, a w HTML:
|
||||
|
||||
```bash
|
||||
grep -c 'key=' build/kb-site/index.html # > 0; 0 = build bez tokenu
|
||||
```
|
||||
|
||||
Generacja bez tokenu **nie jest błędem** — to tryb lokalnego podglądu
|
||||
(`build/` do przejrzenia w przeglądarce, bez publikacji). Nie wolno jej tylko
|
||||
wgrać na PIHA.
|
||||
|
||||
Token nie jest sekretem chroniącym treść — jest zaporą przed przypadkowym
|
||||
trafieniem. Po tej zmianie siedzi w treści każdej serwowanej strony, więc jedna
|
||||
zapisana strona wydaje go w całości. To nie zmienia modelu zagrożeń (URL wejściowy
|
||||
zawsze go niósł), ale przesądza, że bramką prywatności jest `--check` i pole
|
||||
`visibility`, nie ten token.
|
||||
|
||||
### Kontrola wycieków
|
||||
|
||||
`--check` skanuje **wygenerowany HTML**, nie źródła: adresy IP (RFC1918,
|
||||
Tailscale 100.64/10, publiczne v4/v6), porty 1024-65535, ścieżki `/home/`
|
||||
i `/opt/`, długie hexy i ciągi base64 wyglądające na tokeny.
|
||||
|
|
@ -190,13 +232,29 @@ wiersz certyfikatu (szczegóły w `kb/runbooks/npm-api.md`).
|
|||
## 6. Weryfikacja końcowa
|
||||
|
||||
```bash
|
||||
curl -sI https://kb-e2a24af3.okit.pl/ | head -1 # 200
|
||||
curl -s https://kb-e2a24af3.okit.pl/ | grep -c 'class="cards"' # spis jest
|
||||
curl -s https://kb-e2a24af3.okit.pl/subsystems/observer.html | tail -5 # stopka: data + commit
|
||||
set -a; . /opt/homelab/config/kb-site/.env; set +a
|
||||
BASE=https://kb-e2a24af3.okit.pl
|
||||
|
||||
curl -sI "$BASE/?key=$ACCESS_TOKEN" | head -1 # 200
|
||||
curl -s "$BASE/?key=$ACCESS_TOKEN" | grep -c 'class="cards"' # spis jest
|
||||
curl -s "$BASE/subsystems/observer.html?key=$ACCESS_TOKEN" | tail -5 # stopka: data + commit
|
||||
|
||||
# bramka faktycznie broni — bez tokenu ma NIE być 200
|
||||
curl -sI "$BASE/" | head -1
|
||||
|
||||
# przejście po linku ze spisu, tak jak zrobi to przeglądarka:
|
||||
# wyciągnij pierwszy href z indexu i pobierz go bez dopisywania niczego ręcznie
|
||||
LINK=$(curl -s "$BASE/?key=$ACCESS_TOKEN" \
|
||||
| grep -o 'href="[^"]*key=[^"]*"' | head -1 | cut -d'"' -f2)
|
||||
curl -sI "$BASE/$LINK" | head -1 # 200
|
||||
```
|
||||
|
||||
W przeglądarce: `https://kb-e2a24af3.okit.pl` — spis pogrupowany per type, kłódka bez
|
||||
ostrzeżeń, wejście w dowolną kartę, powrót linkiem „All documents".
|
||||
Ostatni test jest właściwą weryfikacją tej bramki: sprawdza, że link *wygenerowany
|
||||
przez generator* przechodzi przez NPM, a nie że ręcznie sklejony URL działa.
|
||||
|
||||
W przeglądarce: `https://kb-e2a24af3.okit.pl/?key=<token>` — spis pogrupowany per
|
||||
type, kłódka bez ostrzeżeń, wejście w dowolną kartę **kliknięciem** (nie przez
|
||||
pasek adresu), powrót linkiem „All documents", i z powrotem w inną kartę.
|
||||
|
||||
Kontrola treści (ręczna, jednorazowa po pierwszej publikacji): przejrzyj spis
|
||||
i potwierdź, że nie ma tam nic, czego nie chcesz mieć w internecie. Generator
|
||||
|
|
@ -209,8 +267,12 @@ pilnuje pola `visibility`, ale to człowiek decyduje, co dostaje `public`.
|
|||
Po każdej zmianie w `kb/**/*.md`, która dotyczy dokumentów `public`:
|
||||
|
||||
```bash
|
||||
python3 scripts/kb/gen_pages.py --base-url https://kb-e2a24af3.okit.pl \
|
||||
set -a; . /opt/homelab/config/kb-site/.env; set +a # ACCESS_TOKEN
|
||||
|
||||
test -n "$ACCESS_TOKEN" \
|
||||
&& python3 scripts/kb/gen_pages.py --base-url https://kb-e2a24af3.okit.pl \
|
||||
&& python3 scripts/kb/gen_pages.py --check \
|
||||
&& grep -q 'key=' build/kb-site/index.html \
|
||||
&& tar -czf /tmp/kb-site.tgz -C build/kb-site . \
|
||||
&& scp /tmp/kb-site.tgz piha:/tmp/kb-site.tgz \
|
||||
&& ssh piha 'docker run --rm -v kb-site_kb-site_content:/content -v /tmp:/src:ro \
|
||||
|
|
@ -219,7 +281,9 @@ python3 scripts/kb/gen_pages.py --base-url https://kb-e2a24af3.okit.pl \
|
|||
&& rm /tmp/kb-site.tgz
|
||||
```
|
||||
|
||||
Łańcuch na `&&` jest celowy: `--check` z exit 1 zatrzymuje publikację.
|
||||
Łańcuch na `&&` jest celowy — każde ogniwo zatrzymuje publikację: pusty
|
||||
`ACCESS_TOKEN` (build byłby nieklikalny), `--check` z exit 1 (wyciek), brak
|
||||
`key=` w spisie (token był pusty albo nie dojechał do generatora).
|
||||
|
||||
Kontener nie wymaga restartu — nginx czyta wolumen na bieżąco.
|
||||
|
||||
|
|
@ -233,6 +297,8 @@ Kontener nie wymaga restartu — nginx czyta wolumen na bieżąco.
|
|||
| stara strona po publikacji | pominięte `rm -rf /content/*` | powtórz krok 3 w całości |
|
||||
| dokument nie pojawia się na stronie | brak `visibility: public` albo niepoprawny frontmatter (fail-closed) | `python3 scripts/kb/check_okf.py`, potem regeneracja |
|
||||
| link renderuje się jako tekst z `[private]` | cel jest prywatny albo nie istnieje | tak ma być — to nie błąd |
|
||||
| index otwiera się z tokenem, ale **kliknięcie w kartę daje 403** | build wygenerowany bez `ACCESS_TOKEN` — linki nie niosą `?key=` | ustaw token (krok 0) i powtórz kroki 2-3; `grep -c 'key=' build/kb-site/index.html` |
|
||||
| 403 także na URL z tokenem | token w NPM został wymieniony i rozjechał się z `/opt/homelab/config/kb-site/.env` | zsynchronizuj plik z advanced config w NPM, przegeneruj i opublikuj — stary build zostaje z martwym tokenem w każdym linku |
|
||||
| `--check` zapala nowe trafienie | do dokumentu `public` wjechał adres/ścieżka/token | popraw źródło; whitelist tylko świadomie |
|
||||
| cert nie schodzi | rekord A nie propagował, proxy Cloudflare włączone, albo port 80 niedostępny | krok 4, potem 5b ponownie |
|
||||
|
||||
|
|
|
|||
|
|
@ -72,10 +72,18 @@ out precisely the readers the token exists to let in. It also worked against its
|
|||
own goal: a crawler blocked before it can read the page never sees the `noindex`
|
||||
tag, and a search engine may still list a bare URL it was forbidden to fetch.
|
||||
|
||||
Because the gate is enforced on the URL, every internal link the generator emits
|
||||
carries the token too — otherwise the entry page would open and every click from
|
||||
it would return 403. The token is therefore a generation-time input, supplied
|
||||
from outside the repository; a build made without it is a local preview, not a
|
||||
publishable site.
|
||||
|
||||
This is obscurity, not access control. A URL token is written to access logs,
|
||||
browser history and outbound `Referer` headers, so anyone who obtains a link
|
||||
keeps it; nothing here resists a deliberate attacker. The leak gate above, not
|
||||
this, is what keeps private material off the site.
|
||||
browser history and outbound `Referer` headers, and — since the links carry it —
|
||||
into the body of every served page, so a single saved page or shared screenshot
|
||||
of the address bar hands it over in full. Anyone who obtains a link keeps it;
|
||||
nothing here resists a deliberate attacker. The leak gate above, not this, is
|
||||
what keeps private material off the site.
|
||||
|
||||
## Operations
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,27 @@
|
|||
# kb-site has NO configuration and NO secrets.
|
||||
# The kb-site CONTAINER has no configuration and no secrets.
|
||||
#
|
||||
# The port bind (8250:80) is static and the content lives in the
|
||||
# kb-site_kb-site_content Docker volume, generated from kb/ by
|
||||
# scripts/kb/gen_pages.py. This file exists only to keep the
|
||||
# services/<service>/ layout from CLAUDE.md complete — there is nothing to
|
||||
# copy to .env.
|
||||
# scripts/kb/gen_pages.py. Nothing here is copied to a .env on PIHA.
|
||||
#
|
||||
# The public address is a generator argument, not an env var:
|
||||
# python3 scripts/kb/gen_pages.py --base-url https://kb-e2a24af3.okit.pl
|
||||
#
|
||||
# ---------------------------------------------------------------------------
|
||||
# GENERATION-TIME secret — belongs on the node you generate from
|
||||
# (SATURN/SOLARIA), NOT on PIHA:
|
||||
#
|
||||
# /opt/homelab/config/kb-site/.env chmod 600
|
||||
# ACCESS_TOKEN=<token bramki NPM>
|
||||
#
|
||||
# The site sits behind a query-parameter gate in NPM. gen_pages.py reads
|
||||
# ACCESS_TOKEN from the environment and appends ?key=<token> to every internal
|
||||
# link; without it every click on the published site returns 403 and only a
|
||||
# hand-assembled URL works.
|
||||
#
|
||||
# Same value as the advanced config of the proxy host in NPM. It lives in the
|
||||
# NPM database and in that file only — never in this repository. Prefer the
|
||||
# environment variable over the --access-token flag: a command-line argument
|
||||
# lands in shell history and is visible in `ps`.
|
||||
#
|
||||
# Full procedure: kb/runbooks/kb-site-deploy.md (steps 0, 2 and 7).
|
||||
|
|
|
|||
|
|
@ -23,7 +23,10 @@ service:
|
|||
paths:
|
||||
- kb-site_kb-site_content
|
||||
runtime:
|
||||
config_files: [] # no .env — the port bind is static, no secrets
|
||||
# The CONTAINER has no .env and no secrets — the port bind is static. The
|
||||
# access token below is not a container env var: it is consumed by the
|
||||
# generator on the node that builds the site, never by nginx on PIHA.
|
||||
config_files: []
|
||||
env_vars: []
|
||||
content:
|
||||
# Only kb/ documents with `visibility: public` are published; the generator
|
||||
|
|
@ -31,3 +34,11 @@ service:
|
|||
generator: scripts/kb/gen_pages.py
|
||||
leak_check: scripts/kb/gen_pages.py --check # must pass before publishing
|
||||
source: kb/**/*.md
|
||||
# NPM enforces a ?key=<token> gate in front of this site, so every internal
|
||||
# href must carry that token or a click lands on 403. The generator reads it
|
||||
# from ACCESS_TOKEN; the value lives in the NPM database and in
|
||||
# /opt/homelab/config/kb-site/.env ON THE GENERATING NODE (saturn/solaria) —
|
||||
# never in this repository. Omitting it yields a valid preview build that
|
||||
# must not be published. See kb/runbooks/kb-site-deploy.md.
|
||||
access_token_env: ACCESS_TOKEN
|
||||
access_token_file: /opt/homelab/config/kb-site/.env # generating node, not piha
|
||||
|
|
|
|||
Loading…
Reference in a new issue