# 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.