# 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 ` (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`