seize-infra-doc/ARCHITECTURE.md
Hedi Kalboussi 1e90a85359 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
2026-06-05 17:49:43 +00:00

345 lines
13 KiB
Markdown

# 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: <user_id>
- X-Forwarded-Email: hedi.kalboussi@seizeconsulting.com
- X-Forwarded-Preferred-Username: HKA
- Authorization: Bearer <jwt_token>
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-<outil>/`**
2. **Un `docker-compose.yml`** avec labels Traefik routant `<outil>.seizeconsulting.com` → OAuth2-Proxy port 4180
3. **Un `.env`** avec :
- `OAUTH2_PROXY_PROVIDER=oidc`
- `OAUTH2_PROXY_CLIENT_ID=<id_zitadel>`
- `OAUTH2_PROXY_CLIENT_SECRET=<secret_zitadel>`
- `OAUTH2_PROXY_OIDC_ISSUER_URL=https://sso.seizeconsulting.com`
- `OAUTH2_PROXY_REDIRECT_URL=https://<outil>.seizeconsulting.com/oauth2/callback`
- `OAUTH2_PROXY_UPSTREAMS=http://<outil>:<port>`
- `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.