Fédération d'identités
Concevoir et mettre en œuvre une architecture où une seule identité, un seul second facteur et un seul jeu de rôles gouvernent l'accès à l'ensemble des services — sans rien exposer sur Internet.
| Rôle | conception, mise en œuvre et validation |
|---|---|
| Stack | Keycloak · OAuth2 Proxy · Nginx Proxy Manager · Let's Encrypt (DNS-01) · Cloudflare · Pi-hole · Tailscale · Docker Compose |
01
Contexte et objectifs
Le contexte
Le serveur est un ancien PC reconditionné : quatre cœurs, 3,7 Go de RAM, 9,1 To de stockage,
sous Debian. Le domaine rocha-server.org est enregistré et géré chez
Cloudflare. Seule l'adresse de ce site vitrine y est publiée : les douze
sous-domaines applicatifs n'existent que sur le réseau interne. La douzaine de services qui y
tournent sont utilisés au quotidien, y compris par d'autres personnes du foyer, ce qui exclut
toute recréation des volumes de données.
Ce que le système devait garantir
- Une identité unique pour toutes les applications, avec des rôles et un second facteur.
- Aucun accès administratif sans passer par le contrôle d'accès.
- Du TLS valide partout, sans publier la moindre adresse IP ni ouvrir de port entrant.
- Une continuité d'administration en cas d'indisponibilité du fournisseur d'identité.
- Une empreinte mémoire compatible avec le matériel — le seul vrai facteur limitant.
02
Cinq couches, aucun contournement
| Couche | Rôle |
|---|---|
| Réseau | Un VPN maillé WireGuard définit le périmètre d'administration. Les interfaces sensibles ne sont joignables que depuis le tunnel, et cette restriction s'évalue sur l'adresse source réelle du client. |
| Résolution | Le DNS interne détient l'intégralité des enregistrements des sous-domaines applicatifs. La zone Cloudflare n'en publie aucun : elle sert à prouver la propriété du domaine auprès de l'autorité de certification, et à résoudre ce seul site vitrine. Les noms du homelab ne résolvent nulle part ailleurs que sur le réseau. |
| Transport | Un unique reverse proxy termine le TLS pour les douze domaines, avec un certificat wildcard ECDSA, une redirection HTTPS forcée et HSTS. C'est le seul composant qui écoute sur les ports web. |
| Identité | Keycloak porte un realm applicatif : rôles, groupes, sessions bornées, protection contre le bourrinage, second facteur conditionnel. |
| Applications | Onze services sur un réseau interne unique. Aucun ne publie de port sur l'hôte : ils ne sont atteignables qu'à travers le proxy. |
Les trois modes d'accès
Toutes les applications ne parlent pas le même langage : certaines implémentent OpenID Connect, d'autres ne connaissent que leur propre mot de passe, d'autres encore utilisent des protocoles qu'un navigateur n'initie jamais. Plutôt que de forcer un mécanisme unique, j'ai défini trois modes et je classe chaque nouvelle application dans l'un d'eux.
| Mode | Quand l'appliquer | Ce qu'il garantit |
|---|---|---|
| SSO natif | l'application implémente OpenID Connect | authentification et identité transmise : l'application sait qui est connecté et applique ses propres rôles |
| Portail (forward-auth) | l'application n'a aucun support SSO | le proxy interroge l'annuaire avant de laisser passer la requête : il contrôle qui a le droit de parler à l'application |
| Réseau privé | services techniques, protocoles non navigateur | l'application n'est joignable que depuis le tunnel VPN |
Encart — la distinction qui gouverne toute l'architecture
Un portail n'est pas un SSO.
Il décide si une requête a le droit d'atteindre l'application ; il ne connecte pas l'utilisateur à l'application. Nommer cette différence explicitement a produit deux décisions opposées sur deux services sans support SSO : pour l'un, l'authentification en amont suffit et l'accès devient transparent ; pour l'autre, une authentification locale reste indispensable, parce qu'un reverse proxy ne peut pas dépendre de ce que le reverse proxy protège.
03
Les arbitrages qui ont structuré le résultat
Un certificat wildcard, zéro port entrant
Let's Encrypt ne délivre un certificat couvrant *.rocha-server.org que par validation
DNS. Le reverse proxy dispose donc d'un jeton d'API Cloudflare restreint à l'édition DNS d'une
seule zone — ni facturation, ni autres domaines, ni le reste du compte, révocable isolément.
Le bénéfice dépasse le confort : le certificat s'obtient et se renouvelle sans que l'autorité ait jamais besoin d'atteindre le serveur, et un seul certificat couvre les douze domaines. Aucun sous-domaine applicatif n'est publié dans la zone : les noms restent introuvables depuis Internet.
Un second facteur ciblé plutôt qu'universel
Le MFA est piloté par un rôle, porté par le groupe d'administration. Il s'impose aux comptes qui peuvent modifier l'infrastructure, et pas aux comptes d'usage quotidien.
C'est un choix délibéré : le MFA universel sur des usages domestiques produit de la fatigue d'authentification, puis du contournement. Le MFA ciblé se respecte parce qu'il se justifie. Le périmètre est extensible à n'importe quel compte par simple attribution du rôle.
Provisionnement automatique : le même réglage, deux décisions opposées
L'intégration OpenID Connect propose partout de créer automatiquement un compte local pour tout utilisateur authentifié.
- Sur le superviseur de conteneurs : désactivé. Cet outil pilote le démon Docker ; un compte y équivaut à un accès privilégié sur la machine entière. Les comptes doivent exister au préalable.
- Sur la photothèque : activé. Un compte n'y donne accès qu'à ses propres photos. Le risque est borné, le confort réel.
Le facteur décisif n'est pas la fonctionnalité, c'est le périmètre de ce qu'un compte obtient.
Une messagerie transactionnelle interne
Keycloak doit expédier des courriels — vérification d'adresse, réinitialisation. Deux mesures ont écarté d'emblée un serveur de messagerie classique : le port 25 sortant est bloqué par le fournisseur d'accès, et l'enregistrement inverse de l'adresse publique est résidentiel générique. Aucun message émis depuis cette ligne ne serait accepté par un grand fournisseur.
La réponse retenue est un serveur SMTP interne qui capture les messages et les expose dans une interface protégée par les mêmes règles que le reste. Le besoin réel — ouvrir un lien de vérification — est couvert pour huit mégaoctets de mémoire, sans dépendance externe ni donnée qui sort du serveur.
Moindre privilège, vérifié dans le conteneur
Les applications manipulant des données personnelles tournent avec un compte non privilégié et l'interdiction d'élever leurs droits. Les chemins sensibles du système de fichiers sont cloisonnés au niveau du noyau, et non par une simple règle applicative — la frontière tient même si l'application est compromise.
Point de méthode
Les variables d'environnement censées fixer l'identité d'exécution d'un conteneur sont une convention propre à certains éditeurs d'images, et sont silencieusement ignorées par d'autres. Leur présence dans un fichier de configuration ne prouve rien. La seule vérification qui vaut est l'identifiant d'utilisateur lu à l'intérieur du conteneur en fonctionnement.
Deux services à la demande
Les deux composants les plus gourmands sont démarrés puis éteints par script, avec un garde-fou qui refuse le démarrage si la mémoire disponible est insuffisante. C'est la façon dont une pile complète tient sur ce matériel, avec une fenêtre d'indisponibilité connue et assumée.
04
Problèmes techniques rencontrés
L'architecture cible tenait en une page. Ce sont les problèmes de mise en œuvre qui ont fait l'essentiel du travail — et chacun a produit une règle de méthode que j'applique depuis.
Le contrôle d'accès par origine réseau, neutralisé par une traduction d'adresse
Symptôme
Depuis un poste légitime connecté au VPN, une interface protégée répondait 403.
Diagnostic
Les journaux du proxy donnaient l'adresse source réelle : celle de la passerelle du réseau Docker, pas celle du client. Le VPN applique une traduction d'adresse sur le trafic qu'il route, et la publication d'un port par Docker transforme toute requête entrante en trafic routé — donc traduit, donc anonymisé avant d'atteindre la règle d'autorisation.
Correction
Désactivation explicite de la traduction sur les routes du VPN, et alignement de la résolution interne pour que chaque nom protégé pointe vers l'adresse qui préserve l'origine. Bénéfice secondaire : ces noms deviennent injoignables sans le VPN, ce qui ajoute une défense par construction.
Le point qui compte
La vérification faite à l'étape précédente était positive. Elle bouclait depuis le serveur vers sa propre adresse VPN : un chemin qui ne traverse jamais la traduction d'adresse. C'était un faux positif complet.
Règle retenue
Un contrôle d'accès par origine ne se teste jamais depuis la machine qui l'héberge, mais depuis un vrai client distant, sur le vrai chemin réseau. Un test qui ne peut pas échouer ne prouve rien.
Un rejet intermittent, causé par une requête d'icône
Symptôme
Avec une authentification réussie et le bon rôle, l'accès échouait environ une fois sur deux. Le journal signalait un jeton anti-CSRF introuvable au retour d'authentification.
Diagnostic
Le navigateur réclame /favicon.ico en parallèle de la page. Cette requête est interceptée par le même contrôle, déclenche son propre cycle d'autorisation et écrase le cookie anti-CSRF, qui était unique. Le cycle légitime ne retrouvait plus le sien.
Correction
Émission d'un jeton anti-CSRF par cycle, indexé sur le paramètre d'état de la transaction, avec une expiration courte.
Règle retenue
Toute application placée derrière un contrôle d'accès délégué est exposée à ce comportement. Il fait désormais partie de la configuration par défaut, pas d'un correctif au cas par cas.
Une contrainte structurelle prise pour un défaut de configuration
Symptôme
Authentification acceptée, puis rejet au retour : l'adresse électronique de l'utilisateur n'était pas vérifiée, et sans serveur de messagerie elle ne pouvait jamais l'être.
Arbitrage
Une option existait pour ignorer la vérification. Elle a été écartée : elle supprime un contrôle au lieu de répondre au besoin. La mesure du blocage réseau et de l'enregistrement inverse a montré qu'aucune configuration ne résoudrait le problème, ce qui a orienté vers la messagerie interne décrite plus haut. Validé de bout en bout sur un compte jetable.
Règle retenue
Avant de chercher une configuration, mesurer si le chemin est seulement possible. Certaines impasses sont structurelles.
Une application injoignable, alors que ses deux couches de sécurité étaient correctes
Symptôme
Une application correctement déclarée côté annuaire et côté reverse proxy restait totalement inaccessible.
Diagnostic
Le sous-domaine n'avait pas d'entrée dans le DNS interne. Comme la zone publique ne contient volontairement aucun enregistrement d'adresse, le nom ne résolvait nulle part : aucune requête n'atteignait jamais le proxy.
Règle retenue
Avant d'ouvrir un journal applicatif, vérifier que la requête est bien arrivée. L'absence d'entrée dans un journal est une information à part entière.
05
Transformer l'expérience en procédure
Aucun de ces problèmes ne devait être découvert deux fois. La procédure d'ajout d'une application a donc été formalisée de bout en bout — choix du mode d'accès, déploiement, création du client ou du rôle, configuration du proxy, entrée DNS, cloisonnement des secrets, documentation, vérification — accompagnée de trois gabarits de configuration correspondant aux trois modes.
L'automatisation s'appuie sur un compte de service dédié, porteur des seuls droits nécessaires, sans second facteur donc scriptable, et révocable indépendamment des comptes humains. Les secrets qu'il génère sont écrits directement dans des fichiers à permissions restreintes, jamais affichés sur une sortie standard.
Les scripts sont idempotents : les rejouer ne casse rien.
06
Ce que le système garantit aujourd'hui
| Identité | Un annuaire unique, six applications fédérées, rôles et groupes centralisés |
|---|---|
| Authentification forte | Second facteur conditionnel sur les comptes d'administration, protection contre le bourrinage, sessions bornées |
| Chiffrement | Certificat wildcard ECDSA, HTTPS forcé et HSTS sur les douze domaines, renouvellement automatique |
| Exposition | Quatre ports en écoute, aucune interface d'administration accessible hors du périmètre de confiance, aucun sous-domaine applicatif publié |
| Cloisonnement | Secrets protégés par permissions et par frontières noyau, exécution non privilégiée pour les services manipulant des données personnelles |
| Empreinte | L'ensemble fonctionne dans 3,7 Go de RAM |
Encart — validation
Chaque ligne a été vérifiée par un test, pas déduite d'un fichier de configuration : autorisation évaluée depuis un vrai client distant, contenu réel des jetons émis, identifiant d'utilisateur lu dans les conteneurs en fonctionnement, comportement après redémarrage complet du serveur.
07
Compétences mobilisées
| Domaine | Mise en œuvre |
|---|---|
| Gestion des identités | OpenID Connect / OAuth 2.0, realm, clients confidentiels, mappeurs de rôles et d'audience, flux d'authentification personnalisés, comptes de service |
| Authentification forte | MFA TOTP conditionnel piloté par rôle |
| Contrôle d'accès | RBAC par rôles et groupes, autorisation déléguée au niveau du proxy, distinction entre absence de session et absence de droit |
| PKI & TLS | ACME par validation DNS, certificat wildcard, HSTS, renouvellement automatique |
| Architecture Zero Trust | périmètre VPN, DNS interne sans exposition publique, aucun chemin de contournement |
| Durcissement | réduction de surface, exécution non privilégiée, cloisonnement noyau des chemins sensibles, gestion des secrets |
| Diagnostic réseau | traduction d'adresse, règles de filtrage, corrélation entre journaux proxy et journaux applicatifs, identification de faux positifs de test |
| Exploitation | Docker Compose, versions épinglées, automatisation idempotente, documentation systématique |