Funk-lab/admin/k8s/auth-portal.md
ALI YESILKAYA fe0aa89ffa
feat(security): durcissement edge funklab.online (vraie IP client + rate-limit + TLS/headers) (#95)
- Traefik externalTrafficPolicy: Local → IP client réelle (fin du SNAT kube-proxy)
- middleware ratelimit par IP (100/min, burst 200) sur les 6 routes publiques
- middleware security-headers (HSTS, nosniff, X-Frame SAMEORIGIN, Referrer-Policy)
- TLSOption globale : plancher TLS 1.2 + ciphers modernes
- Guacamole REMOTE_IP_VALVE → ban anti-bruteforce par IP (fin du piège n°5)
- nftables : fermeture des ports Internet legacy 9080/9081 (HTTP clair redondant)
- allowCrossNamespace pour mutualiser les middlewares depuis infra
- docs : section Durcissement (public-domain.md) + CAA OVH + pièges à jour

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:58:24 +02:00

149 lines
8.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Annuaire + portail consoles — Authentik & Guacamole
Annuaire d'identités **Authentik** (IdP OIDC/LDAP) et portail **Guacamole** donnant
accès aux consoles SSH de storage-01 et gpu-01 **dans le navigateur**, avec un compte
unique par utilisateur. Namespace k8s : **`auth`**.
> Ce document = architecture, déploiement, pièges. Voir aussi :
> **`admin/k8s/auth-portal-admin.md`** (administration au quotidien : utilisateurs,
> machines, apps du hub, révocation) et **`docs/portail-guide-utilisateur.md`**
> (guide à envoyer aux utilisateurs).
## Vue d'ensemble
```
Utilisateur ──▶ portail.lab.local (Guacamole) ──login OIDC──▶ auth.lab.local (Authentik)
└──▶ guacd ──SSH (user « console », clé ed25519)──▶ s01 / g01
```
| URL | Service | Rôle |
|---|---|---|
| `http://auth.lab.local` | Authentik | Annuaire : comptes, groupes, SSO OIDC |
| `http://portail.lab.local` | Guacamole | Portail : consoles SSH web s01/g01 |
## Composants
- **Authentik** (`k8s/apps/authentik/`) : server + worker (`ghcr.io/goauthentik/server:2026.5.3`),
Redis in-cluster, PVC `authentik-media` (RWX, nfs), base **PostgreSQL sur storage-01**
(même pattern que Ghostfolio/n8n).
- **Guacamole** (`k8s/apps/guacamole/`) : webapp `guacamole/guacamole:1.6.0` + proxy
`guacamole/guacd:1.6.0`, base PostgreSQL sur storage-01 (connexions, permissions).
Le Job ArgoCD `guacamole-initdb` pose le schéma si absent (idempotent).
- **Ansible** : rôle `postgresql` (bases `authentik` + `guacamole`), rôle **`console_user`**
(user `console` **sans sudo** sur s01 et g01, clé publique Guacamole).
## Secrets
Vault Ansible (`make vault-view`) :
| Variable | Usage |
|---|---|
| `vault_pg_authentik_password` | Base PG `authentik` |
| `vault_pg_guacamole_password` | Base PG `guacamole` |
| `vault_console_ssh_private_key` | Clé privée SSH des connexions Guacamole (user `console`) |
Secrets k8s (créés à la main, **hors git**, comme `ghostfolio-secret`) — mots de passe
identiques au vault, `secret-key` = 48+ caractères aléatoires :
```bash
kubectl create ns auth
kubectl -n auth create secret generic authentik-secret \
--from-literal=secret-key='<clé aléatoire>' \
--from-literal=pg-password='<vault_pg_authentik_password>' \
--from-literal=redis-password='<aléatoire>'
kubectl -n auth create secret generic guacamole-secret \
--from-literal=pg-password='<vault_pg_guacamole_password>'
```
## Déploiement (ordre)
1. `ansible-playbook -i inventory.yml playbooks/storage-01.yml --tags postgresql,console_user`
2. `ansible-playbook -i inventory.yml playbooks/gpu-01.yml --tags console_user`
3. Créer les secrets k8s ci-dessus (avant le premier sync, sinon pods en `CreateContainerConfigError`)
4. Merge sur `main` → ArgoCD crée les Applications `authentik` et `guacamole` (~3 min)
## Configuration initiale — Authentik
1. **Compte admin** : ouvrir `http://auth.lab.local/if/flow/initial-setup/` (valable
uniquement tant qu'aucun admin n'existe) → définir mot de passe de `akadmin`.
2. **Provider OIDC** pour Guacamole — Admin → Applications → Providers → Create
*OAuth2/OpenID Provider* :
- Name : `guacamole` — Authorization flow : *implicit-consent*
- Client type : **Public** (Guacamole utilise le flux implicite, pas de client secret)
- Client ID : `guacamole`
- Redirect URI : `http://portail.lab.local/`
- **Signing Key : sélectionner le certificat self-signed** (sinon JWKS vide → login KO)
3. **Application** — Applications → Create : Name `Portail consoles`,
**slug `guacamole`** (doit correspondre à `/application/o/guacamole/` configuré côté
Guacamole : issuer + JWKS), Provider `guacamole`.
4. **Utilisateurs** — Directory → Users → Create (+ groupes si besoin). Le
`preferred_username` Authentik devient le nom de compte Guacamole.
## Configuration initiale — Guacamole
1. Se connecter en **`guacadmin` / `guacadmin`** (formulaire local, extension base) et
**changer ce mot de passe immédiatement** (portail exposé à tout le LAN).
2. Settings → Connections → New connection (×2) :
- Protocol `SSH` — Hostname `192.168.10.1` (s01) ou `192.168.10.20` (g01) — Port `22`
- Username `console` — Private key : coller `vault_console_ssh_private_key`
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) — domaine public HTTPS
**Depuis le 2026-07-07, tout passe par le domaine `funklab.online` en HTTPS** (Let's
Encrypt). Authentik (`auth.funklab.online`) est la porte d'entrée ; le SSO fait le reste.
Détail complet de la chaîne (DNS OVH, Freebox, Traefik/TLS) : **`admin/k8s/public-domain.md`**.
| URL publique | Service |
|---|---|
| `https://auth.funklab.online` | **Authentik** — hub + login |
| `https://portail.funklab.online` | Guacamole (consoles) |
| `https://grafana.funklab.online` · `https://openwebui.funklab.online` · `https://argocd.funklab.online` | apps SSO |
| `https://n8n.funklab.online` | n8n (login natif) |
- **URL canonique = le domaine**, LAN inclus (dnsmasq split-horizon : `*.funklab.online`
→ Traefik en interne). Les endpoints OIDC des apps pointent sur `*.funklab.online`.
- Les anciens ports `kaya.freeboxos.fr:9080/9081` (DNAT → VIPs MetalLB `.202`/`.201`)
**restent un chemin réseau** mais le SSO ramène sur le domaine.
- **Un seul compte par personne (Authentik)**. Nouvelle app = nouveau provider + tuile.
- **Garde-fous** : rate-limit nftables + TLS Let's Encrypt (plus de HTTP clair) +
protections applicatives (Authentik lockout, Guacamole ban 5 échecs). Durcissement
possible : MFA/TOTP Authentik.
Config côté Authentik (UI) — Redirect URI par app : voir le tableau de
`admin/k8s/public-domain.md` (ex. Guacamole `https://portail.funklab.online/`).
## Pièges / notes
1. **HTTPS sur Internet, HTTP en interne** : l'accès public passe par
`*.funklab.online` en **TLS Let's Encrypt** (durcissement 2026-07-07) ; les routes
internes `*.lab.local` restent en HTTP clair (LAN de confiance). L'issuer OIDC est
donc le domaine HTTPS (cf. piège n°2).
2. **JWKS via le Service interne** (`authentik.auth.svc.cluster.local:9000`) : l'appel
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, *"`** : 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/`
et token rejeté. Corriger : Applications → Edit → Slug.
5. **Anti-bruteforce intégré** (extension `ban`) : 5 échecs → IP bloquée ~5 min.
**Corrigé (2026-07-07)** : `REMOTE_IP_VALVE_ENABLED=true` (deployment) + Traefik en
`externalTrafficPolicy: Local` → Guacamole lit l'IP client réelle (X-Forwarded-For), le
ban est **par IP** et non plus global. Avant ce fix, l'IP vue était celle du pod Traefik
→ le blocage touchait tout le monde. Si un ban global réapparaît : vérifier que la valve
est active et que Traefik n'est pas repassé en `Cluster`. Purge immédiate : redémarrer le
pod guacamole.
6. **User `console` sans sudo** (volontaire) : le portail sert à observer/opérer léger.
L'administration passe par les comptes admin habituels, pas par Guacamole.
7. **Enregistrement des sessions** : possible (guacd + volume partagé) mais non configuré
— gap connu, à ajouter si le portail s'ouvre à d'autres utilisateurs.
8. La config Authentik (provider, app, utilisateurs) et les connexions Guacamole vivent
**en base, pas en IaC** — comme le Server Manager Satisfactory. Backup PG = gap connu.