docs(bezpieczeństwo): runbook rotacji sekretów + poprawka nieaktualnej treści LOG-33
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
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
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>
This commit was merged in pull request #59.
This commit is contained in:
Binary file not shown.
@@ -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 "<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.
|
||||
Reference in New Issue
Block a user