OpenLDAPをマルチドメインでレプリケーション

CentOS 7 で OpenLDAP サーバー2台をマルチマスタ構成でセットアップする方法です
example.com と example.net のマルチドメインで構成します
SSL / TLS 対応もさせます

## インストール

```bash
sudo yum -y install openldap-servers openldap-clients
```

## 初期化

やり直すときはここから

```bash
sudo systemctl stop slapd
sudo rm -fr /etc/openldap/slapd.d/cn=config/olcDatabase={2}hdb{,.ldif}
sudo rm -fr /etc/openldap/slapd.d/cn=config/olcDatabase={3}hdb{,.ldif}
sudo rm -fr /var/lib/ldap
sudo install -d -o ldap -g ldap -m 0700 /var/lib/ldap
sudo install -d -o ldap -g ldap -m 0700 /var/lib/ldap/example.com
sudo install -d -o ldap -g ldap -m 0700 /var/lib/ldap/example.net
sudo systemctl start slapd
```

## デフォルトの URI を設定

今後の作業を楽にするため、デフォルトの URI を設定します
`/etc/openldap/ldap.conf` に

```
URL ldapi://
```

を設定します。この URI でアクセスすれば root ならなんでもできます。

## LogLevel 設定

```bash
sudo ldapmodify <<__EOD__
dn: cn=config
changetype: modify
replace: olcLogLevel
olcLogLevel: config stats sync conns
__EOD__
```

systemd でのログ確認は `journalctl -aru slapd | less` あたりで。

## my-domain.com の修正

yum でインストールした場合のサンプル設定が my-domain.com なので example.com に変更

```bash
sudo ldapmodify <<__EOD__
dn: olcDatabase={1}monitor,cn=config
changetype: modify
replace: olcAccess
olcAccess: to * by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external
 ,cn=auth" read by dn.base="cn=Manager,dc=example,dc=com" read by * none
__EOD__
```

確認

```
sudo ldapsearch -b cn=config olcDatabase=monitor
```

## スキーマのインポート

```bash
sudo ldapadd -f /etc/openldap/schema/cosine.ldif
sudo ldapadd -f /etc/openldap/schema/inetorgperson.ldif
```

posix アカウントなど必要であればそれ (nis.ldif ?) なども

これは /etc/openldap/slapd.d/cn=config/cn=schema/ に保存されるので /var/lib/ldap/* の削除では消えません

## example.com, example.net 用のデータベース作成

```bash
sudo ldapadd <<__EOD__
dn: olcDatabase={2}hdb,cn=config
objectClass: olcDatabaseConfig
objectClass: olcHdbConfig
olcDatabase: {2}hdb
olcDbDirectory: /var/lib/ldap/example.com
olcSuffix: dc=example,dc=com
olcRootDN: cn=Manager,dc=example,dc=com
olcAccess: to * by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external
 ,cn=auth" manage by * none
olcAccess: to dn.subtree="" by * read
__EOD__
```

```bash
sudo ldapadd <<__EOD__
dn: olcDatabase={3}hdb,cn=config
objectClass: olcDatabaseConfig
objectClass: olcHdbConfig
olcDatabase: {3}hdb
olcDbDirectory: /var/lib/ldap/example.net
olcSuffix: dc=example,dc=net
olcRootDN: cn=Manager,dc=example,dc=net
olcAccess: to * by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external
 ,cn=auth" manage by * none
olcAccess: to dn.subtree="" by * read
__EOD__
```

olcDatabase={2}hdb, olcDatabase={3}hdb と数字部分を明示するところとドメインごとに別のディレクトリ (olcDbDirectory) を指定するところがポイント

## データベースのチューニング

値はなんとなくです

```bash
sudo ldapmodify <<__EOD__
dn: olcDatabase={2}hdb,cn=config
changetype: modify
add: olcDbConfig
olcDbConfig: set_cachesize 0 67108864 1
olcDbConfig: set_lg_dir .
olcDbConfig: set_lg_bsize 33554432
olcDbConfig: set_lk_max_objects 3000
olcDbConfig: set_lk_max_locks 3000
olcDbConfig: set_lk_max_lockers 3000
olcDbConfig: set_flags DB_LOG_AUTOREMOVE
__EOD__
```

```bash
sudo ldapmodify <<__EOD__
dn: olcDatabase={3}hdb,cn=config
changetype: modify
add: olcDbConfig
olcDbConfig: set_cachesize 0 67108864 1
olcDbConfig: set_lg_dir .
olcDbConfig: set_lg_bsize 33554432
olcDbConfig: set_lk_max_objects 3000
olcDbConfig: set_lk_max_locks 3000
olcDbConfig: set_lk_max_lockers 3000
olcDbConfig: set_flags DB_LOG_AUTOREMOVE
__EOD__
```

## ドメイン作成と RootDN アカウント作成

この例のパスワードは `2h6R&9QE-6`

```bash
sudo ldapadd <<__EOD__
dn: dc=example,dc=com
dc: example
o: "Example, Inc."
objectClass: dcObject
objectClass: organization

dn: cn=Manager,dc=example,dc=com
objectClass: organizationalRole
objectClass: simpleSecurityObject
cn: Manager
userPassword: {SSHA}T6fIJME1FHwKEuS9MG7BBhYU6TYLGRnX
__EOD__
```

```bash
sudo ldapadd <<__EOD__
dn: dc=example,dc=net
dc: example
o: "Example, ltd."
objectClass: dcObject
objectClass: organization

dn: cn=Manager,dc=example,dc=net
objectClass: organizationalRole
objectClass: simpleSecurityObject
cn: Manager
userPassword: {SSHA}T6fIJME1FHwKEuS9MG7BBhYU6TYLGRnX
__EOD__
```

`{SSHA}` で始まるパスワードのハッシュ値は `slappasswd` コマンドで生成できます

```bash
$ slappasswd
New password: パスワード
Re-enter new password: パスワード
{SSHA}T6fIJME1FHwKEuS9MG7BBhYU6TYLGRnX
```

## OU 作成

とりあえず People と Group を作成します。(People に対応するのは Groups じゃないのか？と思いつつ)

```bash
sudo ldapadd <<__EOD__
dn: ou=People,dc=example,dc=com
ou: People
objectClass: organizationalUnit

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

dn: ou=People,dc=example,dc=net
ou: People
objectClass: organizationalUnit

dn: ou=Group,dc=example,dc=net
ou: Group
objectClass: organizationalUnit
__EOD__
```

## ACL 設定

```bash
sudo ldapmodify <<__EOD__
dn: olcDatabase={2}hdb,cn=config
replace: olcAccess
olcAccess: to * by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external
 ,cn=auth" manage by * break
olcAccess: to attrs=userPassword
           by anonymous auth
           by self write
           by dn.exact="cn=Replication,dc=example,dc=com" read
           by dn.regex="uid=(user001|user002|user003),ou=People,dc=example,dc=com" write
           by group/groupOfUniqueNames/uniqueMember.exact="cn=Administrators,ou=Groups,dc=example,dc=com" write
           by * none
olcAccess: to *
           by dn.regex="uid=(user001|user002|user003),ou=People,dc=example,dc=com" write
           by group/groupOfUniqueNames/uniqueMember.exact="cn=Administrators,ou=Groups,dc=example,dc=com" write
           by * read
__EOD__
```

```bash
sudo ldapmodify <<__EOD__
dn: olcDatabase={3}hdb,cn=config
replace: olcAccess
olcAccess: to * by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external
 ,cn=auth" manage by * break
olcAccess: to attrs=userPassword
           by anonymous auth
           by self write
           by dn.exact="cn=Replication,dc=example,dc=net" read
           by dn.regex="uid=(user001|user002|user003),ou=People,dc=example,dc=net" write
           by group/groupOfUniqueNames/uniqueMember.exact="cn=Administrators,ou=Groups,dc=example,dc=net" write
           by * none
olcAccess: to *
           by dn.regex="uid=(user001|user002|user003),ou=People,dc=example,dc=net" write
           by group/groupOfUniqueNames/uniqueMember.exact="cn=Administrators,ou=Groups,dc=example,dc=net" write
           by * read
__EOD__
```

* user001, user002, user003 もしくは Administrators グループに所属するメンバーは各エントリの書き換えや新規エントリの追加が可能
* パスワード以外の参照であれば誰でも可能
* パスワードは認証での利用か、本人が認証後に変更することは可能
* Replication (後で設定する) 用ユーザーはパスワードを参照可能

## 認証テスト

ここらで認証のテストをしてみます

```bash
ldapsearch -x -D "cn=Manager,dc=example,dc=com" -H ldap://localhost/ -W
```

```bash
ldapsearch -x -D "cn=Manager,dc=example,dc=net" -H ldap://localhost/ -W
```

どちらでも認証できたでしょうか


## 一意性制約

メールドレスや uid の重複登録を許容しないように Unique 制約をつけます

```bash
sudo ldapadd <<__EOD__
dn: cn=module,cn=config
cn: module
objectClass: olcModuleList
olcModulePath: /usr/lib64/openldap/
olcModuleLoad: unique.la
__EOD__
```

モジュールの定義は `/etc/openldap/slapd.d/cn=config/cn=module{N}.ldif` に書かれるので `/var/lib/ldap/*` を消しても残ります

```bash
sudo ldapadd <<__EOD__
dn: olcOverlay=unique,olcDatabase={2}hdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcUniqueConfig
olcOverlay: unique
olcUniqueUri: ldap:///ou=People,dc=example,dc=com?uid?sub
olcUniqueUri: ldap:///ou=People,dc=example,dc=com?mail?sub
__EOD__
```

```bash
sudo ldapadd <<__EOD__
dn: olcOverlay=unique,olcDatabase={3}hdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcUniqueConfig
olcOverlay: unique
olcUniqueUri: ldap:///ou=People,dc=example,dc=net?uid?sub
olcUniqueUri: ldap:///ou=People,dc=example,dc=net?mail?sub
__EOD__
```

## インデックス作成

性能向上のためインデックスを作成します。レプリケーション時にも使われます。

```bash
sudo ldapmodify <<__EOD__
dn: olcDatabase={2}hdb,cn=config
changetype: modify
add: olcDbIndex
olcDbIndex: uid pres,eq,sub
-
add: olcDbIndex
olcDbIndex: ou pres,eq,sub
-
add: olcDbIndex
olcDbIndex: mail pres,eq,sub
-
add: olcDbIndex
olcDbIndex: objectClass eq
-
add: olcDbIndex
olcDbIndex: entryCSN eq
-
add: olcDbIndex
olcDbIndex: entryUUID eq
__EOD__

sudo ldapmodify <<__EOD__
dn: olcDatabase={3}hdb,cn=config
changetype: modify
add: olcDbIndex
olcDbIndex: uid pres,eq,sub
-
add: olcDbIndex
olcDbIndex: ou pres,eq,sub
-
add: olcDbIndex
olcDbIndex: mail pres,eq,sub
-
add: olcDbIndex
olcDbIndex: objectClass eq
-
add: olcDbIndex
olcDbIndex: entryCSN eq
-
add: olcDbIndex
olcDbIndex: entryUUID eq
__EOD__
```

## SSL / TLS 設定

### 証明書設定

Mozilla の Network Security Services (NSS) ツールを使う例があったりするのですが、手持ちの PEM を使う方法がよくわからなかったため、PEM ファイルをそのまま使います。

certutil で作られているファイルを削除

```bash
sudo rm /etc/openldap/certs/*
```

手持ちの証明書がなければ自己署名の証明書を作成

```bash
sudo openssl req -x509 -days 3650 -out /etc/openldap/certs/server.crt \
   -newkey rsa:2048 -keyout /etc/openldap/certs/server.key -nodes \
   -subj "/C=JP/ST=Tokyo/L=Minato-ku/O=Example, Inc./CN=ldap.example.com/emailAddress=test@example.com"
```

`/etc/openldap/certs/ca.crt` に中間証明書を `/etc/openldap/certs/server.crt` にサーバー証明書を `/etc/openldap/certs/server.key` に秘密鍵を置きます。
秘密鍵は ldap ユーザーだけが読めれば良いので

```bash
sudo chown ldap:ldap /etc/openldap/certs/server.key
sudo chmod 400 /etc/openldap/certs/server.key
```

```bash
sudo ldapmodify <<__EOD__
dn: cn=config
changetype: modify
delete: olcTLSCACertificatePath
-
replace: olcTLSCertificateFile
olcTLSCertificateFile: /etc/openldap/certs/server.crt
-
replace: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/openldap/certs/server.key
-
replace: olcTLSProtocolMin
olcTLSProtocolMin: 3.1
__EOD__
```

中間証明書が必要な場合はこちらも

```bash
sudo ldapmodify <<__EOD__
dn: cn=config
changetype: modify
replace: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/openldap/certs/ca.crt
__EOD__
```

確認

```bash
sudo ldapsearch -b cn=config objectClass=olcGlobal
```

PEM ファイルを更新したら slapd の再起動が必要です

が、その前に ldaps (636) ポートを Listen するように `/etc/sysconfig/slapd` を書き換えます
(SLAP_URLS に ldaps:/// を追加)

```bash
sudo sed -i -e 's#SLAPD_URLS="ldapi:/// ldap:///"#SLAPD_URLS="ldapi:/// ldap:/// ldaps:///"#' /etc/sysconfig/slapd
```

## slapd の再起動

```bash
sudo systemctl restart slapd
```

クライアント側の設定となりますが `/etc/openldap/ldap.conf` で `TLS_REQCERT allow` と指定しない場合、ldapsearch コマンドなどでいわゆるオレオレ証明書（自己署名）サイトなどにアクセスできなくなります。
`TLS_CACERT /etc/pki/tls/certs/ca-bundle.crt` としておくと一般的な証明機関で発行されたサーバーへの接続が可能となります。

### テスト

```bash
ldapsearch -x -H ldaps://localhost/ -W -D cn=Manager,dc=example,dc=com -b dc=example,dc=com cn=Manager
```

```bash
openssl s_client -connect localhost:636
```

openssl コマンドで接続できるのに ldapsearch で接続できない場合は `ldap.conf` の `TLS_REQCERT allow` を確認

## レプリケーション設定

### ServerID 設定

ldap1 と ldap2 というサーバーでレプリケーションするとします
`olcServerID` をサーバーごとに一意に設定します

**ldap1** で

```bash
sudo ldapmodify <<__EOD__
dn: cn=config
changetype: modify
replace: olcServerID
olcServerID: 1
__EOD__
```

**ldap2** で

```bash
sudo ldapmodify <<__EOD__
dn: cn=config
changetype: modify
replace: olcServerID
olcServerID: 2
__EOD__
```

を実行します

### レプリケーション用ユーザー作成

ACL 設定で既に出ていますが Replication というユーザー(?)を作成します
レプリケーションは参照さえできれば良いため RootDN (Manager) とは別にユーザーを作成します

この例のパスワードは `YB)gNF!Q6)`

```bash
sudo ldapadd <<__EOD__
dn: cn=Replication,dc=example,dc=com
objectClass: organizationalRole
objectClass: simpleSecurityObject
cn: Replication
userPassword: {SSHA}bSU9ueMZ93IIlXB7KdiiLby9S0bF0C+J
__EOD__

sudo ldapadd <<__EOD__
dn: cn=Replication,dc=example,dc=net
objectClass: organizationalRole
objectClass: simpleSecurityObject
cn: Replication
userPassword: {SSHA}bSU9ueMZ93IIlXB7KdiiLby9S0bF0C+J
__EOD__
```

パスワード変更は次のようにして行えます (-S はパスワード入力プロンプト表示の指定)

```bash
sudo ldappasswd -S cn=Replication,dc=example,dc=com
```

### レプリケーションモジュール登録

```bash
sudo ldapadd <<__EOD__
dn: cn=module,cn=config
objectClass: olcModuleList
cn: module
olcmodulePath: /usr/lib64/openldap
olcModuleLoad: syncprov.la
__EOD__
```

一意性(Unique)モジュールと同様に `/etc/openldap/slapd.d/cn=config/cn=module{N}.ldif` に書かれます

### プロバイダ側設定

```bash
sudo ldapadd <<__EOD__
dn: olcOverlay=syncprov,olcDatabase={2}hdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcSyncProvConfig
olcOverlay: syncprov
olcSpSessionLog: 1000
__EOD__

sudo ldapadd <<__EOD__
dn: olcOverlay=syncprov,olcDatabase={3}hdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcSyncProvConfig
olcOverlay: syncprov
olcSpSessionLog: 1000
__EOD__
```

### コンシューマ側設定

双方のサーバーでもう一方のサーバーと同期するように設定する
双方向のため(?) oldMirrorMode を有効にします

```bash
sudo ldapmodify <<__EOD__
dn: olcDatabase={2}hdb,cn=config
changetype: modify
add: olcSyncRepl
olcSyncRepl: rid=001
  provider=ldap://{相方サーバー}/
  bindmethod=simple
  binddn="cn=Replication,dc=example,dc=com"
  credentials="レプリケーションユーザーのパスワード"
  type=refreshAndPersist
  interval=00:00:05:00
  searchbase="dc=example,dc=com"
  scope=sub
  retry="5 10 30 +
__EOD__

sudo ldapmodify <<__EOD__
dn: olcDatabase={3}hdb,cn=config
changetype: modify
add: olcSyncRepl
olcSyncRepl: rid=002
  provider=ldap://{相方サーバー}/
  bindmethod=simple
  binddn="cn=Replication,dc=example,dc=net"
  credentials="レプリケーションユーザーのパスワード"
  type=refreshAndPersist
  interval=00:00:05:00
  searchbase="dc=example,dc=net"
  scope=sub
  retry="5 10 30 +
__EOD__

sudo ldapmodify <<__EOD__
dn: olcDatabase={2}hdb,cn=config
changetype: modify
add: olcMirrorMode
olcMirrorMode: TRUE
__EOD__

sudo ldapmodify <<__EOD__
dn: olcDatabase={3}hdb,cn=config
changetype: modify
add: olcMirrorMode
olcMirrorMode: TRUE
__EOD__
```

## ユーザー、グループを登録してみる

```bash
sudo ldapadd <<__EOD__
dn: uid=user01,ou=People,dc=example,dc=com
objectClass: person
objectClass: inetorgperson
objectClass: organizationalperson
objectClass: top
userPassword: {SSHA}OLoVlITmijyBebCFt8f9UfC+w3ikhDTK
mail: user01@example.com
givenName: Taro
uid: user01
cn: Taro Yamada
sn: Yamada

dn: cn=group01,ou=Group,dc=example,dc=com
objectClass: groupOfUniqueNames
objectClass: top
description:: dGVzdCBncm91cAo=
cn: group01
uniqueMember: uid=user01,ou=People,dc=example,dc=com
__EOD__
```

2台のサーバーで検索してレプリケーションがうまく動作しているかを確認する

```bash
ldapsearch -b dc=example,dc=com uid=user01
ldapsearch -b dc=example,dc=com cn=group01
```

レプリケーションがうまく動作していなかったら firewalld の設定を確認してみましょう

```bash
sudo firewall-cmd --list-all
```

`services` に ldap, ldaps が入っていなかったら追加します

```bash
sudo firewall-cmd --add-service=ldap
sudo firewall-cmd --add-service=ldap --permanent
sudo firewall-cmd --add-service=ldaps
sudo firewall-cmd --add-service=ldaps --permanent
```

ログも確認

```bash
sudo journalctl -aru slapd | less
```

## サイズ制限

変える必要はないかもしれないが、検索で全件表示させようと思っても制限にかかって一部しか出力されないので制限を緩和する

```bash
sudo ldapmodify <<__EOD__
dn: cn=config
changetype: modify
replace: olcSizeLimit
olcSizeLimit: 3000
__EOD__
```

## phpLDAPadmin の設定

[phpLDAPadmin](http://phpldapadmin.sourceforge.net/wiki/index.php/Main_Page) は EPEL リポジトリからインストールできます

```bash
sudo yum -y install epel-release
sudo yum -y install phpldapadmin
```

設定ファイルが `/etc/phpldapadmin/config.php` にあります

### base 指定

マルチドメイン環境で利用する場合それぞれの base を指定する必要があります

```php
$servers->setValue('server','base',array('dc=example,dc=com', 'dc=example,dc=net'));
```

### 不要な警告の抑制

期待するスキーマが無いと警告が表示されてちょっとうるさいので表示しないようにします

```php
$config->custom->appearance['hide_template_warning'] = true;
```

### ログインID設定

uid だけでログインするには次のようにしますが、両方のドメインに存在する uid の場合にどちらかのドメインでしかログインできません

```php
$servers->setValue('login','attr','uid');
$servers->setValue('login','base',array('ou=people,dc=example,dc=com', 'ou=people,dc=example,dc=net'));
```

そのため dn で ID 指定する必要があります
ID に uid=user01,ou=people,dc=example,dc=com と入力します

```php
$servers->setValue('login','attr','dn');
```

### 一意性制約

OpenLDAP とは別に Unique 制約の設定があります。デフォルトで `mail`, `uid`, `uidNumber` に制約がかかっていますが、ドメインをまたいでの制約となっており、`uid=user01,ou=people,dc=example,dc=com` が存在すると `uid=user01,ou=people,dc=example,dc=net` が登録できません。そこでこの設定を `mail` だけに変更します。

```php
$servers->setValue('unique','attrs',array('mail'));
```

### アクセス制限

yum で phpldapadmin をインストールした場合、デフォルトでは localhost からしかアクセスできないようになっているため `/etc/httpd/conf.d/phpldapadmin.conf` を編集する必要があります

## おまけ

OpenDJ という LDAP サーバーを非特権ユーザーで使っていたため、既存サーバーは 1389/tcp と 1636/tcp を Listen しています。
これをスムーズに移行させるため iptables (firewalld) に redirect の設定をしてみます。

```xml:/etc/firewalld/direct.xml
<?xml version="1.0" encoding="utf-8"?>
<direct>
    <rule ipv="ipv4" table="nat" chain="PREROUTING" priority="0">-p tcp --dport 1389 -j REDIRECT --to-port 389</rule>
    <rule ipv="ipv4" table="nat" chain="PREROUTING" priority="0">-p tcp --dport 1636 -j REDIRECT --to-port 636</rule>
</direct>
```

localhost からのアクセスでは OUTPUT に書かないとダメらしいですが、外部からのアクセスしかないのでこれだけで良いでしょう。

* [LDAPサーバーOpenDJを使おう(1)](http://qiita.com/yteraoka/items/289ae6a1194f96c30db5)
* [LDAPサーバーOpenDJを使おう(2)](http://qiita.com/yteraoka/items/bac7cabb9b98eee666ef)