第20章 LDAPクライアントとOpenLDAPサーバー

LPIC-2 210.3〜210.4 相当

この章では、LPIC-2 210.3「LDAPクライアントの基本的な使用方法」(配点2)と210.4「基本的なディレクトリサーバーの設定」(配点4)を扱います。前の章(DHCPとPAM認証)で少し触れたLDAP連携について、クライアントとしての設定を掘り下げるとともに、LDAPサーバー本体(OpenLDAP)をゼロから構築する手順を扱います。210.4はLPIC-2全体の中でも配点の大きい項目のひとつです。

ディレクトリサービスとRDBMSの違い

LDAP(Lightweight Directory Access Protocol)は、ユーザー情報や組織情報などを一元管理する「ディレクトリサービス」のためのプロトコルです。MySQLやPostgreSQLのようなRDBMS(リレーショナルデータベース管理システム)と混同されがちですが、設計思想が異なります。

観点RDBMSLDAPディレクトリ
データ構造テーブル(行と列)とテーブル間の結合木構造(DIT)による階層
読み書き比率読み書きが比較的均等な用途を想定読み取りが圧倒的に多い用途に最適化
スキーマテーブルごとに列を定義objectClassごとに属性の要否を定義
複製専用のレプリケーション機構を別途構築syncreplなど複製が標準機能として組み込み
典型的な用途トランザクション処理、集計ユーザー認証、アドレス帳、組織情報の検索

LDAPは「頻繁に読み取られるが、書き込みはたまにしか発生しない」データ(ユーザー一覧、部署構成、連絡先など)を、多数のサーバーから高速に検索できるようにすることに特化しています。トランザクションを伴う複雑な結合処理には向いていません。

LDAPのデータモデル:DIT・DN・RDN・objectClass

LDAPのデータは、DIT(Directory Information Tree)と呼ばれる木構造で管理されます。木の根に近い部分は組織(ドメイン)を表し、枝分かれした先に個々のエントリ(ユーザーやグループなど)が置かれます。

DITの構造イメージ
dc=example,dc=com                          ← ベース(ドメインを表す)
 ├─ ou=people                              ← 組織単位(Organizational Unit)
 │    ├─ uid=alice,ou=people,dc=example,dc=com
 │    └─ uid=bob,ou=people,dc=example,dc=com
 └─ ou=groups
      └─ cn=developers,ou=groups,dc=example,dc=com

各エントリはDN(Distinguished Name、識別名)で一意に特定されます。DNは、そのエントリ自身を表すRDN(Relative Distinguished Name、相対識別名)と、親エントリのDNを連結したものです。たとえばuid=alice,ou=people,dc=example,dc=comの場合、RDNは先頭のuid=alice部分で、残りのou=people,dc=example,dc=comが親の位置を表します。

属性意味用途の例
dcDomain Component(ドメイン構成要素)example.comなら dc=example,dc=com
ouOrganizational Unit(組織単位)ou=people, ou=groups
cnCommon Name(共通名)人物のフルネームやグループ名
uidUser ID(ユーザーID)ログイン名
oOrganization(組織名)o=Example Corp
cCountry(国名、2文字コード)c=JP

各エントリがどんな属性を持てるかは、objectClass属性が指すスキーマ定義によって決まります。objectClassには3種類あります。

種類意味
STRUCTURALエントリの基本的な種類を決定する主要なクラス。1エントリにつき実質1つ
AUXILIARYSTRUCTURALなクラスに追加の属性を補助的に付け加えるクラス(複数付与可)
ABSTRACT他のクラスから継承されることだけを目的とした抽象クラス(topなど、単独では使わない)
objectClassの定義例(cn=schema配下、簡略化)
objectClass ( 2.16.840.1.113730.3.2.2
    NAME 'inetOrgPerson'
    SUP organizationalPerson
    STRUCTURAL
    MAY ( mail $ uid $ jpegPhoto ) )

先頭の数字の並び(2.16.840.1.113730.3.2.2)はOID(Object Identifier)で、そのスキーマ定義を世界的に一意に識別する番号です。MUSTで必須属性、MAYで任意属性を指定します。SUPは継承元のオブジェクトクラスを表し、inetOrgPersonはorganizationalPersonを継承しているため、継承元が持つcnやsnなどの属性もあわせて持てます。よく使われる標準スキーマには、core(cnやsnなどごく基本的な属性)、cosine(電話番号や住所など)、inetorgperson(企業のユーザー向け属性)、nis(posixAccount/posixGroupなどUNIXアカウント関連属性)があります。

dc=example,dc=com ベース(ドメイン) ou=people ou=groups uid=alice DN = uid=alice,ou=people, dc=example,dc=com uid=bob DN = uid=bob,ou=people, dc=example,dc=com cn=developers DN = cn=developers,ou=groups, dc=example,dc=com 各エントリのDN(識別名)=RDN(先頭の1要素)+親エントリのDN
LDAPのディレクトリ情報ツリー(DIT)。ベース(dc=example,dc=com)の下にou=people・ou=groupsという組織単位があり、その配下にuid=alice・uid=bob・cn=developersといった実際のエントリが置かれます。各エントリのDNは、RDN(自身を表す先頭部分)に親エントリのDNをつなげたものです。

OpenLDAPのパッケージ構成

Linuxで最も広く使われるLDAP実装がOpenLDAPです。サーバー機能とクライアント機能でパッケージが分かれています。

ディストリビューションサーバー側パッケージクライアント側パッケージ
Debian/Ubuntuslapdldap-utils(ldapsearch/ldapadd等一式)
RHEL/CentOS系openldap-serversopenldap-clients

サーバー本体のデーモンはslapd(Standalone LDAP Daemon)です。クライアント側のldap-utils/openldap-clientsには、この章で扱うldapsearch・ldapadd・ldapmodify・ldapdelete・ldappasswdが含まれます。サーバーを構築しない場合でも、他社のLDAPサーバーに接続するだけならクライアントパッケージのみで十分です。

設定方式の変遷:slapd.confからcn=configへ

古いバージョンのOpenLDAPでは/etc/ldap/slapd.confという単一のテキストファイルで設定していました。現在は「slapd-config」と呼ばれる、LDAPディレクトリ自体の中に設定情報を保持する方式(cn=config)が標準です。設定は/etc/ldap/slapd.d/以下にLDIFファイル群として格納されますが、これらを直接テキストエディタで編集することは想定されておらず、ldapmodifyでcn=configツリーに対してLDAP操作として変更を加えます。

よくある間違い: /etc/ldap/slapd.d/配下のLDIFファイルを直接テキストエディタで書き換えてしまうケースが見られますが、これはOpenLDAPの内部キャッシュと整合性が取れなくなり、slapdが起動できなくなる原因になります。設定変更は必ずldapmodifyを通して行ってください。

cn=configツリーの中身は、ldapsearchにSASL EXTERNAL認証(ローカルのUnixドメインソケット経由で、root権限があれば無条件に認証される仕組み)を使って確認できます。

$ sudo ldapsearch -Y EXTERNAL -H ldapi:/// -b cn=config "(olcDatabase=*)" dn olcSuffix olcRootDN
# 出力(抜粋)
dn: olcDatabase={-1}frontend,cn=config

dn: olcDatabase={0}config,cn=config

dn: olcDatabase={1}mdb,cn=config
olcSuffix: dc=example,dc=com
olcRootDN: cn=admin,dc=example,dc=com

この行のこの値に注目:olcDatabase={1}mdb,cn=configが実データベース本体の設定エントリです。olcSuffixがそのデータベースが担当するベースDN、olcRootDNが制限なしで操作できる管理者DNを表します。{-1}frontendと{0}configはそれぞれ全体共通のフロントエンド設定とcn=config自身の設定エントリで、通常のデータは持ちません。

ステップ1:パッケージのインストールと初期設定

$ sudo apt install slapd ldap-utils        # Debian系(インストールログは省略)
$ sudo dnf install openldap-servers openldap-clients  # RHEL系(インストールログは省略)
$ sudo dpkg-reconfigure slapd                # Debian系:対話式の初期設定ウィザード
Omit OpenLDAP server configuration? No
DNS domain name: example.com
Organization name: Example Corp
Administrator password: ********
Confirm password: ********
Database backend to use: MDB
Do you want the database to be removed when slapd is purged? No
Move old database? Yes

この行のこの値に注目: dpkg-reconfigure slapdのウィザードで入力したDNS domain name(例:example.com)から、自動的にベースDN(dc=example,dc=com)が導出されます。ここで入力した内容を後から変更するのは面倒なため、事前にドメイン名・組織名を確定させてから実行するのが安全です。

ここで確認すべきこと: dpkg-reconfigure slapdのウィザードでは、ベースDN(例:dc=example,dc=com)・組織名・管理者パスワードを聞かれます。RHEL系にはこの対話ウィザードがなく、後述のLDIF操作でolcSuffixやolcRootDNを手動設定する必要がある点が、Debian系との大きな違いです。

ステップ2:管理者パスワードのハッシュ値を生成する

設定LDIFの中に平文パスワードを書かないよう、slappasswdで事前にハッシュ化します。

$ slappasswd -h {SSHA}
New password:
Re-enter new password:
{SSHA}k3F9pQzT8mLxYh2Rj7wNc1VbAe0oGdSu

この行のこの値に注目:出力される{SSHA}で始まる文字列がそのままLDIFのolcRootPW属性の値になります。{SSHA}はソルト付きSHA-1ハッシュを表すプレフィックスです。

補足: {SSHA}は今も多くの環境でOpenLDAPの既定のハッシュ方式ですが、slappasswd -h {SSHA256}や-h {SSHA512}のように、より新しいハッシュアルゴリズムを明示的に指定することもできます。既存の環境やドキュメントでは{SSHA}表記が引き続き主流ですが、新規に構築する場合はより強いハッシュ方式の採用も検討するとよいでしょう。

ステップ3:olcRootPWを設定する(RHEL系での手動設定例)

rootpw.ldif
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcRootPW
olcRootPW: {SSHA}k3F9pQzT8mLxYh2Rj7wNc1VbAe0oGdSu
$ sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f rootpw.ldif
modifying entry "olcDatabase={1}mdb,cn=config"

ここで確認すべきこと: modifying entryという応答が返れば成功です。この操作はローカルのroot権限で認証しているため、パスワード入力を求められません。

ステップ4:設定の構文チェックとslapdの起動確認

$ sudo slaptest -u
config file testing succeeded
$ sudo systemctl enable --now slapd   # 正常時は無出力
$ sudo systemctl status slapd
● slapd.service - OpenLDAP Server Daemon
     Loaded: loaded (/lib/systemd/system/slapd.service; enabled; preset: enabled)
     Active: active (running) since Fri 2026-08-21 09:05:11 JST; 12s ago
       Docs: man:slapd
             man:slapd-config
   Main PID: 5122 (slapd)
      Tasks: 4 (limit: 4649)
     Memory: 8.9M
        CPU: 62ms
     CGroup: /system.slice/slapd.service
             └─5122 /usr/sbin/slapd -h ldap:/// ldapi:/// -g openldap -u openldap -F /etc/ldap/slapd.d

この行のこの値に注目: Active: active (running)が表示されれば、初期設定が構文的にも実行的にも正しく反映され、slapdが正常に稼働していることが分かります。Main PIDのコマンドライン(-h ldap:/// ldapi:///)から、TCPソケットとUnixドメインソケット(ldapi:///、EXTERNAL認証で使う経路)の両方で待ち受けていることが確認できます。

slaptest -uは設定を実際には適用せず、構文だけを検証するオプションです(-uなしだと実際にデータベースの起動処理まで走ります)。設定変更のたびにslaptest -uで確認してからsystemctl restart slapdする習慣をつけると、typoによる起動失敗を未然に防げます。

LDIF形式の書式ルール

LDIF(LDAP Data Interchange Format)は、LDAPのエントリをテキストで表現するための標準形式です。基本ルールを整理します。

要素意味
dn: ...対象エントリのDNを指定(すべてのレコードの先頭行)
属性名: 値属性と値をコロン+半角スペースで区切って列挙
属性名:: base64値コロン2つは値がbase64エンコードされていることを示す(非ASCII文字やバイナリを含む値で使用)
空行エントリ同士の区切り(複数エントリを1ファイルにまとめる場合)
changetype: add/modify/delete/modrdnldapmodify使用時、エントリに対して行う操作の種類を明示

changetype: modifyのエントリでは、1つのdnに対して複数の変更(追加・置換・削除)をまとめて指定できます。各サブ操作はadd: / replace: / delete:で始め、サブ操作同士は行頭に単独の-(ハイフン)を置いて区切ります。

modify.ldif(1つのdnに複数のサブ操作をまとめる例)
dn: uid=alice,ou=people,dc=example,dc=com
changetype: modify
replace: mail
mail: alice@example.com
-
add: telephoneNumber
telephoneNumber: 03-1234-5678
-
delete: description

base64エンコードが必要になる例として、日本語のような非ASCII文字を含む値を挙げます。

base64エンコードされた属性値の例
dn: uid=alice,ou=people,dc=example,dc=com
changetype: modify
replace: cn
cn:: QWxpY2Ug44Ki44Oq44K5

DNそのものを変更(リネームや移動)する場合はchangetype: modrdnを使います。

rename.ldif
dn: uid=alice,ou=people,dc=example,dc=com
changetype: modrdn
newrdn: uid=alice.example
deleteoldrdn: 1

deleteoldrdn: 1は、リネーム後に旧RDNの値(この場合uid: alice属性)を削除することを指定します。0にすると旧属性値を残したまま新しいRDNを追加します。

ディレクトリツリーを構築する

実際にベースDNからユーザー・グループのエントリまで、LDIFファイルを使って一から構築する手順を示します。

ステップ1:ベースエントリと組織単位(OU)を作成する

base.ldif
dn: dc=example,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
o: Example Corp
dc: example

dn: ou=people,dc=example,dc=com
objectClass: organizationalUnit
ou: people

dn: ou=groups,dc=example,dc=com
objectClass: organizationalUnit
ou: groups
$ ldapadd -x -D "cn=admin,dc=example,dc=com" -W -f base.ldif
Enter LDAP Password:
adding new entry "dc=example,dc=com"
adding new entry "ou=people,dc=example,dc=com"
adding new entry "ou=groups,dc=example,dc=com"

ここで確認すべきこと: adding new entryが3件分(LDIF内のエントリ数と同じ件数)表示されていれば成功です。dpkg-reconfigure slapdでベースDNを指定した場合、dc=example,dc=com自体は既に作成済みのため、その場合はou=peopleとou=groupsのみをbase.ldifに含めます。

ステップ2:ユーザーエントリを作成する

user_alice.ldif
dn: uid=alice,ou=people,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: Alice Example
sn: Example
givenName: Alice
uid: alice
uidNumber: 10001
gidNumber: 10001
homeDirectory: /home/alice
loginShell: /bin/bash
mail: alice@example.com
userPassword: {SSHA}k3F9pQzT8mLxYh2Rj7wNc1VbAe0oGdSu

inetOrgPersonだけではuidNumber・gidNumber・homeDirectoryを持てないため、UNIXアカウントとしてログインさせたい場合はposixAccount(nisスキーマ)をAUXILIARYクラスとして併用します。

ステップ3:グループエントリを作成する

group_developers.ldif
dn: cn=developers,ou=groups,dc=example,dc=com
objectClass: posixGroup
cn: developers
gidNumber: 10001
memberUid: alice
$ ldapadd -x -D "cn=admin,dc=example,dc=com" -W -f user_alice.ldif
Enter LDAP Password:
adding new entry "uid=alice,ou=people,dc=example,dc=com"
$ ldapadd -x -D "cn=admin,dc=example,dc=com" -W -f group_developers.ldif
Enter LDAP Password:
adding new entry "cn=developers,ou=groups,dc=example,dc=com"

この行のこの値に注目: adding new entry "..."の引用符内が、追加されたDNそのものです。表示されたDNが意図したもの(ou=people・ou=groupsそれぞれ配下)と一致しているかを確認します。すでに同じDNのエントリが存在する場合はldap_add: Already exists (68)のようなエラーになります。

ステップ4:登録内容を検索して確認する

$ ldapsearch -x -LLL -b "ou=people,dc=example,dc=com" "(uid=alice)"
dn: uid=alice,ou=people,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: Alice Example
sn: Example
uid: alice
uidNumber: 10001
gidNumber: 10001
homeDirectory: /home/alice

この行のこの値に注目:-LLLオプションはLDIF出力からバージョンコメントや空行を省き、簡潔な表示にします。登録したはずの属性がすべて表示されていることを確認します。表示されない属性がある場合、objectClassの指定漏れ(MUST属性を満たすobjectClassが不足している)が典型的な原因です。

LDAP検索フィルタの書き方

ldapsearchで条件を絞り込む際は、括弧で囲んだフィルタ式を使います。基本形は(属性名=値)で、ワイルドカード*による部分一致も使えます。

$ ldapsearch -x -b "dc=example,dc=com" "(uid=alice)"
# alice, people, example.com
dn: uid=alice,ou=people,dc=example,dc=com
objectClass: inetOrgPerson
uid: alice
cn: Alice Example

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1
$ ldapsearch -x -b "dc=example,dc=com" "(mail=*@example.com)"
...(省略、同様の形式でmail属性を持つ全エントリが表示される)
# numEntries: 3
$ ldapsearch -x -b "dc=example,dc=com" "(&(objectClass=inetOrgPerson)(sn=Example))"
...(省略)
# numEntries: 1

この行のこの値に注目: 各エントリの先頭にある# alice, people, example.comのような行は、コメントとして表示される簡易的なDNの要約です。末尾の# numResponses:・# numEntries:で、実際に何件のエントリがヒットしたかを一目で確認できます。numEntries: 0であれば、フィルタ条件に一致するエントリが存在しないことを意味します。

演算子意味
&AND(すべての条件に一致)
|OR(いずれかの条件に一致)
!NOT(条件に一致しないもの)

複数条件を組み合わせる場合、演算子は各条件式の外側にプレフィックスとして置く点がSQLなどと異なります(例:(&(条件1)(条件2)))。シェル上で&はバックグラウンド実行の特殊文字でもあるため、フィルタ式全体をクォートで囲む必要があります。

クライアントコマンドでエントリを操作する

検索専用のldapsearchに対し、エントリの追加・変更・削除・パスワード変更にはそれぞれ対応するコマンドを使います。

コマンド用途
ldapaddLDIFファイルの内容でエントリを新規追加(ldapmodify -aと実質同等)
ldapmodifyLDIFファイルの内容でエントリを変更・追加・削除(changetypeで動作を指定)
ldapdelete指定したDNのエントリを削除
ldappasswdユーザーのパスワードを変更
$ ldapmodify -x -D "cn=admin,dc=example,dc=com" -W -f modify.ldif
Enter LDAP Password:
modifying entry "uid=alice,ou=people,dc=example,dc=com"

$ ldapdelete -x -D "cn=admin,dc=example,dc=com" -W "uid=bob,ou=people,dc=example,dc=com"
Enter LDAP Password:

$ ldappasswd -x -D "cn=admin,dc=example,dc=com" -W -S "uid=alice,ou=people,dc=example,dc=com"
New password:
Re-enter new password:
Enter LDAP Password:
Result: Success (0)

この行のこの値に注目:ldapdeleteは成功しても標準出力に何も表示しません(沈黙は成功のサイン)。ldappasswd -Sは新パスワードを対話プロンプトで入力させるオプションで、末尾のResult: Success (0)が処理成功を表します。-DはバインドDN(操作を行う権限を持つエントリ)、-WはバインドDNのパスワードをプロンプトで入力することを指定するオプションです。

注意: ldapdeleteで親エントリ(ou=peopleなど)を子エントリが残ったまま削除しようとすると、Operation not allowed on non-leafエラーになります。木構造は葉(末端)から順に削除する必要があります。

サーバー側の管理コマンド

クライアントコマンド(ldapsearchなど)がLDAPプロトコル経由でslapdに接続するのに対し、以下のコマンドはslapdのデータベースファイルを直接操作します。

コマンド用途
slapcatデータベースの内容をLDIF形式でダンプ(バックエンドを直接読むため、slapd停止中でも実行可)
slapaddLDIFファイルの内容をデータベースへ直接ロード(実行前にslapdの停止が必要)
slapindexインデックスを再構築する(実行前にslapdの停止が必要)
slaptest設定ファイル(cn=config)の構文チェックのみを行う
$ sudo systemctl stop slapd            # 正常時は無出力
$ sudo slapcat -n 1 -l backup.ldif    # データベースの内容をLDIFにバックアップ(-lでファイル出力するため正常時は無出力)
$ sudo slapadd -n 1 -l backup.ldif    # バックアップから復元(正常時は無出力)
$ sudo slapindex -n 1                 # インデックスの再構築(正常時は無出力)
$ sudo systemctl start slapd           # 正常時は無出力
$ ls -l /var/lib/ldap/
-rw-r--r-- 1 openldap openldap 12288 Aug 20 10:00 data.mdb
-rw-r--r-- 1 openldap openldap  8192 Aug 20 10:00 lock.mdb

この行のこの値に注目:-n 1はデータベース番号(多くの構成でolcDatabase={1}mdbに対応)を指定します。既定のmdbバックエンドでは、実データがdata.mdb、ロック情報がlock.mdbという2ファイルで/var/lib/ldap/に格納されます。

注意(破壊的操作): slapaddやslapindexはデータベースファイルを直接操作するため、稼働中のslapdと同時に実行するとデータ不整合を起こす危険があります。必ずsystemctl stop slapdで停止してから実行し、実行前にはslapcatで現在の内容をバックアップしておくことが安全です。復旧が必要になった場合は、バックアップLDIFをslapaddで再ロードしてからsystemctl start slapdします。

アクセス制御(ACL)の設計

OpenLDAPのアクセス制御はolcAccess属性(cn=config方式の場合)で定義し、「どの対象に(what)」「誰が(who)」「どんな権限を持つか(access)」を上から順に評価します。権限レベルには段階があります。

権限レベル意味
noneアクセス不可
discloseエントリの存在自体は開示(エラー内容の判別に必要な最低限)
auth認証(バインド)処理にのみ使用可能
compare値の比較(一致するかどうかの検証)が可能
search検索フィルタの対象にできる
read値を読み取れる
write値を追加・変更・削除できる
manageACLの設定自体を含む完全な管理権限

この一覧は右にいくほど強い権限で、上位の権限は下位の権限を暗黙に含みます(writeを持てばreadやsearchも自動的に許可されます)。

アクセス制御の例
olcAccess: {0}to attrs=userPassword
  by self write
  by anonymous auth
  by * none
olcAccess: {1}to attrs=mail,telephoneNumber
  by self write
  by group.exact="cn=admins,ou=groups,dc=example,dc=com" write
  by * read
olcAccess: {2}to *
  by * read

この例では、まずuserPassword属性を本人(self)のみ書き込み可能とし、匿名ユーザーは認証処理(auth)にのみ使用可能、それ以外は一切アクセス不可としています。次にmailとtelephoneNumberは本人と管理者グループが書き込み可能、それ以外は読み取り可能としています。最後のto *は、それまでのルールに一致しなかった残りすべての属性に読み取りを許可する、包括的な既定ルールです。

実務での考え方: olcAccessのルールは記述された順({0}、{1}...という番号順)に評価され、最初に一致したルールが適用されてそれ以降は評価されません。そのため、より限定的で厳しいルール(userPasswordへの制限など)を必ず先に、包括的で緩いルール(to *)を後に書くのが鉄則です。この順序を逆にすると、意図せず全属性が誰でも書き込み可能になってしまうといった重大な事故につながります。

ログレベルの設定

slapdのログの詳細度はolcLogLevel属性で制御します。

値意味
0(none)ログをほぼ出力しない(既定)
256(stats)接続・操作・検索結果の統計情報のみを記録(運用時によく使う実用的なレベル)
128(acl)アクセス制御の判定過程を記録(ACL設定のデバッグ用)
-1全レベルのログを有効化
loglevel.ldif
dn: cn=config
changetype: modify
replace: olcLogLevel
olcLogLevel: stats
注意: olcLogLevel: -1は全ログを有効にするデバッグ専用の設定で、ログ量が爆発的に増加しディスク容量とI/O性能を圧迫します。原因調査が終わったら速やかにstatsやnone相当の値に戻してください。恒常的に-1のまま運用しないよう注意しましょう。

レプリケーション(syncrepl)の概要

複数のLDAPサーバー間でディレクトリ内容を同期する仕組みがsyncreplです。基本構成では、マスターとなる「プロバイダ」から、複製先の「コンシューマ」がデータを取得します。コンシューマ側のcn=configに、プロバイダの接続情報を持つolcSyncRepl属性を設定します。

コンシューマ側の設定例(概念)
dn: olcDatabase={1}mdb,cn=config
changetype: modify
add: olcSyncRepl
olcSyncRepl: rid=001
  provider=ldap://ldap-master.example.com
  bindmethod=simple
  binddn="cn=admin,dc=example,dc=com"
  credentials=adminpassword
  searchbase="dc=example,dc=com"
  type=refreshAndPersist
  retry="5 5 300 3"

type=refreshAndPersistは、初回は全体を取得(refresh)した後、以後は変更をリアルタイムに受信し続ける(persist)方式です。retryは接続失敗時の再試行間隔と回数を表します。実務では、書き込みをすべてプロバイダに集約しコンシューマは読み取り専用として使う構成が典型的で、参照が多い拠点にコンシューマを配置して負荷分散や可用性向上を図ります。

TLSによる暗号化

LDAPの通信は既定では平文です。認証情報や個人情報を扱うため、実務では必ず暗号化を有効にします。方式は2通りあります。

方式ポート特徴
LDAPS636/tcp接続開始時点からTLSで暗号化(ldaps://)
STARTTLS389/tcp平文のLDAP接続(ldap://)を確立した後、途中からTLSに昇格
TLS証明書の設定例
dn: cn=config
changetype: modify
replace: olcTLSCertificateFile
olcTLSCertificateFile: /etc/ldap/certs/server.crt
-
replace: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/ldap/certs/server.key
-
replace: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/ldap/certs/ca.crt
$ ldapsearch -x -ZZ -H ldap://ldap.example.com -b "dc=example,dc=com"
# -ZZ はSTARTTLSを要求し、失敗した場合はエラーで中断するオプション(-Zを1つだけだと失敗を無視して平文続行)
# alice, people, example.com
dn: uid=alice,ou=people,dc=example,dc=com
...(省略、以降は通常のldapsearch出力と同じ形式)

証明書検証に失敗した場合は、次のようなエラーで接続そのものが中断されます。

$ ldapsearch -x -ZZ -H ldap://ldap.example.com -b "dc=example,dc=com"
ldap_start_tls: Connect error (-11)
        additional info: TLS: hostname does not match CN in peer certificate

この行のこの値に注目: 通常のエントリと同じ出力形式で結果が返れば、STARTTLSでの暗号化に成功した上で検索も正常に完了しています。hostname does not match CN in peer certificateのようなエラーは、接続先ホスト名と証明書のCN/SANが一致していない場合に発生し、証明書の再発行か、接続先ホスト名の見直しが必要です。

クライアント側で自己署名証明書やプライベートCAを使う場合は、/etc/ldap/ldap.confのTLS_CACERTにCA証明書のパスを指定しておかないと証明書検証エラーになります。

sssdによるクライアント統合

多数のサーバーで同じユーザー一覧を使い回したい場合、各サーバーをLDAPクライアントとして設定します。PAM/NSSを直接LDAPに向けることも可能ですが、実務ではキャッシュや接続管理を仲介するsssd(System Security Services Daemon)を併用する構成が主流です。

/etc/sssd/sssd.conf の例
[sssd]
services = nss, pam
domains = example.com

[domain/example.com]
id_provider = ldap
auth_provider = ldap
chpass_provider = ldap
ldap_uri = ldap://ldap.example.com
ldap_search_base = dc=example,dc=com
ldap_tls_reqcert = demand
cache_credentials = true

cache_credentials = trueを指定しておくと、直近に認証成功したユーザーの情報をローカルにキャッシュし、LDAPサーバーへの接続が一時的に切れてもキャッシュされた情報でログインを継続できます。ldap_tls_reqcert = demandはTLS証明書の検証を必須にする設定です。sssdの設定ファイルはパーミッションに厳格で、600(所有者のみ読み書き可)になっていないとsssd自体が起動を拒否します。

$ sudo chmod 600 /etc/sssd/sssd.conf   # 正常時は無出力
$ sudo systemctl restart sssd   # 正常時は無出力
$ sudo sss_cache -E              # sssdのキャッシュを全て無効化(強制的に再取得させたい場合)(正常時は無出力)

パーミッションが600になっていない状態でsssdを起動しようとすると、次のようなエラーでサービス自体が起動を拒否します。

$ sudo systemctl status sssd
● sssd.service - System Security Services Daemon
     Active: failed (Result: exit-code) since Fri 2026-08-21 09:10:03 JST; 5s ago
    Process: 5310 ExecStart=/usr/sbin/sssd -i (code=exited, status=1/FAILURE)
sssd[5310]: Cannot read config file /etc/sssd/sssd.conf, please check permissions

この行のこの値に注目: please check permissionsという直接的なメッセージから、パーミッションの問題であることがすぐに分かります。sssdは設定ファイルに認証情報(LDAPのバインドパスワードなど)が含まれうるため、所有者以外が読める状態(644など)だと意図的に起動を拒否する安全設計になっています。

NSSの設定は/etc/nsswitch.confに記述します。

/etc/nsswitch.conf の一部
passwd:  files sss
group:   files sss
shadow:  files sss

# passwd/groupは、まずローカルファイル(/etc/passwd等)を検索し、
# 見つからなければsssd経由でLDAPに問い合わせる、という優先順位になる

getent passwd ユーザー名を実行すると、NSSの設定に従ってローカルファイルとsssd(LDAP)の両方を横断的に検索した結果を確認できるため、LDAP連携のトラブルシューティングでよく使われます。

$ getent passwd alice
alice:*:10001:10001:Alice Example:/home/alice:/bin/bash

PAMスタックにsssdを組み込む作業は、Debian系とRHEL系で手順が異なります。RHEL系ではauthselect、Debian系ではpam-auth-updateを使うのが標準的です。

$ sudo authselect select sssd --force        # RHEL系
Backup stored at /var/lib/authselect/backups/2026-08-21-09-12-30.tar
Profile "sssd" was selected.
The following nsswitch.conf lines were changed:
- passwd: sss files
- group: sss files
- shadow: sss files
Authselect is done, but current configuration may not work as expected.
$ sudo pam-auth-update                        # Debian系:ncursesの対話画面が起動し、SSSDにチェックを入れて<OK>を選ぶ

この行のこの値に注目: authselect select実行時、変更前の状態が自動的にBackup stored at ...のパスへバックアップされるため、設定を誤った場合はauthselect backup-restoreで元に戻せます。The following nsswitch.conf lines were changed:以降で、実際に/etc/nsswitch.confのどの行がどう変わったかを確認できます。pam-auth-updateはテキストベースの対話画面(ncurses)が起動するため、この章の他のコマンドのようにそのまま画面出力を貼り付けることはできませんが、SSSDの項目にチェックを入れて<OK>を選ぶ操作は変わりません。

トラブルシューティング

LDAP操作が失敗した際は、返却されるエラーコードとメッセージからおおよその原因を絞り込めます。

コードメッセージ典型的な原因
49Invalid credentialsバインドDNまたはパスワードが誤っている
32No such object指定したベースDNやエントリが存在しない(DNのタイプミスが多い)
50Insufficient accessolcAccessのACLで操作が拒否されている
65Object class violationMUST属性の不足など、objectClassの定義に反する内容を登録しようとした
$ ldapsearch -x -D "cn=admin,dc=example,dc=com" -W -b "dc=example,dc=com"
Enter LDAP Password:
ldap_bind: Invalid credentials (49)

この行のこの値に注目:末尾の(49)がエラーコードです。パスワードの打ち間違いだけでなく、バインドDNのタイプミス(cn=adminの綴りやou/dcの階層違い)でも同じエラーになります。

slapd自体のログは、多くのディストリビューションでsyslog経由か、systemdのジャーナルに出力されます。

$ sudo journalctl -u slapd --since "10 min ago"
Aug 21 09:05:11 ldap01 slapd[5122]: @(#) $OpenLDAP: slapd 2.6.7 (...)
Aug 21 09:05:11 ldap01 slapd[5122]: slapd starting
Aug 21 09:12:44 ldap01 slapd[5122]: conn=1032 fd=14 ACCEPT from IP=192.168.1.50:52310 (IP=0.0.0.0:389)
Aug 21 09:12:44 ldap01 slapd[5122]: conn=1032 op=0 BIND dn="cn=admin,dc=example,dc=com" mech=SIMPLE ssf=0
Aug 21 09:12:44 ldap01 slapd[5122]: conn=1032 op=0 RESULT tag=97 err=0 text=
$ sudo tail -f /var/log/syslog | grep slapd    # syslog経由で出力するディストリビューションの場合
Aug 21 09:12:44 ldap01 slapd[5122]: conn=1032 op=1 SRCH base="dc=example,dc=com" scope=2 filter="(uid=alice)"
Aug 21 09:12:44 ldap01 slapd[5122]: conn=1032 op=1 SEARCH RESULT tag=101 err=0 nentries=1 text=

この行のこの値に注目: BIND ... RESULT tag=97 err=0のerr=0は認証成功を意味し、0以外の数値であれば前述のエラーコード表(49、32など)に対応するトラブルが発生したことを示します。SEARCH RESULT ... nentries=1は、その検索で実際に何件のエントリがヒットしたかを表し、olcLogLevel: statsを設定していれば、このように接続(conn=)・操作(op=)ごとの詳細な流れを追跡できます。

ログが期待より少ない場合は、前述のolcLogLevelが0のままになっていないか確認します。認証まわりのトラブルは前章のPAM設定、通信経路のトラブルはネットワークトラブルシューティングの章もあわせて参照してください。

暗号化・証明書に関するより詳しい内容はHTTPS/TLSの章の考え方も応用できます。

確認クイズ

Q1. LDAPのエントリを一意に特定する識別名を何と呼びますか?

解説: DN(Distinguished Name)は、dc=example,dc=comのようなドメイン部分からou=people、uid=aliceのように階層をたどって、エントリを一意に特定する識別名です。

Q2. エントリの基本的な種類を決定する主要なobjectClassの種類はどれですか?

解説: STRUCTURALは、そのオブジェクトクラスがエントリの基本的な種類を決定する主要なクラスであることを表し、1エントリにつき実質1つが基本です。AUXILIARYは補助的な属性を追加するクラス、ABSTRACTは継承専用の抽象クラスです。

Q3. 現在のOpenLDAPで標準的に使われる、LDAPディレクトリ自体の中に設定を保持する方式を何と呼びますか?

解説: 現在の標準はslapd-config(cn=config)方式で、設定変更をldapmodifyによるLDAP操作として行えるため、多くの場合サーバーを止めずに反映できます。

Q4. LDIFのchangetype: modifyで、1つのdnに対する複数のサブ操作(add:/replace:/delete:)を区切るために行頭に置く記号はどれですか?

解説: changetype: modifyのエントリでは、各サブ操作をadd:/replace:/delete:で始め、サブ操作同士は行頭に単独のハイフン(-)を置いて区切ります。

Q5. 属性値がbase64エンコードされていることを示すLDIFの表記はどれですか?

解説: コロンを2つ重ねる(::)ことで、続く値がbase64エンコードされていることを示します。日本語などの非ASCII文字やバイナリを含む値でよく使われます。

Q6. 稼働中のslapdを止めずに実行するとデータ不整合の危険があるため、実行前にslapdの停止が必要なコマンドはどれですか?

解説: slapaddはデータベースファイルを直接操作してLDIFの内容をロードするため、稼働中のslapdと同時に実行するとデータ不整合を起こす危険があり、事前にslapdを停止する必要があります。slapcatはバックエンドを直接読むだけなので、slapd稼働中でも実行できます。

Q7. LDAP検索フィルタで、複数の条件をすべて満たすエントリを検索したい場合に使う演算子はどれですか?

解説: &(AND)は、括弧内のすべての条件に一致するエントリを検索する演算子です。演算子は各条件式の外側にプレフィックスとして置きます。

Q8. olcAccessによるアクセス制御の権限レベルのうち、read(読み取り)よりも強い権限はどれですか?

解説: 権限レベルはnone < disclose < auth < compare < search < read < write < manageの順に強くなり、上位の権限は下位の権限を暗黙に含みます。writeはreadより強い権限です。

Q9. olcAccessの複数のルールが記述されている場合、どのように評価されますか?

解説: olcAccessのルールは記述順に評価され、最初に一致したルールが適用されてそれ以降は評価されません。そのため限定的なルールを先に、包括的なルールを後に書く必要があります。

Q10. PAM/NSSを直接LDAPに向ける代わりに、キャッシュや接続管理を仲介するために実務でよく使われるデーモンはどれですか?

解説: sssd(System Security Services Daemon)は、LDAP認証情報をキャッシュし、LDAPサーバーへの接続が一時的に切れてもログインを継続できるようにする仲介デーモンです。

Q11. 次のldapsearchの実行結果に含まれるエラーコード(49)が示す原因として最も適切なものはどれですか?
ldap_bind: Invalid credentials (49)

解説: エラーコード49(Invalid credentials)は、バインドDNまたはパスワードが誤っている場合に返されます。32はNo such object、50はInsufficient accessが典型的な原因です。

Q12. ldaps://(LDAPS)が使用する既定のポート番号はどれですか?

解説: LDAPSは接続開始時点からTLSで暗号化する方式で、既定のポートは636/tcpです。389/tcpは平文LDAPおよびSTARTTLSで使われるポートです。