Table des matières
Serveur d'authentification LLDAP + configuration réseau
Dernière mise à jour : juin 2026
État : opérationnel et validé (login système en ligne ET hors ligne fonctionnels via sssd côté client — voir doc client).
Remplace Kanidm (abandonné : authentification hors ligne cassée). LLDAP = annuaire LDAP léger avec UI web. Le login système et le cache offline sont gérés côté client par sssd (doc séparée).
Architecture réseau
Internet
|
v
[ Routeur OpenWrt ] 192.168.1.90 (auth.beafrancois.fr pointe ici)
| redirections par port :
| 443 -> 192.168.1.25:443 (proxy Caddy) [web autres services, zones wan/lan/invite]
| 6360 -> 192.168.1.9:6360 (serveur LLDAP) [LDAPS, zones lan/invite]
|
└──(6360)─> [ Serveur LLDAP ] 192.168.1.9 (LDAPS direct, sssd)
[ Proxy Caddy ] 192.168.1.25
reverse_proxy HTTP :
ldap.beafrancois.fr -> 192.168.1.9:17170 (UI web LLDAP, tls internal)
[ Serveur LLDAP ] 192.168.1.9 (cahute)
:3890 LDAP clair (NON exposé : interne serveur uniquement)
:6360 LDAPS chiffré (cert auto-signé, pour sssd — atteint via routeur:6360)
:17170 UI web (HTTP) (atteint via Caddy / ldap.beafrancois.fr)
Séparation des deux noms :
auth.beafrancois.fr = le LDAP lui-même (LDAPS 6360, pour sssd)
-> routeur:6360 -> serveur .9:6360
ldap.beafrancois.fr = l'UI web d'administration
-> proxy .25 -> serveur .9:17170
[ Portables ] LAN filaire 192.168.1.0/26 + WiFi invité 192.168.115.0/24
sssd -> ldaps://auth.beafrancois.fr:6360 (= routeur:6360 -> serveur)
Réseaux :
- LAN filaire : 192.168.1.0/26 (serveur .9, proxy .25, routeur .90)
- WiFi invité : 192.168.115.0/24 (isolé)
auth.beafrancois.fr= le LDAP (LDAPS). Résout vers le routeur (.90), qui relaie le 6360 vers le serveur. Ne sert PAS au web.ldap.beafrancois.fr= l'UI web d'administration. Résout vers le proxy (.25, DNS local), qui reverse-proxy vers LLDAP:17170 (tls internal).- Le LDAPS ne passe PAS par Caddy (Caddy ne fait que du HTTP) : le routeur relaie directement le 6360 vers LLDAP.
Déploiement LLDAP (Docker)
Structure
/mnt/stockage/docker/
├── compose/lldap/docker-compose.yml
└── data/lldap/
├── lldap_config.toml (généré au 1er démarrage)
├── users.db (base SQLite)
├── ca-cert.pem / ca-key.pem (CA auto-signé)
├── cert.pem / key.pem (cert serveur LDAPS, CN=auth.beafrancois.fr)
├── san.ext
└── secrets/
├── jwt_secret
└── ldap_user_pass (mot de passe admin LLDAP)
Secrets
mkdir -p /mnt/stockage/docker/data/lldap/secrets tr -cd '[:alnum:]' < /dev/urandom | fold -w 64 | head -n 1 > .../secrets/jwt_secret tr -cd '[:alnum:]' < /dev/urandom | fold -w 24 | head -n 1 > .../secrets/ldap_user_pass chmod 600 .../secrets/*
Certificat LDAPS auto-signé
cd /mnt/stockage/docker/data/lldap # CA sudo openssl req -x509 -newkey rsa:4096 -keyout ca-key.pem -out ca-cert.pem \ -days 3650 -nodes -subj "/CN=LLDAP-CA-beafrancois" # Clé serveur + CSR sudo openssl req -newkey rsa:4096 -keyout key.pem -out server.csr \ -nodes -subj "/CN=auth.beafrancois.fr" # SAN sudo tee san.ext > /dev/null <<'EOF' subjectAltName = DNS:auth.beafrancois.fr EOF # Signature sudo openssl x509 -req -in server.csr -CA ca-cert.pem -CAkey ca-key.pem \ -CAcreateserial -out cert.pem -days 3650 -extfile san.ext
Vérification :
openssl s_client -connect 192.168.1.9:6360 -CAfile .../ca-cert.pem < /dev/null 2>/dev/null \ | grep -E "subject=|Verify return code" # Attendu : subject=CN=auth.beafrancois.fr / Verify return code: 0 (ok)
docker-compose.yml
services: lldap: image: lldap/lldap:stable container_name: lldap restart: unless-stopped ports: - "192.168.1.9:3890:3890" # LDAP clair (interne) - "192.168.1.9:6360:6360" # LDAPS chiffré - "192.168.1.9:17170:17170" # Web UI volumes: - /mnt/stockage/docker/data/lldap:/data - /mnt/stockage/docker/data/lldap/secrets:/secrets:ro environment: - LLDAP_LDAP_BASE_DN=dc=beafrancois,dc=fr - LLDAP_JWT_SECRET_FILE=/secrets/jwt_secret - LLDAP_LDAP_USER_PASS_FILE=/secrets/ldap_user_pass - LLDAP_LDAPS_OPTIONS__ENABLED=true - LLDAP_LDAPS_OPTIONS__PORT=6360 - LLDAP_LDAPS_OPTIONS__CERT_FILE=/data/cert.pem - LLDAP_LDAPS_OPTIONS__KEY_FILE=/data/key.pem - UID=0 - GID=0 networks: default: name: lldap_net
Lancement : cd /mnt/stockage/docker/compose/lldap && docker compose up -d
Version déployée : LLDAP 0.6.3.
Pare-feu serveur (UFW)
# LDAPS : /24 (PAS /26 !) + wifi invité /24 # ⚠️ /24 nécessaire : le LDAPS arrive via le ROUTEUR (.90), qui est HORS du /26 # (le /26 s'arrête à .63). Une règle /26 bloque le trafic relayé par le routeur. sudo ufw allow from 192.168.1.0/24 to any port 6360 proto tcp sudo ufw allow from 192.168.115.0/24 to any port 6360 proto tcp # Web admin : via proxy Caddy uniquement sudo ufw allow from 192.168.1.25 to any port 17170 proto tcp # Port 3890 (LDAP clair) : NON ouvert (interne au serveur)
⚠️ Piège vécu : le LAN filaire est en /26 (.1 à .62), mais le routeur .90 est hors de cette plage. Comme le LDAPS d'auth.beafrancois.fr est relayé par le routeur, la source vue par le serveur peut être .90. Une règle UFW en /26 bloque alors le LDAPS. Utiliser /24 pour le port 6360.
Reverse proxy Caddy
Caddy ne gère QUE l'UI web (HTTP). Le LDAPS d'auth.beafrancois.fr ne passe pas par Caddy (voir architecture).
Sur le proxy (/opt/proxy/Caddyfile) :
# UI web LLDAP (cert interne, pas de Let's Encrypt car pas de DNS public)
ldap.beafrancois.fr {
tls internal
reverse_proxy 192.168.1.9:17170
}
Recharge : docker compose exec caddy caddy reload –config /etc/caddy/Caddyfile
Note : tls internal génère un certificat auto-signé Caddy (avertissement navigateur à accepter). Utilisé car ldap.beafrancois.fr n'a pas d'entrée DNS publique (Let's Encrypt échouerait avec NXDOMAIN).
Configuration OpenWrt (routeur .90)
Principe
auth.beafrancois.fr résout vers le routeur. Le routeur redirige par port vers la bonne machine. Les services web internes sont résolus par DNS local ciblé (pas de redirection 443 globale qui casserait l'accès internet).
Redirections de port (firewall)
Règles DNAT nécessaires, par zone source (wan + lan + invite) :
- 443 → 192.168.1.25:443 (proxy) — une règle par zone
- 6360 → 192.168.1.9:6360 (LLDAP) — zones lan + invite
- Cockpit 9090, git ssh 8329, etc. : redirections existantes conservées
⚠️ Piège rencontré : une règle 443 avec uniquement src=wan casse l'accès web interne. Il faut des règles 443 aussi pour lan et invite (ou NAT loopback).
Port admin du routeur déplacé
⚠️ Piège majeur : l'interface admin OpenWrt écoutait sur 443, donc la redirection 443 invité → proxy coupait l'accès à l'admin du routeur. Solution : déplacer l'admin sur 8443.
uci del_list uhttpd.main.listen_https='0.0.0.0:443' uci del_list uhttpd.main.listen_https='[::]:443' uci add_list uhttpd.main.listen_https='0.0.0.0:8443' uci add_list uhttpd.main.listen_https='[::]:8443' uci commit uhttpd /etc/init.d/uhttpd restart
Admin routeur désormais : https://routeur:8443
DNS local ciblé (dnsmasq)
⚠️ Ne PAS faire de wildcard /beafrancois.fr/… : ça casserait Gitea SSH (un nom ne peut pas résoudre vers 2 IP selon le protocole). Déclarer les services un par un :
uci add_list dhcp.@dnsmasq[0].address='/nuage.beafrancois.fr/192.168.1.25' uci add_list dhcp.@dnsmasq[0].address='/wiki.beafrancois.fr/192.168.1.25' uci add_list dhcp.@dnsmasq[0].address='/blog.beafrancois.fr/192.168.1.25' uci add_list dhcp.@dnsmasq[0].address='/musique.beafrancois.fr/192.168.1.25' uci add_list dhcp.@dnsmasq[0].address='/ldap.beafrancois.fr/192.168.1.25' uci commit dhcp /etc/init.d/dnsmasq restart
auth.beafrancois.fr reste vers le routeur (.90) pour la redirection par port.
Comptes et groupes (LLDAP)
Base DN : dc=beafrancois,dc=fr. Users sous ou=people, groupes sous ou=groups.
Attributs POSIX — à créer manuellement
⚠️ LLDAP n'a PAS d'attributs POSIX par défaut. L'UUID affiché dans l'UI n'est PAS le uidNumber. Créer manuellement dans le schéma (UI : section Schema) :
- Schéma User :
uidNumber,gidNumber,homeDirectory(+unixShelloptionnel) - Schéma Group :
gidNumber
Puis remplir ces attributs pour chaque user et groupe.
Modèle de groupes retenu
Groupe primaire partagé (modèle simple, validé). Tous les users ont famille comme groupe primaire. PAS de groupes privés par utilisateur, PAS de auto_private_groups (voir piège MPG dans la doc client).
| Groupe | gidNumber | Rôle |
|---|---|---|
| famille | 302 | groupe primaire de tous les users + accès partages communs |
| partage | (GID serveur) | partage /export/partage |
| multimedia | (GID serveur) | partage /export/multimedia |
| Utilisateur | uidNumber | gidNumber (primaire) |
|---|---|---|
| francois | 10000 | 302 (famille) |
| beatrice | 10001 | 302 |
| corentin | 10002 | 302 |
| louis | 10003 | 302 |
| testldap | 15000 | 302 |
Plage UID haute (10000+) choisie pour éviter toute collision avec les comptes locaux (1000-9999) sur les portables. Implique un chown des homes existants lors de la bascule (voir doc client).
Note : les objectClass réels dans LLDAP — user = inetOrgPerson/posixAccount/mailAccount/person ; groupe = groupOfNames/groupOfUniqueNames (PAS posixGroup). Important pour le mapping sssd.
Compte bind (lecture seule)
User binduser (DN : uid=binduser,ou=people,dc=beafrancois,dc=fr), membre du groupe lldap_strict_readonly, SANS attributs POSIX (pas un compte loginnable). Sert à sssd pour interroger l'annuaire. Mot de passe stocké dans sssd.conf côté client.
Opérations courantes
Tout se fait via l'interface web : https://ldap.beafrancois.fr (UI d'administration), compte admin (mot de passe = secrets/ldap_user_pass). Rappel : auth.beafrancois.fr est le LDAP lui-même (sssd), pas l'UI.
Ajouter un utilisateur
- Create a user : renseigner User ID (identifiant de login, ex.
paul), email (obligatoire, même interne), display name. - Définir le mot de passe du compte (c'est lui qui servira au login système).
- Ouvrir la fiche du user et remplir les attributs POSIX (sinon sssd l'ignore) :
uidNumber: prochaine valeur libre en plage haute (10004, 10005…)gidNumber: 302 (famille) — groupe primaire partagé, modèle retenuhomeDirectory:/home/paul
- Ajouter le user comme membre du groupe
famille(etpartage/multimediaselon les accès voulus).
⚠️ Rappels :
- Le
gidNumberprimaire DOIT être 302 (un groupe existant). Ne PAS créer de groupe privé du même nom que le user (conflit MPG → casse le offline côté client). - Ne PAS réutiliser un uidNumber déjà pris. Vérifier :
ldapsearch … “(uidNumber=10004)”.
Changer le mot de passe d'un utilisateur
Deux méthodes selon qui fait le changement.
Par l'administrateur (interface web)
Interface web : ouvrir la fiche du user → changer le mot de passe. Pas de contrainte particulière. Utile pour réinitialiser un mot de passe oublié.
Par l'utilisateur lui-même (commande passwd)
Sur son portable, l'utilisateur lance simplement :
passwd
Demande le mot de passe actuel, puis le nouveau (deux fois). Le changement est écrit directement dans LLDAP. Validé et fonctionnel (grâce à chpass_provider = ldap côté sssd).
⚠️ Contraintes :
- Doit être EN LIGNE (le portable doit joindre LLDAP en LDAPS).
passwdécrit dans l'annuaire — impossible hors réseau. Hors ligne, la commande échoue. - La commande standard
passwdsuffit, rien de spécial à installer. - Le nouveau mot de passe est actif immédiatement en ligne ; le cache offline du portable se met à jour au login suivant.
Propagation du cache offline (les deux méthodes)
Après tout changement (web ou passwd), côté chaque autre portable : le cache offline conserve l'ancien mot de passe jusqu'au prochain login en ligne réussi de cet utilisateur sur ce portable. Tant qu'il ne s'est pas reconnecté en ligne sur une machine donnée, l'ancien mot de passe y reste valable hors ligne.
Supprimer / désactiver un utilisateur
- Supprimer : fiche user → Delete. L'accès en ligne est coupé immédiatement.
- ⚠️ Offline : un portable qui ne se reconnecte jamais garde le cache (donc l'accès) indéfiniment avec
offline_credentials_expiration = 0. Pour révoquer réellement un accès, il faut que le portable se reconnecte au moins une fois (sssd constate la suppression), ou intervenir sur le portable (sss_cache -u paul/ vider le cache). D'où l'intérêt de LUKS sur les portables.
Ajouter un groupe
- Create a group (ex.
projets). - Remplir l'attribut POSIX
gidNumber(valeur cohérente avec les permissions serveur si lié à un partage). - Ajouter les membres.
- Côté serveur, créer le groupe Unix correspondant si besoin pour les partages (
groupadd -g <gid> projets).
Gérer l'appartenance aux groupes
Fiche du groupe → ajouter/retirer des membres. Les groupes secondaires (partage, multimedia…) sont gérés par appartenance ; le groupe primaire reste famille (302) via le gidNumber du user.
Après toute modification : rafraîchissement côté client
Les changements LLDAP sont pris en compte par sssd selon ses délais de cache. Pour forcer un rafraîchissement immédiat sur un portable :
sudo sss_cache -E # invalide tout le cache (resync au prochain accès, en ligne) # ou ciblé : sudo sss_cache -u paul # un user sudo sss_cache -g famille # un groupe
(N'efface PAS les credentials offline, contrairement à rm du cache.)
Vérifications utiles (depuis le serveur)
# Test bind + lecture d'un user LDAPTLS_CACERT=/mnt/stockage/docker/data/lldap/ca-cert.pem LDAPTLS_REQCERT=allow \ ldapsearch -x -H ldaps://192.168.1.9:6360 \ -D "uid=binduser,ou=people,dc=beafrancois,dc=fr" -W \ -b "dc=beafrancois,dc=fr" "(uid=testldap)" uidNumber gidNumber homeDirectory # Vérifier les objectClass d'un user / groupe ... "(uid=testldap)" objectClass ... "(cn=famille)" objectClass gidNumber member
Accès distant (hors LAN) — à faire
WireGuard (prévu aussi pour les partages réseau). Le LDAPS reste fermé au WAN. Le cache offline sssd gère le “aucun réseau” ; WireGuard gère le “réseau distant” (rafraîchissement du cache + accès partages).
