diff --git a/docs/astrololo_wymagania.xlsx b/docs/astrololo_wymagania.xlsx index 382c5fd..e8260d6 100644 Binary files a/docs/astrololo_wymagania.xlsx and b/docs/astrololo_wymagania.xlsx differ diff --git a/docs/log33-sekrety-i-rotacja.md b/docs/log33-sekrety-i-rotacja.md new file mode 100644 index 0000000..fbf1db8 --- /dev/null +++ b/docs/log33-sekrety-i-rotacja.md @@ -0,0 +1,171 @@ +# 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.** + +```bash +read -rs -p "Nowy ANTHROPIC_API_KEY: " NEW; echo +``` + +```bash +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 - +``` + +```bash +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. + +```bash +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 - +``` + +```bash +kubectl -n astrololo rollout restart deploy/presentation deploy/render +``` + +```bash +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ę. + +```bash +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 - +``` + +```bash +kubectl -n astrololo rollout restart deploy/data deploy/logic deploy/presentation deploy/render +``` + +```bash +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`](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**: + +```bash +kubectl -n astrololo get pods +``` + +```bash +curl -s -o /dev/null -w "aplikacja: %{http_code}\n" -u ":" 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.