Outils pour utilisateurs

Outils du site


commun:serveur_d_authentification_lldap_sssd

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 (+ unixShell optionnel)
  • 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

  1. Create a user : renseigner User ID (identifiant de login, ex. paul), email (obligatoire, même interne), display name.
  2. Définir le mot de passe du compte (c'est lui qui servira au login système).
  3. 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 retenu
    • homeDirectory : /home/paul
  4. Ajouter le user comme membre du groupe famille (et partage/multimedia selon les accès voulus).

⚠️ Rappels :

  • Le gidNumber primaire 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 passwd suffit, 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

  1. Create a group (ex. projets).
  2. Remplir l'attribut POSIX gidNumber (valeur cohérente avec les permissions serveur si lié à un partage).
  3. Ajouter les membres.
  4. 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).

commun/serveur_d_authentification_lldap_sssd.txt · Dernière modification : de francois

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki