commit 1e90a853591b8c65a9bc8fa55f9b0a3eea066177 Author: Hedi Kalboussi Date: Fri Jun 5 17:49:43 2026 +0000 Documentation initiale infra Seize — passation J7 - 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 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 ` + + + + + + + + +