- 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
354 lines
9.9 KiB
Markdown
354 lines
9.9 KiB
Markdown
# 04 — Pattern OAuth2-Proxy par outil
|
|
|
|
> Comment chaque outil "non-OIDC-native" reçoit le SSO via OAuth2-Proxy.
|
|
|
|
---
|
|
|
|
## 🎯 Principe
|
|
|
|
Beaucoup d'outils (Portainer, n8n, Code Server, Uptime Kuma...) **n'ont pas de SSO OIDC natif**. Pour leur ajouter le SSO, on intercale **OAuth2-Proxy** entre Traefik et l'outil :
|
|
|
|
```
|
|
User → Traefik → OAuth2-Proxy → Outil
|
|
↓
|
|
Zitadel (auth)
|
|
```
|
|
|
|
### Pourquoi 1 OAuth2-Proxy par outil (et pas centralisé) ?
|
|
|
|
Voir [DECISIONS.md D2](../DECISIONS.md). En bref : **isolation**, **config différenciée**, **debug plus simple**.
|
|
|
|
---
|
|
|
|
## 📁 Anatomie d'un OAuth2-Proxy
|
|
|
|
Pour chaque outil, on a un **dossier** dédié :
|
|
|
|
```
|
|
~/docker/oauth2-proxy-<outil>/
|
|
├── docker-compose.yml
|
|
└── .env
|
|
```
|
|
|
|
⚠️ L'OAuth2-Proxy de **l'intranet** est dans `~/docker/oauth2-proxy/` (sans suffixe) pour des raisons historiques.
|
|
|
|
---
|
|
|
|
## 📄 docker-compose.yml — Template
|
|
|
|
```yaml
|
|
services:
|
|
oauth2-proxy-<outil>:
|
|
image: quay.io/oauth2-proxy/oauth2-proxy:v7.6.0
|
|
container_name: oauth2-proxy-<outil>
|
|
restart: unless-stopped
|
|
env_file:
|
|
- .env
|
|
networks:
|
|
- web
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.<outil>-sso.rule=Host(`<outil>.seizeconsulting.com`)"
|
|
- "traefik.http.routers.<outil>-sso.entrypoints=websecure"
|
|
- "traefik.http.routers.<outil>-sso.tls.certresolver=letsencrypt"
|
|
- "traefik.http.routers.<outil>-sso.service=<outil>-sso"
|
|
- "traefik.http.routers.<outil>-sso.priority=300"
|
|
- "traefik.http.services.<outil>-sso.loadbalancer.server.port=4180"
|
|
|
|
networks:
|
|
web:
|
|
external: true
|
|
```
|
|
|
|
### ⚠️ Détails importants
|
|
|
|
- `priority=300` : pour que ce router prenne le pas sur d'autres routers potentiellement définis sur le même host (notamment Gitea SSO natif).
|
|
- Le service backend (`<outil>`) **ne doit PAS avoir ses propres labels Traefik** pour exposer le host. Sinon conflit.
|
|
- `loadbalancer.server.port=4180` : OAuth2-Proxy écoute sur 4180 en interne.
|
|
|
|
---
|
|
|
|
## 📄 .env — Template
|
|
|
|
```env
|
|
# ===== PROVIDER OIDC ZITADEL =====
|
|
OAUTH2_PROXY_PROVIDER=oidc
|
|
OAUTH2_PROXY_CLIENT_ID=<RECUPERER_DEPUIS_ZITADEL>
|
|
OAUTH2_PROXY_CLIENT_SECRET=<RECUPERER_DEPUIS_ZITADEL>
|
|
OAUTH2_PROXY_COOKIE_SECRET=<GENERER_32_CHARS_ALEATOIRES>
|
|
OAUTH2_PROXY_OIDC_ISSUER_URL=https://sso.seizeconsulting.com
|
|
OAUTH2_PROXY_REDIRECT_URL=https://<outil>.seizeconsulting.com/oauth2/callback
|
|
|
|
# ===== UPSTREAM (l'outil derrière) =====
|
|
OAUTH2_PROXY_UPSTREAMS=http://<outil>:<port_interne>
|
|
|
|
# ===== COOKIES =====
|
|
OAUTH2_PROXY_COOKIE_DOMAINS=.seizeconsulting.com
|
|
OAUTH2_PROXY_COOKIE_SECURE=true
|
|
OAUTH2_PROXY_COOKIE_HTTPONLY=true
|
|
OAUTH2_PROXY_COOKIE_SAMESITE=lax
|
|
|
|
# ===== SÉCURITÉ =====
|
|
OAUTH2_PROXY_EMAIL_DOMAINS=*
|
|
OAUTH2_PROXY_SKIP_PROVIDER_BUTTON=true
|
|
OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER=true
|
|
OAUTH2_PROXY_PASS_ACCESS_TOKEN=true
|
|
OAUTH2_PROXY_SET_AUTHORIZATION_HEADER=true
|
|
OAUTH2_PROXY_SET_XAUTHREQUEST=true
|
|
|
|
# ===== RÉSEAU =====
|
|
OAUTH2_PROXY_HTTP_ADDRESS=0.0.0.0:4180
|
|
OAUTH2_PROXY_REVERSE_PROXY=true
|
|
OAUTH2_PROXY_WHITELIST_DOMAINS=.seizeconsulting.com
|
|
|
|
# ===== SCOPES OIDC =====
|
|
# Le scope urn:zitadel:iam:org:project:roles est CRITIQUE pour le RBAC
|
|
OAUTH2_PROXY_SCOPE=openid email profile urn:zitadel:iam:org:project:roles
|
|
|
|
# ===== DIVERS =====
|
|
OAUTH2_PROXY_REDIRECT_URL_AUTH_DOMAIN_AS_REDIRECT_URL_SUFFIX=false
|
|
OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL=true
|
|
OAUTH2_PROXY_FOOTER=-
|
|
```
|
|
|
|
### 🔑 Génération du cookie secret
|
|
|
|
```bash
|
|
python3 -c 'import secrets; print(secrets.token_urlsafe(32))'
|
|
```
|
|
|
|
→ 32 caractères aléatoires, à utiliser comme `OAUTH2_PROXY_COOKIE_SECRET`.
|
|
|
|
⚠️ **Si tu changes le cookie secret**, tous les utilisateurs seront déconnectés et devront se relogger.
|
|
|
|
---
|
|
|
|
## 🔐 Côté Zitadel — Créer l'application
|
|
|
|
Pour chaque outil, créer une **application** dans Zitadel :
|
|
|
|
### 1. Console Zitadel → Projects → Infra Seize → Applications → New
|
|
|
|
### 2. Configuration
|
|
|
|
- **Name** : `<Outil>` (ex: "n8n", "Portainer")
|
|
- **Type** : **Web**
|
|
- Click Continue
|
|
|
|
### 3. Authentication Method
|
|
|
|
- **Basic** (Client ID + Client Secret)
|
|
|
|
### 4. Authorization
|
|
|
|
- **Authorization Code**
|
|
- Cocher **Authorization Code** uniquement
|
|
- Ne PAS cocher Refresh Token sauf besoin
|
|
|
|
### 5. Redirect URLs
|
|
|
|
- Redirect URLs : `https://<outil>.seizeconsulting.com/oauth2/callback`
|
|
- Post-logout Redirect URLs : `https://<outil>.seizeconsulting.com/`
|
|
|
|
### 6. Confirmation et Token Settings
|
|
|
|
Après création, aller dans **Token Settings** de l'app :
|
|
|
|
| Paramètre | Valeur |
|
|
|---|---|
|
|
| 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 | ✅ |
|
|
| ClockSkew | 0 (par défaut OK) |
|
|
|
|
**Save**.
|
|
|
|
### 7. Récupérer Client ID + Client Secret
|
|
|
|
- Client ID : visible en haut de l'app
|
|
- Client Secret : Actions → Generate Client Secret → copier (montré 1 seule fois)
|
|
|
|
⚠️ **Sauvegarder dans le gestionnaire de mots de passe immédiatement**.
|
|
|
|
---
|
|
|
|
## 🚀 Déploiement d'un OAuth2-Proxy (pas à pas)
|
|
|
|
### Exemple : OAuth2-Proxy pour n8n
|
|
|
|
```bash
|
|
# 1. Créer le dossier
|
|
mkdir -p ~/docker/oauth2-proxy-n8n
|
|
cd ~/docker/oauth2-proxy-n8n
|
|
|
|
# 2. Copier le template
|
|
cp ~/seize-infra-doc/configs/oauth2-proxy/docker-compose.yml.template docker-compose.yml
|
|
cp ~/seize-infra-doc/configs/oauth2-proxy/.env.example .env
|
|
|
|
# 3. Adapter le docker-compose.yml
|
|
# Remplacer <outil> par "n8n"
|
|
# Le sed ci-dessous fait ça automatiquement
|
|
sed -i 's|<outil>|n8n|g' docker-compose.yml
|
|
|
|
# 4. Éditer le .env (nano ou vim) avec les vraies valeurs
|
|
nano .env
|
|
# Remplir : CLIENT_ID, CLIENT_SECRET, COOKIE_SECRET, REDIRECT_URL, UPSTREAMS
|
|
|
|
# 5. Sécuriser le .env
|
|
chmod 600 .env
|
|
|
|
# 6. Démarrer
|
|
docker compose up -d
|
|
|
|
# 7. Vérifier les logs
|
|
docker logs --tail 30 oauth2-proxy-n8n
|
|
```
|
|
|
|
### Vérification
|
|
|
|
```bash
|
|
# Test depuis le serveur
|
|
curl -sI http://localhost:4180/ # depuis le conteneur OAuth2-Proxy
|
|
|
|
# Test depuis l'extérieur (navigateur)
|
|
# Aller sur https://n8n.seizeconsulting.com
|
|
# → Redirection vers https://sso.seizeconsulting.com/oauth/v2/authorize?...
|
|
```
|
|
|
|
---
|
|
|
|
## 🐛 Troubleshooting OAuth2-Proxy
|
|
|
|
### "Redirect loop infini"
|
|
|
|
Symptôme : le browser fait des allers-retours entre l'outil et Zitadel sans jamais aboutir.
|
|
|
|
Cause #1 : **Cookie pas transmis par Traefik**.
|
|
→ Vérifier `~/docker/traefik/dynamic.yml` : `authRequestHeaders` contient bien `Cookie`.
|
|
|
|
Cause #2 : **Cookie domain incorrect**.
|
|
→ `OAUTH2_PROXY_COOKIE_DOMAINS=.seizeconsulting.com` (avec le point au début).
|
|
|
|
Cause #3 : **Cookie secure mais HTTP utilisé**.
|
|
→ S'assurer que toute la chaîne est en HTTPS.
|
|
|
|
### "Bad Gateway" après login
|
|
|
|
Symptôme : login Zitadel OK, mais erreur 502 ensuite.
|
|
|
|
Cause : OAuth2-Proxy ne peut pas joindre l'upstream.
|
|
|
|
```bash
|
|
# Tester depuis le conteneur OAuth2-Proxy
|
|
docker exec -it oauth2-proxy-n8n wget -qO- http://n8n:5678
|
|
```
|
|
|
|
Si erreur : vérifier que l'upstream est sur le même réseau Docker (`web`).
|
|
|
|
### "Erreur 401 sur /oauth2/callback"
|
|
|
|
Cause : Client Secret incorrect ou expiré.
|
|
|
|
```bash
|
|
# Vérifier dans Zitadel que le secret correspond à celui dans .env
|
|
# Regenerer le secret dans Zitadel et le remettre dans .env
|
|
```
|
|
|
|
### "Access Denied" après login (alors qu'on a un compte)
|
|
|
|
Cause : projet Zitadel a "Only authorized users" coché, mais l'user n'a pas de rôle attribué.
|
|
|
|
Solution :
|
|
- Soit attribuer un rôle à l'user
|
|
- Soit décocher "Only authorized users" sur le project (voir [DECISIONS.md D9](../DECISIONS.md))
|
|
|
|
### "Le scope `urn:zitadel:iam:org:project:roles` ne fonctionne pas"
|
|
|
|
Vérifier :
|
|
1. La case "Return user roles during authentication" est cochée sur le **Project** (pas seulement l'app)
|
|
2. Le scope est bien dans `.env` OAuth2-Proxy
|
|
3. Restart le conteneur après modif de `.env`
|
|
|
|
```bash
|
|
docker compose down
|
|
docker compose up -d
|
|
```
|
|
|
|
---
|
|
|
|
## 🔍 Voir le contenu du token JWT (debug)
|
|
|
|
⚠️ **Procédure de debug uniquement** — ne pas laisser activée en prod.
|
|
|
|
Voir [09-troubleshooting.md](./09-troubleshooting.md) section "Nginx diagnostic".
|
|
|
|
---
|
|
|
|
## 📊 Headers transmis par OAuth2-Proxy à l'outil
|
|
|
|
Une fois la chaîne d'auth complète, OAuth2-Proxy passe ces headers à l'outil :
|
|
|
|
| Header | Valeur exemple | Usage |
|
|
|---|---|---|
|
|
| `X-Forwarded-User` | `375119988404518915` | User ID Zitadel |
|
|
| `X-Forwarded-Email` | `hedi.kalboussi@seizeconsulting.com` | Email principal |
|
|
| `X-Forwarded-Preferred-Username` | `HKA` | Trigramme ou username |
|
|
| `X-Forwarded-Groups` | `(vide si claim Zitadel non plat)` | Groupes (pas exploitable directement avec Zitadel) |
|
|
| `Authorization` | `Bearer eyJ...` | Token JWT complet |
|
|
|
|
⚠️ Pour récupérer les **rôles**, il faut **décoder le JWT** (les rôles sont dans un claim `urn:zitadel:iam:org:project:roles`, pas dans `X-Forwarded-Groups`).
|
|
|
|
→ C'est ce qu'on fait sur l'intranet via JavaScript + `roles-mapping.json`.
|
|
|
|
---
|
|
|
|
## 📋 Liste des OAuth2-Proxy déployés
|
|
|
|
| OAuth2-Proxy | Outil protégé | Host |
|
|
|---|---|---|
|
|
| `oauth2-proxy` | Intranet | intranet.seizeconsulting.com |
|
|
| `oauth2-proxy-n8n` | n8n | n8n.seizeconsulting.com |
|
|
| `oauth2-proxy-portainer` | Portainer | portainer.seizeconsulting.com |
|
|
| `oauth2-proxy-code` | Code Server | code.seizeconsulting.com |
|
|
| `oauth2-proxy-status` | Uptime Kuma | status.seizeconsulting.com |
|
|
|
|
⚠️ **Gitea** n'utilise PAS OAuth2-Proxy : il a son **SSO OIDC natif**. Voir [05-applications.md](./05-applications.md).
|
|
|
|
---
|
|
|
|
## 🔄 Maintenance
|
|
|
|
### Mise à jour OAuth2-Proxy
|
|
|
|
```bash
|
|
cd ~/docker/oauth2-proxy-<outil>
|
|
docker compose pull
|
|
docker compose up -d
|
|
```
|
|
|
|
### Renouveler le Client Secret
|
|
|
|
```bash
|
|
# 1. Dans Zitadel, regénérer le Client Secret de l'app
|
|
# 2. Copier la nouvelle valeur
|
|
# 3. Éditer .env
|
|
nano ~/docker/oauth2-proxy-<outil>/.env
|
|
# Remplacer OAUTH2_PROXY_CLIENT_SECRET
|
|
|
|
# 4. Restart
|
|
cd ~/docker/oauth2-proxy-<outil>
|
|
docker compose down
|
|
docker compose up -d
|
|
```
|
|
|
|
### Rotation du cookie secret
|
|
|
|
⚠️ Tous les utilisateurs seront déconnectés.
|
|
|
|
```bash
|
|
NEW_SECRET=$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')
|
|
echo "Nouveau cookie secret : $NEW_SECRET"
|
|
# → Mettre à jour .env
|
|
```
|