OpenLDAP構築ガイド:cn=config・TLS・ACL・バックアップ・複製
EdwardMoon
OpenLDAP 2.6の動的設定cn=configとMDB backendを基準に、TLS、最小権限ACL、インデックス、バックアップ、復旧、syncreplによるHAを順に説明します。認証基盤のため、インストール成功以上に暗号化、権限、整合性、復旧可能性を確認します。
Rocky Linux 9のサーバーパッケージ提供元とディレクトリ構成は、ベンダーやビルド方式で異なります。検証済みOpenLDAP 2.6パッケージを前提に、実パスを確認してからLDAP操作でcn=configを変更します。ホストログイン連携にはnss-pam-ldapdよりSSSDのLDAP providerを検討してください。

構成要素と設計
| 要素 | 役割 | 運用基準 |
|---|---|---|
| slapd | LDAP要求処理 | 非特権サービスアカウント、systemd、TLS listener |
| cn=config | 実行中の動的設定DIT | LDAPI + SASL EXTERNALで変更 |
| MDB | ディレクトリデータ保存 | map size、checkpoint、FS容量 |
| ACL | attribute・DN別権限 | 具体的なルールを先に置き、既定は拒否 |
| TLS | bind、検索、複製の暗号化 | CA信頼、hostname、期限管理 |
| syncrepl | provider間の変更同期 | 一意のserverID・rid、時刻同期 |
| slapcat/slapadd | 論理バックアップと復旧 | configとdataを一緒に保存 |
パッケージとパスの確認
名称を推測せず、サービスのbinary、設定DIT、データ、証明書パスを記録します。ソースビルドならconfigure、test、installの結果と依存版を保存し、ベンダーパッケージならGPG署名とサポート方針を確認します。
slapd -VVV 2>&1 | head -n 40
ldapsearch -VV 2>&1 | head -n 20
command -v slapd ldapsearch ldapmodify slapcat slapadd slaptest
systemctl cat slapd
systemctl show slapd -p User -p Group -p ExecStart
findmnt -T /var/lib/ldap
df -hT /var/lib/ldap
df -i /var/lib/ldap
任意のリポジトリ追加や未検証スクリプトのshell接続は避けます。本番ではOpenLDAP 2.6の保守版、TLS library、LMDB、Cyrus SASL、systemd構成を一緒に更新し、stagingでLDIF互換性を確認します。
cn=configとMDBの確認
LDAPIによるローカル管理接続
sudo ldapwhoami -Q -Y EXTERNAL -H ldapi:///
sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:/// -b cn=config -s base dn cn
sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:/// -b cn=config '(&(objectClass=olcMdbConfig)(olcSuffix=*))' dn olcSuffix olcRootDN olcDbDirectory olcDbMaxSize olcDbIndex
-Q -Y EXTERNALはUnix socketのpeer credentialを使うため、管理パスワードをコマンドラインへ残しません。cn=configを遠隔公開せず、変更前にentryをLDIFへ保存します。database DNの{番号}は導入ごとに異なるため推測しません。
現在のcn=configのバックアップ
sudo install -d -m 0700 /var/backups/openldap
sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:/// -b cn=config '(objectClass=*)' '*' '+' | sudo tee "/var/backups/openldap/cn-config-before-$(date +%F-%H%M%S).ldif" >/dev/null
sudo chmod 0600 /var/backups/openldap/*.ldif
sudo sha256sum /var/backups/openldap/*.ldif
suffixと管理者DN
例はdc=example,dc=comとcn=adminです。実組織名、DNS所有権、命名方針を先に確定します。rootDNはアプリケーション用ではなく全ACLを迂回するため、緊急復旧だけに使い、日常変更は委任した管理者groupで行います。
MDBのDNとrootPWハッシュ
MDB_DN=$(sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:/// -b cn=config '(&(objectClass=olcMdbConfig)(olcSuffix=*))' dn | awk '/^dn: / {print substr($0,5); exit}')
printf 'MDB_DN=%s
' "$MDB_DN"
test -n "$MDB_DN"
# パスワードは対話入力し、シェル履歴には残りません。
ROOT_HASH=$(slappasswd)
test -n "$ROOT_HASH"
suffix・rootDN・rootPWの変更
sudo install -m 0600 /dev/null /root/openldap-database.ldif
sudo tee /root/openldap-database.ldif >/dev/null <<EOF
dn: ${MDB_DN}
changetype: modify
replace: olcSuffix
olcSuffix: dc=example,dc=com
-
replace: olcRootDN
olcRootDN: cn=admin,dc=example,dc=com
-
replace: olcRootPW
olcRootPW: ${ROOT_HASH}
EOF
unset ROOT_HASH
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f /root/openldap-database.ldif
sudo shred -u /root/openldap-database.ldif
基本DITとユーザー
base DNと組織単位
install -m 0600 /dev/null base.ldif
cat > base.ldif <<'LDIF'
dn: dc=example,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
o: Example Directory
dc: example
dn: ou=People,dc=example,dc=com
objectClass: organizationalUnit
ou: People
dn: ou=Groups,dc=example,dc=com
objectClass: organizationalUnit
ou: Groups
LDIF
ldapadd -x -W -D 'cn=admin,dc=example,dc=com' -H ldapi:/// -f base.ldif
shred -u base.ldif
試験ユーザーと管理者group
USER_HASH=$(slappasswd)
install -m 0600 /dev/null people.ldif
cat > people.ldif <<EOF
dn: uid=alice,ou=People,dc=example,dc=com
objectClass: inetOrgPerson
uid: alice
cn: Alice Example
sn: Example
mail: alice@example.com
userPassword: ${USER_HASH}
dn: cn=ldap-admins,ou=Groups,dc=example,dc=com
objectClass: groupOfNames
cn: ldap-admins
member: uid=alice,ou=People,dc=example,dc=com
EOF
unset USER_HASH
ldapadd -x -W -D 'cn=admin,dc=example,dc=com' -H ldapi:/// -f people.ldif
shred -u people.ldif
パスワードを平文でLDIF、Git、チケットへ保存しません。service accountを個人用から分け、groupOfNamesの最初のmemberを削除するときはスキーマ制約を考慮します。uidとmailの一意性はアプリケーションだけに任せず、overlayやprovisioning手順で保証します。
TLSの強制と検証
olcSecurity: simple_bind=128は、パスワードを使うsimple bindに十分な暗号化を要求します。既存olcSecurityがあれば、制限を保持してマージします。LDAPIのローカルSSFが128未満ならLDAPI simple bindも拒否され得るため、その後の管理変更はSASL EXTERNAL、ユーザー・複製接続はCA検証付きStartTLS/LDAPSを使います。この方針自体がクライアントの送信を暗号化するわけではなく、クライアントも必ず-ZZかldaps://を指定します。
TLSなしのsimple bindはパスワードを保護しません。証明書SANへ実接続DNS名を入れ、秘密鍵はslapd実行アカウントだけが読めるようにします。組織CAか公開CAのchainを全クライアントと複製ノードへ配布します。
証明書の権限と有効性
sudo install -d -o root -g ldap -m 0750 /etc/openldap/certs
sudo install -o root -g ldap -m 0640 ldap01.key /etc/openldap/certs/
sudo install -o root -g ldap -m 0644 ldap01.crt /etc/openldap/certs/
sudo install -o root -g ldap -m 0644 org-ca.crt /etc/openldap/certs/
openssl x509 -in /etc/openldap/certs/ldap01.crt -noout -subject -issuer -dates -ext subjectAltName
openssl verify -CAfile /etc/openldap/certs/org-ca.crt /etc/openldap/certs/ldap01.crt
cn=configへのTLSパス適用
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<'LDIF'
dn: cn=config
changetype: modify
replace: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/openldap/certs/org-ca.crt
-
replace: olcTLSCertificateFile
olcTLSCertificateFile: /etc/openldap/certs/ldap01.crt
-
replace: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/openldap/certs/ldap01.key
-
replace: olcTLSProtocolMin
olcTLSProtocolMin: 3.3
-
replace: olcSecurity
olcSecurity: simple_bind=128
LDIF
sudo systemctl restart slapd
sudo systemctl status slapd --no-pager
StartTLSとLDAPSのhostname検証
openssl s_client -connect ldap01.example.com:636 -servername ldap01.example.com -verify_hostname ldap01.example.com -CAfile /etc/openldap/certs/org-ca.crt -verify_return_error </dev/null
LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt ldapwhoami -x -ZZ -H ldap://ldap01.example.com -D 'uid=alice,ou=People,dc=example,dc=com' -W
LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt ldapsearch -LLL -x -H ldaps://ldap01.example.com -D 'uid=alice,ou=People,dc=example,dc=com' -W -b 'dc=example,dc=com' '(uid=alice)' dn cn mail
専用複製アカウントと非暗号化接続の拒否試験
両providerで使う専用アカウントを先に作ります。パスワードは複製設定の秘密値と一致させます。既存なら追加せず、そのアカウントとACLを確認します。
REPL_HASH=$(slappasswd)
umask 077
cat > replicator.ldif <<EOF
dn: uid=replicator,ou=People,dc=example,dc=com
objectClass: inetOrgPerson
uid: replicator
cn: Replication Reader
sn: Reader
userPassword: ${REPL_HASH}
EOF
unset REPL_HASH
LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt \
ldapadd -x -ZZ -H ldap://ldap01.example.com \
-D 'cn=admin,dc=example,dc=com' -W -f replicator.ldif
rm -f replicator.ldif
# 実パスワードではなく試験用文字列だけを送ります。confidentiality requiredによる拒否を確認します。
ldapwhoami -x -H ldap://ldap01.example.com \
-D 'uid=transport-probe,ou=People,dc=example,dc=com' -w not-a-real-password
最小権限ACL
ACLは上から評価され、最初に一致するtoとbyが決定します。userPasswordなど機密attributeを先に、一般ルールを最後に置きます。既定の公開readに頼らず、anonymousにはbind用authだけを許可します。
sudo tee /root/openldap-acl.ldif >/dev/null <<EOF
dn: ${MDB_DN}
changetype: modify
replace: olcAccess
olcAccess: {0}to * by dn.exact="uid=replicator,ou=People,dc=example,dc=com" read by * break
olcAccess: {1}to attrs=userPassword by self =xw by anonymous auth by * none
olcAccess: {2}to dn.subtree="ou=People,dc=example,dc=com" attrs=entry,children,cn,sn,uid,mail by self write by group.exact="cn=ldap-admins,ou=Groups,dc=example,dc=com" write by users read by * none
olcAccess: {3}to dn.subtree="ou=Groups,dc=example,dc=com" by group.exact="cn=ldap-admins,ou=Groups,dc=example,dc=com" write by users read by * none
olcAccess: {4}to * by users read by * none
-
add: olcLimits
olcLimits: {0}dn.exact="uid=replicator,ou=People,dc=example,dc=com" time.soft=unlimited time.hard=unlimited size.soft=unlimited size.hard=unlimited
EOF
sudo chmod 0600 /root/openldap-acl.ldif
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f /root/openldap-acl.ldif
sudo shred -u /root/openldap-acl.ldif
許可・拒否の試験
# anonymousがuserPasswordを読めないことを確認します。
LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt ldapsearch -LLL -x -ZZ -H ldap://ldap01.example.com -b 'uid=alice,ou=People,dc=example,dc=com' userPassword
# 本人のbindと一般attributeの読み取りを確認します。
LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt ldapsearch -LLL -x -ZZ -H ldap://ldap01.example.com -D 'uid=alice,ou=People,dc=example,dc=com' -W -b 'uid=alice,ou=People,dc=example,dc=com' cn mail
sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:/// -b "$MDB_DN" olcAccess
MDBインデックスと状態
実際のsearch filterとbind attributeから設計します。全attributeへeq・subを付けると書き込みコストとDBサイズが増えます。事前にaccess logやqueryを分析し、既存データがあれば保守時間にslapindexを実施します。
sudo tee /root/openldap-index.ldif >/dev/null <<EOF
dn: ${MDB_DN}
changetype: modify
replace: olcDbIndex
olcDbIndex: objectClass eq
olcDbIndex: entryUUID,entryCSN eq
olcDbIndex: uid,mail eq
olcDbIndex: cn,sn eq,sub
EOF
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f /root/openldap-index.ldif
sudo shred -u /root/openldap-index.ldif
sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:/// -b "$MDB_DN" olcDbIndex olcDbMaxSize olcDbCheckpoint
既存データへの索引適用と所有権はパッケージパスで異なります。サービス対象から外してslapdを停止し、slapindexを実行してからslaptestと所有権を確認します。稼働中MDBを任意にコピー・編集しません。
slapcatによるバックアップと復旧訓練
cn=configとapplication suffixの両方が必要です。単一ノードなら保守時間に停止し、HAなら1 replicaをLBから外して一貫したLDIFを取得します。暗号化して別システムへコピーし、checksumを記録します。
sudo bash -euo pipefail <<'BASH'
backup_dir="/var/backups/openldap/$(date +%F-%H%M%S)"
install -d -m 0700 "$backup_dir"
umask 077
systemctl stop slapd
trap 'systemctl start slapd' EXIT
slapcat -F /etc/openldap/slapd.d -n 0 > "$backup_dir/cn-config.ldif"
slapcat -F /etc/openldap/slapd.d -b 'dc=example,dc=com' > "$backup_dir/example-data.ldif"
test -s "$backup_dir/cn-config.ldif"
test -s "$backup_dir/example-data.ldif"
(cd "$backup_dir" && sha256sum ./*.ldif > SHA256SUMS && sha256sum -c SHA256SUMS)
BASH
sudo systemctl status slapd --no-pager
sudo journalctl -u slapd --since '-5 min' --no-pager
復旧リハーサルの検証
sudo slaptest -F /etc/openldap/slapd.d -u
sudo slapcat -F /etc/openldap/slapd.d -n 0 | head
sudo slapcat -F /etc/openldap/slapd.d -b 'dc=example,dc=com' | head
LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt ldapsearch -LLL -x -ZZ -H ldap://ldap01.example.com -D 'cn=admin,dc=example,dc=com' -W -b 'dc=example,dc=com' '(objectClass=*)' dn | tail
- 隔離ホストの空のslapd.dとデータディレクトリへ、config、dataの順でslapaddする。
- サービスアカウントの所有権とSELinux contextを復元する。
- slaptest後にLDAPI、StartTLS・LDAPS、hostnameを検証する。
- base DN、entry数、主要group member、password bindを標本確認する。
- service accountの許可操作とanonymous拒否を再試験する。
- 復旧時間と不足データを記録し、RPO・RTOを調整する。
syncreplとHA
同じ認証データを保つため、複製アカウントはuserPasswordを含む原本と運用属性をすべて読める必要があります。先の最初のACLは複製DNだけに全readを与え、他はby * breakで後続ルールへ進めます。このアカウントをアプリケーションへ流用しません。syncprovが動的モジュールなら、提供されたパスを確認して先にロードします。
multiproviderは両方へ常時同時書き込みするという意味ではありません。競合とsplit brainを避けるため、LBは同時に1つのアクティブ書き込み経路を選び、読取分散、health check、failbackを別途設計します。serverIDとsyncrepl ridは一意にします。
syncprov overlayとcheckpoint
sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcOverlay=syncprov,${MDB_DN}
objectClass: olcOverlayConfig
objectClass: olcSyncProvConfig
olcOverlay: syncprov
olcSpCheckpoint: 100 10
olcSpSessionlog: 1000
EOF
providerごとのsyncrepl構造例
dn: cn=config
changetype: modify
replace: olcServerID
olcServerID: 1 ldaps://ldap01.example.com
olcServerID: 2 ldaps://ldap02.example.com
dn: olcDatabase={MDB_NUMBER}mdb,cn=config
changetype: modify
add: olcSyncrepl
olcSyncrepl: rid=001 provider=ldaps://ldap02.example.com bindmethod=simple binddn="uid=replicator,ou=People,dc=example,dc=com" credentials=<REPLICATION_SECRET> searchbase="dc=example,dc=com" type=refreshAndPersist retry="5 5 300 +" timeout=10 tls_reqcert=demand tls_cacert=/etc/openldap/certs/org-ca.crt
-
add: olcMultiProvider
olcMultiProvider: TRUE
これは構造例です。{MDB_NUMBER}、serverID、rid、provider、secretを各ノードへ合わせ、保護したファイルから適用します。simple bind secretはcn=configへ保存されるため、権限とバックアップ暗号化を強化するか、SASL EXTERNALのクライアント証明書を検討します。
複製整合性とfailover
LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt ldapsearch -LLL -x -H ldaps://ldap01.example.com -D 'uid=replicator,ou=People,dc=example,dc=com' -W -b 'dc=example,dc=com' -s base contextCSN
LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt ldapsearch -LLL -x -H ldaps://ldap02.example.com -D 'uid=replicator,ou=People,dc=example,dc=com' -W -b 'dc=example,dc=com' -s base contextCSN
sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:/// -b cn=Monitor '(objectClass=monitorOperation)' cn monitorOpInitiated monitorOpCompleted
timedatectl status
chronyc tracking
同じ試験entryのadd・modify・deleteが相手へ届くか確認し、1 providerをLBから外して読取、書込、再参加を試します。contextCSNの差だけで即障害とせず、ログ、pending operation、接続、収束時間を併せて見ます。
運用確認
systemctl --no-pager --full status slapd
ss -lntp | grep -E ':(389|636)\b'
sudo journalctl -u slapd --since '-30 min' --no-pager
sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:/// -b cn=Monitor -s base '*' '+'
df -hT /var/lib/ldap /var/log
df -i /var/lib/ldap /var/log
findmnt -T /var/lib/ldap
- systemd終了コードと最初のエラー時点を確認する。
- 389・636が予定アドレスだけに開いているか確認する。
- CA chain、SAN、期限、クライアントのhostname検証を確認する。
- bind DN、base DN、filter、scope、ACL順序を最小queryで再現する。
- MDB map size、FS・inode、checkpoint、長時間queryを見る。
- syncrepl、serverID・rid、contextCSN、時刻同期を見る。
- 最新slapcatのchecksumと隔離復旧訓練を確認する。
よくある誤りと対策
| 誤り | 影響 | 対策 |
|---|---|---|
| 平文simple bind | パスワード露出 | critical StartTLSまたはLDAPSとCA検証 |
| アプリケーションでrootDN使用 | 全ACL迂回 | service DNと最小権限group |
| anonymousの全read | 個人情報・組織図の露出 | password authだけ許可し、既定拒否 |
| database DN番号を固定 | 別導入のentryを誤変更 | cn=config検索で実DNを確認 |
| 稼働中MDBのコピー | 不整合バックアップ | slapcatと復元試験 |
| 両providerへの無制限同時書込 | 競合・split brain | 単一書き込み経路とfailover制御 |
| TLS検証省略 | 偽サーバーへ接続 | CA、SAN、hostname、期限確認 |
関連資料
- OpenLDAP 2.6 Administrator’s Guide
- OpenLDAP Access Control
- OpenLDAP Replication
- Red Hat SSSD LDAP・TLSクライアント
- Linuxのディスク・inode不足診断
- DRBD・Pacemaker・STONITH HA
- Ansible Playbook・Vault・段階配布
まとめ
完了条件はbindが一度成功することではありません。cn=configとMDBパス、TLS hostname、attribute単位ACL、実queryに基づく索引、config・dataの同時バックアップ、隔離復旧を検証します。HAでは一意のserverID・rid、時刻同期、単一の書き込み経路を守り、認証基盤を予測可能に運用します。