Files
astrololo/docs/log33-sekrety-i-rotacja.md
gitea e3114f3e7c
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m31s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m29s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 11s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 8s
build / build (push) Successful in 14s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m31s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m28s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m28s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 12s
Testy / Kontrola składni wszystkich warstw (push) Successful in 8s
docs(bezpieczeństwo): runbook rotacji sekretów + poprawka nieaktualnej treści LOG-33
Wymaganie mówiło o restarcie „WSZYSTKICH TRZECH usług" przy zmianie INTERNAL_TOKEN.
Sprawdzenie żywych deploymentów pokazuje, że token czytają CZTERY: data, logic,
presentation ORAZ render (doszedł przy PRE-24, a wymaganie tego nie nadgoniło).
Kto zrobiłby rotację literalnie, zostawiłby render ze starym tokenem i usługa po
cichu przestałaby się dogadywać — dokładnie ta awaria, przed którą wymaganie
ostrzega. Treść w arkuszu poprawiona.

docs/log33-sekrety-i-rotacja.md:
- MACIERZ zależności wyliczona z deploymentów, nie z założeń. Wniosek praktyczny:
  klucze łącz rotuje się PARAMI usług (logic+data, presentation+logic,
  presentation+render), a nie całą czwórką — restartu wszystkiego wymaga tylko
  INTERNAL_TOKEN.
- Procedury per sekret, od najbezpieczniejszej do przećwiczenia (klucze LLM —
  dotykają tylko logiki, awaria widoczna i nieszkodliwa) po INTERNAL_TOKEN.
  Wszystkie zachowują pozostałe klucze przez odczyt z istniejącego sekretu —
  inaczej rotacja jednego skasowałaby resztę.
- Weryfikacja: sam „Running" NIE wystarcza (pody wstaną, nawet gdy warstwy się nie
  dogadują) — trzeba przeliczyć horoskop i wygenerować PDF, żeby dotknąć wszystkich
  łącz.
- Kroki hartowania z jasnym podziałem: co zrobione (automount tokenów — deploy #13),
  co wymaga węzła (szyfrowanie at-rest), co świadomie odłożone (Sealed Secrets —
  dziś sekrety NIE są w repo GitOps, więc ta zasada już jest spełniona; Sealed
  Secrets dodałyby odtwarzalność kosztem nowego pojedynczego punktu awarii).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-04 17:10:23 +02:00

7.1 KiB

LOG-33 — sekrety w spoczynku i procedura rotacji

Sekrety (APP_PASSWORD/APP_USERS, INTERNAL_TOKEN, klucze łącz AES, klucze API do dostawców LLM) trafiają do obiektów Secret w Kubernetesie, gdzie domyślnie są tylko zakodowane base64 — jawne dla każdego, kto przeczyta magazyn stanu k3s albo ma prawo odczytu sekretów w namespace.

Ten dokument opisuje: macierz rotacji (co restartować przy zmianie czego), procedury rotacji per sekret i kroki hartowania, które wymagają dostępu do węzła.


Macierz zależności — kto używa którego sekretu

Wyliczona z żywych deploymentów, nie z założeń:

Sekret / klucz Usługi, które go czytają Restart obejmuje
astrololo-auth / INTERNAL_TOKEN data, logic, presentation, render wszystkie cztery, równocześnie
astrololo-auth / APP_PASSWORD, APP_USERS presentation tylko presentation
astrololo-link / LINK_KEY_LOGIC_DATA logic, data para: logic + data
astrololo-link / LINK_KEY_PRESENTATION_LOGIC presentation, logic para: presentation + logic
astrololo-link / LINK_KEY_PRESENTATION_RENDER presentation, render para: presentation + render
astrololo-llm / OPENAI_API_KEY, ANTHROPIC_API_KEY logic tylko logic

UWAGA — częsty błąd. Wcześniejsza wersja tego wymagania mówiła o „wszystkich trzech usługach" przy INTERNAL_TOKEN. To już nieprawda: render (PRE-24) również go używa. Restart trzech zostawi render ze starym tokenem i usługa po cichu przestanie się dogadywać — dokładnie ta awaria, przed którą wymaganie ostrzega.

Klucze łącz są parami — rotacja jednego wymaga restartu tylko dwóch usług, nie całej czwórki. W trakcie wymiany para chwilowo się nie dogaduje (klucze muszą być zgodne po obu stronach łącza), dlatego restart obu naraz.


Procedury rotacji

A. Klucze LLM (najbezpieczniejsze do przećwiczenia)

Dotykają wyłącznie logiki, a awaria jest widoczna od razu i nieszkodliwa (niedostępna chmura, model lokalny działa dalej). Zacznij ćwiczenie od tego.

read -rs -p "Nowy ANTHROPIC_API_KEY: " NEW; echo
kubectl -n astrololo create secret generic astrololo-llm \
  --from-literal=OPENAI_API_KEY="$(kubectl -n astrololo get secret astrololo-llm -o jsonpath='{.data.OPENAI_API_KEY}' | base64 -d)" \
  --from-literal=ANTHROPIC_API_KEY="$NEW" \
  --dry-run=client -o yaml | kubectl apply -f -
unset NEW && kubectl -n astrololo rollout restart deploy/logic && kubectl -n astrololo rollout status deploy/logic

B. Klucz łącza (para usług)

Przykład dla LINK_KEY_PRESENTATION_RENDER. Pozostałe klucze zachowujemy bez zmian, odczytując je z istniejącego sekretu — inaczej skasowalibyśmy pozostałe łącza.

kubectl -n astrololo create secret generic astrololo-link \
  --from-literal=LINK_KEY_PRESENTATION_LOGIC="$(kubectl -n astrololo get secret astrololo-link -o jsonpath='{.data.LINK_KEY_PRESENTATION_LOGIC}' | base64 -d)" \
  --from-literal=LINK_KEY_LOGIC_DATA="$(kubectl -n astrololo get secret astrololo-link -o jsonpath='{.data.LINK_KEY_LOGIC_DATA}' | base64 -d)" \
  --from-literal=LINK_KEY_PRESENTATION_RENDER="$(openssl rand -hex 32)" \
  --dry-run=client -o yaml | kubectl apply -f -
kubectl -n astrololo rollout restart deploy/presentation deploy/render
kubectl -n astrololo rollout status deploy/presentation && kubectl -n astrololo rollout status deploy/render

C. INTERNAL_TOKEN (wszystkie cztery naraz)

Najbardziej wrażliwa rotacja: w trakcie usługi z różnymi tokenami odrzucają się nawzajem, więc restart musi objąć całą czwórkę.

kubectl -n astrololo create secret generic astrololo-auth \
  --from-literal=APP_PASSWORD="$(kubectl -n astrololo get secret astrololo-auth -o jsonpath='{.data.APP_PASSWORD}' | base64 -d)" \
  --from-literal=INTERNAL_TOKEN="$(openssl rand -hex 32)" \
  --dry-run=client -o yaml | kubectl apply -f -
kubectl -n astrololo rollout restart deploy/data deploy/logic deploy/presentation deploy/render
kubectl -n astrololo rollout status deploy/data && kubectl -n astrololo rollout status deploy/logic && kubectl -n astrololo rollout status deploy/presentation && kubectl -n astrololo rollout status deploy/render

Jeśli używasz kont imiennych (PRE-17), zamiast APP_PASSWORD zachowaj APP_USERS — patrz konta-i-audyt.md.

Weryfikacja po KAŻDEJ rotacji

Sam Running nie wystarczy — pody wstaną nawet, gdy warstwy się nie dogadują. Trzeba sprawdzić realny przelot przez wszystkie łącza:

kubectl -n astrololo get pods
curl -s -o /dev/null -w "aplikacja: %{http_code}\n" -u "<login>:<hasło>" https://astrololo.czernobog.pl/

Policz horoskop w przeglądarce (dotyka presentation→logic→data) i wygeneruj PDF (dotyka presentation→render). Dopiero to potwierdza, że wszystkie cztery klucze i token są spójne.


Hartowanie — kroki wymagające dostępu do węzła

1. Szyfrowanie sekretów w spoczynku (najważniejsze)

k3s z jednym serwerem trzyma stan w SQLite, nie w etcd, więc „szyfrowanie etcd" sprowadza się do wbudowanej funkcji k3s. Kroki są w sekcji „Co musisz zrobić sam" poniżej. Efekt: kopia pliku stanu albo snapshot VM przestaje być wyciekiem haseł i kluczy API.

Granica: klucz szyfrujący leży na tym samym serwerze. Chroni przed kradzieżą pliku/snapshotu — nie przed kimś, kto ma roota na węźle.

2. Ograniczenie tokenów kont serwisowych

Zrobione: automountServiceAccountToken: false we wszystkich czterech usługach (deploy #13). Żadna nie rozmawia z API Kubernetesa — sekrety wstrzykuje kubelet, nie pod — więc token był zbędny, a stanowił gotowy punkt wyjścia do klastra.

3. RBAC

Stan sprawdzony: zero RoleBindings w astrololo, cluster-admin tylko dla system:masters i dwóch kont Helma w kube-system. Nie ma rozdanych nadmiarowych uprawnień do cofania.

Realna ekspozycja to kubeconfig admina. Sensowny krok: osobny, ograniczony kubeconfig do codziennej pracy, a admin tylko wtedy, gdy naprawdę potrzebny.

4. Sealed Secrets / SOPS — świadomie ODŁOŻONE

Dziś sekrety tworzone są ręcznie i nie ma ich w repo GitOps — czyli zasada „nie wpisywać sekretów do repozytorium" jest już spełniona. Kosztem jest odtwarzalność: po utracie klastra nikt nie wie, co tam było.

Sealed Secrets pozwoliłoby trzymać je w gicie w postaci zaszyfrowanej, ale to zmiana filozofii i nowy pojedynczy punkt awarii: utrata klucza kontrolera = utrata wszystkich sekretów. Rekomendacja: dopiero po punkcie 1, i tylko jeśli zależy Ci na odtwarzalności klastra z gita.


Czego NIE robimy

  • Nie wpisujemy sekretów do logów. Dziennik audytowy (PRE-17) niesie wyłącznie metadane i liczby.
  • Nie wpisujemy sekretów do repo GitOps. Manifesty odwołują się do sekretów przez secretKeyRef i celowo nie zawierają wartości.
  • Nie zostawiamy haseł w historii powłoki — stąd read -rs i odczyt istniejących wartości przez kubectl … | base64 -d zamiast wpisywania ich ponownie.