Fullmoon System

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を検討してください。

OpenLDAP構成:TLSクライアント、ロードバランサー、2ノードsyncrepl、暗号化バックアップ
TLSクライアント、単一のアクティブ書き込み経路、2つのproviderのsyncrepl、外部バックアップを分離します

構成要素と設計

要素役割運用基準
slapdLDAP要求処理非特権サービスアカウント、systemd、TLS listener
cn=config実行中の動的設定DITLDAPI + SASL EXTERNALで変更
MDBディレクトリデータ保存map size、checkpoint、FS容量
ACLattribute・DN別権限具体的なルールを先に置き、既定は拒否
TLSbind、検索、複製の暗号化CA信頼、hostname、期限管理
syncreplprovider間の変更同期一意のserverID・rid、時刻同期
slapcat/slapadd論理バックアップと復旧configとdataを一緒に保存
LDAPは汎用RDBMSではありません。読み取りが多く階層・スキーマが安定したidentity・directoryデータに適します。トランザクション処理、自由な関係の照会、頻繁なスキーマ変更が中心ならRDBMSが適します。

パッケージとパスの確認

名称を推測せず、サービスの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を使います。この方針自体がクライアントの送信を暗号化するわけではなく、クライアントも必ず-ZZldaps://を指定します。

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
  1. systemd終了コードと最初のエラー時点を確認する。
  2. 389・636が予定アドレスだけに開いているか確認する。
  3. CA chain、SAN、期限、クライアントのhostname検証を確認する。
  4. bind DN、base DN、filter、scope、ACL順序を最小queryで再現する。
  5. MDB map size、FS・inode、checkpoint、長時間queryを見る。
  6. syncrepl、serverID・rid、contextCSN、時刻同期を見る。
  7. 最新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、期限確認

関連資料

まとめ

完了条件はbindが一度成功することではありません。cn=configとMDBパス、TLS hostname、attribute単位ACL、実queryに基づく索引、config・dataの同時バックアップ、隔離復旧を検証します。HAでは一意のserverID・rid、時刻同期、単一の書き込み経路を守り、認証基盤を予測可能に運用します。