diff --git a/astrololo/README-astroklient.md b/astrololo/README-astroklient.md new file mode 100644 index 0000000..bb03657 --- /dev/null +++ b/astrololo/README-astroklient.md @@ -0,0 +1,136 @@ +# Astroklient — wdrożenie warstwy pośredniej + +Pełne funkcje astrologiczne, bez generowania tekstu i bez administracji. +Trzeci produkt drabiny: **astrodemo** (dwie funkcje) → **astroklient** → **astrololo**. + +Host: `astroklient.czernobog.pl` · obraz `astrololo-astroklient` · port 8006. + +## Jak to jest odizolowane + +Trzy granice, w kolejności od najmocniejszej: + +**Obraz.** Plików ekranów, których ten produkt nie ma, NIE MA w obrazie — usuwa +je `usun.txt` przy budowaniu. Nie ma więc czego wyłączać ani czym odsłaniać: +uprawnienia liczą się z katalogu funkcji, a katalog składa się ze zgłoszeń +ekranów obecnych w obrazie. Test w repo aplikacji sprawdza to na złożonym +drzewie, razem z zaporą słownikową — po nieobecnych funkcjach nie zostaje nawet +słowo w komentarzu. + +**Udział NFS.** Własny `astrololo-klient`, osobny od produkcyjnego i od demo. +Pule kont izolują klientów od siebie, ale to izolacja PROGRAMOWA — opiera się na +poprawności mechanizmu pul. Granica na poziomie systemu plików nie zależy od +tego, czy w kodzie niczego nie przeoczono. Przy sprzątaniu demo nie da się przez +pomyłkę skasować cudzych danych, bo leżą gdzie indziej. + +**Warstwa danych i logiki.** Własna para `data-astroklient` + `logic-astroklient`, +z silnikiem własnym (permisywnym) — silnik B (AGPL) nie wchodzi do produktu +oddawanego klientom. + +--- + +## Krok 1 — udział `astrololo-klient` na TrueNAS + +```bash +midclt call sharing.nfs.create '{ + "path": "/mnt/Tank1/astrololo-klient", + "hosts": ["192.168.1.73", "192.168.1.80", "192.168.1.81", "192.168.1.82"], + "maproot_user": "root", "maproot_group": "root", "enabled": true +}' +midclt call service.restart nfs +``` + +**Wszystkie CZTERY węzły.** Klaster ma cztery (`kubectl get nodes -o wide`), a nie +trzy — pominięcie jednego daje `access denied by server` dopiero wtedy, gdy +scheduler tam coś postawi, czyli w losowym momencie tygodnie później. + +Kontrolę wyłapującą braki i literówki w adresach masz w +[README.md](README.md) → „Kontrola: czy każdy udział obejmuje każdy węzeł". + +Jeśli katalog jeszcze nie istnieje, utwórz dataset przed eksportem. + +## Krok 2 — konta klientów + +Każde konto to osobna pula, więc **rozdajesz konta, nie jedno hasło**. +Wszystkie mają ten sam poziom dostępu — konta rozdziela się dla rozdzielenia +plików, nie uprawnień. + +```bash +# skrypt w repo aplikacji; format hasha wspólny dla wszystkich usług +python services/presentation/scripts/make_user.py klientA # wypisze: scrypt$… +``` + +```bash +kubectl -n astrololo create secret generic astrololo-astroklient \ + --from-literal=ASTROKLIENT_USERS='klientA:scrypt$…,klientB:scrypt$…' \ + --from-literal=SESSION_SECRET="$(openssl rand -hex 32)" +``` + +`SESSION_SECRET` jest **własny**, nie ten z pełnej aplikacji ani z demo: trzy +produkty nie mają powodu uznawać nawzajem swoich sesji. Ciasteczko też ma własną +nazwę (`astroklient_sesja`), więc logowanie do jednego nie wyrzuca z drugiego. + +**Pod bez `SESSION_SECRET` celowo nie wstanie** — logowanie bez klucza podpisu +nie miałoby czym się bronić. + +### Unieważnienie dostępu + +Nie ma wolumenu stanu, więc nie ma licznika pokolenia sesji. Zamiast tego: + +- usunięcie konta z `ASTROKLIENT_USERS` albo zmiana jego hasła **natychmiast ubija + jego otwarte sesje** — odcisk poświadczenia w ciasteczku przestaje pasować, +- rotacja `SESSION_SECRET` wylogowuje wszystkich naraz. + +**Pula zostaje na udziale.** Odebranie dostępu nie kasuje plików klienta — +przestaje być tylko komu je pokazywać. + +### Dodanie konta później + +```bash +STARE=$(kubectl -n astrololo get secret astrololo-astroklient -o jsonpath='{.data.ASTROKLIENT_USERS}' | base64 -d) + +kubectl -n astrololo create secret generic astrololo-astroklient \ + --from-literal=ASTROKLIENT_USERS="${STARE},klientC:scrypt\$…" \ + --dry-run=client -o yaml | kubectl apply -f - + +kubectl -n astrololo rollout restart deploy/astroklient +``` + +## Krok 3 — wdrożenie + +Sekret i udział **muszą istnieć wcześniej**. Potem wystarczy merge do `master` — +ArgoCD zsynchronizuje, a image-updater będzie odtąd podbijał tag sam, bo obraz +jest dopisany do [`image-updater.yaml`](image-updater.yaml). + +## Sprawdzenie po wdrożeniu + +```bash +kubectl -n astrololo get pods -l app=astroklient -o wide +kubectl -n astrololo logs -l app=astroklient --tail=30 +curl -sk https://astroklient.czernobog.pl/ -o /dev/null -w '%{http_code}\n' # 303 → bramka logowania odpowiada +``` + +Po zalogowaniu w nawigacji ma być **sześć** pozycji: Horoskop, Interpretacje, +Kalendarz, Synastria, Sygnifikatory, Pliki. + +**Bez zalogowania każdy adres oddaje 303** — także nieistniejący. Bramka +logowania działa przed trasowaniem, więc niezalogowany nie wyczyta z kodów +odpowiedzi, które ekrany istnieją. Sprawdzanie nieobecnych adresów bez sesji nic +więc nie mówi: pokaże 303 dla wszystkiego, łącznie z `/nie-ma-takiego-ekranu`. + +Sprawdzenie ma sens dopiero Z SESJĄ, i wtedy nieobecne ekrany dają **404, nie +403** — odmowa z powodem byłaby informacją, że coś tam jest: + +```bash +CIASTKO='astroklient_sesja=' +for A in / /compile /settings /accounts /files; do + printf "%-12s %s\n" "$A" \ + "$(curl -sk -o /dev/null -w '%{http_code}' -H "Cookie: $CIASTKO" https://astroklient.czernobog.pl$A)" +done +# oczekiwane: / → 200, reszta → 404 +``` + +## Import puli klienta do pełnej aplikacji + +Pula to zwykły katalog na udziale — kopiuje się go tak samo jak w demo, +patrz [README-astrodemo.md](README-astrodemo.md) → „Import puli klienta". +Ścieżka źródłowa to `/mnt/Tank1/astrololo-klient/`. diff --git a/astrololo/astroklient-stack.yaml b/astrololo/astroklient-stack.yaml new file mode 100644 index 0000000..ed810af --- /dev/null +++ b/astrololo/astroklient-stack.yaml @@ -0,0 +1,134 @@ +# Własna warstwa danych i logiki dla astroklienta (PR 5). +# +# OSOBNY UDZIAŁ, nie osobny katalog na wspólnym. Pule kont izolują klientów od +# siebie w każdym wariancie, ale to jest izolacja PROGRAMOWA — opiera się na +# poprawności mechanizmu pul. Granica na poziomie systemu plików nie zależy od +# tego, czy w kodzie niczego nie przeoczono, i dlatego pliki klientów nie leżą +# ani przy bazach produkcyjnych, ani przy danych demonstracyjnych. +# +# Przy sprzątaniu demo nie da się przez pomyłkę skasować cudzych danych, bo są +# gdzie indziej. +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: data-astroklient + namespace: astrololo +spec: + replicas: 1 + selector: + matchLabels: { app: data-astroklient } + template: + metadata: + labels: { app: data-astroklient } + spec: + # Warstwa danych ciągnie pandas, a przez nią NumPy budowane z bazą + # x86-64-v2. Węzeł bez tych rozszerzeń nie uruchomi kontenera, a komunikat + # mówi o „baseline optimizations", nie o węźle. Etykieta jest wymagana, + # więc nowy, nieoznaczony węzeł daje czytelny Pending zamiast pętli + # restartów (patrz README.md, „Wymóg węzłów: rozszerzenia procesora"). + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: astrololo.czernobog.pl/cpu-x86-64-v2 + operator: In + values: ["true"] + automountServiceAccountToken: false + imagePullSecrets: [{ name: gitea-registry }] + containers: + - name: data + image: gitea.czernobog.pl/gitea/astrololo-data:latest + ports: [{ containerPort: 8002 }] + env: + - name: DATA_PROVIDER + value: "excel" + - name: EXCEL_DIR + value: "/app/data_files" + - name: CACHE_DIR + value: "/app/.cache" + - name: INTERNAL_TOKEN + valueFrom: + secretKeyRef: { name: astrololo-auth, key: INTERNAL_TOKEN } + - name: LINK_KEY_LOGIC_DATA + valueFrom: + secretKeyRef: { name: astrololo-link, key: LINK_KEY_LOGIC_DATA } + - name: LINK_ENCRYPTION_REQUIRED + value: "true" + volumeMounts: + - name: cache + mountPath: /app/.cache + - name: klient + mountPath: /app/data_files + resources: + requests: { cpu: "100m", memory: "256Mi" } + limits: { cpu: "500m", memory: "512Mi" } + volumes: + - name: cache + emptyDir: {} + - name: klient + nfs: + server: 192.168.1.34 + path: /mnt/Tank1/astrololo-klient +--- +apiVersion: v1 +kind: Service +metadata: + name: data-astroklient + namespace: astrololo +spec: + type: ClusterIP + selector: { app: data-astroklient } + ports: [{ port: 8002, targetPort: 8002 }] +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: logic-astroklient + namespace: astrololo +spec: + replicas: 1 + selector: + matchLabels: { app: logic-astroklient } + template: + metadata: + labels: { app: logic-astroklient } + spec: + automountServiceAccountToken: false + imagePullSecrets: [{ name: gitea-registry }] + containers: + - name: logic + image: gitea.czernobog.pl/gitea/astrololo-logic:latest + ports: [{ containerPort: 8001 }] + env: + - name: DATA_URL + value: "http://data-astroklient:8002" + # Silnik własny, permisywny. Silnik B (Swiss Ephemeris, AGPL) nie + # wchodzi do produktu oddawanego klientom. + - name: EPHEMERIS_ENGINE + value: "own" + - name: INTERNAL_TOKEN + valueFrom: + secretKeyRef: { name: astrololo-auth, key: INTERNAL_TOKEN } + - name: LINK_KEY_PRESENTATION_LOGIC + valueFrom: + secretKeyRef: { name: astrololo-link, key: LINK_KEY_PRESENTATION_LOGIC } + - name: LINK_KEY_LOGIC_DATA + valueFrom: + secretKeyRef: { name: astrololo-link, key: LINK_KEY_LOGIC_DATA } + - name: LINK_ENCRYPTION_REQUIRED + value: "true" + resources: + requests: { cpu: "100m", memory: "128Mi" } + limits: { cpu: "500m", memory: "512Mi" } +--- +apiVersion: v1 +kind: Service +metadata: + name: logic-astroklient + namespace: astrololo +spec: + type: ClusterIP + selector: { app: logic-astroklient } + ports: [{ port: 8001, targetPort: 8001 }] diff --git a/astrololo/astroklient.yaml b/astrololo/astroklient.yaml new file mode 100644 index 0000000..b7af674 --- /dev/null +++ b/astrololo/astroklient.yaml @@ -0,0 +1,64 @@ +# Astroklient — warstwa pośrednia (PR 4/5). +# +# Pełne funkcje astrologiczne, bez generowania tekstu i bez administracji. +# Obraz NIE ZAWIERA plików usuniętych wg usun.txt, więc nie ma tu czego wyłączać +# ani czym odsłaniać: to jest osobny produkt, a nie pełna aplikacja z zaciągniętą +# kurtyną. +# +# Konta pochodzą wyłącznie z sekretu (ASTROKLIENT_USERS), każde ma własną pulę +# plików na udziale astrololo-klient. Nie ma pliku kont, więc nie ma wolumenu +# stanu: sesje żyją w podpisanym ciasteczku. +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: astroklient + namespace: astrololo +spec: + replicas: 1 + selector: + matchLabels: { app: astroklient } + template: + metadata: + labels: { app: astroklient } + spec: + automountServiceAccountToken: false + imagePullSecrets: [{ name: gitea-registry }] + containers: + - name: astroklient + image: gitea.czernobog.pl/gitea/astrololo-astroklient:latest + ports: [{ containerPort: 8006 }] + env: + - name: LOGIC_URL + value: "http://logic-astroklient:8001" + - name: ASTROKLIENT_USERS + valueFrom: + secretKeyRef: { name: astrololo-astroklient, key: ASTROKLIENT_USERS } + - name: SESSION_SECRET + valueFrom: + secretKeyRef: { name: astrololo-astroklient, key: SESSION_SECRET } + - name: INTERNAL_TOKEN + valueFrom: + secretKeyRef: { name: astrololo-auth, key: INTERNAL_TOKEN } + - name: LINK_KEY_PRESENTATION_LOGIC + valueFrom: + secretKeyRef: { name: astrololo-link, key: LINK_KEY_PRESENTATION_LOGIC } + - name: LINK_ENCRYPTION_REQUIRED + value: "true" + - name: TRUST_PROXY + value: "true" + - name: RATE_LIMIT_PER_MIN + value: "60" + resources: + requests: { cpu: "50m", memory: "128Mi" } + limits: { cpu: "500m", memory: "384Mi" } +--- +apiVersion: v1 +kind: Service +metadata: + name: astroklient + namespace: astrololo +spec: + type: ClusterIP + selector: { app: astroklient } + ports: [{ port: 8006, targetPort: 8006 }] diff --git a/astrololo/image-updater.yaml b/astrololo/image-updater.yaml index cb82916..78a8b73 100644 --- a/astrololo/image-updater.yaml +++ b/astrololo/image-updater.yaml @@ -38,6 +38,11 @@ spec: # podbije, choćby CI go budowało — tak przez chwilę wisiał render na :latest. - alias: astrodemo imageName: gitea.czernobog.pl/gitea/astrololo-astrodemo + # BEZ TEGO WPISU nowa usługa NIE DEPLOYUJE SIĘ SAMA: obraz powstaje + # w rejestrze, ale kustomization zostaje na starym tagu i wygląda to na + # zepsute CI. Tak zawisł kiedyś render. + - alias: astroklient + imageName: gitea.czernobog.pl/gitea/astrololo-astroklient commonUpdateSettings: updateStrategy: newest-build writeBackConfig: diff --git a/astrololo/ingress.yaml b/astrololo/ingress.yaml index c736992..8ccccb4 100644 --- a/astrololo/ingress.yaml +++ b/astrololo/ingress.yaml @@ -116,3 +116,42 @@ spec: pathType: Prefix backend: service: { name: astrodemo, port: { number: 8005 } } +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: astroklient + namespace: astrololo + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.middlewares: astrololo-security-headers@kubernetescrd +spec: + tls: + - hosts: [astroklient.czernobog.pl] + secretName: astrololo-tls # ten sam certyfikat obejmuje wszystkie hosty (tls.yaml) + rules: + - host: astroklient.czernobog.pl + http: + paths: + - path: / + pathType: Prefix + backend: + service: { name: astroklient, port: { number: 8006 } } +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: astroklient-redirect + namespace: astrololo + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: web + traefik.ingress.kubernetes.io/router.middlewares: astrololo-redirect-https@kubernetescrd +spec: + rules: + - host: astroklient.czernobog.pl + http: + paths: + - path: / + pathType: Prefix + backend: + service: { name: astroklient, port: { number: 8006 } } diff --git a/astrololo/kustomization.yaml b/astrololo/kustomization.yaml index cb723d3..fb7a0a9 100644 --- a/astrololo/kustomization.yaml +++ b/astrololo/kustomization.yaml @@ -7,6 +7,8 @@ resources: - logic.yaml - astrodemo-stack.yaml # OSOBNA warstwa danych i logiki dla demo (PRE-29) - astrodemo.yaml # wersja demo (PRE-28) — sekret astrololo-astrodemo POZA repo + - astroklient-stack.yaml # OSOBNA warstwa danych i logiki dla astroklienta (PR 5) + - astroklient.yaml # warstwa pośrednia — sekret astrololo-astroklient POZA repo - presentation.yaml - render.yaml # składanie raportu PDF (PRE-24), osobny obraz z TeX Live - tls.yaml # certyfikat z własnego CA (wymaga cert-managera) @@ -22,3 +24,5 @@ images: newTag: 10970c57 - name: gitea.czernobog.pl/gitea/astrololo-astrodemo newTag: fd79513c + - name: gitea.czernobog.pl/gitea/astrololo-astroklient + newTag: 10970c57 diff --git a/astrololo/tls.yaml b/astrololo/tls.yaml index 6aba13f..cde531a 100644 --- a/astrololo/tls.yaml +++ b/astrololo/tls.yaml @@ -62,6 +62,7 @@ spec: # Jeden certyfikat na oba hosty. Osobny wymagałby osobnego sekretu i osobnego # odnawiania, a to ten sam klaster i ten sam wystawca. - astrodemo.czernobog.pl + - astroklient.czernobog.pl duration: 2160h # 90 dni renewBefore: 720h # 30 dni zapasu privateKey: { algorithm: ECDSA, size: 256, rotationPolicy: Always }