- 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
9.9 KiB
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. 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
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
# ===== 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
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
# 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
# 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.
# 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é.
# 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)
"Le scope urn:zitadel:iam:org:project:roles ne fonctionne pas"
Vérifier :
- La case "Return user roles during authentication" est cochée sur le Project (pas seulement l'app)
- Le scope est bien dans
.envOAuth2-Proxy - Restart le conteneur après modif de
.env
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 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.
🔄 Maintenance
Mise à jour OAuth2-Proxy
cd ~/docker/oauth2-proxy-<outil>
docker compose pull
docker compose up -d
Renouveler le Client Secret
# 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.
NEW_SECRET=$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')
echo "Nouveau cookie secret : $NEW_SECRET"
# → Mettre à jour .env