Table of Contents

Openldap

C'est LE backend de référence. Ça fait le café, d'après les chamanes des profondeurs les plus lointaines de la Crypte.

Ne suivez pas ce qui suit trop aveuglément, je me rends compte que j'ai rien compris à certains morceaux, qui sont datés, et donc que ça risque d'être crasseux.

Installation

Pour l'installer :

sudo apt install slapd ldap-utils

La configuration par défaut s'appuie sur le FQDN de la machine. Avant de tout bidouiller, vérifions…

sudo slapcat | grep dn:

Le résultat devrait être “dn: dc=example,dc=org” (ou autre domaine). Si c'est ce que vous voulez, parfait. Sinon, dpkg-reconfigure slapd, répondre “non” à “Omit OpenLDAP server configuration?” et ensuite adapter le reste. À la fin, à la question “Do you want the database to be removed when slapd is purged?”, répondre “Oui”.

On peut admirer la base de son annuaire avec la commande suivante :

ldapsearch -x -LLL -H ldap://localhost -b "dc=example,dc=org"

Dans mon cas, avec l'installation par défaut et un “domaine” appelé ldap.local :

# ldapsearch -x -LLL -H ldap://localhost -b "dc=ldap,dc=local"
dn: dc=ldap,dc=local
objectClass: top
objectClass: dcObject
objectClass: organization
o: ldap.local
dc: ldap

Concernant la structure et la configuration d'Openldap, cela reste assez difficile à lire au début mais c'est assez “logique” pour s'apprendre au fil des manipulations.

Un petit coup de tree /etc/ldap/ permet de voir qu'on a deux types de fichiers : .schema et .ldif. Et des noms de dossiers étranges, qui utilisent en fait la syntaxe de ldap. L'une des parties qu'on va bidouiller est ici :

└── slapd.d
    ├── cn=config
    │   ├── cn=module{0}.ldif
    │   ├── cn=schema
    │   │   ├── cn={0}core.ldif
    │   │   ├── cn={1}cosine.ldif
    │   │   ├── cn={2}nis.ldif
    │   │   └── cn={3}inetorgperson.ldif
    │   ├── cn=schema.ldif
    │   ├── olcDatabase={0}config.ldif
    │   ├── olcDatabase={-1}frontend.ldif
    │   └── olcDatabase={1}mdb.ldif
    └── cn=config.ldif

Ce qui est dans le dossier cn=config concerne… la configuration. Autrefois il y avait un fichier “/etc/ldap/sldap.conf”, si je comprend bien ce n'est plus trop d'actualité. Mais on n'édite pas directement les ldif ici, il faut passer par l'utilitaire ldapmodify.

Dans la suite du document, nous allons donc écrire nos documents “ldif”, puis les injecter dans l'annuaire avec cette commande ldapmodify. Il est conseillé de bosser dans un répertoire dédié (par exemple /root/ldap/conf/) histoire de tout nettoyer à la fin avec un petit “rm -R”. Ou de garder trace des bidouilles effectuées.

Initialiser LDAP

La méthode officielle consiste à modifier ldap à chaud (sans le redémarrer), en intervenant directement dans sa base via des commandes comme ldapmodify et ldapadd. Et il faut impérativement utiliser cela, mais la syntaxe et le fonctionnement peuvent piquer les yeux (et c'est rien de le dire). Auparavant, on utilisait /etc/ldap/sldap.conf et on peut encore voir son écriture dans /usr/share/doc/slapd/examples/slapd.conf1). Et c'est tellement plus lisible…

En fait, LDAP étant un vieux dinosaure utilisé très largement, l'usage de “/etc/ldap/sldap.conf” reste possible telle quelle. C'est cependant déconseillé et considéré comme une pratique obsolète depuis la version 2.3. L'usage du fichier de configuration “sldap.conf” demande un redémarrage du service à chaque modification, alors que la modification de la configuration dans l'arbre ldap lui-même (via ces fichiers ldif dans “cn=config”) est immédiatement pris en compte.

Cela semble anodin lors des tests, mais un gros annuaire peut mettre beaucoup de temps à redémarrer… Alors autant prendre l'habitude des bonnes pratiques, même si la syntaxe pique les yeux.

On peut essayer de combiner le meilleur des deux mondes en paramétrant le fichier slapd.conf, puis en le transformant en ldif via la commande slaptest. Cette méthode permet avant tout de vérifier si la syntaxe de nos ldif est adaptée à notre version actuelle de ldap. C'est utile pour relire et apprendre. Mais il y aura certainement des ldif qui seront à copier tel quel ou presque. Dans ce cas, il restera alors à injecter ces ldif (via ldapadd ou ldapmodify) dans l'annuaire. Attention, autant ça devrait marcher sur un serveur vierge, autant ça semble plutôt risqué sur un serveur déjà rempli, et dans tout les cas, évitons d'y aller de façon trop bourrine.

Les commandes de barbare :

# Convertir slapd.conf en fichiers ldif
slaptest -f slapd.conf -F /tmp/ldif/
# Injecter un fichier ldif dans l'annuaire
ldapadd -Y EXTERNAL -H ldapi:/// -f /tmp/ldif/cn=config.ldif
# etc pour chaque ldif dans l'arbre

On peut séparer les données (le peuplement de l'annuaire) de la configuration avec l'option -n.

# Générer les ldif de la configuration seulement
slaptest -f slapd.conf -F /tmp/ldif_config/ -n 0
# Générer les ldif des données seulement
slaptest -f slapd.conf -F /tmp/ldif_data/ -n 1

Avoir des logs plus bavards

Pour avoir des logs plus complets, il va falloir l'activer. On commence par copier le truc actuel, qu'on va bidouiller avant de réinjecter :

# ldapsearch -Y external -H ldapi:/// -b cn=config "(objectClass=olcGlobal)" olcLogLevel -LLL > log.ldif

Ce fichier “log.ldif” est assez court par défaut, il contient deux lignes :

On va faire ceci :

log.ldif
dn: cn=config
changetype: modify
replace: olcLogLevel
olcLogLevel: 256

On injecte avec la commande suivante :

# ldapmodify -Y EXTERNAL -H ldapi:/// -f log.ldif

On peut vérifier le résultat avec ça :

# ldapsearch -Y external -H ldapi:/// -b cn=config "(objectClass=olcGlobal)" olcLogLevel

Ou simplement en faisant une requête avec slapd (par exemple ldapsearch -Y external -H ldapi:/// -b dc=ldap,dc=local ) et en admirant le résultat avec tail -f /var/log/syslog.

On retrouve les logs propres à slapd aussi dans journalctl :

journalctl -eu slapd.service

J'ai des trucs dans /var/log/syslog parce que je demande à systemd de m'écrire ce genre de log.

Il y a moyen de paramétrer pour que ce soit dans un fichier de log propre à slapd. Les infos sont par défaut envoyés à “local4”2). Yep, y'a d'autres logiciels qui loguent de façon plus clair, mais visiblement local4 n'est pas trop utilisé par ailleurs et de toute façon sur le serveur avec openldap on ne devrait pas avoir trop d'autres services. On va donc dire à rsyslog d'envoyer le flux de local4 dans un fichier de log séparé via /etc/rsyslog.d/ldap.conf.

/etc/rsyslog.d/ldap.conf
local4.*    /var/log/ldap.log

Puis on redémarre rsyslog :

sudo systemctl restart rsyslog

Un peu de syntaxe (en vrac) et autres trucs trouvés au fil de l'exploration

Cette partie va être un gros brouillon pour le moment, je grappille dans les documentations. Mais autant noter ce que je trouve, je rangerais pus tard !

Indexation

Pour faire des recherches efficaces dans l'annuaire, mieux vaut bien paramétrer l'indexation((Cf https://www.openldap.org/doc/admin26/tuning.html#Indexes. On va indiquer à OpenLDAP que certaines entrées sont à indexer (et comment) pour éviter qu'il scanne tout (vraiment tout) à chaque recherche.

J'ai trouvé ça dans un slapd.conf :

index		objectClass	eq
index		cn		eq,sub
index		sn		eq,sub
index		uid		eq
index		mail		eq
index		memberOf	eq

Cela liste les attributs sur lequels faire les recherches, limiter ça aux plus pertinent. Trop d'index ralentit l'écriture. Par exemple, pas besoin de sn chez nous, cette info ne nous intéresse pas ; possible que le mail non plus (pas dans les recherches globales en tout cas). Les types d'index sont les suivants :

Les index doivent être créé avant de peupler l'annuaire, sinon il faut le reconstruire :

sudo systemctl stop slapd
sudo slapindex
sudo chown -R openldap:openldap /var/lib/ldap/
sudo systemctl start slapd

Et comme dit plus haut, redémarrer un ldap actif peut être très, très long suivant sa taille…

Cependant l'usage des index est à utiliser avec discernement. La construction des index prend de la place sur l'espace disque, et on peut quand même chercher dans les données non indexées (par exemple faire une recherche sur “ou”). On indexe quand il y a beaucoup d'entrées (par exemple quelques milliers d'UID ⇒ là ça devient vital), qu'on fait régulièrement des recherches dessus, voir qu'on utilise des filtres un peu compliqués dans les recherches. Pas besoin quand il y a moins de 1000 entrées ou sur les trucs qu'on cherche rarement.

La blague étant donc que je saurais réellement ce que je dois indexer une fois que mon annuaire sera utilisé et donc peuplé. Il reste donc à trouver les bons index avant que la base ne grossisse trop, afin d'éviter une opération de maintenance trop longue…

Overlays

Les overlays sont des fonctionnalités supplémentaires qui se rajoutent. (Source)

La doc complète est sur https://www.openldap.org/doc/admin26/overlays.html ; je ne documente ici que ce qui nous est utile.

La configuration demande plusieurs fichiers : un pour l'activation (utile juste une fois), l'autre pour la configuration (donc pouvant changer avec le temps).

Généralement (mais pas toujours) nous avons un module à activer qui va fournir une fonctionnalité de base, puis l'overlay lui-même qui indique comment utiliser les fonctionnalités du module ou ajoute des fonctionnalités à LDAP. Les modules (extensions en .so et .la) sont des bibliothèques compilées pour Openldap. Ils sont généralement dans /usr/lib/ldap/

Certains overlay sont assez essentiels :

Ceux où je ne sais pas encore quand ce sera utile (mais ça le sera très certainement à un moment)

J'ai compris ça avec la doc et quelques exemples piochés sur le web et autant dire que je ne suis pas sûre de moi. Donc lisez aussi la doc, et corrigez si j'ai mal traduit/compris.

Pour voir les modules chargés :

ldapsearch -Q -Y EXTERNAL -H ldapi:/// -b cn=config | grep olcModuleLoad

Ou en mode plus verbeux

ldapsearch -Q -Y EXTERNAL -H ldapi:/// -b cn=config '(olcModuleLoad=*)' olcModuleLoad

Pour les overlays activés :

ldapsearch -Q -Y EXTERNAL -H ldapi:/// -b cn=config '(olcOverlay=*)' olcOverlay

On note que le retour peut ressemble à ceci :

olcModuleLoad: {0}back_mdb
olcModuleLoad: {1}ppolicy
olcModuleLoad: {0}memberof.la

Le {0} indique les modules chargés en premier, {1} ensuite, etc. Bref faut pas s'inquiéter des chiffres.

MemberOf

Cela permet de lister plus facilement de quels groupes une utilisatrice est membre.

Créer les documents suivants :

memberof_act.ldif
dn: cn=module,cn=config
cn:module
objectclass: olcModuleList
objectclass: top
olcmoduleload: memberof.la
olcmodulepath: /usr/lib/ldap
memberof_conf.ldif
dn: olcOverlay=memberof,olcDatabase={1}mdb,cn=config
changetype: add
objectClass: olcMemberOf
objectClass: olcOverlayConfig
objectClass: olcConfig
objectClass: top
olcOverlay: memberof
olcMemberOfDangling: ignore
olcMemberOfRefInt: TRUE
olcMemberOfGroupOC: groupOfNames
olcMemberOfMemberAD: member
olcMemberOfMemberOfAD: memberOf

Puis on injecte ces fichiers :

root
ldapadd -Y EXTERNAL -H ldapi:/// -f memberof_act.ldif
ldapadd -Y EXTERNAL -H ldapi:/// -f memberof_conf.ldif

Pour vérifier la configuration, au choix, une de ces commandes dans le terminal :

ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" "Objectclass=olcModuleList"
tree /etc/ldap/slapd.d/
ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" "Objectclass=olcmemberOf"

Pour chercher tous les groupes dont Truc est membre :

ldapsearch -xLLL  "(uid=truc)" memberof

Cohérence de l'annuaire lors de suppression d'entrées (refint)

Cela évitera d'avoir des membres fantômes qui ne sont plus liés à rien. Si une utilisatrice est supprimée, alors elle le sera aussi des groupes dans lesquels elle était. Si un groupe se retrouve vide, l'admin y est automatiquement ajouté (ce qui évitera des erreurs).

Créer les fichiers suivants :

refint_act.ldif
dn: cn=module,cn=config
cn: module
objectclass: olcModuleList
objectclass: top
olcmoduleload: refint.la
olcmodulepath: /usr/lib/ldap
refint_conf.ldif
dn: olcOverlay=refint,olcDatabase={1}mdb,cn=config
objectClass: olcConfig
objectClass: olcOverlayConfig
objectClass: olcRefintConfig
objectClass: top
olcOverlay: refint
olcRefintAttribute: memberof member manager owner
olcRefintNothing: cn=admin,dc=ldap,dc=local

Injection

ldapadd -Q -Y EXTERNAL -H ldapi:/// -f refint_act.ldif
ldapadd -Q -Y EXTERNAL -H ldapi:/// -f refint_conf.ldif

Vérification, et vérification de la configuration :

ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" "Objectclass=olcModuleList"
ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" "Objectclass=olcRefintConfig"

Enregistrer les modifications de l'annuaire (auditlog)

Ce qui pourra être utile pour savoir ce qui s'est passé.

On commence par créer le dossier où cela s'écrira (automatique suivant comment on a configuré ses logs par ailleurs…) :

mkdir -m 755 -p /var/log/openldap
touch /var/log/openldap/auditlog.log
chmod 755 /var/log/openldap/auditlog.log
chown -R openldap:openldap /var/log/openldap

Puis on crée les fichiers :

auditlog_act.ldif
dn: cn=module,cn=config
cn: module
objectclass: olcModuleList
objectclass: top
olcModuleLoad: auditlog.la
olcmodulepath: /usr/lib/ldap
auditlog_conf.ldif
dn: olcOverlay=auditlog,olcDatabase={1}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcAuditLogConfig
olcOverlay: auditlog
olcAuditlogFile: /var/log/openldap/auditlog.log

Injection :

ldapadd -Q -Y EXTERNAL -H ldapi:/// -f auditlog_act.ldif
ldapadd -Q -Y EXTERNAL -H ldapi:/// -f auditlog_conf.ldif

Vérification :

ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" "Objectclass=olcModuleList"
ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" "Objectclass=olcAuditLogConfig"

Unicité des attributs (unique)

C'est par exemple pour que les UID attribués à chacune soit bien uniques. Ou qu'une adresse mail ne soit liée qu'à une seule personne. Éviter les doublons sur les attributs évitera des erreurs, voir des exploits, et améliore les recherches.

Créer les fichiers suivants :

unique_act.ldif
dn: cn=module,cn=config
cn: module
objectclass: olcModuleList
objectclass: top
olcModuleLoad: unique.la
olcmodulepath: /usr/lib/ldap

Attention ici, remplacez dc=ldap,dc=local par votre DC. Notez aussi qu'on va forcer l'unicité uniquement sur quelques attributs :

unique_conf.ldif
dn: olcOverlay=unique,olcDatabase={1}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcUniqueConfig
olcOverlay: unique
olcUniqueUri: ldap:///ou=people,dc=ldap,dc=local?uid?sub
olcUniqueUri: ldap:///ou=people,dc=ldap,dc=local?mail?sub
olcUniqueUri: ldap:///ou=people,dc=ldap,dc=local?uidNumber?sub

Injection :

ldapadd -Q -Y EXTERNAL -H ldapi:/// -f unique_act.ldif
ldapadd -Q -Y EXTERNAL -H ldapi:/// -f unique_conf.ldif

Vérification :

ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" "Objectclass=olcModuleList"
ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" "Objectclass=olcUniqueConfig"

Policy

Je suis Debuguo MAIS je ne sais pas encore quels contraintes vont être amenées par Oauth et compagnie, et je ne suis pas encore certaine que ces paramètres sont “bons”. Mais après réflexion, ça me semble quand même mieux que rien pour mes tests.

Par ailleurs ma méthode est un peu différente, puisque j'active le module de ppolicy.

La “policy” est donc la politique de mots de passe, y compris : les utilisatrices peuvent-elles changer leurs mots de passe ? Combien d'essais de connexion avant de bloquer ? Etc. Je ne sais pas trop où on définit le type de hash (je vais chercher).

On commence par charger le module ppolicy. Et du coup on vérifie avant tout s'il existe (sinon, voir le tuto de Debuguo, mais chez moi “ça marchait pas”) :

find /usr/lib/ldap -name "*ppolicy*"

Type de retour attendu :

/usr/lib/ldap/ppolicy.so
/usr/lib/ldap/ppolicy.la

Pour charger le module, on crée le fichier ldif suivant :

ppolicy_module.ldif
dn: cn=module{0},cn=config
changetype: modify
add: olcModuleLoad
olcModuleLoad: ppolicy

Et appliquer :

ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f ppolicy_module.ldif

Pour vérifier la liste des modules (ppolicy doit apparaitre dedans) :

ldapsearch -Q -Y EXTERNAL -H ldapi:/// -b cn=config | grep olcModuleLoad

TODO : c'est beau d'appliquer le module, mais faut quand même définir la politique… Or ça me demande quelques aller-retours entre les tutos compréhensibles (mais peut-être trop datés) et la doc d'Openldap.

Plus de doc

1)
En tout cas sur Debian Bookworm.
2)
Cherchez de la doc sur “facility”, le petit nom de ces canaux ; c'est sur les facility de local0 à local7 que les applications sont censées envoyer leurs infos