4. Déployer l'annuaire LDAP

Le paquet slapd a déjà été installé « sans configuration » dans la partie Section 3.3, « Installer les paquets TLS et générer les certificats », uniquement pour disposer du compte système openldap nécessaire au dépôt des certificats TLS. Les certificats (/etc/ldap/certs/) et le fichier /etc/ldap/ldap.keytab sont donc déjà en place, mais le démon slapd n'a encore aucune configuration cn=config. Il reste en échec de démarrage tant que le contexte de nommage n'est pas défini.

Cette partie reprend les étapes décrites dans le support Introduction aux annuaires LDAP avec OpenLDAP et les adapte à ce scénario en trois sous-parties :

  • Initialiser une configuration minimale du service avec le contexte de nommage dc=lab,dc=local.

  • Activer le chiffrement TLS et l'authentification forte SASL/GSSAPI, à partir des certificats et du keytab déjà déployés.

  • Construire l'arbre de l'annuaire (unités organisationnelles users, groups et hosts) puis y implanter les comptes utilisateurs.

4.1. Installer et initialiser le service d'annuaire

Contrairement au support Introduction aux annuaires LDAP avec OpenLDAP, il ne reste ici qu'à générer la configuration cn=config et à l'injecter dans le paquet déjà installé.

Q193.

Comment générer et appliquer une configuration minimale pour ce nouveau contexte de nommage ?

Créez le script prepare-apply-bootstrap-ldif.sh avec le code ci-dessous. Il reprend la structure du fichier LDIF de bootstrap déjà étudiée dans le support Introduction aux annuaires LDAP avec OpenLDAP, avec deux adaptations : le contexte de nommage dc=lab,dc=local retenu pour ce scénario, et le niveau de journalisation stats activé dès le démarrage plutôt que dans une étape séparée.

cat << 'SCRIPT_EOF' >prepare-apply-bootstrap-ldif.sh
#!/usr/bin/env bash
set -euo pipefail

# 1. Global variables
readonly DOMAIN="lab.local"
# Extract suffix for domain (e.g. lab.local -> dc=lab,dc=local)
readonly SUFFIX="dc=${DOMAIN%%.*},dc=${DOMAIN#*.}"
readonly SLAPD_CONFIG_DIR="/etc/ldap/slapd.d"
readonly SLAPD_DATA_DIR="/var/lib/ldap"
readonly LDAP_BOOTSTRAP_FILE="${HOME}/bootstrap-config.ldif"
readonly ADMIN_PASS_FILE="${HOME}/.ldap_admin_pass"

echo "=== 1. Generating administrator credentials ==="
# 18 random bytes yield exactly 24 base64 characters without padding
ADMIN_PASS=$(openssl rand -base64 18)
( umask 077 && printf '%s' "${ADMIN_PASS}" > "${ADMIN_PASS_FILE}" )

# Compute Argon2 password hash for slapd
ROOT_HASH="$(sudo slappasswd -o module-load=argon2.la -h "{ARGON2}" -s "${ADMIN_PASS}")"

echo "=== 2. Generating cn=config bootstrap LDIF ==="
cat <<EOF >"${LDAP_BOOTSTRAP_FILE}"
dn: cn=config
objectClass: olcGlobal
cn: config
olcArgsFile: /run/slapd/slapd.args
olcPidFile: /run/slapd/slapd.pid
olcLogLevel: none
olcToolThreads: 1

dn: cn=schema,cn=config
objectClass: olcSchemaConfig
cn: schema

include: file:///etc/ldap/schema/core.ldif
include: file:///etc/ldap/schema/cosine.ldif
include: file:///etc/ldap/schema/nis.ldif
include: file:///etc/ldap/schema/inetorgperson.ldif

dn: cn=module{0},cn=config
objectClass: olcModuleList
cn: module{0}
olcModulePath: /usr/lib/ldap
olcModuleLoad: back_mdb
olcModuleLoad: argon2

dn: olcDatabase={-1}frontend,cn=config
objectClass: olcDatabaseConfig
objectClass: olcFrontendConfig
olcDatabase: {-1}frontend
olcPasswordHash: {ARGON2}
olcAccess: {0}to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage by * none

dn: olcDatabase={0}config,cn=config
objectClass: olcDatabaseConfig
olcDatabase: {0}config
olcAccess: {0}to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage by * none

dn: olcDatabase={1}mdb,cn=config
objectClass: olcDatabaseConfig
objectClass: olcMdbConfig
olcDatabase: {1}mdb
olcDbDirectory: ${SLAPD_DATA_DIR}
olcDbMaxSize: 1073741824
olcSuffix: ${SUFFIX}
olcRootDN: cn=admin,${SUFFIX}
olcRootPW: ${ROOT_HASH}
olcLastMod: TRUE
olcDbCheckpoint: 512 30
olcAccess: {0}to * by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage by * break
olcAccess: {1}to attrs=userPassword by self write by anonymous auth by * none
olcAccess: {2}to attrs=shadowLastChange by self write by * read
olcAccess: {3}to * by users read by * none
olcDbIndex: objectClass eq
olcDbIndex: uid eq
olcDbIndex: cn,sn,mail eq,sub
olcDbIndex: uidNumber,gidNumber eq
olcDbIndex: member,memberUid eq
EOF

echo "=== 3. Offline import and directory initialization ==="
# Stop slapd to prevent database locking during offline import
sudo systemctl stop slapd 2>/dev/null || true

# Clear directories to guarantee idempotency across multiple runs
sudo rm -rf "${SLAPD_CONFIG_DIR:?}"/* "${SLAPD_DATA_DIR:?}"/*
sudo mkdir -p "${SLAPD_CONFIG_DIR}" "${SLAPD_DATA_DIR}"

# Import LDIF into cn=config (-n0)
sudo slapadd -F "${SLAPD_CONFIG_DIR}" -n0 -l "${LDAP_BOOTSTRAP_FILE}"

# Set proper ownership for the openldap daemon
sudo chown -R openldap:openldap "${SLAPD_CONFIG_DIR}" "${SLAPD_DATA_DIR}"

echo "=== 4. Finalizing package configuration and restarting slapd ==="
sudo dpkg --configure --pending
sudo systemctl restart slapd

echo ""
echo "Success: OpenLDAP server is configured and running."
echo "Base DN: ${SUFFIX}"
echo "Admin DN: cn=admin,${SUFFIX}"
echo "Admin password saved in: ${ADMIN_PASS_FILE}"
SCRIPT_EOF
bash prepare-apply-bootstrap-ldif.sh
[Note] Note

Les attributs olcTLS* n'apparaissent pas dans ce fichier de démarrage, alors que les certificats sont déjà déployés dans /etc/ldap/certs/. Ce choix isole la mise en route du contexte de nommage de l'activation de la sécurité des échanges, qui fait l'objet de la sous-partie suivante Section 4.2, « Sécuriser les échanges avec TLS et l'authentification SASL/GSSAPI ».

Q194.

Comment vérifier que le service est bien opérationnel avec ce nouveau contexte de nommage ?

Contrôlez d'abord l'état du service.

systemctl status slapd.service --no-pager
● slapd.service - OpenLDAP Server Daemon
     Loaded: loaded (/usr/lib/systemd/system/slapd.service; enabled; preset: enabled)
     Active: active (running) since Fri 2026-09-04 15:46:45 CEST; 34s ago
 Invocation: cab64b3b59194245ae78eb78920b6db6
       Docs: man:slapd
             man:slapd-config
             man:slapd-mdb
   Main PID: 3701 (slapd)
      Tasks: 3 (limit: 971)
     Memory: 6.9M (peak: 6.9M)
        CPU: 42ms
     CGroup: /system.slice/slapd.service
             └─3701 /usr/sbin/slapd -d0 -h "ldap:/// ldapi:///" -u openldap -g openldap

sept. 04 15:46:45 srvr systemd[1]: Starting slapd.service - OpenLDAP Server Daemon...
sept. 04 15:46:45 srvr slapd[3701]: @(#) $OpenLDAP: slapd 2.6.14+dfsg-2 (Aug 25 2026 00:46:58) $
                                            Debian OpenLDAP Maintainers <pkg-openldap-devel@lists.alioth.debian.org>
sept. 04 15:46:45 srvr slapd[3701]: slapd starting
sept. 04 15:46:45 srvr systemd[1]: Started slapd.service - OpenLDAP Server Daemon.

Vérifiez ensuite que le port tcp/389 est bien en écoute.

sudo ss -antlp 'sport = :389'
State   Recv-Q  Send-Q  Local Address:Port  Peer Address:Port   Process
LISTEN  0       2048          0.0.0.0:389        0.0.0.0:*       users:(("slapd",pid=3701,fd=7))
LISTEN  0       2048             [::]:389           [::]:*       users:(("slapd",pid=3701,fd=8))

Contrôlez enfin, via la connexion locale SASL/EXTERNAL, que le niveau de journalisation stats est bien actif dès ce premier démarrage.

sudo ldapsearch -LLL \
  -Y EXTERNAL \
  -H ldapi:/// \
  -b cn=config \
  -s base olcLogLevel
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
dn: cn=config
olcLogLevel: stats

Le contexte de nommage dc=lab,dc=local est désormais déclaré dans la base mdb, mais l'entrée racine de l'arbre n'existe pas encore.

ldapsearch -x -H ldap:/// \
  -b dc=lab,dc=local
# extended LDIF
#
# LDAPv3
# base <dc=lab,dc=local> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# search result
search: 2
result: 32 No such object

# numResponses: 1

C'est normal : la création de cette entrée racine, ainsi que celle des unités organisationnelles, fait l'objet de la sous-partie Section 4.3, « Construire l'arbre et peupler l'annuaire ».

4.2. Sécuriser les échanges avec TLS et l'authentification SASL/GSSAPI

Les certificats TLS (/etc/ldap/certs/) et le fichier /etc/ldap/ldap.keytab ont déjà été déployés dans la partie Section 3.2, « Définir les objets gérés par l'autorité de certification » et Section 3.4, « Installer et initialiser le service Kerberos ». Il ne reste donc qu'à les activer dans la configuration cn=config du service, en cinq étapes : chiffrement TLS, authentification SASL/GSSAPI, correspondance des identités, configuration du client LDAP local, puis vérifications croisées.

Q195.

Comment activer le chiffrement TLS à partir des certificats déjà déployés ?

Injectez les trois attributs de désignation des certificats (olcTLSCACertificateFile, olcTLSCertificateFile, olcTLSCertificateKeyFile) ainsi que les seuils SSF (olcLocalSSF, olcSecurity) dans la même opération, puis activez le service ldaps:// à côté de ldap://.

cat << 'SCRIPT_EOF' >enable-ldap-tls.sh
#!/usr/bin/env bash
set -euo pipefail

# 1. Global variables
readonly CERT_DIR="/etc/ldap/certs"
readonly CA_CERT="${CERT_DIR}/labCA.crt"
readonly SERVER_CERT="${CERT_DIR}/ldap-srvr.crt"
readonly SERVER_KEY="${CERT_DIR}/ldap-srvr.key"
readonly SLAPD_DEFAULT="/etc/default/slapd"

echo "=== 1. Checking prerequisite certificates ==="
for file in "${CA_CERT}" "${SERVER_CERT}" "${SERVER_KEY}"; do
  if [[ ! -f "${file}" ]]; then
    echo "Error: Required certificate or key not found: ${file}" >&2
    exit 1
  fi
done

echo "=== 2. Preparing and injecting dynamic TLS configuration ==="
TMP_LDIF=$(mktemp)
trap 'rm -f "${TMP_LDIF:-}"' EXIT

cat <<EOF >"${TMP_LDIF}"
dn: cn=config
changetype: modify
replace: olcTLSCACertificateFile
olcTLSCACertificateFile: ${CA_CERT}
-
replace: olcTLSCertificateFile
olcTLSCertificateFile: ${SERVER_CERT}
-
replace: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: ${SERVER_KEY}
-
replace: olcLocalSSF
olcLocalSSF: 256
-
replace: olcSecurity
olcSecurity: ssf=128
EOF

# Apply modifications via SASL/EXTERNAL on local socket
sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f "${TMP_LDIF}"

echo "=== 3. Enabling ldaps:// endpoint in /etc/default/slapd ==="
# Append ldaps:/// to SLAPD_SERVICES if not already declared
sudo sed -i '/^SLAPD_SERVICES=/{
  /ldaps:\/\//! s/"$/ ldaps:\/\/\/"/
}' "${SLAPD_DEFAULT}"

echo "=== 4. Restarting slapd service ==="
# Single restart to take into account olcLocalSSF and new network endpoints
sudo systemctl restart slapd

echo ""
echo "Success: TLS has been enabled for OpenLDAP."
echo "CA certificate: ${CA_CERT}"
echo "Server certificate: ${SERVER_CERT}"
echo "SSF thresholds: local=256, minimum network=128"
echo "Listening sockets:"
sudo ss -tlnp 'sport = :389 or sport = :636'
SCRIPT_EOF

bash enable-ldap-tls.sh
Success: TLS has been enabled for OpenLDAP.
CA certificate: /etc/ldap/certs/labCA.crt
Server certificate: /etc/ldap/certs/ldap-srvr.crt
SSF thresholds: local=256, minimum network=128
Listening sockets:
State    Recv-Q  Send-Q  Local Address:Port   Peer Address:Port   Process
LISTEN   0       2048          0.0.0.0:389         0.0.0.0:*       users:(("slapd",pid=4004,fd=7))
LISTEN   0       2048          0.0.0.0:636         0.0.0.0:*       users:(("slapd",pid=4004,fd=10))
LISTEN   0       2048             [::]:389            [::]:*       users:(("slapd",pid=4004,fd=8))
LISTEN   0       2048             [::]:636            [::]:*       users:(("slapd",pid=4004,fd=11))
[Note] Note

Le seuil olcSecurity: ssf=128 impose désormais une confidentialité minimale à toute connexion, y compris pour les accès anonymes en clair sur ldap://. Cette contrainte est vérifiée dans la dernière question de cette sous-partie.

Q196.

Comment activer l'authentification forte SASL/GSSAPI à partir du keytab déjà déployé ?

Comme pour le client Kerberos, le démon slapd a besoin du greffon libsasl2-modules-gssapi-mit. Indiquez-lui ensuite, via la variable d'environnement KRB5_KTNAME, le keytab dédié /etc/ldap/ldap.keytab plutôt que le keytab système /etc/krb5.keytab, puis déclarez le nom d'hôte SASL et le royaume dans cn=config.

cat << 'SCRIPT_EOF' >enable-ldap-gssapi.sh
#!/usr/bin/env bash
set -euo pipefail

# 1. Global variables
readonly DOMAIN="lab.local"
readonly REALM="${DOMAIN^^}"
readonly LDAP_HOST="ldap-srvr.${DOMAIN}"
readonly KEYTAB_FILE="/etc/ldap/ldap.keytab"
readonly SLAPD_DEFAULT="/etc/default/slapd"

echo "=== 1. Checking prerequisite keytab ==="
if [[ ! -f "${KEYTAB_FILE}" ]]; then
  echo "Error: Keytab file '${KEYTAB_FILE}' not found." >&2
  echo "Ensure the service principal was created and extracted with ktadd." >&2
  exit 1
fi

echo "=== 2. Installing Cyrus SASL GSSAPI plugin ==="
sudo apt update -qq
sudo DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends \
    libsasl2-modules-gssapi-mit

echo "=== 3. Configuring environment and keytab path ==="
# Update or append KRB5_KTNAME in /etc/default/slapd
if grep -q "KRB5_KTNAME" "${SLAPD_DEFAULT}"; then
  sudo sed -i "s|^#\?KRB5_KTNAME=.*|KRB5_KTNAME=${KEYTAB_FILE}|" "${SLAPD_DEFAULT}"
else
  echo "KRB5_KTNAME=${KEYTAB_FILE}" | sudo tee -a "${SLAPD_DEFAULT}" >/dev/null
fi

echo "=== 4. Declaring SASL host and realm in cn=config ==="
TMP_LDIF=$(mktemp)
trap 'rm -f "${TMP_LDIF:-}"' EXIT

cat <<EOF >"${TMP_LDIF}"
dn: cn=config
changetype: modify
replace: olcSaslHost
olcSaslHost: ${LDAP_HOST}
-
replace: olcSaslRealm
olcSaslRealm: ${REALM}
EOF

sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f "${TMP_LDIF}"

echo "=== 5. Restarting slapd service ==="
# Single restart to load KRB5_KTNAME and initialize the SASL GSSAPI provider
sudo systemctl restart slapd

echo ""
echo "Success: slapd is configured to accept Kerberos (GSSAPI) binds."
echo "Configured realm: ${REALM}"
echo "SASL hostname: ${LDAP_HOST}"
echo "Supported SASL mechanisms:"
# Queried over ldapi:/// to comply with olcSecurity (ssf=128) via olcLocalSSF (256)
sudo ldapsearch -Q -Y EXTERNAL \
  -H ldapi:/// \
  -b "" \
  -s base supportedSASLMechanisms |\
  grep -E "^supportedSASLMechanisms:"
SCRIPT_EOF
bash enable-ldap-gssapi.sh

Q197.

Comment faire correspondre les principaux Kerberos aux entrées du DIT ?

Insérez deux expressions rationnelles olcAuthzRegexp :

  • la première traduit un principal utilisateur uid@LAB.LOCAL en entrée ou=users

  • la seconde traduit un principal machine host/nom@LAB.LOCAL en entrée ou=hosts, conformément au découpage retenu pour l'arbre de l'annuaire.

cat << 'SCRIPT_EOF' >configure-identity-mapping.sh
#!/usr/bin/env bash
set -euo pipefail

# 1. Global variables
readonly DOMAIN="lab.local"
readonly REALM="${DOMAIN^^}"
# Dynamically extract Base DN (e.g. lab.local -> dc=lab,dc=local)
readonly BASE_DN="dc=${DOMAIN%%.*},dc=${DOMAIN#*.}"
readonly USERS_OU="ou=users,${BASE_DN}"
readonly HOSTS_OU="ou=hosts,${BASE_DN}"

echo "=== 1. Checking slapd daemon availability ==="
if ! systemctl is-active --quiet slapd; then
  echo "Error: The slapd service is not running." >&2
  echo "Start it with 'sudo systemctl start slapd' before applying configuration." >&2
  exit 1
fi

echo "=== 2. Preparing and injecting identity mapping rules (olcAuthzRegexp) ==="
TMP_LDIF=$(mktemp)
trap 'rm -f "${TMP_LDIF:-}"' EXIT

# Translation rules breakdown:
# - replace: ensures idempotency by overwriting existing rules safely
# - cn=[^,]+: matches the realm component regardless of case
# - Rule {0} (Users): [^/,]+ captures the UID while strictly forbidding '/' to prevent overlap with service principals
# - Rule {1} (Hosts): host/([^,]+) extracts the machine FQDN into cn=$1
cat <<EOF >"${TMP_LDIF}"
dn: cn=config
changetype: modify
replace: olcAuthzRegexp
olcAuthzRegexp: {0}uid=([^/,]+),cn=[^,]+,cn=gssapi,cn=auth uid=\$1,${USERS_OU}
olcAuthzRegexp: {1}uid=host/([^,]+),cn=[^,]+,cn=gssapi,cn=auth cn=\$1,${HOSTS_OU}
EOF

# Inject rules into cn=config using local SASL/EXTERNAL authentication
sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f "${TMP_LDIF}"

echo "=== 3. Verifying active mapping rules in cn=config ==="
sudo ldapsearch -Q -Y EXTERNAL -H ldapi:/// \
    -b "cn=config" \
    "(olcAuthzRegexp=*)" olcAuthzRegexp || true

echo ""
echo "Success: Identity mapping (SASL/GSSAPI -> LDAP) is configured."
echo "Expected transformations:"
echo "  Kerberos 'user@${REALM}'          -> LDAP 'uid=user,${USERS_OU}'"
echo "  Kerberos 'host/machine@${REALM}' -> LDAP 'cn=machine,${HOSTS_OU}'"
echo ""
echo "Verification command (after 'kinit <user>'):"
echo "  ldapwhoami -H ldap://ldap-srvr.${DOMAIN} -Y GSSAPI"
SCRIPT_EOF
bash configure-identity-mapping.sh

Q198.

Depuis le client, comment configurer les outils LDAP (ldapsearch, ldapmodify, ldapwhoami) pour qu'ils fassent confiance à l'autorité de certification locale et se connectent par défaut au serveur ?

Recherchez dans la page de manuel ldap.conf les paramètres de connexion et de vérification TLS par défaut.

Sans ce fichier, les outils clients ne connaissent pas le chemin de l'autorité de certification locale et considèrent le certificat du serveur comme non fiable, même si celui-ci a déjà été correctement installé côté serveur. Lancez cette commande depuis le client.

ldapsearch -H ldaps://ldap-srvr.lab.local \
    -D "cn=admin,dc=lab,dc=local" -y ~/.ldap_admin_pass \
    -b dc=lab,dc=local
ldap_sasl_bind(SIMPLE): Can't contact LDAP server (-1)
additional info: error:0A000086:SSL routines::certificate verify failed (self-signed certificate in certificate chain)

Créez, toujours sur le client, le fichier /etc/ldap/ldap.conf avec l'URI et la base par défaut du service, le chemin du certificat de l'autorité de certification locale, et l'exigence stricte de vérification du certificat serveur.

sudo mkdir -p /etc/ldap
cat << EOF | sudo tee /etc/ldap/ldap.conf >/dev/null
URI ldaps://ldap-srvr.lab.local
BASE dc=lab,dc=local
TLS_CACERT /etc/ssl/certs/labCA.crt
TLS_REQCERT demand
SASL_NOCANON on
EOF
[Note] Note

Avec la directive SASL_NOCANON on, on désactive la résolution DNS que la bibliothèque SASL effectuerait sinon sur le nom d'hôte fourni via -H, avant de construire le nom de service attendu par le mécanisme GSSAPI. Or l'adresse du serveur porte plusieurs enregistrements PTR (Section 3.1, « Planifier les noms de services ») : sans cette directive, le nom résolu reviendrait à srvr.lab.local, et un bind SASL/GSSAPI viserait alors le principal ldap/srvr.lab.local@LAB.LOCAL, absent du royaume. Cette précaution côté client LDAP complète celle déjà retenue pour le client Kerberos lui-même (rdns = false dans /etc/krb5.conf, Section 3.4, « Installer et initialiser le service Kerberos »). Son effet sera vérifié plus loin, lors du premier bind SASL/GSSAPI réel, une fois l'entrée hôte créée dans l'annuaire (Section 4.4, « Configurer l'accès transparent à l'annuaire depuis le client »).

[Note] Note

Sur le serveur, l'autorité de certification est désignée par /etc/ldap/certs/labCA.crt (Section 3.2, « Définir les objets gérés par l'autorité de certification »). Sur le client, ce même certificat a été installé sous /etc/ssl/certs/labCA.crt et ajouté au magasin de confiance du système par update-ca-certificates (Section 3.3, « Installer les paquets TLS et générer les certificats ») : les deux chemins ne coïncident pas, d'où l'importance de bien distinguer, dans chaque question de cette sous-partie, si les commandes s'exécutent sur le serveur (via ldapi:///) ou sur le client (via le réseau).

Copiez le mot de passe de l'administrateur LDAP depuis le serveur sur le client pour valider l'authentification.

scp .ldap_admin_pass clnt:~

Relancez la même requête : la connexion TLS aboutit désormais, l'entrée racine restant introuvable puisque l'arbre n'a pas encore été construit.

ldapsearch -H ldaps://ldap-srvr.lab.local \
    -D "cn=admin,dc=lab,dc=local" -y ~/.ldap_admin_pass \
    -b dc=lab,dc=local
# extended LDIF
#
# LDAPv3
# base <dc=lab,dc=local> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# search result
search: 2
result: 32 No such object

# numResponses: 1
[Note] Note

Le paramètre TLS_REQCERT demand impose la vérification du certificat serveur, y compris son nom d'hôte : c'est pourquoi l'URI utilise le nom ldap-srvr.lab.local, qui figure dans le subjectAltName du certificat (Section 3.3, « Installer les paquets TLS et générer les certificats »), plutôt que l'alias srvr.lab.local.

Q199.

Comment vérifier que le service répond bien en TLS ?

Toutes les commandes de cette question s'exécutent depuis le client. Vérifiez d'abord que le seuil SSF rejette bien un accès anonyme en clair.

ldapsearch -LLL -x -H ldap://ldap-srvr.lab.local \
  -b dc=lab,dc=local
ldap_bind: Confidentiality required (13)
additional info: confidentiality required

Vérifiez ensuite qu'un bind administrateur authentifié via ldaps:// est accepté : le fichier /etc/ldap/ldap.conf créé à l'étape précédente fournit désormais l'URI et la base par défaut, ce qui permet d'omettre l'option -H. L'entrée racine n'existe toujours pas : seule l'authentification est validée à ce stade.

ldapsearch \
    -D "cn=admin,dc=lab,dc=local" -y ~/.ldap_admin_pass
# extended LDIF
#
# LDAPv3
# base <dc=lab,dc=local> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# search result
search: 2
result: 32 No such object

4.3. Construire l'arbre et peupler l'annuaire

La sous-partie Section 4.1, « Installer et initialiser le service d'annuaire » a montré que le contexte de nommage dc=lab,dc=local est déclaré dans la configuration cn=config, mais que l'entrée racine du DIT n'existe pas encore. Cette sous-partie construit l'arborescence retenue pour cette maquette : les trois unités organisationnelles ou=users, ou=groups et ou=hosts, puis peuple l'annuaire avec les comptes de la famille Skywalker et une entrée représentant l'hôte client.

Q200.

Comment créer l'entrée racine du DIT et les trois unités d'organisation retenues pour cette maquette ?

Les opérations de cette question s'exécutent sur le serveur, via la connexion locale SASL/EXTERNAL sur ldapi:/// : l'identité du compte système root correspond à la règle {0} de olcAccess (Section 4.1, « Installer et initialiser le service d'annuaire »), qui accorde un accès en écriture complet sans avoir à fournir le mot de passe administrateur.

Créez le script build-ldap-tree.sh avec le code ci-dessous.

cat << 'SCRIPT_EOF' >build-ldap-tree.sh
#!/usr/bin/env bash
set -euo pipefail

# 1. Global variables
readonly DOMAIN="lab.local"
readonly BASE_DN="dc=${DOMAIN%%.*},dc=${DOMAIN#*.}"
readonly TREE_LDIF="${HOME}/build-tree.ldif"

echo "=== 1. Generating the DIT root entry and organizational units ==="
cat <<EOF >"${TREE_LDIF}"
dn: ${BASE_DN}
objectClass: top
objectClass: dcObject
objectClass: organization
o: Lab SysAdmNet
dc: ${DOMAIN%%.*}

dn: ou=users,${BASE_DN}
objectClass: organizationalUnit
ou: users

dn: ou=groups,${BASE_DN}
objectClass: organizationalUnit
ou: groups

dn: ou=hosts,${BASE_DN}
objectClass: organizationalUnit
ou: hosts
EOF

echo "=== 2. Importing the tree with local SASL/EXTERNAL authentication ==="
sudo ldapadd -Y EXTERNAL -H ldapi:/// -f "${TREE_LDIF}"

echo ""
echo "Success: root entry and organizational units users/groups/hosts created."
SCRIPT_EOF
bash build-ldap-tree.sh

Vérifiez la structure obtenue.

sudo ldapsearch -LLL -Y EXTERNAL -H ldapi:/// \
    -b dc=lab,dc=local \
    -s children dn
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
dn: ou=users,dc=lab,dc=local

dn: ou=groups,dc=lab,dc=local

dn: ou=hosts,dc=lab,dc=local
[Attention] Attention

Assurez-vous que l'annuaire contient bien un arbre avec les trois branches users, groups et hosts avant de passer aux questions suivantes.

Q201.

Comment générer les comptes de la famille Skywalker avec un mot de passe aléatoire condensé en Argon2, à l'image du compte administrateur ?

Chaque compte utilisateur reçoit un groupe primaire dédié (private user group). Un groupe secondaire partagé cn=users,ou=groups (gidNumber: 100) rassemble les quatre comptes et sert pour l'accès aux ressources communes montées sous /ahome (Section 5.2, « Assurer la cohérence du schéma de nommage entre serveur et client »).

Créez le script generate-skywalker-accounts.sh avec le code ci-dessous.

cat << 'SCRIPT_EOF' >generate-skywalker-accounts.sh
#!/usr/bin/env bash
set -euo pipefail

# 1. Global variables
readonly DOMAIN="lab.local"
readonly BASE_DN="dc=${DOMAIN%%.*},dc=${DOMAIN#*.}"
readonly USERS_OU="ou=users,${BASE_DN}"
readonly GROUPS_OU="ou=groups,${BASE_DN}"
readonly IMPORT_LDIF="${HOME}/import-skywalkers.ldif"
readonly START_UID=10000
readonly SHARED_GID=100

# uid:cn:sn:givenName
readonly ACCOUNTS=(
    "padme:Padme Amidala:Amidala:Padme"
    "anakin:Anakin Skywalker:Skywalker:Anakin"
    "leia:Leia Organa:Organa:Leia"
    "luke:Luke Skywalker:Skywalker:Luke"
)

echo "=== 1. Preparing passwords and password hashes ==="
: >"${IMPORT_LDIF}"
UID_NUMBER=${START_UID}

for ACCOUNT in "${ACCOUNTS[@]}"; do
    IFS=':' read -r UID_ CN SN GIVEN_NAME <<<"${ACCOUNT}"
    PASS_FILE="${HOME}/.ldap_${UID_}_pass"

    # Reuse existing password if present to maintain sync with Kerberos KDC
    if [[ -f "${PASS_FILE}" ]]; then
        USER_PASS=$(head -n 1 "${PASS_FILE}")
    else
        # 18 random bytes yield exactly 24 base64 characters without padding
        USER_PASS=$(openssl rand -base64 18)
        ( umask 077 && printf '%s' "${USER_PASS}" >"${PASS_FILE}" )
    fi

    USER_HASH="$(sudo slappasswd -o module-load=argon2.la -h "{ARGON2}" -s "${USER_PASS}")"

    echo "=== 2. Appending entries for ${UID_} (uidNumber ${UID_NUMBER}) ==="
    cat <<EOF >>"${IMPORT_LDIF}"
dn: cn=${UID_},${GROUPS_OU}
objectClass: posixGroup
cn: ${UID_}
gidNumber: ${UID_NUMBER}

dn: uid=${UID_},${USERS_OU}
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: ${UID_}
cn: ${CN}
sn: ${SN}
givenName: ${GIVEN_NAME}
mail: ${UID_}@${DOMAIN}
uidNumber: ${UID_NUMBER}
gidNumber: ${UID_NUMBER}
homeDirectory: /ahome/${UID_}
loginShell: /bin/bash
userPassword: ${USER_HASH}
shadowLastChange: $(( $(date +%s) / 86400 ))

EOF

    UID_NUMBER=$((UID_NUMBER + 1))
done

echo "=== 3. Appending the shared secondary group ==="
cat <<EOF >>"${IMPORT_LDIF}"
dn: cn=users,${GROUPS_OU}
objectClass: posixGroup
cn: users
gidNumber: ${SHARED_GID}
memberUid: padme
memberUid: anakin
memberUid: leia
memberUid: luke

EOF

echo "=== 4. Removing existing entries if present (idempotency) ==="
# Delete leaf entries first (users and private groups), then shared group
for ACCOUNT in "${ACCOUNTS[@]}"; do
    IFS=':' read -r UID_ _ <<<"${ACCOUNT}"
    sudo ldapdelete -Y EXTERNAL -H ldapi:/// "uid=${UID_},${USERS_OU}" 2>/dev/null || true
    sudo ldapdelete -Y EXTERNAL -H ldapi:/// "cn=${UID_},${GROUPS_OU}" 2>/dev/null || true
done
sudo ldapdelete -Y EXTERNAL -H ldapi:/// "cn=users,${GROUPS_OU}" 2>/dev/null || true

echo "=== 5. Importing accounts with local SASL/EXTERNAL authentication ==="
sudo ldapadd -Y EXTERNAL -H ldapi:/// -f "${IMPORT_LDIF}"

echo ""
echo "Success: 4 Skywalker accounts imported (uidNumber ${START_UID}-$((UID_NUMBER - 1)))."
echo "Individual passwords saved in \${HOME}/.ldap_<uid>_pass (mode 400)."
SCRIPT_EOF
bash generate-skywalker-accounts.sh
[Attention] Attention

Le mot de passe individuel de chaque compte n'est stocké que sur le serveur, dans les fichiers ~/.ldap_<uid>_pass (mode 400). Copiez le fichier correspondant sur tout hôte depuis lequel vous devez authentifier ce compte, comme cela a déjà été fait pour ~/.ldap_admin_pass (Section 4.1, « Installer et initialiser le service d'annuaire »).

Vérifiez le résultat.

sudo ldapsearch -LLL -Y EXTERNAL -H ldapi:/// \
    -b ou=users,dc=lab,dc=local \
    "(objectClass=posixAccount)" uid uidNumber homeDirectory
dn: uid=padme,ou=users,dc=lab,dc=local
uid: padme
uidNumber: 10000
homeDirectory: /ahome/padme

dn: uid=anakin,ou=users,dc=lab,dc=local
uid: anakin
uidNumber: 10001
homeDirectory: /ahome/anakin

dn: uid=leia,ou=users,dc=lab,dc=local
uid: leia
uidNumber: 10002
homeDirectory: /ahome/leia

dn: uid=luke,ou=users,dc=lab,dc=local
uid: luke
uidNumber: 10003
homeDirectory: /ahome/luke

Q202.

Comment déclarer l'hôte client dans l'unité ou=hosts ?

Les expressions olcAuthzRegexp mises en place dans la Section 4.2, « Sécuriser les échanges avec TLS et l'authentification SASL/GSSAPI » traduisent le principal machine host/clnt.lab.local@LAB.LOCAL en entrée cn=clnt.lab.local,ou=hosts,dc=lab,dc=local. Créez cette entrée avec la classe d'objet device, qui ne porte que l'attribut cn comme attribut obligatoire.

cat << 'SCRIPT_EOF' >register-lab-host.sh
#!/usr/bin/env bash
set -euo pipefail

# 1. Global variables
readonly DOMAIN="lab.local"
# Dynamically compute Base DN (e.g. lab.local -> dc=lab,dc=local)
readonly BASE_DN="dc=${DOMAIN%%.*},dc=${DOMAIN#*.}"
readonly HOSTS_OU="ou=hosts,${BASE_DN}"
readonly CLIENT_HOSTNAME="clnt.${DOMAIN}"
readonly HOST_DN="cn=${CLIENT_HOSTNAME},${HOSTS_OU}"

echo "=== 1. Checking slapd service and parent DIT structure ==="
if ! systemctl is-active --quiet slapd; then
  echo "Error: The slapd service is not running." >&2
  echo "Start it with 'sudo systemctl start slapd' before registering hosts." >&2
  exit 1
fi

# Verify that the parent organizational unit (ou=hosts) exists
if ! sudo ldapsearch -Q -Y EXTERNAL -H ldapi:/// -b "${BASE_DN}" -s one "(ou=hosts)" dn | grep -q "^dn:"; then
  echo "Error: Parent container '${HOSTS_OU}' not found." >&2
  echo "Ensure the base directory tree (add_tree.ldif) has been populated." >&2
  exit 1
fi

echo "=== 2. Generating host entry LDIF ==="
TMP_LDIF=$(mktemp)
trap 'rm -f "${TMP_LDIF:-}"' EXIT

cat <<EOF >"${TMP_LDIF}"
dn: ${HOST_DN}
objectClass: top
objectClass: device
cn: ${CLIENT_HOSTNAME}
description: Client workstation for autofs/LDAP/NFSv4 lab setup

EOF

echo "=== 3. Removing existing entry if present (idempotency) ==="
sudo ldapdelete -Y EXTERNAL -H ldapi:/// "${HOST_DN}" 2>/dev/null || true

echo "=== 4. Importing the host entry with local SASL/EXTERNAL authentication ==="
sudo ldapadd -Y EXTERNAL -H ldapi:/// -f "${TMP_LDIF}"

echo "=== 5. Verifying host registration in the directory ==="
sudo ldapsearch -Q -Y EXTERNAL -H ldapi:/// \
    -b "${HOSTS_OU}" \
    -s base \
    -b "${HOST_DN}" \
    cn description

echo ""
echo "Success: Host entry '${HOST_DN}' registered."
echo "Kerberos identity mapping link:"
echo "  Kerberos 'host/${CLIENT_HOSTNAME}@${DOMAIN^^}' -> LDAP '${HOST_DN}'"
SCRIPT_EOF
bash register-lab-host.sh
[Note] Note

Cette entrée est le préalable indispensable à la vérification du bind GSSAPI reportée depuis la Section 4.2, « Sécuriser les échanges avec TLS et l'authentification SASL/GSSAPI », ainsi qu'à la configuration du compte de service nslcd côté client : les deux points sont traités dans la sous-partie suivante, Section 4.4, « Configurer l'accès transparent à l'annuaire depuis le client ».

Q203.

Comment vérifier le cloisonnement des données imposé par les listes de contrôle d'accès (ACL) déjà configurées dans la Section 4.1, « Installer et initialiser le service d'annuaire » ?

Les règles olcAccess {2} et {3} autorisent tout compte authentifié à lire l'ensemble de l'annuaire, à l'exception de l'attribut userPassword, réservé au titulaire du compte (règle {1}). Authentifiez-vous avec le compte luke plutôt qu'avec le compte administrateur, sur la connexion locale ldapi:/// (l'attribut olcLocalSSF y couvre le seuil olcSecurity: ssf=128 quel que soit le compte qui s'authentifie).

Recherchez l'attribut uid: de tous les comptes depuis la racine de l'annuaire :

ldapsearch -LLL -x -H ldapi:/// \
    -D "uid=luke,ou=users,dc=lab,dc=local" \
    -y ~/.ldap_luke_pass \
    -b dc=lab,dc=local \
    uid
dn: dc=lab,dc=local

dn: ou=users,dc=lab,dc=local

dn: ou=groups,dc=lab,dc=local

dn: ou=hosts,dc=lab,dc=local

dn: cn=padme,ou=groups,dc=lab,dc=local

dn: uid=padme,ou=users,dc=lab,dc=local
uid: padme

dn: cn=anakin,ou=groups,dc=lab,dc=local

dn: uid=anakin,ou=users,dc=lab,dc=local
uid: anakin

dn: cn=leia,ou=groups,dc=lab,dc=local

dn: uid=leia,ou=users,dc=lab,dc=local
uid: leia

dn: cn=luke,ou=groups,dc=lab,dc=local

dn: uid=luke,ou=users,dc=lab,dc=local
uid: luke

dn: cn=users,ou=groups,dc=lab,dc=local

dn: cn=clnt.lab.local,ou=hosts,dc=lab,dc=local

L'utilisateur luke a la capacité de parcourir l'annuaire et d'afficher l'attribut uid: de tous les autres comptes. Recherchez à présent l'attribut userPassword: :

ldapsearch -LLL -x -H ldapi:/// \
    -D "uid=luke,ou=users,dc=lab,dc=local" \
    -y ~/.ldap_luke_pass \
    -b dc=lab,dc=local \
    userPassword
dn: dc=lab,dc=local

dn: ou=users,dc=lab,dc=local

dn: ou=groups,dc=lab,dc=local

dn: ou=hosts,dc=lab,dc=local

dn: cn=padme,ou=groups,dc=lab,dc=local

dn: uid=padme,ou=users,dc=lab,dc=local

dn: cn=anakin,ou=groups,dc=lab,dc=local

dn: uid=anakin,ou=users,dc=lab,dc=local

dn: cn=leia,ou=groups,dc=lab,dc=local

dn: uid=leia,ou=users,dc=lab,dc=local

dn: cn=luke,ou=groups,dc=lab,dc=local

dn: uid=luke,ou=users,dc=lab,dc=local
userPassword:: e0FSR09OMn0kYXJnb24yaWQkdj0xOSRtPTcxNjgsdD01LHA9MSR2ZkxXckNiNlV
 Bb3BCSFhVSG5Ga2ZBJE1DbVJhRE5XbWNtcG5UaDlLSDEvRWp5QXVnbUtodDU2MHU1NXlLWW00V3c=

dn: cn=users,ou=groups,dc=lab,dc=local

dn: cn=clnt.lab.local,ou=hosts,dc=lab,dc=local

L'utilisateur luke a seulement le droit d'afficher le condensat de son propre mot de passe, encodé en base64 : le contenu réel dépend des mots de passe générés à la question précédente et diffère donc de cet exemple.

4.4. Configurer l'accès transparent à l'annuaire depuis le client

Le paquet nslcd et ses dépendances ont déjà été installés sans configuration sur le client dans la partie Section 3.3, « Installer les paquets TLS et générer les certificats », uniquement pour disposer du compte système nslcd nécessaire au dépôt de la clé privée LDAP. Le fichier /etc/nslcd.conf qui en résulte ne porte donc que des valeurs par défaut, sans aucune information de connexion à l'annuaire. L'unité ou=users existant désormais (Section 4.3, « Construire l'arbre et peupler l'annuaire »), cette sous-partie effectue la première configuration réelle de nslcd, directement avec un compte de service dédié, nslcd-proxy, dont les droits restent ceux d'un compte authentifié ordinaire au regard des listes de contrôle d'accès déjà en place (Section 4.1, « Installer et initialiser le service d'annuaire »). Elle complète également, à présent que l'entrée hôte existe dans ou=hosts, la vérification du bind SASL/GSSAPI laissée en suspens dans la Section 4.2, « Sécuriser les échanges avec TLS et l'authentification SASL/GSSAPI ».

Q204.

Comment créer le compte de service nslcd-proxy dans l'annuaire ?

Ce compte n'a besoin d'aucun attribut POSIX (pas de connexion interactive, pas de répertoire personnel) : la classe d'objet inetOrgPerson seule suffit à porter l'attribut userPassword nécessaire à un bind simple. L'opération s'exécute sur le serveur, via la connexion locale ldapi:///, selon le même schéma d'idempotence que les comptes Skywalker (Section 4.3, « Construire l'arbre et peupler l'annuaire »).

cat << 'SCRIPT_EOF' >create-nslcd-proxy.sh
#!/usr/bin/env bash
set -euo pipefail

# 1. Global variables
readonly DOMAIN="lab.local"
readonly BASE_DN="dc=${DOMAIN%%.*},dc=${DOMAIN#*.}"
readonly USERS_OU="ou=users,${BASE_DN}"
readonly PROXY_UID="nslcd-proxy"
readonly PROXY_DN="uid=${PROXY_UID},${USERS_OU}"
readonly PASS_FILE="${HOME}/.ldap_${PROXY_UID}_pass"
TMP_LDIF=$(mktemp)
trap 'rm -f "${TMP_LDIF:-}"' EXIT

echo "=== 1. Preparing the password and its Argon2 hash ==="
# Reuse existing password if present to maintain sync with the client configuration
if [[ -f "${PASS_FILE}" ]]; then
    PROXY_PASS=$(head -n 1 "${PASS_FILE}")
else
    # 18 random bytes yield exactly 24 base64 characters without padding
    PROXY_PASS=$(openssl rand -base64 18)
    ( umask 077 && printf '%s' "${PROXY_PASS}" >"${PASS_FILE}" )
fi
PROXY_HASH=$(sudo slappasswd -o module-load=argon2.la -h "{ARGON2}" -s "${PROXY_PASS}")

echo "=== 2. Generating the LDIF entry ==="
cat <<EOF >"${TMP_LDIF}"
dn: ${PROXY_DN}
objectClass: inetOrgPerson
uid: ${PROXY_UID}
cn: NSLCD Proxy Service
sn: Service
description: Read-only service account for NSS/PAM resolution
userPassword: ${PROXY_HASH}
EOF

echo "=== 3. Removing the existing entry if present (idempotency) ==="
sudo ldapdelete -Y EXTERNAL -H ldapi:/// "${PROXY_DN}" 2>/dev/null || true

echo "=== 4. Importing the service account with local SASL/EXTERNAL authentication ==="
sudo ldapadd -Y EXTERNAL -H ldapi:/// -f "${TMP_LDIF}"

echo ""
echo "Success: service account '${PROXY_UID}' created."
echo "Bind DN: ${PROXY_DN}"
echo "Password saved in: ${PASS_FILE} (mode 400)."
SCRIPT_EOF
bash create-nslcd-proxy.sh
=== 1. Preparing the password and its Argon2 hash ===
=== 2. Generating the LDIF entry ===
=== 3. Removing the existing entry if present (idempotency) ===
=== 4. Importing the service account with local SASL/EXTERNAL authentication ===
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
adding new entry "uid=nslcd-proxy,ou=users,dc=lab,dc=local"

Success: service account 'nslcd-proxy' created.
Bind DN: uid=nslcd-proxy,ou=users,dc=lab,dc=local
Password saved in: /home/etu/.ldap_nslcd-proxy_pass (mode 400).

Q205.

Comment transférer ce mot de passe sur le client, puis appliquer la première configuration réelle de nslcd avec cette identité ?

Transférez d'abord le fichier de mot de passe, comme cela a déjà été fait pour ~/.ldap_admin_pass (Section 4.2, « Sécuriser les échanges avec TLS et l'authentification SASL/GSSAPI »).

scp ~/.ldap_nslcd-proxy_pass clnt:~

Le script de reconfiguration s'exécute intégralement avec sudo bash ... : à l'intérieur du script, HOME (donc ~) désigne /root et non le répertoire personnel de l'utilisateur connecté en SSH. Déplacez donc le fichier transféré vers cet emplacement avant de lancer le script.

sudo mv ~/.ldap_nslcd-proxy_pass /root/
sudo chmod 400 /root/.ldap_nslcd-proxy_pass

Reprenez la démarche de la question Comment installer le paquet libnss-ldapd et ses dépendances en mode non-interactif ? du support Introduction aux annuaires LDAP avec OpenLDAP : la commande debconf-set-selections est fournie par le paquet debconf-utils, qui n'a pas encore été installé sur ce client. Les paquets NSS/PAM le sont déjà, installés sans configuration dans la partie Section 3.3, « Installer les paquets TLS et générer les certificats » : il s'agit donc d'injecter, pour la première fois, l'intégralité des réponses debconf du paquet nslcd, identifiant et mot de passe de bind compris, puis de lancer sa configuration en mode non-interactif, sur le client.

cat << 'SCRIPT_EOF' >configure-nslcd-proxy.sh
#!/usr/bin/env bash
set -euo pipefail

echo "=== 1. Installing debconf-utils ==="
sudo apt update -qq
sudo apt install -y --no-install-recommends debconf-utils

readonly PROXY_DN="uid=nslcd-proxy,ou=users,dc=lab,dc=local"
readonly PROXY_PASS=$(head -n 1 /root/.ldap_nslcd-proxy_pass)

echo "=== 2. Purging the default /etc/nslcd.conf left by the unconfigured install ==="
# The nslcd package was installed without configuration (generate.certs step):
# nslcd.config seeds debconf FROM this existing file when present, which would
# override the values set below. Removing it forces a clean re-seed.
sudo rm -f /etc/nslcd.conf

sudo debconf-set-selections <<EOF
nslcd nslcd/ldap-uris string ldaps://ldap-srvr.lab.local
nslcd nslcd/ldap-base string dc=lab,dc=local
nslcd nslcd/ldap-auth-type select simple
nslcd nslcd/ldap-starttls boolean false
nslcd nslcd/ldap-reqcert select demand
nslcd nslcd/ldap-cacertfile string /etc/ssl/certs/labCA.crt
nslcd nslcd/ldap-binddn string ${PROXY_DN}
nslcd nslcd/ldap-bindpw password ${PROXY_PASS}
libnss-ldapd libnss-ldapd/nsswitch multiselect passwd, group, shadow
EOF

echo "=== 3. Reconfiguring packages non-interactively ==="
sudo dpkg-reconfigure -f noninteractive nslcd libnss-ldapd

echo "=== 4. Restarting nslcd service ==="
sudo systemctl restart nslcd

echo ""
echo "Success: nslcd configured and restarted."
SCRIPT_EOF
sudo bash configure-nslcd-proxy.sh

Affichez la liste des paramètres effectivement appliqués, comme dans le support Introduction aux annuaires LDAP avec OpenLDAP, avec la commande debconf-get-selections.

sudo debconf-get-selections | grep ^nslcd
nslcd    nslcd/ldap-auth-type    select    simple
nslcd    nslcd/ldap-base    string    dc=lab,dc=local
nslcd    nslcd/ldap-binddn    string    uid=nslcd-proxy,ou=users,dc=lab,dc=local
nslcd    nslcd/ldap-bindpw    password
nslcd    nslcd/ldap-cacertfile    string    /etc/ssl/certs/labCA.crt
nslcd    nslcd/ldap-reqcert    select    demand
nslcd    nslcd/ldap-sasl-authcid    string
nslcd    nslcd/ldap-sasl-authzid    string
nslcd    nslcd/ldap-sasl-krb5-ccname    string
nslcd    nslcd/ldap-sasl-mech    select
nslcd    nslcd/ldap-sasl-realm    string
nslcd    nslcd/ldap-sasl-secprops    string
nslcd    nslcd/ldap-starttls    boolean    false
nslcd    nslcd/ldap-uris    string    ldaps://ldap-srvr.lab.local

Vérifiez enfin que la résolution des comptes reste transparente pour le système.

id luke
uid=10003(luke) gid=10003(luke) groupes=10003(luke),100(users)
[Note] Note

Le résultat de id luke confirme que la résolution des comptes POSIX fonctionne de façon transparente avec ce compte de service authentifié : l'utilisateur luke est bien résolu avec son uidNumber, son gidNumber et son appartenance au groupe secondaire cn=users (Section 4.3, « Construire l'arbre et peupler l'annuaire »). Seules les journalisations côté serveur permettent de vérifier que les requêtes portent bien l'identité uid=nslcd-proxy,ou=users,dc=lab,dc=local, un compte authentifié ordinaire au regard des règles olcAccess déjà en place (Section 4.1, « Installer et initialiser le service d'annuaire »).

Q206.

Maintenant que l'entrée cn=clnt.lab.local,ou=hosts,dc=lab,dc=local existe (Section 4.3, « Construire l'arbre et peupler l'annuaire »), comment vérifier que le bind SASL/GSSAPI mis en place dans la Section 4.2, « Sécuriser les échanges avec TLS et l'authentification SASL/GSSAPI » fonctionne réellement ?

Le principal machine host/clnt.lab.local@LAB.LOCAL et son keytab ont déjà été déployés sur le client (Q : Q192). Obtenez, sur le client, un ticket à partir de ce keytab.

sudo kinit -k -t /etc/krb5.keytab host/clnt.lab.local && \
sudo klist
Ticket cache: FILE:/tmp/krb5cc_0
Default principal: host/clnt.lab.local@LAB.LOCAL

Valid starting       Expires              Service principal
02/09/2026 15:39:17  03/09/2026 01:39:17  krbtgt/LAB.LOCAL@LAB.LOCAL
        renew until 03/09/2026 15:39:17

Utilisez ce ticket pour effectuer un bind SASL/GSSAPI vers le serveur, avec la commande déjà annoncée à la fin de la Section 4.2, « Sécuriser les échanges avec TLS et l'authentification SASL/GSSAPI ».

sudo ldapwhoami -H ldap://ldap-srvr.lab.local -Y GSSAPI
SASL/GSSAPI authentication started
SASL username: host/clnt.lab.local@LAB.LOCAL
SASL SSF: 256
SASL data security layer installed.
dn:cn=clnt.lab.local,ou=hosts,dc=lab,dc=local

La règle olcAuthzRegexp {1} (Section 4.2, « Sécuriser les échanges avec TLS et l'authentification SASL/GSSAPI ») traduit bien le principal host/clnt.lab.local@LAB.LOCAL en identité cn=clnt.lab.local,ou=hosts,dc=lab,dc=local, confirmant que l'entrée créée dans Section 4.3, « Construire l'arbre et peupler l'annuaire » est correctement reconnue par slapd.

[Note] Note

Cette commande utilise ldap:// et non ldaps:// : le seuil olcSecurity: ssf=128 (Section 4.1, « Installer et initialiser le service d'annuaire ») est ici satisfait par la couche de sécurité propre au mécanisme SASL/GSSAPI (SASL SSF: 256), indépendamment de tout chiffrement TLS. Seul un accès anonyme en clair reste rejeté par ce seuil, comme observé dans la Section 4.2, « Sécuriser les échanges avec TLS et l'authentification SASL/GSSAPI ».