⚠️ DESIGN ABANDONNÉ — 2026-07-09. Ce plan (lldap + FreeRADIUS) a été écrit avant le
déploiement d'Authentik. L'appliquer créerait un second annuaire, sans MFA.
Le design en vigueur est :lorva/infrastructure→
docs/superpowers/specs/2026-07-09-sso-centralisation-design.md.
Page conservée pour mémoire.
Statut : Planifié — stack Docker préparée dans
/tmp/ldap-design/, pas encore déployée en production.
Serveur cible :lorva-infra(10.10.10.10)
Path GitLab :infrastructure/ldap-radius
Centraliser les identités LORVA pour qu'un employé n'ait qu'un seul compte pour accéder à tous les services : SSH, VPN, pfSense, GitLab, Proxmox, Wiki.js, Chatwoot, NetBox.
Avant ce système, chaque service gère ses propres credentials locaux. Ce déploiement établit un référentiel unique : lldap comme source de vérité, avec des intégrations par protocole selon les besoins de chaque service.
| Composant | Rôle | Tech |
|---|---|---|
| lldap | Serveur LDAP + interface web | Rust, ~50 MB, SQLite |
| FreeRADIUS | Auth réseau (pfSense VPN + admin) | C, protocole RADIUS |
| lldap-webhook | Sync auto lldap → NetBox contacts | Python Flask |
Service Protocole Intermédiaire
─────────────────────────────────────────────────────
pfSense VPN RADIUS FreeRADIUS → LDAP bind
pfSense admin web RADIUS FreeRADIUS → LDAP bind
GitLab LDAP direct ldap_servers dans gitlab.rb
Proxmox LDAP direct Realm LDAP dans l'UI
Wiki.js LDAP direct Config dans l'interface admin
Chatwoot webhook lldap-webhook via GraphQL
NetBox LDAP + webhook ldap_config.py + contacts auto
SSH (toutes VMs) AuthorizedKeysCommand ldapsearch sshPublicKey
| Groupe | Accès accordé |
|---|---|
admins |
Accès total : Proxmox UI, pfSense admin, tous les services sans restriction |
team_infra |
Proxmox, pfSense, runners GitLab, accès SSH lorva-infra |
network_team |
pfSense RADIUS (VPN + admin réseau), SSH lorva-infra |
developers |
SSH lorva-infra + taskori-prod, GitLab member |
team_taskori |
Repos Taskori + pipelines CI/CD Taskori dans GitLab |
team_klibri |
Repos Klibri + pipelines CI/CD Klibri dans GitLab |
team_lanprobe |
Repo LanProbe dans GitLab |
employees |
Wiki.js (lecture/édition) + Chatwoot (accès agents) |
dc=lorva,dc=dev
├── ou=people
│ ├── uid=admin ← compte admin lldap
│ ├── uid=readonly ← compte service (FreeRADIUS + scripts SSH)
│ └── uid=<utilisateurs>
└── ou=groups
├── cn=admins
├── cn=team_infra
├── cn=network_team
├── cn=developers
├── cn=team_taskori
├── cn=team_klibri
├── cn=team_lanprobe
└── cn=employees
objectClass: groupOfUniqueNames
uniqueMember: uid=alice,ou=people,dc=lorva,dc=dev
L'appartenance à un groupe se fait via l'attribut uniqueMember pointant vers le DN complet de l'utilisateur.
readonlyCréé automatiquement par init_groups.sh. Utilisé pour toutes les recherches LDAP en lecture seule :
authorized_keys_from_ldap.sh sur chaque VM SSHldap_config.pyEmail : [email protected]. Mot de passe dans Flatnotes (taskori-sync → section LDAP secrets).
lorva-infranetbox-net existant (créé par le stack NetBox)lorva-public avec accès à 10.10.10.10ldap.lorva.dev résolu via DNS-01 Cloudflare (Traefik gère le cert automatiquement)ssh lorva-infra
sudo mkdir -p /docker/ldap
cd /docker/ldap
# Copier les fichiers depuis /tmp/ldap-design/
# docker-compose.yml, freeradius/, lldap-init/, ssh/
# Créer le fichier .env (voir variables ci-dessous)
cp .env.example .env
nano .env # Remplir les secrets — voir Flatnotes pour les valeurs
Variables .env requises (valeurs dans Flatnotes) :
| Variable | Description |
|---|---|
LLDAP_JWT_SECRET |
Secret JWT pour les sessions web lldap |
LLDAP_KEY_SEED |
Seed pour la génération des clés internes lldap |
LLDAP_ADMIN_PASS |
Mot de passe du compte admin lldap |
LDAP_READONLY_PASS |
Mot de passe du compte service readonly |
RADIUS_PFSENSE_SECRET |
Secret partagé RADIUS entre FreeRADIUS et pfSense |
NETBOX_URL |
URL de l'instance NetBox (http://netbox:8080 si même stack) |
NETBOX_TOKEN |
Token API NetBox — voir Flatnotes (reference_lorva_secrets) |
WEBHOOK_SECRET |
Secret HMAC pour sécuriser les appels webhook lldap→NetBox |
# Démarrer lldap en premier (les autres dépendent du healthcheck)
docker compose up -d lldap
# Vérifier que lldap est healthy avant de continuer
docker compose ps lldap
# Attendre STATUS = healthy (peut prendre 10-15s)
L'interface web lldap est accessible sur ldap.lorva.dev (via Traefik lorva-public → 10.10.10.10:17170). Sur lorva-infra en local : http://localhost:17170.
Configuration Traefik pour ldap.lorva.dev : créer traefik-config/lldap.yml sur lorva-public avec une route HTTP vers http://10.10.10.10:17170. Ce fichier n'est pas inclus dans /tmp/ldap-design/ — à créer avant la mise en prod.
ssh lorva-infra
cd /docker/ldap
# Rendre le script exécutable
chmod +x lldap-init/init_groups.sh
# Exécuter via docker exec (accès réseau lldap-net)
docker compose exec lldap-init bash /init_groups.sh
# OU directement depuis l'hôte si 17170 accessible :
./lldap-init/init_groups.sh
Le script init_groups.sh :
ou=groups,dc=lorva,dc=devreadonly avec email [email protected]Vérifier dans l'interface web ldap.lorva.dev → Groups : les 8 groupes doivent apparaître.
Ajouter l'attribut sshPublicKey avant de créer des users SSH :
sshPublicKey, Type=Text (multi-valued)ssh lorva-infra
cd /docker/ldap
# Démarrer FreeRADIUS (healthcheck lldap doit être OK)
docker compose up -d freeradius
# Vérifier les logs (le -X de debug_mode doit montrer la connexion LDAP)
docker compose logs -f freeradius
Points de vérification dans les logs FreeRADIUS :
LDAP - Connecting to ldap://lldap:3890 — connexion au conteneur lldapLDAP - Failed binding as... — le compte readonly doit être créé avantRetirer le mode debug en production : dans docker-compose.yml, supprimer le flag -X de la commande FreeRADIUS et redémarrer le conteneur.
Sur chaque VM devant utiliser l'auth SSH LDAP (lorva-infra, lorva-public, taskori-prod) :
# Copier le script depuis lorva-infra
scp lorva-infra:/docker/ldap/ssh/authorized_keys_from_ldap.sh /etc/ssh/
chmod 755 /etc/ssh/authorized_keys_from_ldap.sh
chown root:root /etc/ssh/authorized_keys_from_ldap.sh
# Créer le fichier de mot de passe readonly
echo "MOT_DE_PASSE_READONLY" > /etc/ssh/ldap_readonly_pass
chmod 600 /etc/ssh/ldap_readonly_pass
chown nobody:nogroup /etc/ssh/ldap_readonly_pass
Ajouter dans /etc/ssh/sshd_config :
AuthorizedKeysCommand /etc/ssh/authorized_keys_from_ldap.sh %u
AuthorizedKeysCommandUser nobody
# Valider la config et recharger sshd
sshd -t
systemctl reload sshd
Fonctionnement du script :
ldap://10.10.10.10:3890 via ldapsearch (timeout 5s)sshPublicKey pour l'utilisateur demandé~/.ssh/authorized_keys local (non bloquant)Test rapide :
sudo -u nobody /etc/ssh/authorized_keys_from_ldap.sh alice
# Doit afficher la clé SSH d'alice si elle est configurée dans lldap
Sur lorva-infra, éditer gitlab.rb :
ssh lorva-infra
sudo nano /etc/gitlab/gitlab.rb
gitlab_rails['ldap_enabled'] = true
gitlab_rails['ldap_servers'] = YAML.load <<-EOS
main:
label: 'LORVA LDAP'
host: '10.10.10.10'
port: 3890
uid: 'uid'
bind_dn: 'uid=readonly,ou=people,dc=lorva,dc=dev'
password: 'VOIR_FLATNOTES'
encryption: 'plain'
verify_certificates: false
active_directory: false
allow_username_or_email_login: true
base: 'ou=people,dc=lorva,dc=dev'
group_base: 'ou=groups,dc=lorva,dc=dev'
admin_group: 'admins'
user_filter: ''
EOS
sudo gitlab-ctl reconfigure
sudo gitlab-rake gitlab:ldap:check
Les utilisateurs LDAP peuvent se connecter avec leur uid ou email. Les membres du groupe admins obtiennent automatiquement le rôle Admin GitLab.
Deux mécanismes complémentaires :
A. Auth LDAP pour la connexion web NetBox :
Créer/éditer /opt/netbox/netbox/netbox/ldap_config.py :
import ldap
from django_auth_ldap.config import LDAPSearch, GroupOfUniqueNamesType
AUTH_LDAP_SERVER_URI = "ldap://10.10.10.10:3890"
AUTH_LDAP_BIND_DN = "uid=readonly,ou=people,dc=lorva,dc=dev"
AUTH_LDAP_BIND_PASSWORD = "VOIR_FLATNOTES"
AUTH_LDAP_USER_SEARCH = LDAPSearch(
"ou=people,dc=lorva,dc=dev",
ldap.SCOPE_SUBTREE,
"(uid=%(user)s)"
)
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
"ou=groups,dc=lorva,dc=dev",
ldap.SCOPE_SUBTREE,
"(objectClass=groupOfUniqueNames)"
)
AUTH_LDAP_GROUP_TYPE = GroupOfUniqueNamesType()
AUTH_LDAP_USER_FLAGS_BY_GROUP = {
"is_active": "cn=employees,ou=groups,dc=lorva,dc=dev",
"is_staff": "cn=team_infra,ou=groups,dc=lorva,dc=dev",
"is_superuser": "cn=admins,ou=groups,dc=lorva,dc=dev",
}
B. Webhook lldap → contacts NetBox (sync automatique) :
ssh lorva-infra
cd /docker/ldap
docker compose up -d lldap-webhook
docker compose logs -f lldap-webhook
Le webhook écoute sur le port interne 8765. Il est connecté à lldap-net ET netbox-net. Lors de la création ou modification d'un utilisateur dans lldap, un contact NetBox est créé ou mis à jour automatiquement.
Note : le fichier netbox-webhook/webhook_server.py est à créer (non inclus dans /tmp/ldap-design/). Configurer lldap pour appeler http://lldap-webhook:8765/sync sur les événements user create/update.
Dans l'interface admin Wiki.js → Authentication :
| Paramètre | Valeur |
|---|---|
| Strategy | LDAP / Active Directory |
| LDAP URL | ldap://10.10.10.10:3890 |
| Admin Bind DN | uid=readonly,ou=people,dc=lorva,dc=dev |
| Admin Bind Credentials | Voir Flatnotes |
| Search Base | ou=people,dc=lorva,dc=dev |
| Search Filter | (uid={{username}}) |
| Display Name Field | cn |
| Email Field | mail |
| TLS | Désactivé (réseau interne) |
| Self-registration | Activé pour le groupe employees |
Mapper le groupe employees comme groupe par défaut pour tous les nouveaux utilisateurs LDAP.
Dans pfSense → System → User Manager → Authentication Servers → Add :
| Paramètre | Valeur |
|---|---|
| Descriptive Name | LORVA RADIUS |
| Type | RADIUS |
| Hostname or IP Address | 10.10.10.10 |
| Shared Secret | Voir Flatnotes (RADIUS_PFSENSE_SECRET) |
| Authentication Port | 1812 |
| Accounting Port | 1813 |
| Authentication Timeout | 10 |
Activer pour :
LORVA RADIUS comme serveur d'authLORVA RADIUSSeuls les membres du groupe network_team (ou admins) doivent avoir accès à pfSense — configurer un filtre RADIUS Filter-Id ou utiliser les attributs de groupe FreeRADIUS si nécessaire.
Synchroniser le secret RADIUS : le RADIUS_PFSENSE_SECRET défini dans .env sur lorva-infra doit être identique à celui saisi côté pfSense. C'est une synchronisation manuelle à ne faire qu'une fois.
Ordre obligatoire (les depends_on du docker-compose.yml l'imposent, mais à vérifier manuellement) :
ssh lorva-infra
cd /docker/ldap
# 1. lldap en premier
docker compose up -d lldap
docker compose ps lldap # attendre healthy
# 2. Init groupes (une seule fois)
./lldap-init/init_groups.sh
# 3. FreeRADIUS
docker compose up -d freeradius
# 4. Webhook NetBox
docker compose up -d lldap-webhook
# Vérification globale
docker compose ps
docker compose logs --tail=50
Aller sur ldap.lorva.dev → Users → Create a user :
| Champ | Valeur |
|---|---|
| Username | prenom.nom (minuscules, sans accent) |
[email protected] |
|
| Display Name | Prénom Nom |
| Password | Générer un mot de passe fort, communiquer à l'employé via canal sécurisé |
Dans le profil de l'utilisateur → attribut sshPublicKey → ajouter la clé publique au format standard (ssh-ed25519 AAAA... ou ssh-rsa AAAA...).
Plusieurs clés peuvent être ajoutées (multi-valued) — utile si l'employé travaille depuis plusieurs machines.
Dans lldap → Groups → sélectionner chaque groupe → Add member :
employeesdevelopers + team_taskori et/ou team_klibri et/ou team_lanprobeteam_infra (+ network_team si accès réseau pfSense)adminsUne fois les groupes assignés, les accès suivants sont automatiquement actifs :
| Accès | Délai | Condition |
|---|---|---|
SSH sur lorva-infra |
Immédiat | Groupe developers ou team_infra + clé SSH renseignée |
SSH sur taskori-prod |
Immédiat | Groupe developers + clé SSH renseignée |
SSH sur lorva-public |
Immédiat | Groupe team_infra + clé SSH renseignée |
| GitLab login | Immédiat | Auth LDAP activée |
| pfSense VPN/admin | Immédiat | Groupe network_team |
| Wiki.js | Immédiat | Groupe employees |
| NetBox contact | ~quelques secondes | Webhook lldap → NetBox |
# Tester la résolution LDAP de l'utilisateur (depuis n'importe quelle VM)
ldapsearch -H ldap://10.10.10.10:3890 \
-D "uid=readonly,ou=people,dc=lorva,dc=dev" \
-w "$(cat /etc/ssh/ldap_readonly_pass)" \
-b "ou=people,dc=lorva,dc=dev" \
"(uid=prenom.nom)"
# Tester la récupération de la clé SSH
sudo -u nobody /etc/ssh/authorized_keys_from_ldap.sh prenom.nom
# Tester l'auth RADIUS (depuis lorva-infra)
docker compose exec freeradius radtest prenom.nom MOT_DE_PASSE 127.0.0.1 0 RADIUS_SECRET
# Réponse attendue : Access-Accept
Internet
│
▼
lorva-public (10.10.30.10)
├── Traefik v3 (DNS-01 Cloudflare, cert auto)
│ └── ldap.lorva.dev → http://10.10.10.10:17170 (FileProvider lldap.yml)
├── Wiki.js → LDAP → 10.10.10.10:3890
└── Chatwoot → webhook → 10.10.10.10:8765
lorva-infra (10.10.10.10) — réseau Docker lldap-net
├── lldap :3890/tcp (LDAP) :17170/tcp (Web UI) :6360/tcp (LDAPS, optionnel)
├── FreeRADIUS :1812/udp (auth) :1813/udp (accounting)
├── lldap-webhook :8765/tcp (interne)
├── GitLab → LDAP 10.10.10.10:3890 (loopback interne)
└── NetBox → LDAP 10.10.10.10:3890 + webhook contacts
pfSense (10.10.10.1)
└── RADIUS auth → 10.10.10.10:1812/udp
SSH — AuthorizedKeysCommand
├── lorva-infra → ldapsearch 10.10.10.10:3890
├── lorva-public → ldapsearch 10.10.10.10:3890
└── taskori-prod → ldapsearch 10.10.10.10:3890
| Réseau | Usage |
|---|---|
lldap-net |
Réseau interne : lldap, freeradius, lldap-webhook |
netbox-net |
Réseau existant NetBox — lldap-webhook s'y connecte pour joindre NetBox |
Le TLS est désactivé sur le réseau Docker interne (même hôte, réseau isolé). Si des VMs externes doivent se connecter directement au port LDAP (hors SSH qui passe par authorized_keys_from_ldap.sh), activer LDAPS :
6360 dans docker-compose.ymltls {} dans freeradius/ldap.confldaps://10.10.10.10:6360 dans les configs clientes| Fichier | Description |
|---|---|
.env |
Copier .env.example, remplir les secrets (voir Flatnotes) |
freeradius/mods-enabled/ldap |
Symlink vers mods-available/ldap |
freeradius/sites/default |
Site FreeRADIUS principal (authorize + authenticate) |
netbox-webhook/webhook_server.py |
Serveur Flask de sync lldap → NetBox |
traefik-config/lldap.yml |
Route Traefik sur lorva-public pour ldap.lorva.dev |
Le docker-compose.yml fourni lance FreeRADIUS avec -X (mode debug verbeux). Retirer ce flag avant la mise en production pour éviter la fuite d'informations sensibles dans les logs.
Le RADIUS_PFSENSE_SECRET dans .env doit être saisi manuellement dans pfSense (System → User Manager → Authentication Servers). Cette synchronisation est manuelle — toute rotation du secret nécessite une mise à jour des deux côtés simultanément.
Cet attribut personnalisé doit être créé dans lldap (Administration → User Attributes) avant de créer des utilisateurs ayant besoin d'accès SSH. Si l'attribut n'existe pas, le script authorized_keys_from_ldap.sh retournera une clé vide mais ne bloquera pas la connexion (fallback authorized_keys local).
La base lldap est un fichier SQLite à /data/users.db dans le volume Docker. Inclure ce volume dans les sauvegardes régulières de lorva-infra. Une perte de ce fichier implique la recréation de tous les comptes et groupes.
# Lister tous les utilisateurs LDAP
ldapsearch -H ldap://10.10.10.10:3890 \
-D "uid=readonly,ou=people,dc=lorva,dc=dev" \
-w "$(cat /etc/ssh/ldap_readonly_pass)" \
-b "ou=people,dc=lorva,dc=dev" \
"(objectClass=person)" uid cn mail
# Lister les membres d'un groupe
ldapsearch -H ldap://10.10.10.10:3890 \
-D "uid=readonly,ou=people,dc=lorva,dc=dev" \
-w "$(cat /etc/ssh/ldap_readonly_pass)" \
-b "ou=groups,dc=lorva,dc=dev" \
"(cn=developers)" uniqueMember
# Tester l'auth RADIUS (remplacer les valeurs)
docker compose -f /docker/ldap/docker-compose.yml exec freeradius \
radtest USERNAME PASSWORD 127.0.0.1 0 SHARED_SECRET
# Vérifier la clé SSH récupérée pour un user
sudo -u nobody /etc/ssh/authorized_keys_from_ldap.sh USERNAME
# Restart propre du stack (ordre conservé)
cd /docker/ldap
docker compose restart lldap
docker compose restart freeradius lldap-webhook
# Logs en temps réel
docker compose logs -f --tail=100
taskori-sync — clés et secrets LDAP/RADIUStaskori-infra — architecture générale des 9 repos LORVAinfrastructure/proxmox — configuration du realm LDAP dans Proxmoxinfrastructure/pfsense — configuration complète pfSense (OpenVPN + RADIUS)infrastructure/gitlab — gitlab.rb complet avec section LDAP