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

## 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) — Authentik en hub utilisateur

**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`).

| 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

1. **Tout est en HTTP** (pas de TLS) — accepté y compris pour l'accès Internet
   (cf. section ci-dessus), avec garde-fous réseau + mots de passe dédiés.
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. Derrière
   Traefik, l'IP vue est celle du proxy → **le blocage touche tout le monde**. Patienter
   ou redémarrer le pod guacamole pour purger.
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.
