From 1e90a853591b8c65a9bc8fa55f9b0a3eea066177 Mon Sep 17 00:00:00 2001 From: Hedi Kalboussi Date: Fri, 5 Jun 2026 17:49:43 +0000 Subject: [PATCH] =?UTF-8?q?Documentation=20initiale=20infra=20Seize=20?= =?UTF-8?q?=E2=80=94=20passation=20J7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .env.example | 68 +++ .gitignore | 16 + ARCHITECTURE.md | 344 ++++++++++++ DECISIONS.md | 356 ++++++++++++ HANDOVER.md | 226 ++++++++ INSTALL.md | 522 ++++++++++++++++++ OPERATIONS.md | 512 +++++++++++++++++ README.md | 149 +++++ .../code-server/docker-compose.yml.template | 22 + configs/gitea/docker-compose.yml.template | 48 ++ configs/intranet/docker-compose.yml.template | 18 + configs/intranet/nginx-rbac.conf.template | 48 ++ configs/n8n/docker-compose.yml.template | 39 ++ configs/oauth2-proxy/.env.example | 51 ++ .../oauth2-proxy/docker-compose.yml.template | 26 + configs/portainer/docker-compose.yml.template | 16 + configs/postgres/docker-compose.yml.template | 23 + configs/traefik/docker-compose.yml.template | 63 +++ configs/traefik/dynamic.yml.template | 54 ++ .../uptime-kuma/docker-compose.yml.template | 14 + configs/zitadel/.env.template | 30 + configs/zitadel/docker-compose.yml.template | 136 +++++ docs/01-dns-prerequis.md | 218 ++++++++ docs/02-traefik-setup.md | 255 +++++++++ docs/03-zitadel-setup.md | 281 ++++++++++ docs/04-oauth2-proxy-pattern.md | 353 ++++++++++++ docs/05-applications.md | 358 ++++++++++++ docs/06-import-furious.md | 435 +++++++++++++++ docs/07-rbac-intranet.md | 403 ++++++++++++++ docs/08-secrets-management.md | 314 +++++++++++ docs/09-troubleshooting.md | 486 ++++++++++++++++ install.sh | 468 ++++++++++++++++ matrices/job-title-to-role.csv | 21 + matrices/rbac-matrix.md | 131 +++++ scripts/furious-to-zitadel/README.md | 125 +++++ scripts/furious-to-zitadel/assign-roles.py | 179 ++++++ .../extract-distribution-csv.py | 36 ++ .../generate-roles-mapping.py | 101 ++++ .../import-furious-to-zitadel.py | 314 +++++++++++ .../furious-to-zitadel/merge-duplicates.py | 177 ++++++ scripts/furious-to-zitadel/read-all-users.sh | 49 ++ scripts/furious-to-zitadel/test-auth.sh | 27 + scripts/intranet/index.html.template | 239 ++++++++ 43 files changed, 7751 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 ARCHITECTURE.md create mode 100644 DECISIONS.md create mode 100644 HANDOVER.md create mode 100644 INSTALL.md create mode 100644 OPERATIONS.md create mode 100644 README.md create mode 100644 configs/code-server/docker-compose.yml.template create mode 100644 configs/gitea/docker-compose.yml.template create mode 100644 configs/intranet/docker-compose.yml.template create mode 100644 configs/intranet/nginx-rbac.conf.template create mode 100644 configs/n8n/docker-compose.yml.template create mode 100644 configs/oauth2-proxy/.env.example create mode 100644 configs/oauth2-proxy/docker-compose.yml.template create mode 100644 configs/portainer/docker-compose.yml.template create mode 100644 configs/postgres/docker-compose.yml.template create mode 100644 configs/traefik/docker-compose.yml.template create mode 100644 configs/traefik/dynamic.yml.template create mode 100644 configs/uptime-kuma/docker-compose.yml.template create mode 100644 configs/zitadel/.env.template create mode 100644 configs/zitadel/docker-compose.yml.template create mode 100644 docs/01-dns-prerequis.md create mode 100644 docs/02-traefik-setup.md create mode 100644 docs/03-zitadel-setup.md create mode 100644 docs/04-oauth2-proxy-pattern.md create mode 100644 docs/05-applications.md create mode 100644 docs/06-import-furious.md create mode 100644 docs/07-rbac-intranet.md create mode 100644 docs/08-secrets-management.md create mode 100644 docs/09-troubleshooting.md create mode 100755 install.sh create mode 100644 matrices/job-title-to-role.csv create mode 100644 matrices/rbac-matrix.md create mode 100644 scripts/furious-to-zitadel/README.md create mode 100755 scripts/furious-to-zitadel/assign-roles.py create mode 100755 scripts/furious-to-zitadel/extract-distribution-csv.py create mode 100755 scripts/furious-to-zitadel/generate-roles-mapping.py create mode 100755 scripts/furious-to-zitadel/import-furious-to-zitadel.py create mode 100755 scripts/furious-to-zitadel/merge-duplicates.py create mode 100755 scripts/furious-to-zitadel/read-all-users.sh create mode 100755 scripts/furious-to-zitadel/test-auth.sh create mode 100644 scripts/intranet/index.html.template diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..54c137b --- /dev/null +++ b/.env.example @@ -0,0 +1,68 @@ +# ============================================================================ +# .ENV.EXAMPLE — Variables globales pour l'install +# ============================================================================ +# Copier ce fichier en .env et adapter les valeurs. +# NE JAMAIS commit .env sur Git. + +# ===== INFRA ===== +DOMAIN=seizeconsulting.com +SERVER_IP=151.115.148.243 +SERVER_USER=admin +LETSENCRYPT_EMAIL=admin@seizeconsulting.com + +# ===== POSTGRES PRINCIPAL ===== +POSTGRES_PASSWORD=GENERATE_STRONG_PASSWORD_HERE +POSTGRES_USER=postgres + +# Databases dédiées par service +GITEA_DB_PASSWORD=GENERATE_STRONG_PASSWORD_HERE +N8N_DB_PASSWORD=GENERATE_STRONG_PASSWORD_HERE + +# ===== ZITADEL ===== +ZITADEL_DOMAIN=sso.seizeconsulting.com +ZITADEL_MASTER_KEY=GENERATE_32_CHARS_RANDOM +ZITADEL_ADMIN_EMAIL=zitadel-admin@zitadel.sso.seizeconsulting.com +ZITADEL_ADMIN_PASSWORD=GENERATE_STRONG_PASSWORD_HERE + +# ===== OAUTH2-PROXY (commun à tous les outils) ===== +# Pour chaque outil, créer une App dans Zitadel et récupérer Client ID / Secret + +# Cookie secret : généré par exemple via +# python3 -c 'import secrets; print(secrets.token_urlsafe(32))' + +# ===== GITEA ===== +GITEA_DOMAIN=git.seizeconsulting.com +GITEA_SSH_PORT=22 + +# ===== N8N ===== +N8N_DOMAIN=n8n.seizeconsulting.com +N8N_ENCRYPTION_KEY=GENERATE_32_CHARS_RANDOM + +# ===== CODE SERVER ===== +CODE_DOMAIN=code.seizeconsulting.com +CODE_PASSWORD=GENERATE_STRONG_PASSWORD_HERE_FALLBACK +# Note : avec SSO, ce mdp ne sert que de fallback si SSO down + +# ===== UPTIME KUMA ===== +STATUS_DOMAIN=status.seizeconsulting.com + +# ===== PORTAINER ===== +PORTAINER_DOMAIN=portainer.seizeconsulting.com + +# ===== INTRANET ===== +INTRANET_DOMAIN=intranet.seizeconsulting.com + +# ===== TRAEFIK DASHBOARD ===== +TRAEFIK_DOMAIN=traefik.seizeconsulting.com +# Pour le basic auth Traefik : +# Générer avec : htpasswd -nb admin +TRAEFIK_DASHBOARD_AUTH=admin:$$apr1$$xxxxxxxx$$xxxxxxxxxxxxxxxxxxxxxxxx + +# ===== FURIOUS SQUAD API ===== +FURIOUS_USERNAME=GENERATE_FROM_FURIOUS_CONFIG +FURIOUS_PASSWORD=GENERATE_FROM_FURIOUS_CONFIG +FURIOUS_API_URL=https://seize-consulting.furious-squad.com/api/v2 + +# ===== ZITADEL SERVICE ACCOUNT (pour scripts) ===== +# Chemin vers la clé JSON du service account furious-import-service +ZITADEL_SERVICE_KEY_PATH=/home/admin/furious-to-zitadel/zitadelfuriousimportkey.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fc491b3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +.env +*.env +!.env.example +!.env.template +*.key +*.pem +zitadelfuriousimportkey.json +users-credentials.csv +distribution-teams.csv +roles-mapping.json +letsencrypt/acme.json +*.log +venv/ +__pycache__/ +*.pyc +.DS_Store diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..17ea295 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,344 @@ +# ARCHITECTURE — Vue d'ensemble technique + +> Comprendre comment tout s'imbrique dans l'infra Seize. + +--- + +## 🏗️ Vue d'ensemble + +``` + Internet + │ + │ HTTPS (443) / HTTP (80) + ▼ + ┌──────────────────────┐ + │ Traefik (proxy) │ + │ - Let's Encrypt │ + │ - Routing par Host │ + └──────────┬───────────┘ + │ + ┌──────────────┬─────────┼─────────┬──────────────┐ + │ │ │ │ │ + ▼ ▼ ▼ ▼ ▼ + ┌──────────┐ ┌─────────────┐ ... ┌─────────────┐ ┌─────────────┐ + │ Zitadel │ │ OAuth2-Proxy│ │ OAuth2-Proxy│ │ Gitea │ + │ (sso.*) │ │ (intranet) │ │ (n8n) │ │ (git.*) SSO │ + │ │ │ │ │ │ │ integré │ + └────┬─────┘ └──────┬──────┘ └──────┬──────┘ └─────────────┘ + │ │ │ + │ Auth OIDC │ Forward proxy │ Forward proxy + │ ▼ ▼ + │ ┌───────────┐ ┌───────────┐ + │ │ Intranet │ │ n8n │ + │ │ (nginx) │ │ │ + │ │ + RBAC JS │ │ │ + │ └───────────┘ └─────┬─────┘ + │ │ + │ │ + ▼ ▼ + ┌──────────┐ ┌──────────┐ + │ Postgres │ │ Postgres │ + │ (zitadel)│ │ (n8n) │ + └──────────┘ └──────────┘ +``` + +--- + +## 🌐 Réseaux Docker + +| Réseau | Type | Utilisé par | +|---|---|---| +| `web` | external | Traefik, tous les services exposés publiquement | +| `backend` | external | Postgres, Redis, et services backend internes | +| `zitadel` | local | Zitadel + son Traefik dédié + Postgres | +| `proxy` | bridge | Réseau auxiliaire (vérifier usage) | + +⚠️ **Important** : Zitadel a son **propre réseau** + son **propre Traefik** (intégré dans son docker-compose). Il est totalement isolé du Traefik principal pour des raisons de sécurité. Le Traefik principal route uniquement vers le Traefik Zitadel sur `sso.seizeconsulting.com`. + +--- + +## 🔐 Flux d'authentification SSO + +### Flux complet pour un user accédant à l'intranet + +``` +1. User → https://intranet.seizeconsulting.com + │ + ▼ +2. Traefik principal → route vers OAuth2-Proxy "intranet" + │ + ▼ +3. OAuth2-Proxy vérifie le cookie de session + │ + ┌────┴────┐ + │ Pas de │ + │ session │ + ▼ │ +4. OAuth2-Proxy redirige vers Zitadel + (URL : /oauth2/start → Zitadel login) + │ + ▼ +5. User saisit ses identifiants Zitadel + MFA + │ + ▼ +6. Zitadel valide + génère un code OIDC + │ + ▼ +7. Redirect vers OAuth2-Proxy /oauth2/callback?code=xxx + │ + ▼ +8. OAuth2-Proxy échange le code contre un token JWT + (avec scope : openid email profile urn:zitadel:iam:org:project:roles) + │ + ▼ +9. OAuth2-Proxy stocke le token + cookie de session + │ + ▼ +10. OAuth2-Proxy → forward request vers nginx (intranet) + avec headers : + - X-Forwarded-User: + - X-Forwarded-Email: hedi.kalboussi@seizeconsulting.com + - X-Forwarded-Preferred-Username: HKA + - Authorization: Bearer + │ + ▼ +11. nginx lit le header X-Forwarded-Email + Substitue __USER_EMAIL__ par la vraie valeur dans index.html (sub_filter) + │ + ▼ +12. Browser reçoit l'HTML avec const USER_EMAIL = "hedi.kalboussi@..." + │ + ▼ +13. JavaScript : + - Fetch /roles-mapping.json + - Lookup userEmail → role (ex: "consultant_junior") + - Cache les cartes que ce rôle n'a pas le droit de voir + │ + ▼ +14. User voit uniquement les cartes autorisées +``` + +--- + +## 🗂️ Structure des dossiers Docker sur le serveur + +``` +/home/hedi/docker/ +├── traefik/ # Reverse proxy principal +│ ├── docker-compose.yml +│ ├── traefik.yml # Config statique +│ ├── dynamic.yml # Config dynamique (middlewares, etc.) +│ └── letsencrypt/ # Certificats TLS auto +│ +├── zitadel/ # IDP Zitadel (avec son propre Traefik) +│ ├── docker-compose.yml # Multi-services (proxy + login + api + db) +│ ├── .env # Tous les secrets Zitadel +│ └── machinekey/ # Clés service accounts +│ +├── postgres/ # Postgres principal (Gitea, n8n, Authentik historique) +│ ├── docker-compose.yml +│ └── data/ # Persistance +│ +├── redis/ # Plus utilisé (Authentik historique) +│ +├── intranet/ # ⭐ Page d'accueil avec RBAC +│ ├── docker-compose.yml +│ ├── index.html # HTML avec data-app="xxx" + script RBAC +│ ├── roles-mapping.json # Mapping email → rôle (généré par script) +│ ├── nginx-rbac.conf # nginx avec sub_filter pour __USER_EMAIL__ +│ └── logo.png +│ +├── gitea/ # Forge Git avec SSO Zitadel intégré +│ ├── docker-compose.yml +│ ├── .env +│ └── data/ +│ +├── n8n/ # Workflows +│ ├── docker-compose.yml +│ ├── .env +│ └── data/ +│ +├── portainer/ # Gestion Docker +│ ├── docker-compose.yml +│ └── data/ +│ +├── code-server/ # VS Code dans le navigateur +│ ├── docker-compose.yml +│ ├── .env +│ └── config/ +│ +├── uptime-kuma/ # Monitoring +│ ├── docker-compose.yml +│ └── data/ +│ +├── oauth2-proxy/ # ⭐ OAuth2-Proxy pour l'intranet +│ ├── docker-compose.yml +│ └── .env +│ +├── oauth2-proxy-n8n/ # OAuth2-Proxy pour n8n +│ ├── docker-compose.yml +│ └── .env +│ +├── oauth2-proxy-portainer/ # OAuth2-Proxy pour Portainer +│ ├── docker-compose.yml +│ └── .env +│ +├── oauth2-proxy-code/ # OAuth2-Proxy pour Code Server +│ ├── docker-compose.yml +│ └── .env +│ +├── oauth2-proxy-status/ # OAuth2-Proxy pour Uptime Kuma +│ ├── docker-compose.yml +│ └── .env +│ +├── authentik/ # ❌ Plus utilisé (à archiver/supprimer) +│ +├── whoami/ # Test debug (à garder ou supprimer) +│ +└── ctop/ # Outil CLI monitoring conteneurs +``` + +--- + +## 🎭 Le pattern "OAuth2-Proxy par outil" + +### Pourquoi ce pattern ? + +Chaque outil (n8n, Portainer, etc.) a son **propre OAuth2-Proxy dédié**. C'est volontaire : + +✅ **Avantages** : +- Configuration différenciée par outil (scopes, callbacks, etc.) +- Pas de SPOF : si un OAuth2-Proxy crash, les autres outils marchent +- Plus simple à débugger +- Permet d'avoir des secrets/clients OIDC différents + +❌ **Inconvénients** : +- 5 conteneurs OAuth2-Proxy en plus (impact RAM minime) +- 5 configurations à maintenir + +### Anatomie d'un OAuth2-Proxy + +Pour chaque outil, on a : + +1. **Un dossier `~/docker/oauth2-proxy-/`** +2. **Un `docker-compose.yml`** avec labels Traefik routant `.seizeconsulting.com` → OAuth2-Proxy port 4180 +3. **Un `.env`** avec : + - `OAUTH2_PROXY_PROVIDER=oidc` + - `OAUTH2_PROXY_CLIENT_ID=` + - `OAUTH2_PROXY_CLIENT_SECRET=` + - `OAUTH2_PROXY_OIDC_ISSUER_URL=https://sso.seizeconsulting.com` + - `OAUTH2_PROXY_REDIRECT_URL=https://.seizeconsulting.com/oauth2/callback` + - `OAUTH2_PROXY_UPSTREAMS=http://:` + - `OAUTH2_PROXY_COOKIE_SECRET=<32_chars_random>` + - `OAUTH2_PROXY_SCOPE=openid email profile urn:zitadel:iam:org:project:roles` +4. **Une App créée dans Zitadel** (project "Infra Seize") avec les bons callbacks + +Détails complets dans [docs/04-oauth2-proxy-pattern.md](./docs/04-oauth2-proxy-pattern.md). + +--- + +## 🧬 Le bug Traefik historique (à connaître) + +**Symptôme initial** : tous les outils SSO étaient déconnectés à chaque requête. + +**Cause** : Traefik supprimait par défaut le header `Cookie` quand il faisait un ForwardAuth vers OAuth2-Proxy. Du coup OAuth2-Proxy ne voyait pas le cookie de session et redirigeait vers login à chaque fois. + +**Fix** : ajouter dans `~/docker/traefik/dynamic.yml` : +```yaml +http: + middlewares: + oauth2-headers: + forwardAuth: + address: "http://oauth2-proxy:4180/oauth2/auth" + authRequestHeaders: + - Cookie # ← LIGNE CRITIQUE + - Authorization + authResponseHeaders: + - X-Auth-Request-User + - X-Auth-Request-Email +``` + +→ Sans `Cookie` dans `authRequestHeaders`, **rien ne fonctionne**. + +--- + +## 🎯 RBAC : architecture choisie (Approche A) + +``` +1. Furious Squad (source de vérité RH) + │ + │ API GraphQL-like + ▼ +2. Scripts Python d'import (~/furious-to-zitadel/) + │ + ▼ +3. Zitadel : 88 users avec metadata custom + │ + ▼ +4. Script generate-roles-mapping.py + → génère /docker/intranet/roles-mapping.json + │ + ▼ +5. Intranet (nginx + sub_filter) + → injecte __USER_EMAIL__ depuis X-Forwarded-Email + → JavaScript lookup dans roles-mapping.json + → cache/affiche les cartes selon rôle +``` + +### Pourquoi Approche A (et pas RBAC complet sur chaque outil) ? + +Détails dans [DECISIONS.md](./DECISIONS.md), section "RBAC Approche A". + +**TL;DR** : 0€, 2-3h de boulot, suffisant pour Seize (88 employés majoritairement non-techniques). + +--- + +## 📊 Volumes critiques à backuper + +| Données | Volume / Chemin | Criticité | +|---|---|---| +| Base PostgreSQL Zitadel | `~/docker/zitadel/.../data/postgres/` | 🔴 Critique | +| Base PostgreSQL Gitea/n8n | `~/docker/postgres/data/` | 🔴 Critique | +| Données Gitea (repos) | `~/docker/gitea/data/` | 🔴 Critique | +| Données Portainer | `~/docker/portainer/data/` | 🟡 Important | +| Données Uptime Kuma | `~/docker/uptime-kuma/data/` | 🟡 Important | +| Données n8n | volume Docker `n8n_data` | 🟡 Important | +| Données Code Server | `~/docker/code-server/config/` | 🟢 Récupérable | +| Certificats Let's Encrypt | `~/docker/traefik/letsencrypt/` | 🟢 Auto-régénéré | +| Scripts d'import + clés | `~/furious-to-zitadel/` | 🔴 Critique | + +⚠️ **Aucun backup automatique en place** au moment de la passation. À mettre en priorité. + +--- + +## 🔧 Outils CLI utiles installés + +| Outil | Usage | +|---|---| +| `docker` | Gestion conteneurs | +| `docker compose` | Orchestration | +| `ctop` | Top des conteneurs (consommation ressources) | +| `sudo journalctl` | Logs système | +| `htop` | Top processus | +| `python3` + venv | Pour les scripts d'import | + +--- + +## 🌍 DNS — Records configurés (chez le registrar) + +Tous les sous-domaines pointent vers `151.115.148.243` : + +| Record | Type | Valeur | +|---|---|---| +| `seizeconsulting.com` | A | 151.115.148.243 | +| `sso.seizeconsulting.com` | A | 151.115.148.243 | +| `intranet.seizeconsulting.com` | A | 151.115.148.243 | +| `git.seizeconsulting.com` | A | 151.115.148.243 | +| `n8n.seizeconsulting.com` | A | 151.115.148.243 | +| `portainer.seizeconsulting.com` | A | 151.115.148.243 | +| `code.seizeconsulting.com` | A | 151.115.148.243 | +| `status.seizeconsulting.com` | A | 151.115.148.243 | +| `traefik.seizeconsulting.com` | A | 151.115.148.243 | +| `app.seizeconsulting.com` | A | 151.115.148.243 | + +⚠️ **Ne PAS oublier** d'ajouter un record DNS si tu ajoutes un nouvel outil. Sinon Let's Encrypt échoue. diff --git a/DECISIONS.md b/DECISIONS.md new file mode 100644 index 0000000..b77c370 --- /dev/null +++ b/DECISIONS.md @@ -0,0 +1,356 @@ +# 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` diff --git a/HANDOVER.md b/HANDOVER.md new file mode 100644 index 0000000..26ce01f --- /dev/null +++ b/HANDOVER.md @@ -0,0 +1,226 @@ +# 🚨 HANDOVER — Passation au prochain mainteneur + +> Ce document est **prioritaire**. Lis-le **avant tout** si tu reprends le projet. + +--- + +## ✅ Ce qui marche (état au 5 juin 2026) + +### Infrastructure +- ✅ Stack Docker sur Scaleway, 24 conteneurs actifs +- ✅ Traefik comme reverse proxy avec Let's Encrypt auto +- ✅ Zitadel comme IDP, accessible sur `sso.seizeconsulting.com` +- ✅ 7 sous-domaines en HTTPS opérationnels +- ✅ Pattern OAuth2-Proxy par outil pour le SSO + +### Utilisateurs +- ✅ 88 collaborateurs Seize importés dans Zitadel +- ✅ 4 anciens comptes (HKA, PSP, PYT, Jules) conservés avec leur MFA +- ✅ Metadata Furious copiées sur tous les comptes (job_title, bu_name, manager, etc.) +- ✅ Mots de passe temporaires générés dans `users-credentials.csv` + +### RBAC +- ✅ 8 rôles créés dans le project "Infra Seize" (Zitadel) +- ✅ 87/88 users avec rôle attribué (1 sans : le user "vide" Furious à corriger) +- ✅ Intranet filtre les cartes selon le rôle +- ✅ Testé sur HKA (consultant_junior) : voit Gitea + Code Server uniquement + +### Imports automatisés +- ✅ Scripts Python dans `~/furious-to-zitadel/` opérationnels +- ✅ Mode dry-run / test / prod pour `import-furious-to-zitadel.py` +- ✅ Script de fusion de doublons (`merge-duplicates.py`) +- ✅ Script de génération du mapping RBAC (`generate-roles-mapping.py`) +- ✅ Script d'attribution des rôles (`assign-roles.py`) + +--- + +## 🔴 À faire en priorité (urgent) + +### 1. Rotation des secrets exposés + +Plusieurs secrets ont été partagés dans des conversations IA pendant le développement. **À régénérer immédiatement** : + +| Secret | Où | Action | +|---|---|---| +| Compte API Furious `mick.emmaus.90849` | Furious > Configuration API | Régénérer le mdp | +| Token JWT user HKA (1 fois, expiré) | N/A | Déjà expiré, juste vérifier | +| Mdp Postgres Gitea | `~/docker/gitea/.env` (`GITEA_DB_PASSWORD`) | Régénérer + restart | +| Secrets Gitea (LFS_JWT, INTERNAL_TOKEN, JWT_SECRET) | `~/docker/gitea/.env` | Régénérer + restart | +| Clé service account Zitadel `zitadelfuriousimportkey.json` | `~/furious-to-zitadel/` | Régénérer si suspicion compromission | + +### 2. Distribuer les 88 mdp temporaires + +Le fichier `~/furious-to-zitadel/distribution-teams.csv` contient 88 lignes : +`Nom complet | Email | Mot de passe temporaire` + +**Procédure recommandée** (validée avec Philippe) : +- Envoyer 1 mdp par message **privé Teams** (jamais en canal public) +- Indiquer URL : https://intranet.seizeconsulting.com +- Préciser : "Mdp temporaire à changer à la 1ère connexion" +- Étaler sur 5-7 jours (10-20 par jour) pour gérer le support 1er login + +**Une fois distribution terminée** : supprimer le CSV (corbeille + vider la corbeille). + +### 3. Configurer SMTP M365 (délégué à l'admin M365) + +Sans SMTP, Zitadel ne peut pas envoyer d'emails (welcome, reset password, MFA). + +**Procédure** : +1. Demander à l'admin M365 de créer `noreply@seizeconsulting.com` +2. Activer SMTP AUTH sur ce compte +3. Générer un App Password +4. Configurer dans Zitadel : Default settings → SMTP Provider +5. Tester l'envoi + +Détails dans [docs/08-secrets-management.md](./docs/08-secrets-management.md). + +### 4. Nettoyer Authentik (plus utilisé) + +```bash +cd ~/docker/authentik +sudo docker compose down +# Optionnel : sauvegarder le dossier avant suppression +mv ~/docker/authentik ~/docker/authentik.archived +``` + +Idem pour Redis si plus utilisé par d'autres services (à vérifier). + +--- + +## 🟡 À faire ensuite (important) + +### 5. Documenter pour Philippe + +Philippe a validé l'import et la matrice RBAC en oral mais **n'a pas vu la liste exacte** des 88 users. + +À lui transmettre : +- Liste des 88 users importés (sans les mdp, pour traçabilité) +- Matrice RBAC effective (cf. [matrices/rbac-matrix.md](./matrices/rbac-matrix.md)) +- Date/heure d'import : 5 juin 2026 07:27-07:42 + +### 6. Workshop validation matrice RBAC + +La matrice actuelle a été **tranchée par Hedi seul** car Philippe pas dispo le jour de l'import. À valider en workshop : +- Direction = Président + Directeur + Directeur Associé ? +- Manager = inclut Manager BI ? +- Experts = au-dessus ou en parallèle des Managers ? +- Apprentis vs Stagiaires : 2 rôles distincts ou 1 seul ? + +### 7. Corriger l'anomalie du user "vide" Furious + +1 collaborateur a `job_title` vide dans Furious → pas de rôle Zitadel attribué. +**Action** : identifier qui (Hedi sait) et compléter dans Furious, puis relancer `assign-roles.py`. + +### 8. Corriger les anomalies d'orthographe Furious + +Doublons dans Furious à standardiser : +- `Senior Manager` vs `Sénior Manager` +- `Consultant Expérimenté` vs `Consultant Expérimentée` vs `Consultant Sénior` +- `Consultant Expert niveau 1` (avec espace en début parfois) + +Le script `assign-roles.py` gère ces variantes via mapping mais c'est mieux de standardiser à la source. + +--- + +## 🟢 À faire à terme (moyen/long terme) + +### 9. Décision business : migrations outils gratuits OU licences pro ? + +Pour avoir un **vrai RBAC** (pas juste cosmétique sur l'intranet), il faut : + +**Option A** — Migrations gratuites (5-6j de boulot, 0€/an) : +- n8n → Activepieces +- Portainer CE → Komodo +- Uptime Kuma → Gatus +- Code Server → OpenVSCode Server (RBAC limité) + +**Option B** — Licences pro (~25 800€/an, 1j de config) : +- n8n Enterprise +- Portainer Business +- (le reste reste gratuit) + +**Décision sponsor** : Philippe + Pierre-Yves Tachon. + +### 10. Workflow n8n de sync continue Furious → Zitadel + +Idée : tous les jours à 3h du matin, n8n : +- Récupère la liste des users Furious +- Identifie les nouveaux arrivants → crée dans Zitadel +- Identifie les départs (`departure_date != "0000-00-00"`) → désactive dans Zitadel +- Met à jour les metadata si changement + +⏱️ ~1 journée de dev n8n. + +### 11. Custom Claims OIDC (attendre Actions V2 Zitadel) + +Pour vraiment transmettre les rôles aux outils backends (Gitea, n8n, etc.) en plus de l'intranet, on attend la stabilisation d'**Actions V2** dans Zitadel. + +### 12. Fédération M365/Entra ID ↔ Zitadel + +Plus tard : permettre aux users de se logger via leur compte M365 entreprise. + +### 13. Backup chiffré + plan de reprise + +Actuellement, **aucun backup automatisé**. Critique pour la base PostgreSQL de Zitadel et Gitea. + +À mettre en place : +- Backup quotidien de PostgreSQL (avec `pg_dump`) +- Backup des volumes Docker (Gitea data, Zitadel data) +- Stockage offsite (Scaleway Object Storage chiffré) + +### 14. Monitoring sécurité avancé + +- **Fail2ban** ou **Crowdsec** pour bloquer les IPs malveillantes (les logs OAuth2-Proxy montrent beaucoup de tentatives de scans) +- Alerting sur tentatives de login échouées (via Uptime Kuma ou autre) + +--- + +## 🗣️ Conversations / décisions clés à connaître + +### Décisions architecturales tranchées + +1. **Zitadel plutôt qu'Authentik** : meilleur support API, plus stable, mieux documenté +2. **OAuth2-Proxy par outil (pas centralisé)** : permet config différenciée par app +3. **RBAC sur intranet uniquement (Approche A)** : zéro coût, suffisant à 90% pour 88 employés non-techniques +4. **Garder anciens comptes (HKA, PSP, PYT, Jules)** avec MFA, supprimer les doublons importés +5. **Metadata custom plutôt que rôles "vrais" Zitadel** au moment de l'import (rôles ajoutés ensuite) +6. **Pas de SMTP configuré** : mdp distribués manuellement via Teams +7. **Pas de migration M365 immédiate** : les users gardent leurs comptes Zitadel locaux + +### Anti-patterns identifiés + +- ❌ **Ne PAS donner IAM_OWNER aux service accounts** → utiliser le rôle minimal (`IAM_USER_MANAGER`) +- ❌ **Ne PAS créer un nouveau project Zitadel** pour les rôles si "Infra Seize" existe déjà (sinon OAuth2-Proxy ne les voit pas) +- ❌ **Ne PAS exécuter `scp` depuis le serveur SSH** vers lui-même — toujours depuis le PC client +- ❌ **Ne PAS coller de secrets** (tokens, mdp, clés API) dans des conversations IA — utiliser un gestionnaire de mots de passe +- ❌ **Ne PAS supprimer un user admin** sans avoir vérifié qu'on a un autre accès admin (risque de blocage) + +--- + +## 📞 Contacts utiles + +| Personne | Rôle | Contact | +|---|---|---| +| Hedi Kalboussi (HKA) | Mainteneur sortant | Slack/Teams interne | +| Philippe Spoljaric (PSP) | Sponsor + Admin Infra | Slack/Teams interne | +| Pierre-Yves Tachon (PYT) | CEO + arbitrage budget | Slack/Teams interne | +| Support Furious Squad | API + données RH | seize-consulting.furious-squad.com | +| Scaleway | Hébergeur | console.scaleway.com | + +--- + +## 🎯 Premier jour de la passation — checklist + +- [ ] Lire ce HANDOVER en entier +- [ ] Lire ARCHITECTURE.md +- [ ] Se connecter en SSH au serveur : `ssh hedi@151.115.148.243` +- [ ] Demander accès Zitadel admin à Hedi +- [ ] Lister les conteneurs : `sudo docker ps` +- [ ] Vérifier les logs récents : `sudo docker logs --tail 50 traefik` +- [ ] Tester un login en HKA depuis incognito +- [ ] Faire un point téléphone avec Hedi pour Q&A +- [ ] Identifier les 1-2 actions urgentes à reprendre + +--- + +**Bon courage, et bienvenue dans le projet !** 🚀 diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..65d2dba --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,522 @@ +# INSTALL — Déploiement from scratch sur un nouveau serveur + +> Procédure complète pour reproduire l'infrastructure Seize sur un nouveau serveur. + +⏱️ **Temps total estimé** : 4-6h pour un sysadmin expérimenté. + +--- + +## 📋 Prérequis + +### Côté infrastructure + +- [ ] **Serveur Linux** : 4 vCPU, 8 Go RAM minimum (16 Go recommandé), 80 Go disque +- [ ] **OS** : Debian 13 (Trixie) ou Ubuntu 22.04+ +- [ ] **IP publique fixe** (chez Scaleway ou autre) +- [ ] **Ports ouverts** : 22 (SSH), 80 (HTTP), 443 (HTTPS) + +### Côté domaine + +- [ ] Un **domaine** que vous contrôlez (ex: `seizeconsulting.com`) +- [ ] Accès à la **zone DNS** pour ajouter des records A + +### Côté tiers + +- [ ] Compte **Furious Squad** avec droits admin (pour créer un compte API) +- [ ] (Optionnel) Compte **Microsoft 365** avec admin pour le SMTP + +### Côté local + +- [ ] Un **PC** avec SSH et SCP (Windows PowerShell, Linux, ou macOS) +- [ ] Un **gestionnaire de mots de passe** (Bitwarden, KeePass, 1Password) + +--- + +## 🌐 Étape 0 — Configuration DNS + +⏱️ **15 min** (mais propagation peut prendre 1h+) + +Chez votre registrar (Scaleway, OVH, Gandi, etc.), ajouter les records A suivants : + +| Record | Type | Valeur | +|---|---|---| +| `@` (ou `seizeconsulting.com`) | A | `` | +| `sso` | A | `` | +| `intranet` | A | `` | +| `git` | A | `` | +| `n8n` | A | `` | +| `portainer` | A | `` | +| `code` | A | `` | +| `status` | A | `` | +| `traefik` | A | `` | + +**Vérification** : +```bash +nslookup sso.seizeconsulting.com +``` +→ Doit retourner l'IP du serveur. + +--- + +## 🐧 Étape 1 — Préparation du serveur + +⏱️ **20 min** + +### 1.1 — Connexion SSH initiale + +```bash +ssh root@ +``` + +### 1.2 — Créer un user non-root + +```bash +adduser admin +usermod -aG sudo admin +mkdir -p /home/admin/.ssh +cp ~/.ssh/authorized_keys /home/admin/.ssh/ +chown -R admin:admin /home/admin/.ssh +chmod 600 /home/admin/.ssh/authorized_keys +``` + +### 1.3 — Hardening SSH + +Éditer `/etc/ssh/sshd_config` : +``` +PermitRootLogin no +PasswordAuthentication no +PubkeyAuthentication yes +``` + +Restart : `systemctl restart sshd` + +### 1.4 — Reconnexion en tant que l'utilisateur non-root + +```bash +exit +ssh admin@ +``` + +### 1.5 — Mise à jour système + +```bash +sudo apt update && sudo apt upgrade -y +sudo apt install -y curl wget git vim htop unzip +``` + +--- + +## 🐳 Étape 2 — Installation Docker + +⏱️ **10 min** + +```bash +# Installation Docker depuis le repo officiel +curl -fsSL https://get.docker.com | sudo sh + +# Ajouter l'utilisateur au groupe docker +sudo usermod -aG docker $USER + +# Se relogger pour prendre en compte le groupe +exit +ssh admin@ + +# Vérification +docker --version +docker compose version +``` + +→ Tu dois voir : Docker version 27.x ou plus récent, Docker Compose v2.x + +--- + +## 🌐 Étape 3 — Création des réseaux Docker + +⏱️ **2 min** + +```bash +docker network create web +docker network create backend +``` + +→ Ces réseaux seront partagés entre tous les services. + +--- + +## 🛡️ Étape 4 — Installation Traefik + +⏱️ **30 min** + +Voir [docs/02-traefik-setup.md](./docs/02-traefik-setup.md) pour les détails. + +### Résumé + +```bash +mkdir -p ~/docker/traefik/letsencrypt +cd ~/docker/traefik +# Copier le docker-compose.yml depuis configs/traefik/ +# Copier traefik.yml et dynamic.yml depuis configs/traefik/ +# Adapter les emails Let's Encrypt +docker compose up -d +``` + +**Vérification** : +```bash +docker logs traefik +# Doit afficher "Server is up" +``` + +Visiter `https://traefik.seizeconsulting.com` → doit afficher la page de login basic auth. + +--- + +## 🔐 Étape 5 — Installation Zitadel + +⏱️ **45 min** + +Voir [docs/03-zitadel-setup.md](./docs/03-zitadel-setup.md) pour les détails complets. + +### Résumé des points clés + +1. Créer `~/docker/zitadel/` avec son `docker-compose.yml` et `.env` +2. **Important** : Zitadel a son propre Traefik intégré + Postgres dédié +3. Lancer : `docker compose up -d` +4. Aller sur `https://sso.seizeconsulting.com/ui/console` +5. Créer le user **zitadel-admin** initial via console +6. Configurer les politiques : + - Password complexity (14 chars, maj+min+chiffres+symboles) + - MFA obligatoire + - Lockout après 5 échecs + +⚠️ **Sauvegarder le mdp zitadel-admin** immédiatement dans le gestionnaire de mots de passe. + +--- + +## 🗄️ Étape 6 — Installation Postgres (pour Gitea, n8n) + +⏱️ **10 min** + +```bash +mkdir -p ~/docker/postgres/data +cd ~/docker/postgres +# Copier docker-compose.yml et .env.example depuis configs/postgres/ +# Renommer .env.example en .env et générer un mdp fort +cat > .env << EOF +POSTGRES_PASSWORD=$(openssl rand -base64 32 | tr -d '+/=') +EOF +chmod 600 .env + +docker compose up -d + +# Vérification +docker exec -it postgres psql -U postgres -c "SELECT version();" +``` + +### Créer les databases pour Gitea et n8n + +```bash +docker exec -it postgres psql -U postgres << EOF +CREATE USER gitea WITH PASSWORD 'PWD_GITEA'; +CREATE DATABASE gitea OWNER gitea; +CREATE USER n8n WITH PASSWORD 'PWD_N8N'; +CREATE DATABASE n8n OWNER n8n; +EOF +``` + +⚠️ Remplacer `PWD_GITEA` et `PWD_N8N` par des mdp générés (`openssl rand -base64 24`) et les stocker dans le gestionnaire. + +--- + +## 🐙 Étape 7 — Installation Gitea avec SSO Zitadel + +⏱️ **45 min** + +Voir [docs/05-applications.md](./docs/05-applications.md) section Gitea. + +### Étapes principales + +1. Créer `~/docker/gitea/` avec son `docker-compose.yml` et `.env` +2. Démarrer Gitea : `docker compose up -d` +3. Aller sur `https://git.seizeconsulting.com` → faire le setup initial +4. **Dans Zitadel** : créer une application "Gitea" dans le project "Infra Seize" +5. Configurer dans Gitea : Settings → Authentication Sources → Add OAuth2 +6. **Activer auto-registration** dans `app.ini` : + ```ini + [oauth2_client] + ENABLE_AUTO_REGISTRATION = true + USERNAME = preferred_username + ``` +7. Restart Gitea : `docker compose restart` + +--- + +## 🔐 Étape 8 — Pattern OAuth2-Proxy pour les autres outils + +⏱️ **2h** (à répéter pour chaque outil) + +Voir [docs/04-oauth2-proxy-pattern.md](./docs/04-oauth2-proxy-pattern.md) pour le pattern complet. + +### Outils à protéger via OAuth2-Proxy + +- n8n +- Portainer +- Code Server +- Uptime Kuma +- Intranet + +### Étapes par outil (exemple : n8n) + +1. **Déployer l'outil** (sans SSO) : + ```bash + mkdir -p ~/docker/n8n + # Copier docker-compose.yml depuis configs/n8n/ + docker compose up -d + ``` + +2. **Créer l'application dans Zitadel** : + - Console Zitadel → Projects → Infra Seize → Applications → New + - Type : Web + - Authentication Method : Basic + - Grant Types : Authorization Code + - Redirect URL : `https://n8n.seizeconsulting.com/oauth2/callback` + - Token Type : JWT + - Cocher : User roles inside ID Token, User profile info, Add user roles to access token + +3. **Récupérer Client ID + Client Secret** + +4. **Déployer OAuth2-Proxy dédié** : + ```bash + mkdir -p ~/docker/oauth2-proxy-n8n + # Copier docker-compose.yml et .env.example depuis configs/oauth2-proxy/ + # Adapter les valeurs (host, client_id, client_secret, upstream) + cd ~/docker/oauth2-proxy-n8n + docker compose up -d + ``` + +5. **Tester** : aller sur `https://n8n.seizeconsulting.com` → doit rediriger vers Zitadel login. + +--- + +## 🏠 Étape 9 — Installation Intranet avec RBAC + +⏱️ **30 min** + +Voir [docs/07-rbac-intranet.md](./docs/07-rbac-intranet.md) pour les détails. + +### Étapes + +1. **Créer le dossier intranet** : + ```bash + mkdir -p ~/docker/intranet + ``` + +2. **Copier les fichiers** depuis `configs/intranet/` : + - `docker-compose.yml` + - `nginx-rbac.conf` + - `index.html.template` (à personnaliser) + - `logo.png` (le logo Seize) + +3. **Créer le `roles-mapping.json`** : + Il sera généré plus tard par le script `generate-roles-mapping.py` (cf. Étape 10). + + En attendant, créer un mapping vide : + ```bash + echo '{}' > roles-mapping.json + ``` + +4. **Déployer OAuth2-Proxy pour l'intranet** : voir Étape 8 mais avec : + - Host : `intranet.seizeconsulting.com` + - Upstream : `http://intranet:80` + +5. **Démarrer l'intranet** : + ```bash + cd ~/docker/intranet + docker compose up -d + ``` + +6. **Tester** : aller sur `https://intranet.seizeconsulting.com` → login Zitadel → voir les cartes (toutes affichées tant que roles-mapping.json est vide). + +--- + +## 👥 Étape 10 — Import des users depuis Furious Squad + +⏱️ **1h** + +Voir [docs/06-import-furious.md](./docs/06-import-furious.md) pour la procédure complète. + +### Étapes principales + +1. **Créer un compte API Furious** : + - Connexion Furious → Configuration → Générer un nouvel utilisateur d'API + - Cocher les permissions : `User > Search` uniquement + - Sauvegarder identifiant + mdp dans le gestionnaire + +2. **Créer le service account Zitadel** : + - Console Zitadel → Users → New → Service user + - Nom : `furious-import-service` + - Access Token Type : JWT + - Dans Default settings → IAM Members → ajouter ce service user avec rôle `IAM User Manager` + - Générer une clé : Keys → New → Type JSON → télécharger immédiatement + +3. **Préparer l'environnement Python** : + ```bash + mkdir -p ~/furious-to-zitadel + cd ~/furious-to-zitadel + python3 -m venv venv + source venv/bin/activate + pip install requests pyjwt cryptography + ``` + +4. **Copier les scripts** depuis `scripts/furious-to-zitadel/` du repo. + +5. **Créer le `.env`** : + ```env + FURIOUS_USERNAME= + FURIOUS_PASSWORD= + FURIOUS_API_URL=https://seize-consulting.furious-squad.com/api/v2 + ``` + `chmod 600 .env` + +6. **Placer la clé JSON Zitadel** : + - Transférer la clé depuis le PC vers le serveur via SCP + - `chmod 600 zitadelfuriousimportkey.json` + +7. **Lancer l'import** : + ```bash + # Étape A : Lire les users depuis Furious + ./read-all-users.sh + # → Génère furious-users-raw.json + + # Étape B : Dry-run (simulation, aucune écriture) + python3 import-furious-to-zitadel.py --mode dry-run + + # Étape C : Test sur 3 users + python3 import-furious-to-zitadel.py --mode test + + # Étape D : Vérifier dans Zitadel UI que les 3 users sont créés avec metadata + + # Étape E : Import production (les 85 restants) + python3 import-furious-to-zitadel.py --mode prod + ``` + +8. **Gérer les doublons** (si users existaient déjà dans Zitadel) : + ```bash + # Adapter DUPLICATES dans merge-duplicates.py selon ton cas + python3 merge-duplicates.py + ``` + +9. **Vérifier le CSV** : + ```bash + wc -l users-credentials.csv + # Doit être : 1 header + N users + ``` + +--- + +## 🎭 Étape 11 — Configuration des rôles RBAC + +⏱️ **30 min** + +Voir [docs/07-rbac-intranet.md](./docs/07-rbac-intranet.md) pour les détails. + +### Étapes + +1. **Créer les 8 rôles dans Zitadel** : + - Console Zitadel → Projects → Infra Seize → Roles → New + - Créer un par un : + - direction / Direction / hierarchie + - manager / Manager / hierarchie + - expert / Expert / hierarchie + - consultant_senior / Consultant Senior / hierarchie + - consultant_junior / Consultant Junior / hierarchie + - alternant / Alternant / hierarchie + - stagiaire / Stagiaire / hierarchie + - support / Support / hierarchie + +2. **Activer "Return user roles during authentication"** : + - Console Zitadel → Projects → Infra Seize → General + - Cocher "Return user roles during authentication" + - Save + +3. **Activer "User roles inside ID Token"** sur chaque application : + - Pour chaque app (Intranet, n8n, etc.) : Token Settings + - Auth Token Type : JWT + - Cocher : User roles inside ID Token, Include user's profile info, Add user roles to the access token + +4. **Modifier OAuth2-Proxy** pour demander le scope des rôles : + - Pour chaque OAuth2-Proxy : éditer `.env` + - Modifier : `OAUTH2_PROXY_SCOPE=openid email profile urn:zitadel:iam:org:project:roles` + - Restart : `docker compose down && docker compose up -d` + +5. **Lancer les scripts d'attribution** : + ```bash + cd ~/furious-to-zitadel + source venv/bin/activate + + # Générer le mapping email → rôle (pour l'intranet) + python3 generate-roles-mapping.py + + # Attribuer les rôles dans Zitadel (87 users) + python3 assign-roles.py + ``` + +6. **Copier le mapping vers l'intranet** : + ```bash + cp roles-mapping.json ~/docker/intranet/ + ``` + +7. **Restart de l'intranet** : + ```bash + cd ~/docker/intranet + docker compose down + docker compose up -d + ``` + +8. **Test final** : + - Login en incognito avec un user (ex: consultant_junior) + - Doit voir uniquement les cartes autorisées (Gitea + Code Server par défaut) + +--- + +## ✅ Étape 12 — Validation finale + +⏱️ **30 min** + +### Checklist de validation + +- [ ] Tous les conteneurs `Up` : `docker ps` +- [ ] Tous les sous-domaines répondent en HTTPS +- [ ] Login Zitadel fonctionne avec MFA +- [ ] Login intranet fonctionne et filtre les cartes +- [ ] Login Gitea fonctionne avec auto-registration +- [ ] Login n8n / Portainer / Code Server / Uptime Kuma fonctionne +- [ ] Le service account Furious peut lire l'API Furious +- [ ] Les scripts Python sont opérationnels +- [ ] Les fichiers sensibles ont `chmod 600` +- [ ] Les `.env` ne sont pas committés sur Git +- [ ] Backup automatique en place (à mettre en place — voir OPERATIONS.md) + +--- + +## 🚨 Points d'attention pendant l'install + +### Ne pas oublier +- ✅ Sauvegarder TOUS les secrets dans le gestionnaire de mdp +- ✅ Configurer le firewall (ufw ou iptables) pour limiter aux ports 22/80/443 +- ✅ Tester chaque étape avant de passer à la suivante +- ✅ Documenter les écarts par rapport à cette procédure si tu en fais + +### Pièges classiques +- ❌ Oublier d'ajouter le record DNS avant de tester le HTTPS (Let's Encrypt échoue) +- ❌ Lancer `docker compose up -d` sans avoir créé le `.env` (erreurs cryptiques) +- ❌ Donner IAM_OWNER au service account (utiliser IAM_USER_MANAGER) +- ❌ Coller un secret dans une conversation IA (gestionnaire de mdp uniquement) + +--- + +## 📚 Pour aller plus loin + +Une fois l'install terminée, consulter : +- [OPERATIONS.md](./OPERATIONS.md) pour la maintenance quotidienne +- [docs/09-troubleshooting.md](./docs/09-troubleshooting.md) pour les problèmes fréquents +- [HANDOVER.md](./HANDOVER.md) pour les actions à mener diff --git a/OPERATIONS.md b/OPERATIONS.md new file mode 100644 index 0000000..2806c14 --- /dev/null +++ b/OPERATIONS.md @@ -0,0 +1,512 @@ +# OPERATIONS — Maintenance et opérations courantes + +> Procédures pour maintenir, dépanner, et faire évoluer l'infrastructure Seize. + +--- + +## 🔍 Vérifications quotidiennes (5 min/jour) + +### Healthcheck rapide + +```bash +# Tous les conteneurs sont-ils Up ? +sudo docker ps --format "table {{.Names}}\t{{.Status}}" | grep -v "Up" +# → Aucune ligne sauf le header = tout va bien + +# Tous les sous-domaines répondent-ils en HTTPS ? +for domain in sso intranet git n8n portainer code status; do + echo -n "$domain : " + curl -sk -o /dev/null -w "%{http_code}\n" "https://$domain.seizeconsulting.com" +done +# → 200 ou 302 sur chaque ligne = OK +# → 500, 502, 503 = problème + +# Espace disque restant +df -h / +# → Surveiller si > 80% utilisé +``` + +### Logs récents + +```bash +# Logs Traefik (erreurs uniquement) +sudo docker logs --tail 100 traefik 2>&1 | grep -i "error\|warn" + +# Logs Zitadel (erreurs) +sudo docker logs --tail 100 zitadel-zitadel-api-1 2>&1 | grep -i "error\|level=err" + +# Logs OAuth2-Proxy intranet (erreurs récentes) +sudo docker logs --tail 100 oauth2-proxy 2>&1 | grep -i "error" +``` + +--- + +## 📦 Backups + +### ⚠️ État actuel : AUCUN backup automatique + +À mettre en place en priorité. + +### Procédure backup manuel (à exécuter avant grosse modif) + +```bash +# Créer un dossier de backup daté +BACKUP_DIR=~/backups/$(date +%Y%m%d-%H%M%S) +mkdir -p $BACKUP_DIR + +# Backup Postgres (Gitea, n8n) +docker exec postgres pg_dumpall -U postgres > $BACKUP_DIR/postgres.sql + +# Backup Postgres Zitadel (sa propre instance) +docker exec zitadel-postgres-1 pg_dumpall -U postgres > $BACKUP_DIR/postgres-zitadel.sql + +# Backup volumes Docker (Gitea data, etc.) +sudo tar czf $BACKUP_DIR/gitea-data.tar.gz -C ~/docker/gitea data/ +sudo tar czf $BACKUP_DIR/uptime-data.tar.gz -C ~/docker/uptime-kuma data/ +sudo tar czf $BACKUP_DIR/portainer-data.tar.gz -C ~/docker/portainer data/ + +# Backup configs (.env, docker-compose.yml) +tar czf $BACKUP_DIR/configs.tar.gz -C ~/docker . + +# Backup scripts furious-to-zitadel +tar czf $BACKUP_DIR/furious-scripts.tar.gz -C ~/ furious-to-zitadel/ + +echo "✅ Backup terminé dans $BACKUP_DIR" +ls -lh $BACKUP_DIR +``` + +### Procédure backup automatique (à mettre en place) + +Voir [docs/09-troubleshooting.md](./docs/09-troubleshooting.md) section Backup automatisé. + +--- + +## 🔄 Mises à jour + +### Mise à jour d'un service Docker + +```bash +cd ~/docker/ +docker compose pull +docker compose up -d +``` + +### Vérifier qu'une mise à jour ne casse rien + +```bash +# 1. Backup avant +~/backup-manual.sh # script à créer si pas existant + +# 2. Pull la nouvelle image +docker compose pull + +# 3. Restart +docker compose down +docker compose up -d + +# 4. Vérifier les logs +docker compose logs -f +# Ctrl+C pour quitter + +# 5. Tester le service (login, fonctionnalités principales) +``` + +### Mises à jour critiques à surveiller + +| Service | Risque | Procédure | +|---|---|---| +| Traefik | Changement breaking config | Lire le CHANGELOG avant | +| Zitadel | Schema DB peut changer | Backup obligatoire avant | +| Postgres | Migration majeure | Backup + lire migration guide | +| OAuth2-Proxy | Changement de scopes/headers | Tester en dry-run | + +--- + +## ➕ Ajouter un nouvel outil + +### Procédure standard (~1h) + +#### 1. Ajouter le record DNS + +Chez le registrar : créer record A `.seizeconsulting.com` → IP du serveur. + +#### 2. Déployer l'outil (sans SSO) + +```bash +mkdir -p ~/docker/ +cd ~/docker/ + +# Créer docker-compose.yml +cat > docker-compose.yml << EOF +services: + : + image: : + container_name: + restart: unless-stopped + networks: + - web + # PAS de labels Traefik ici → c'est OAuth2-Proxy qui sera exposé +networks: + web: + external: true +EOF + +docker compose up -d +``` + +#### 3. Créer l'application dans Zitadel + +Console Zitadel → Projects → Infra Seize → New application : +- Type : **Web** +- Authentication Method : **Basic** +- Grant Types : **Authorization Code** +- Redirect URL : `https://.seizeconsulting.com/oauth2/callback` +- Token Settings : + - Auth Token Type : **JWT** + - User roles inside ID Token : ✅ + - Include user's profile info in the ID Token : ✅ + - Add user roles to the access token : ✅ + +Récupérer : **Client ID** + **Client Secret**. + +#### 4. Déployer un OAuth2-Proxy dédié + +```bash +mkdir -p ~/docker/oauth2-proxy- +cd ~/docker/oauth2-proxy- + +# docker-compose.yml (adapter depuis configs/oauth2-proxy/docker-compose.yml.template) +# .env (adapter depuis configs/oauth2-proxy/.env.example) +``` + +`.env` (variables à remplacer) : +```env +OAUTH2_PROXY_PROVIDER=oidc +OAUTH2_PROXY_CLIENT_ID= +OAUTH2_PROXY_CLIENT_SECRET= +OAUTH2_PROXY_COOKIE_SECRET=<32 chars random> +OAUTH2_PROXY_OIDC_ISSUER_URL=https://sso.seizeconsulting.com +OAUTH2_PROXY_REDIRECT_URL=https://.seizeconsulting.com/oauth2/callback +OAUTH2_PROXY_UPSTREAMS=http://: +OAUTH2_PROXY_SCOPE=openid email profile urn:zitadel:iam:org:project:roles +OAUTH2_PROXY_EMAIL_DOMAINS=* +OAUTH2_PROXY_COOKIE_DOMAINS=.seizeconsulting.com +OAUTH2_PROXY_COOKIE_SECURE=true +OAUTH2_PROXY_HTTP_ADDRESS=0.0.0.0:4180 +OAUTH2_PROXY_REVERSE_PROXY=true +OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER=true +OAUTH2_PROXY_PASS_ACCESS_TOKEN=true +OAUTH2_PROXY_SET_AUTHORIZATION_HEADER=true +OAUTH2_PROXY_SET_XAUTHREQUEST=true +OAUTH2_PROXY_WHITELIST_DOMAINS=.seizeconsulting.com +OAUTH2_PROXY_SKIP_PROVIDER_BUTTON=true +``` + +`chmod 600 .env` + `docker compose up -d`. + +#### 5. Ajouter une carte sur l'intranet + +Éditer `~/docker/intranet/index.html` et ajouter une nouvelle carte dans la section `` : + +```html + +
+
+
+
+
+
+
Description courte
+
+ .seizeconsulting.com + Catégorie +
+
+
+``` + +#### 6. Mettre à jour la matrice RBAC dans le JavaScript + +Éditer le ` +``` + +--- + +## 🎭 Création des 8 rôles dans Zitadel + +### Étape 1 — Aller dans le project + +Console Zitadel → Projects → **Infra Seize** → Roles → New + +### Étape 2 — Créer un par un + +| Key | Display Name | Group | +|---|---|---| +| `direction` | Direction | hierarchie | +| `manager` | Manager | hierarchie | +| `expert` | Expert | hierarchie | +| `consultant_senior` | Consultant Senior | hierarchie | +| `consultant_junior` | Consultant Junior | hierarchie | +| `alternant` | Alternant | hierarchie | +| `stagiaire` | Stagiaire | hierarchie | +| `support` | Support | hierarchie | + +### Étape 3 — Activer "Return user roles during authentication" + +Console Zitadel → Projects → **Infra Seize** → General → Settings : +- ☑️ **Return user roles during authentication** +- ❌ **Only authorized users can authenticate** (à laisser DÉCOCHÉ — cf. [D9](../DECISIONS.md)) +- ❌ **Authentication restricted to authorized orgs** + +### Étape 4 — Configurer chaque App pour transmettre les rôles + +Pour chaque application (Intranet, n8n, Portainer, etc.) : + +Console Zitadel → Projects → Infra Seize → Applications → `` → Token Settings : +- Auth Token Type : **JWT** (pas Bearer Token) +- ☑️ User roles inside ID Token +- ☑️ Include user's profile info in the ID Token +- ☑️ Add user roles to the access token + +**Save**. + +--- + +## 📊 Matrice RBAC effective + +| Outil | direction | manager | expert | cons_sr | cons_jr | alternant | stagiaire | support | +|---|---|---|---|---|---|---|---|---| +| **Intranet** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| **Gitea** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| **Code Server** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| **n8n** | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **Portainer** | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **Uptime Kuma (Status)** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | +| **Traefik Dashboard** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | + +Voir aussi `matrices/rbac-matrix.md` pour la vue détaillée. + +--- + +## 🔄 OAuth2-Proxy — Activer le scope des rôles + +Modifier le `.env` de **chaque** OAuth2-Proxy pour demander le scope des rôles : + +```env +OAUTH2_PROXY_SCOPE=openid email profile urn:zitadel:iam:org:project:roles +``` + +Puis restart : + +```bash +cd ~/docker/oauth2-proxy- +docker compose down +docker compose up -d +``` + +--- + +## 🎬 Workflow complet : du recrutement au compte fonctionnel + +``` +1. Nouveau collaborateur arrive + │ + ▼ +2. RH crée la fiche dans Furious Squad (job_title, manager, BU, etc.) + │ + │ Le jour J ou J+1 + ▼ +3. Sync Furious → Zitadel + - Soit manuel : python3 import-furious-to-zitadel.py + - Soit auto : workflow n8n (à mettre en place) + │ + ▼ +4. Génération du mapping : python3 generate-roles-mapping.py + → roles-mapping.json mis à jour + │ + ▼ +5. Copie vers l'intranet : cp roles-mapping.json ~/docker/intranet/ + → nginx ne nécessite pas de restart (volume monté) + │ + ▼ +6. Attribution du rôle : python3 assign-roles.py + → Le user reçoit son rôle dans Zitadel + │ + ▼ +7. Distribution du mdp temporaire via Teams + │ + ▼ +8. User se logge → voit les bonnes cartes selon son rôle +``` + +--- + +## 🐛 Troubleshooting RBAC + +### "Toutes les cartes sont visibles, le filtrage ne marche pas" + +#### Vérification 1 : le sub_filter nginx fonctionne ? + +```bash +# Depuis le serveur, vérifier que le HTML rendu contient le bon email +docker exec intranet sh -c "curl -s -H 'X-Forwarded-Email: test@seizeconsulting.com' http://localhost/ | grep 'USER_EMAIL'" +# Doit afficher : const USER_EMAIL = "test@seizeconsulting.com"; +``` + +Si le HTML contient encore `__USER_EMAIL__` : +- Vérifier que `nginx-rbac.conf` est bien monté dans le conteneur +- Vérifier les logs nginx : `docker logs intranet` + +#### Vérification 2 : roles-mapping.json est chargé ? + +Ouvrir la console JS du browser (F12) : +``` +[RBAC] User: xxx | Rôle: xxx +``` + +Si Rôle = `null` : +- Vérifier que l'email est bien dans roles-mapping.json +- Attention à la casse (le script fait `.toLowerCase()`) + +#### Vérification 3 : la matrice JS est correcte ? + +Dans la console : +```js +document.querySelectorAll('.service-card[data-app]').forEach(c => console.log(c.dataset.app, c.style.display)); +``` + +Doit afficher chaque carte avec son `display: none` ou `display: ""`. + +### "Le user voit __USER_EMAIL__ littéralement" + +Cause : nginx ne fait pas le sub_filter. + +Solutions : +1. Vérifier que `nginx-rbac.conf` est bien monté en RO sur `/etc/nginx/nginx.conf` (pas un autre chemin) +2. Vérifier que le header `X-Forwarded-Email` arrive bien (debug : tail logs nginx) +3. Restart : `docker compose down && docker compose up -d` + +### "Le user voit ses cartes mais quand il clique sur une carte non-autorisée, il y accède quand même" + +C'est **normal** et c'est la limite acceptée de l'Approche A. + +Pour vrai RBAC, voir [DECISIONS.md D7](../DECISIONS.md). + +### "Un user vient d'être ajouté dans Furious mais ne voit aucune carte" + +Causes : +1. roles-mapping.json pas à jour → relancer `generate-roles-mapping.py` + copier +2. User pas encore dans Zitadel → relancer l'import +3. job_title pas dans le mapping → ajouter dans `JOB_TITLE_TO_ROLE` du script + +--- + +## 🔄 Mise à jour de la matrice RBAC + +### Cas 1 : Ajouter un nouvel outil + +1. Ajouter la carte HTML dans `index.html` avec `data-app="newapp"` +2. Ajouter la ligne dans `ACCESS_MATRIX` : + ```javascript + "newapp": ["direction", "manager"], + ``` +3. Restart : `cd ~/docker/intranet && docker compose restart` + +### Cas 2 : Changer les autorisations d'un outil + +1. Modifier la liste de rôles dans `ACCESS_MATRIX` du JS +2. Restart : `docker compose restart` +3. Tester avec différents profils + +### Cas 3 : Ajouter un nouveau rôle + +1. Créer le rôle dans Zitadel (Projects → Roles → New) +2. Mettre à jour le mapping dans `assign-roles.py` (variable `JOB_TITLE_TO_ROLE`) +3. Mettre à jour `ACCESS_MATRIX` dans le JS pour préciser ce que ce rôle voit +4. Lancer `generate-roles-mapping.py` + `assign-roles.py` +5. Copier `roles-mapping.json` vers l'intranet +6. Restart intranet + +--- + +## 🎨 Personnalisation visuelle + +Le CSS de l'intranet est dans ` + + + + + + + + +