From 5a4190faaaca75dce2daf6808ca953b4112a07aa Mon Sep 17 00:00:00 2001 From: ALI YESILKAYA Date: Tue, 7 Jul 2026 11:25:56 +0200 Subject: [PATCH] =?UTF-8?q?feat(auth):=20Authentik=20en=20hub=20utilisateu?= =?UTF-8?q?r=20public=20=E2=80=94=20SSO=20de=20bout=20en=20bout=20(#82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'entrée unique devient la page « My applications » d'Authentik (kaya.freeboxos.fr:9081) : login → tuiles des apps autorisées → SSO vers Guacamole (:9080). Plus de comptes locaux Guacamole. - Services LoadBalancer dédiés (MetalLB .201 Authentik / .202 Guacamole) : le DNAT par port ne peut pas router par Host derrière Traefik :80 - gateway : DNAT 9081→.201:9000 et 9080→.202:8080 + rate-limit par VIP - Guacamole : endpoints OIDC sur l'URL publique canonique (LAN inclus, hairpin Freebox) + retour au SSO direct (openid prioritaire) - doc : section « Accès externe » réécrite (hub, chaîne, config provider) Co-authored-by: Claude Fable 5 --- CLAUDE.md | 2 +- admin/k8s/auth-portal.md | 52 ++++++++++++------- .../roles/gateway/templates/nftables.conf.j2 | 20 ++++--- k8s/apps/authentik/service.yaml | 18 +++++++ k8s/apps/guacamole/deployment.yaml | 22 ++++---- k8s/apps/guacamole/ingress.yaml | 2 +- k8s/apps/guacamole/service.yaml | 16 ++++++ 7 files changed, 92 insertions(+), 40 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2ca449f..c0e58ac 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,7 +21,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - ✅ SearXNG opérationnel — `searxng.lab.local`, namespace `ai`, **méta-moteur de recherche web self-host** (privé, in-cluster) consommé par l'outil `web_search` d'Asa (API JSON). Manifests `k8s/apps/searxng/` - ✅ Ghostfolio opérationnel — `ghostfolio.lab.local`, namespace `ai`, suivi de portefeuille (PostgreSQL s01 + Redis in-cluster) - ✅ hermes-auto-improve — analyse quotidienne de `admin/` par Hermes à 22h (n8n → trigger :9095), PR sur `hermes/daily-work` -- 🆕 Annuaire + portail consoles (2026-07-06) — **Authentik** (`auth.lab.local`, IdP OIDC) + **Guacamole** (`portail.lab.local`, consoles SSH web vers s01/g01 via user `console` sans sudo), namespace `auth`. Config initiale (admin Authentik, provider OIDC, connexions) : voir `admin/k8s/auth-portal.md`. **Accès Internet** : `kaya.freeboxos.fr:9080` (comptes locaux Guacamole, Authentik non exposé, rate-limit nftables) +- 🆕 Annuaire + portail consoles (2026-07-06) — **Authentik** (`auth.lab.local`, IdP OIDC) + **Guacamole** (`portail.lab.local`, consoles SSH web vers s01/g01 via user `console` sans sudo), namespace `auth`. Config initiale (admin Authentik, provider OIDC, connexions) : voir `admin/k8s/auth-portal.md`. **Accès Internet** : Authentik = hub utilisateur (`kaya.freeboxos.fr:9081`, page « My applications ») → SSO vers Guacamole (`:9080`) — VIPs MetalLB `.201`/`.202`, rate-limit nftables, URL canonique publique pour tous (hairpin LAN) - 🆕 WireGuard (2026-07-06) — serveur VPN prêt sur s01 (pairs `full`/`portal`, filtrage nftables) mais **dormant** : pas de redirection Freebox `51820/udp` (choix simplicité 2026-07-07, accès portail via `:9080` direct). Voir `admin/infra/wireguard.md` - ⏳ WOL compute nodes non configuré (BIOS) — allumage manuel requis diff --git a/admin/k8s/auth-portal.md b/admin/k8s/auth-portal.md index a2bd079..379ec38 100644 --- a/admin/k8s/auth-portal.md +++ b/admin/k8s/auth-portal.md @@ -85,22 +85,35 @@ kubectl -n auth create secret generic guacamole-secret \ 3. Settings → Users : les comptes Authentik apparaissent après leur **premier login** (`POSTGRESQL_AUTO_CREATE_ACCOUNTS=true`) → leur assigner les connexions (READ). -## Accès externe (Internet) — `kaya.freeboxos.fr:9080` +## Accès externe (Internet) — Authentik en hub utilisateur -Le portail est joignable depuis Internet **sans VPN** (choix assumé 2026-07-07, -simplicité d'abord — le VPN WireGuard reste dispo, cf. `admin/infra/wireguard.md`) : +**Authentik est la porte d'entrée** (page « My applications » : une tuile par app +autorisée), le SSO fait le reste. Choix assumé 2026-07-07 : exposition directe sans +VPN, simplicité d'abord (WireGuard reste dispo, cf. `admin/infra/wireguard.md`). -- **Chaîne** : `kaya.freeboxos.fr:9080` → redirection Freebox `9080/tcp → funk-s01` - → DNAT nftables `→ 192.168.10.200:80` (Traefik) → IngressRoute (host - `kaya.freeboxos.fr` accepté) → Guacamole -- **Auth externe = comptes locaux Guacamole** (Settings → Users → New user + mot de - passe fort + cocher ses connexions). **Authentik n'est PAS exposé** : le SSO ne - fonctionne que du LAN (le lien « OpenID » échoue depuis Internet — normal). -- **Garde-fous** : rate-limit nftables (30 connexions neuves/min) + anti-bruteforce - Guacamole (5 échecs → ~5 min) + mots de passe forts obligatoires. ⚠️ HTTP **non - chiffré** : mots de passe des comptes locaux interceptables en théorie — ne pas - réutiliser un mot de passe important, et envisager TLS (vrai domaine + Let's - Encrypt) ou l'option TOTP de Guacamole si l'usage s'installe. +| URL publique | Service | Chaîne | +|---|---|---| +| `http://kaya.freeboxos.fr:9081` | **Authentik** (hub + login) | Freebox `9081/tcp → s01` → DNAT → VIP MetalLB `192.168.10.201:9000` | +| `http://kaya.freeboxos.fr:9080` | Guacamole (via tuile SSO) | Freebox `9080/tcp → s01` → DNAT → VIP `192.168.10.202:8080` | + +- **URL canonique unique** : les endpoints OIDC de Guacamole pointent sur + `kaya.freeboxos.fr` → **tout le monde, LAN inclus**, doit utiliser ces URLs (le LAN + repasse par la Freebox, hairpin NAT). `portail.lab.local` affiche encore l'UI mais + le SSO ramène sur l'URL publique. +- Les VIPs dédiées **contournent Traefik** : un DNAT par port ne peut pas router par + Host sur l'entrypoint unique :80. +- **Un seul compte par personne (Authentik)** — plus de comptes locaux Guacamole. + Parcours ami : `kaya.freeboxos.fr:9081` → login → tuile « Portail consoles » → + console. Nouvelle app plus tard = nouveau provider + tuile, zéro compte en plus. +- **Garde-fous** : rate-limit nftables (30 connexions neuves/min par service) + + protections applicatives (Authentik : flows/lockout ; Guacamole : ban 5 échecs). + ⚠️ HTTP **non chiffré** : mots de passe interceptables en théorie — mots de passe + dédiés, MFA/TOTP Authentik recommandé pour durcir, TLS (vrai domaine + Let's + Encrypt) si l'usage s'installe. + +Config côté Authentik (UI) : provider `guacamole` → **Redirect URI +`http://kaya.freeboxos.fr:9080/`** ; application « Portail consoles » → **Launch URL +`http://kaya.freeboxos.fr:9080/`**. ## Pièges / notes @@ -110,12 +123,11 @@ simplicité d'abord — le VPN WireGuard reste dispo, cf. `admin/infra/wireguard serveur→serveur de Guacamole ne dépend pas de la résolution `lab.local` des pods. L'issuer, lui, reste `http://auth.lab.local/...` (doit matcher le claim `iss` des tokens émis via le navigateur). -3. **`EXTENSION_PRIORITY="*, openid"`** : formulaire local affiché en premier (requis - pour les comptes locaux de l'accès Internet), lien « OpenID » en dessous pour le SSO - LAN. La variante `"openid, *"` (redirection SSO immédiate, testée puis abandonnée - le 2026-07-07) est incompatible avec l'accès externe sans exposer Authentik. - NB : le compte SSO `Alkatrazz` a « Administer system » — admin possible par les - deux chemins. +3. **`EXTENSION_PRIORITY="openid, *"`** : arriver sur Guacamole = redirection + immédiate vers Authentik (le hub). Conséquence : `guacadmin` inaccessible — + l'admin passe par le compte SSO `Alkatrazz` (« Administer system »). Dépannage si + Authentik est HS : remettre `"*, openid"` dans le deployment + re-sync (le + formulaire local revient). 4. **Le slug de l'application Authentik doit être exactement `guacamole`** : Authentik le génère depuis le nom (« Portail consoles » → `portail-consoles`) et l'issuer/JWKS deviennent faux → log Guacamole `Non 200 status code (404) ... /o/guacamole/jwks/` diff --git a/ansible/roles/gateway/templates/nftables.conf.j2 b/ansible/roles/gateway/templates/nftables.conf.j2 index 75e198a..5ac64d2 100644 --- a/ansible/roles/gateway/templates/nftables.conf.j2 +++ b/ansible/roles/gateway/templates/nftables.conf.j2 @@ -77,12 +77,14 @@ table inet filter { ip daddr 192.168.10.20 tcp dport { 7777, 8888 } ct state new,established,related accept ip daddr 192.168.10.20 udp dport 7777 ct state new,established,related accept - # Portail public — trafic Internet DNATé (9080 → Traefik :80). - # Rate-limit sur les connexions neuves : anti-bruteforce/scan réseau, - # en plus de la protection intégrée de Guacamole. Le LAN domestique n'est - # pas concerné (accepté par la règle 192.168.1.0/24 plus haut). - ip daddr 192.168.10.200 tcp dport 80 ct state new limit rate 30/minute burst 15 packets accept - ip daddr 192.168.10.200 tcp dport 80 ct state established,related accept + # Hub public Authentik (:9081) + portail Guacamole (:9080) — trafic Internet + # DNATé vers les VIPs MetalLB dédiées. Rate-limit sur les connexions neuves : + # anti-bruteforce/scan réseau, en plus des protections applicatives. Le LAN + # domestique n'est pas concerné (règle 192.168.1.0/24 plus haut, hairpin + # Freebox inclus). + ip daddr 192.168.10.201 tcp dport 9000 ct state new limit rate 30/minute burst 15 packets accept + ip daddr 192.168.10.202 tcp dport 8080 ct state new limit rate 30/minute burst 15 packets accept + ip daddr { 192.168.10.201, 192.168.10.202 } ct state established,related accept # WireGuard → lab : filtrage par pair (host_vars wireguard_peers) {% for peer in wireguard_peers | selectattr('access', 'equalto', 'full') | list %} @@ -111,8 +113,10 @@ table ip nat { iif {{ wan_interface }} tcp dport { 7777, 8888 } dnat to 192.168.10.20 iif {{ wan_interface }} udp dport 7777 dnat to 192.168.10.20:7777 - # Portail Guacamole public — kaya.freeboxos.fr:9080 (redirection Freebox → s01) - iif {{ wan_interface }} tcp dport 9080 dnat to 192.168.10.200:80 + # Hub Authentik + portail Guacamole publics (redirections Freebox → s01) + # kaya.freeboxos.fr:9081 → Authentik (VIP .201) ; :9080 → Guacamole (VIP .202) + iif {{ wan_interface }} tcp dport 9081 dnat to 192.168.10.201:9000 + iif {{ wan_interface }} tcp dport 9080 dnat to 192.168.10.202:8080 } chain postrouting { diff --git a/k8s/apps/authentik/service.yaml b/k8s/apps/authentik/service.yaml index 7056a61..3acc5a5 100644 --- a/k8s/apps/authentik/service.yaml +++ b/k8s/apps/authentik/service.yaml @@ -9,3 +9,21 @@ spec: ports: - port: 9000 targetPort: 9000 +--- +# VIP MetalLB dédiée — exposition Internet via DNAT s01 (kaya.freeboxos.fr:9081). +# Bypass Traefik : le DNAT par port ne peut pas router par Host derrière un +# entrypoint unique. +apiVersion: v1 +kind: Service +metadata: + name: authentik-external + namespace: auth + annotations: + metallb.io/loadBalancerIPs: 192.168.10.201 +spec: + type: LoadBalancer + selector: + app: authentik-server + ports: + - port: 9000 + targetPort: 9000 diff --git a/k8s/apps/guacamole/deployment.yaml b/k8s/apps/guacamole/deployment.yaml index f8c6ad7..86df0e9 100644 --- a/k8s/apps/guacamole/deployment.yaml +++ b/k8s/apps/guacamole/deployment.yaml @@ -42,28 +42,30 @@ spec: # Servir sur / plutôt que /guacamole - name: WEBAPP_CONTEXT value: "ROOT" - # --- SSO OIDC → Authentik (app « guacamole » à créer dans l'UI Authentik) --- - # Endpoints navigateur : via l'IngressRoute ; JWKS : appel serveur→serveur, - # on passe par le Service interne pour ne pas dépendre du DNS lab.local des pods + # --- SSO OIDC → Authentik (app « guacamole » dans l'UI Authentik) --- + # Endpoints navigateur : URL publique kaya.freeboxos.fr (une seule URL + # canonique, LAN inclus — hairpin NAT Freebox) ; JWKS : appel + # serveur→serveur via le Service interne - name: OPENID_AUTHORIZATION_ENDPOINT - value: "http://auth.lab.local/application/o/authorize/" + value: "http://kaya.freeboxos.fr:9081/application/o/authorize/" - name: OPENID_JWKS_ENDPOINT value: "http://authentik.auth.svc.cluster.local:9000/application/o/guacamole/jwks/" - name: OPENID_ISSUER - value: "http://auth.lab.local/application/o/guacamole/" + value: "http://kaya.freeboxos.fr:9081/application/o/guacamole/" - name: OPENID_CLIENT_ID value: "guacamole" - name: OPENID_REDIRECT_URI - value: "http://portail.lab.local/" + value: "http://kaya.freeboxos.fr:9080/" - name: OPENID_USERNAME_CLAIM_TYPE value: "preferred_username" - name: OPENID_SCOPE value: "openid profile email" - # Formulaire local en premier : requis pour l'accès Internet - # (kaya.freeboxos.fr:9080 — comptes locaux Guacamole, Authentik n'étant - # pas exposé). Le lien « OpenID » reste dispo pour le SSO depuis le LAN. + # SSO direct : Authentik est le hub utilisateurs (page « My applications », + # exposée en :9081) — arriver sur Guacamole = redirection immédiate vers lui. + # guacadmin inaccessible : l'admin passe par le compte SSO « Administer + # system » (Alkatrazz). Dépannage sans Authentik : "*, openid" + re-sync. - name: EXTENSION_PRIORITY - value: "*, openid" + value: "openid, *" - name: TZ value: "Europe/Paris" resources: diff --git a/k8s/apps/guacamole/ingress.yaml b/k8s/apps/guacamole/ingress.yaml index 262a3be..a7ef817 100644 --- a/k8s/apps/guacamole/ingress.yaml +++ b/k8s/apps/guacamole/ingress.yaml @@ -7,7 +7,7 @@ spec: entryPoints: - web routes: - - match: Host(`portail.lab.local`) || Host(`kaya.freeboxos.fr`) + - match: Host(`portail.lab.local`) kind: Rule services: - name: guacamole diff --git a/k8s/apps/guacamole/service.yaml b/k8s/apps/guacamole/service.yaml index f74e510..8424c5b 100644 --- a/k8s/apps/guacamole/service.yaml +++ b/k8s/apps/guacamole/service.yaml @@ -9,3 +9,19 @@ spec: ports: - port: 8080 targetPort: 8080 +--- +# VIP MetalLB dédiée — exposition Internet via DNAT s01 (kaya.freeboxos.fr:9080) +apiVersion: v1 +kind: Service +metadata: + name: guacamole-external + namespace: auth + annotations: + metallb.io/loadBalancerIPs: 192.168.10.202 +spec: + type: LoadBalancer + selector: + app: guacamole + ports: + - port: 8080 + targetPort: 8080