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

13 KiB

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.


🧬 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 :

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, 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.