- 6 docs racine (README, HANDOVER, ARCHITECTURE, DECISIONS, INSTALL, OPERATIONS) - 9 docs thématiques dans docs/ - 10 templates docker-compose dans configs/ - 7 scripts Python/Shell d'import Furious vers Zitadel - 2 matrices RBAC Etat infra au 5 juin 2026 : - 88 users Furious importes dans Zitadel - 8 roles RBAC crees et attribues - Intranet filtre les cartes selon role (Approche A) - 7 outils SSO operationnels
357 lines
14 KiB
Markdown
357 lines
14 KiB
Markdown
# DECISIONS — Choix architecturaux et anti-patterns
|
|
|
|
> Pourquoi telle décision plutôt qu'une autre. À lire pour comprendre les **trade-offs** et **ne pas refaire les mêmes erreurs**.
|
|
|
|
---
|
|
|
|
## 🎯 D1 — Zitadel plutôt qu'Authentik comme IDP
|
|
|
|
### Contexte
|
|
Au jour 2, on a commencé par déployer **Authentik** comme IDP. Au jour 3, on a migré vers **Zitadel**.
|
|
|
|
### Décision
|
|
**Zitadel gagne.**
|
|
|
|
### Pourquoi
|
|
- ✅ **API plus stable** : la doc Zitadel est plus claire, l'API v2 est très propre
|
|
- ✅ **Support de Custom Claims** plus mature (urn:zitadel:iam:org:project:roles)
|
|
- ✅ **Performance** : Zitadel scale mieux que Authentik en background workers
|
|
- ✅ **Communauté** plus active sur les vrais besoins entreprise
|
|
- ✅ **MFA TOTP** plus simple à configurer
|
|
|
|
### Inconvénients acceptés
|
|
- ❌ Pas de GUI de gestion des flows aussi avancée qu'Authentik
|
|
- ❌ Custom Claims via Actions V2 (instable au moment de la décision)
|
|
|
|
### Anti-pattern évité
|
|
**Ne PAS hésiter à migrer** si l'outil initial pose problème, même si on a investi du temps. Mieux vaut perdre 1 jour de migration que rester bloqué 6 mois sur une stack inadaptée.
|
|
|
|
---
|
|
|
|
## 🎯 D2 — Pattern "OAuth2-Proxy par outil" plutôt que centralisé
|
|
|
|
### Contexte
|
|
Pour activer le SSO sur 5 outils différents (intranet, n8n, Portainer, Code Server, Uptime Kuma), 2 options :
|
|
- **Centralisé** : 1 seul OAuth2-Proxy pour tous les outils, distinguant par route
|
|
- **Par outil** : 1 OAuth2-Proxy dédié à chaque outil
|
|
|
|
### Décision
|
|
**OAuth2-Proxy par outil.**
|
|
|
|
### Pourquoi
|
|
- ✅ **Isolation** : si un OAuth2-Proxy crash, les autres outils continuent
|
|
- ✅ **Configuration différenciée** : chaque outil peut avoir son propre cookie, scope, upstream
|
|
- ✅ **Debugging simple** : `docker logs oauth2-proxy-n8n` ne mélange pas avec les autres
|
|
- ✅ **Clients OIDC séparés** dans Zitadel : permission fine
|
|
|
|
### Inconvénients acceptés
|
|
- 5 conteneurs OAuth2-Proxy au lieu de 1 (~50 Mo RAM chacun, négligeable)
|
|
- 5 configurations à maintenir (mais elles se ressemblent toutes)
|
|
|
|
### Anti-pattern évité
|
|
**Ne PAS factoriser à outrance** "pour faire propre" si ça crée du couplage entre outils indépendants.
|
|
|
|
---
|
|
|
|
## 🎯 D3 — Bug Traefik authRequestHeaders=Cookie
|
|
|
|
### Contexte
|
|
Pendant 1 journée entière, **TOUS les SSO étaient cassés** : à chaque requête, l'user était redirigé vers le login Zitadel, même après login réussi.
|
|
|
|
### Cause découverte
|
|
Par défaut, le middleware **ForwardAuth de Traefik** ne transmet **PAS** le header `Cookie` à OAuth2-Proxy. Du coup OAuth2-Proxy ne voit jamais le cookie de session qu'il a lui-même créé → tout est "non authentifié".
|
|
|
|
### Décision / Fix
|
|
Dans `~/docker/traefik/dynamic.yml` :
|
|
```yaml
|
|
http:
|
|
middlewares:
|
|
oauth2-headers:
|
|
forwardAuth:
|
|
address: "http://oauth2-proxy:4180/oauth2/auth"
|
|
authRequestHeaders:
|
|
- Cookie # ← LA LIGNE CRITIQUE
|
|
- Authorization
|
|
authResponseHeaders:
|
|
- X-Auth-Request-User
|
|
- X-Auth-Request-Email
|
|
- X-Auth-Request-Preferred-Username
|
|
- X-Auth-Request-Groups
|
|
```
|
|
|
|
### Anti-pattern évité
|
|
**Ne PAS supposer que les valeurs par défaut sont sensées.** Toujours vérifier la doc des middlewares quand quelque chose ne marche pas.
|
|
|
|
### Référence
|
|
- Issue Traefik : https://github.com/traefik/traefik/issues/...
|
|
- Forum OAuth2-Proxy : https://github.com/oauth2-proxy/oauth2-proxy/issues/...
|
|
|
|
---
|
|
|
|
## 🎯 D4 — Service account avec IAM_USER_MANAGER (pas IAM_OWNER)
|
|
|
|
### Contexte
|
|
Pour le script d'import Furious → Zitadel, il fallait un compte API avec droits admin Zitadel.
|
|
|
|
### Décision
|
|
**`IAM_USER_MANAGER`** uniquement, **pas** `IAM_OWNER`.
|
|
|
|
### Pourquoi (principe du moindre privilège)
|
|
| Rôle | Peut faire |
|
|
|---|---|
|
|
| IAM_USER_MANAGER | Créer, modifier, supprimer des **users** uniquement |
|
|
| IAM_OWNER | **TOUT** : modifier l'instance, supprimer projects, désactiver MFA d'admins, etc. |
|
|
|
|
→ Si la clé du service account fuite, l'attaquant peut **créer des users** mais **pas détruire l'instance**.
|
|
|
|
### Anti-pattern évité
|
|
**Ne JAMAIS donner les droits maximum à un service account** par flemme. Toujours commencer minimal, augmenter si besoin.
|
|
|
|
---
|
|
|
|
## 🎯 D5 — Fusion des doublons : garder anciens, supprimer nouveaux
|
|
|
|
### Contexte
|
|
Au moment de l'import, 4 users existaient déjà dans Zitadel avec leur trigramme :
|
|
- HKA (Hedi Kalboussi)
|
|
- PSP (Philippe Spoljaric)
|
|
- PYT (Pierre-Yves Tachon)
|
|
- Jules (Jules Bataille)
|
|
|
|
L'import a créé des **doublons** : `hedi.kalboussi@...`, `philippe.spoljaric@...`, etc.
|
|
|
|
### Décision
|
|
**Garder les anciens (avec MFA configuré), supprimer les nouveaux.**
|
|
|
|
### Pourquoi
|
|
| Critère | Anciens (HKA, PSP, ...) | Nouveaux (email) |
|
|
|---|---|---|
|
|
| MFA configuré | ✅ Oui | ❌ Non |
|
|
| Mdp connu par l'user | ✅ Oui | 🟡 Temporaire dans CSV |
|
|
| Si suppression : impact | 🔴 Hedi se bloque hors de Zitadel | 🟢 Aucun |
|
|
| Trigramme pratique | ✅ Oui (HKA, PSP) | ❌ Email long |
|
|
| Metadata Furious | ❌ Non (à recopier) | ✅ Oui |
|
|
|
|
### Solution finale
|
|
Script `merge-duplicates.py` :
|
|
1. Lit les metadata du nouveau compte
|
|
2. Les copie sur l'ancien compte
|
|
3. Supprime le nouveau
|
|
|
|
### Anti-pattern évité
|
|
**Ne JAMAIS supprimer un compte admin sans avoir validé** qu'on garde un accès admin alternatif. Risque de blocage hors de Zitadel.
|
|
|
|
---
|
|
|
|
## 🎯 D6 — Metadata custom plutôt que rôles "vrais" Zitadel au moment de l'import
|
|
|
|
### Contexte
|
|
Au moment de l'import (Phase 1 le jour 7 matin), 2 options :
|
|
- A : Importer les users **avec rôles** (decisions business prises seul)
|
|
- B : Importer les users **avec metadata custom** (job_title, bu_name, manager...), rôles attribués ensuite après validation
|
|
|
|
### Décision
|
|
**Option B — metadata d'abord, rôles ensuite (Phase 2)**.
|
|
|
|
### Pourquoi
|
|
- ✅ **Pas de décision business prématurée** (Philippe a validé l'import, pas le mapping de rôles)
|
|
- ✅ **Évolutif** : si Philippe change la matrice plus tard, on relance juste `assign-roles.py`
|
|
- ✅ **Traçabilité** : metadata Furious préservées de toute façon (utiles plus tard)
|
|
|
|
### Inconvénient accepté
|
|
- Phase 2 (attribution des rôles) à faire dans la même journée → finalement OK car on a eu le temps
|
|
|
|
### Anti-pattern évité
|
|
**Ne PAS mélanger import technique et décision business** dans un même script. Séparer les responsabilités.
|
|
|
|
---
|
|
|
|
## 🎯 D7 — RBAC Approche A (intranet uniquement) — la grande décision
|
|
|
|
### Contexte
|
|
Pour que chaque user voie uniquement les outils auxquels il a accès, 3 options évaluées :
|
|
|
|
### Option A — RBAC sur l'intranet uniquement
|
|
- Le HTML de l'intranet **cache** les cartes selon le rôle
|
|
- L'outil lui-même reste accessible si l'URL directe est tapée
|
|
- **Sécurité** : par "obscurité" (suffisant pour 88 employés non-malveillants)
|
|
|
|
### Option B — RBAC complet avec migrations gratuites
|
|
- Migrer n8n → Activepieces, Portainer CE → Komodo, Uptime Kuma → Gatus, Code Server → OpenVSCode
|
|
- Chaque outil filtre nativement par OIDC claims
|
|
- **Effort** : 5-6 jours
|
|
- **Coût** : 0€
|
|
|
|
### Option C — RBAC complet avec licences pro
|
|
- n8n Enterprise (~12k€/an), Portainer Business (~1800€/an), etc.
|
|
- **Effort** : 1 jour
|
|
- **Coût** : ~25 800€/an
|
|
|
|
### Décision (par Hedi, sponsor Philippe)
|
|
**Option A.**
|
|
|
|
### Pourquoi
|
|
- ✅ **0€** vs ~25 800€/an
|
|
- ✅ **2-3h** vs 5-6 jours
|
|
- ✅ **Suffisant** pour la culture Seize (88 employés, peu de techniciens)
|
|
- ✅ **Évolutif** : on peut passer à B ou C plus tard si besoin
|
|
- 🟡 **Limite acceptée** : un user qui connaît `n8n.seizeconsulting.com` peut bypass
|
|
|
|
### Risque accepté
|
|
Si un user malveillant connaît l'URL directe d'un outil, il y accède. Mais :
|
|
- L'authentification reste obligatoire (OAuth2-Proxy bloque les non-authentifiés)
|
|
- Tous les accès sont **logués** par Zitadel + Traefik
|
|
- On peut détecter des accès anormaux dans les logs
|
|
|
|
### Anti-pattern évité
|
|
**Ne JAMAIS sur-ingénier la sécurité** au-delà de la menace réelle. Pour 88 collègues non-malveillants, l'Approche A est suffisante.
|
|
|
|
---
|
|
|
|
## 🎯 D8 — Pas de SMTP configuré : distribution manuelle des mdp
|
|
|
|
### Contexte
|
|
Sans serveur SMTP configuré dans Zitadel, **aucun email automatique** :
|
|
- Pas d'email de bienvenue
|
|
- Pas de "Mot de passe oublié"
|
|
- Pas de notifications MFA
|
|
|
|
### Décision (validée par Philippe)
|
|
**Distribution manuelle des 88 mdp temporaires via Teams en privé.**
|
|
|
|
### Pourquoi
|
|
- 🟡 L'admin M365 chez Seize n'est pas Hedi → solliciter quelqu'un d'autre
|
|
- 🟡 Configuration SMTP M365 = créer compte `noreply@`, App Password, etc.
|
|
- ✅ Distribution manuelle = 88 messages Teams en privé, étalés sur 5-7 jours
|
|
|
|
### Anti-pattern évité
|
|
**Ne PAS bloquer le projet** parce qu'une dépendance externe (admin M365) n'est pas prête. Trouver un workaround manuel acceptable.
|
|
|
|
### À faire plus tard
|
|
Configurer SMTP M365 pour les futurs onboardings (cf. HANDOVER.md).
|
|
|
|
---
|
|
|
|
## 🎯 D9 — Action "Return user roles during authentication" cochée mais "Only authorized users" décochée
|
|
|
|
### Contexte
|
|
Dans la config du Project Zitadel "Infra Seize", il y a 2 options :
|
|
- **"Return user roles during authentication"** : ajoute les rôles dans le token OIDC
|
|
- **"Only authorized users can authenticate"** : bloque les users sans rôle
|
|
|
|
### Décision (par Hedi)
|
|
- ✅ **Cocher "Return user roles"** : nécessaire pour transmettre les rôles à l'intranet
|
|
- ❌ **Ne PAS cocher "Only authorized users"** : un nouvel arrivant qui n'a pas encore son rôle attribué doit pouvoir se logger
|
|
|
|
### Pourquoi le 2ème non
|
|
- Pendant l'onboarding RH d'un nouveau collaborateur, son compte est créé dans Furious, importé dans Zitadel, mais le **rôle** est attribué peut-être 1-2 jours après (le temps de remplir son `job_title`).
|
|
- Si on coche "Only authorized users", il se prendra un **"Access Denied"** à son premier login, même s'il a un compte valide.
|
|
|
|
### Anti-pattern évité
|
|
**Ne PAS optimiser pour le 99% des cas** si ça casse 1% des cas critiques (nouveau collaborateur premier jour).
|
|
|
|
---
|
|
|
|
## 🎯 D10 — Token JWT dans Authorization plutôt que dans cookie
|
|
|
|
### Contexte
|
|
OAuth2-Proxy peut transmettre le token JWT à l'outil via :
|
|
- Header `Authorization: Bearer <token>` (par défaut)
|
|
- Cookie HTTP-only
|
|
|
|
### Décision
|
|
**Authorization header.**
|
|
|
|
### Pourquoi
|
|
- ✅ **Standard OIDC** : tous les outils backends savent lire le header Authorization
|
|
- ✅ **Pas accessible par JavaScript** côté navigateur (sécurité XSS)
|
|
- ❌ **JavaScript ne peut PAS lire** le token côté browser (mais ce n'est pas notre besoin avec l'Approche A)
|
|
|
|
### Anti-pattern évité
|
|
**Ne PAS exposer le token JWT au JavaScript** côté navigateur via cookie non-HTTP-only. C'est une faille de sécurité courante.
|
|
|
|
---
|
|
|
|
## 🎯 D11 — Sub_filter nginx pour injecter l'email dans le HTML
|
|
|
|
### Contexte
|
|
Le JavaScript de l'intranet a besoin de connaître l'email de l'user connecté pour déterminer son rôle. Mais JS ne peut pas lire les headers HTTP serveur.
|
|
|
|
### Décision
|
|
**Module `sub_filter` de nginx** pour substituer `__USER_EMAIL__` par la vraie valeur du header `X-Forwarded-Email`.
|
|
|
|
### Pourquoi
|
|
- ✅ **Module natif nginx** : pas besoin d'installer Lua/njs
|
|
- ✅ **Simple à comprendre** : 3 lignes de config nginx
|
|
- ✅ **Performant** : aucun overhead notable
|
|
|
|
### Inconvénient accepté
|
|
- Si on change la valeur du placeholder (`__USER_EMAIL__`) dans l'HTML, faut aussi changer la config nginx
|
|
|
|
### Anti-pattern évité
|
|
**Ne PAS sur-ingénier** avec un mini-backend ou des modules nginx exotiques quand `sub_filter` natif suffit.
|
|
|
|
---
|
|
|
|
## 🎯 D12 — Stockage des secrets dans des `.env` séparés (1 par service)
|
|
|
|
### Contexte
|
|
Tous les services Docker ont leurs secrets dans des fichiers `.env`. On aurait pu :
|
|
- A : 1 seul `.env` global avec tous les secrets
|
|
- B : 1 `.env` par service
|
|
|
|
### Décision
|
|
**Option B — 1 `.env` par service.**
|
|
|
|
### Pourquoi
|
|
- ✅ **Isolation** : si un .env fuite, seul ce service est compromis
|
|
- ✅ **Permissions Linux** : `chmod 600` par fichier
|
|
- ✅ **Modifications ciblées** : changer le mdp Postgres ne touche pas les autres services
|
|
- ✅ **Sauvegarde différenciée** possible
|
|
|
|
### Anti-pattern évité
|
|
**Ne JAMAIS centraliser tous les secrets dans un seul fichier** "pour simplifier". C'est juste centraliser le risque.
|
|
|
|
---
|
|
|
|
## 🎯 D13 — Gitea SSO auto-registration activée
|
|
|
|
### Contexte
|
|
Gitea peut être configuré pour :
|
|
- A : Pré-créer les users en base + ils se loggent ensuite
|
|
- B : **Auto-créer** les users à leur 1ère connexion via OIDC
|
|
|
|
### Décision
|
|
**Option B — auto-registration via OIDC.**
|
|
|
|
### Configuration
|
|
Dans `~/docker/gitea/data/gitea/conf/app.ini` (ou via env vars) :
|
|
```ini
|
|
[oauth2_client]
|
|
ENABLE_AUTO_REGISTRATION = true
|
|
USERNAME = preferred_username
|
|
```
|
|
|
|
### Pourquoi
|
|
- ✅ **Pas de double création** : l'user existe dans Zitadel → 1ère connexion crée le compte Gitea
|
|
- ✅ **Username = trigramme** (préféré dans Seize)
|
|
- ✅ **Mise à jour automatique** des infos profile
|
|
|
|
### Anti-pattern évité
|
|
**Ne PAS créer manuellement les comptes** dans chaque outil si l'outil supporte l'auto-registration OIDC.
|
|
|
|
---
|
|
|
|
## 📚 Récap des anti-patterns à NE PAS reproduire
|
|
|
|
1. ❌ **IAM_OWNER pour les service accounts** → utiliser le rôle minimal
|
|
2. ❌ **Supprimer un compte admin sans backup** → garder un accès alternatif
|
|
3. ❌ **Nouveau project Zitadel** quand un existant a déjà les apps → ajouter dedans
|
|
4. ❌ **`scp` depuis le serveur SSH vers lui-même** → toujours depuis le PC client
|
|
5. ❌ **Coller des secrets dans des conversations IA** → gestionnaire de mots de passe
|
|
6. ❌ **Activer "Only authorized users"** sans process d'onboarding fluide
|
|
7. ❌ **Importer avec rôles + decisions business non validées** → metadata d'abord
|
|
8. ❌ **Pas de backup des données** → critique pour Zitadel et Gitea
|
|
9. ❌ **Sur-ingénierie sécurité** au-delà de la menace réelle
|
|
10. ❌ **Hardcoder les secrets** dans les `docker-compose.yml` → utiliser des `.env`
|