Webアプリケーションの認証動作テスト用に Docker で openldap を立てみる

# 背景

Webアプリケーションで認証を行う際、外部のプロバイダと連携してログインできるようにする機会が増えてきた。
Google OAuth 2.0 等、OAuth を使う機会もあるが、社内等では LDAP を使う機会も多いと思われる。

Webアプリケーション開発時に手軽に本番 LDAP サーバを使うのは困難だと思われるので、Docker で使い捨てられるテスト用の openldap サーバを立ててみることにした。

また、Webアプリケーション例として Rocket.Chat を立て、LDAP サーバを立てた後に GUI により LDAP サーバ操作が出来る phpLDAPadmin も管理の利便性から立ててみる。


Docker が動作する環境であること、Webアプリケーションも Docker コンテナで動作することが前提となる。
※ Docker を動作させる環境の構築方法は [Vagrant （プロバイダは Virtual Box） と Ubuntu で始める Docker / Docker Compose 入門](https://qiita.com/tatsurou313/items/591b361954f80a129215) に記載。

# 事前準備

Vagrant を使って Docker を起動している場合は Vagrantfile でポートフォワードしておくこと。（設定値は↓）

```
config.vm.network "forwarded_port", guest: 3000, host: 3000
config.vm.network "forwarded_port", guest: 80, host: 80
```

# Docker コンテナで LDAP サーバを起動する

今回、LDAP サーバ用 Docker コンテナは [DockerHub - osixia/openldap](https://hub.docker.com/r/osixia/openldap/) で提供されているイメージを利用した。
（Docker Hub で Pull 数 +5M, GitHub Star 700超）'17/12/15時点

```sh:osixia/openldapを起動する
$ docker run -d \
  --env "LDAP_DOMAIN=localhost" \
  --env "LDAP_ADMIN_PASSWORD=CAHNGEME" \
  --name openldap \
osixia/openldap
```

|変数名|意味|
| --- | --- |
|LDAP_DOMAIN|LDAP サーバのドメイン名。BINDDN を指定しない場合は dc=${LDAP_DOMAIN} となる|
|LDAP_ADMIN_PASSWORD|LDAP管理者ユーザ（cn=admin）のパスワード|

他に利用できる変数や、コンテナの利用方法の詳細は [GitHub - osixia/openldap](https://github.com/osixia/docker-openldap) を参照のこと。

# LDAP ユーザを作成する (ldapphpadminを利用)

1. osixia/ldapphpadminを起動する

```sh:osixia/ldapphpadminを起動する
$ docker run -d \
  --env "PHPLDAPADMIN_LDAP_HOSTS"="openldap" \
  --env "PHPLDAPADMIN_HTTPS"="false" \
  --link openldap \
  --name phpldapadmin \
  -p 80:80 \
osixia/phpldapadmin
```

1. ブラウザにて http://localhost へアクセスして phpLDAPadmin の管理ユーザでログインする
    - Login DN：cn=admin,dc=localhost
    - パスワード：＜ openldap起動時の${LDAP_ADMIN_PASSWORD} ＞
1. Userグループが無ければ以下に従って作成する（あれば 4. に進む）
1. ou=Users (RDN=ou, objectClass=organizationalUnit) を作成する
    1. dc=localhost と同レベルの最下部項目 **Create new entry here** をクリックする
    1. Templates に **Default** を選択する
    1. ObjectClasses に **organizationalUnit** を 1 つだけ選択する
    1. RDN に **ou** を選択し、ou に **'Users'** と入力する
    1. **Create Object** ボタン -> **Commit** ボタンを押す
1. uid=usernameXX,ou=Users (RDN=uid, objectClass=inetOrgPerson&&posixAccount) のユーザを作成する
    1. ou=Users と同レベルの最下部項目 **Create new entry here** をクリックする
    1. Templates に **Default** を選択する
    1. ObjectClasses に **inetOrgPerson** と **posixAccount** の 2 つ選択する
    1. RDN に **uid** を選択し、必須項目と Email, パスワードを入力する（下記は例）
        - cn: 'LdapUser01'
        - gidNumber: 10001
        - homeDirectory: '/home/ldapuser01'
        - sn: 'ldapuser'
        - uidNumber: 10001
        - User Name: 'ldapuser01'
        - Email: 'ldapuser01@localhost.localdomain'
        - Password: 'hogehoge'
            - 暗号化方式は ssha, md5 等
    1. **Create Object** ボタン -> **Commit** ボタンを押す

# Rocket.Chat を起動する

Web アプリケーションとして Rocket.Chat を使ってみる。
（Rocket.Chat は OSS の Slack ライクなチャットアプリケーション）

```sh:Rocket.Chatを起動する
$ docker run --name db -d mongo:3.0 --smallfiles
$ docker run -d \
  --env ROOT_URL=http://localhost \
  --name rocketchat \
  --link db \
  --link openldap \
  -p 3000:3000 \
  rocket.chat
```

Rocket.Chat の起動方法についての詳細は [Docker Hub - Rocket.Chat](https://hub.docker.com/_/rocket.chat/) を参照のこと。

# Rocket.Chat の認証方法を LDAP に設定する

1. ブラウザにて http://localhost:3000 へアクセスして Rocket.Chat の管理ユーザを作成する。
（アカウント、パスワードは適宜設定する）
1. 管理ユーザでログインしたら左ペインの最上部「：」をクリックしてプルダウンメニューから「管理」を選択する
1. 検索ボックスに "LDAP" と入力して結果に出てきた「LDAP」を選択
1. 次の設定内容を入力して LDAP を有効にする

|項目名|値|
|-----|---|
|（LDAP）||
|有効にする|はい|
|ホスト|openldap|
|ポート|389|
|Base DN|ou=Users,dc=localhost|
|（LDAP - Authentication）||
|Enable|はい|
|User DN|cn=admin,dc=localhost|
|Password|hogehoge|
|（LDAP - Sync / Import）||
|ユーザ名フィールド|uid<br>※エンティティ内に指定した属性が見つからない場合は、初ログイン時にユーザ名を指定できる<br>![image.png](https://qiita-image-store.s3.amazonaws.com/0/206493/28b48f6e-716f-9b8a-b565-a7ba7425e6f6.png)|
|（LDAP - User Search）||
|Search Field|uid|



変更を保存してから接続テストして「成功」と表示されれば OK。

一度管理ユーザをログアウトして LDAP ユーザでログインしてみる。

# 作成した LDAP ユーザでログインする

例で利用したユーザの場合、次の情報でログインする。

- アカウント：ldapuser01
- パスワード：hogehoge

ログイン成功すると LDAP 情報を元に Rocket.Chat にユーザが作成される。
（Rocket.Chat の管理ユーザで確認）

![image.png](https://qiita-image-store.s3.amazonaws.com/0/206493/9b60b040-6c29-59ad-ff1f-ab9424f47503.png)


# LDAP ログの見方

```
5a3630de conn=1088 fd=16 ACCEPT from IP=172.17.0.3:50740 (IP=0.0.0.0:389)
5a3630de conn=1088 op=0 BIND dn="cn=admin,dc=localhost" method=128
5a3630de conn=1088 op=0 BIND dn="cn=admin,dc=localhost" mech=SIMPLE ssf=0
5a3630de conn=1088 op=0 RESULT tag=97 err=0 text=
5a3630de conn=1088 op=1 SRCH base="ou=Users,dc=localhost" scope=2 deref=0 filter="(&(objectClass=*)(uid=ldapuser01))"
5a3630de conn=1088 op=1 SEARCH RESULT tag=101 err=0 nentries=1 text=
5a3630de conn=1088 op=2 BIND anonymous mech=implicit ssf=0
5a3630de conn=1088 op=2 BIND dn="uid=ldapuser01,ou=Users,dc=localhost" method=128
5a3630de conn=1088 op=2 BIND dn="uid=ldapuser01,ou=Users,dc=localhost" mech=SIMPLE ssf=0
5a3630de conn=1088 op=2 RESULT tag=97 err=0 text=
5a3630df conn=1088 op=3 UNBIND
5a3630df conn=1088 fd=16 closed
```

|項目|説明|
|---|---|
|>全般||
|conn|コネクション番号。LDAP接続開始から終了までを1コネクションとして付けられる番号|
|op|オペレーション番号。コネクション内において1回の操作（BIND, SEARCH, UNBIND 等）に対して付けられる番号|
|>BIND||
|dn|認証対象となるユーザのエントリ|
|method|認証方式<br>128 ... 簡易認証<br>163 ... SASL認証|
|mech|SASL認証<br>SIMPLE ... 簡易認証<br>DIGEST-MD5 ... SASL認証でDIGEST-MD5メカニズムを利用<br>CRAM-MD5 ... SASL認証でCRAM-MD5メカニズムを利用<br>implicit ... 既存コネクションがある場合に、再 BIND 要求を受けた時に記録される。 (ref: [OpenLDAP - Re: dn="" and anonymous](https://www.openldap.org/lists/openldap-software/200707/msg00422.html))|
|ssf|暗号強度<br>0 ... 簡易認証<br>128 ... DIGEST-MD5|
|anonymous|既存コネクションがある場合に、再 BIND 要求を受けた時に記録される。 (ref: [OpenLDAP - Re: dn="" and anonymous](https://www.openldap.org/lists/openldap-software/200707/msg00422.html))|
|>SRCH||
|base|検索開始エントリ|
|scope|検索対象となる範囲<br>0 ... base (ベースのみ)<br>1 ... one (検索ベース以下1レベル)<br>2 ... sub (検索ベース以下全て) （デフォルト）<br>3 ... children (検索ベース自身を除く検索ベース以下全て)|
|deref|エイリアス利用是非<br>0 ... never (常にエイリアス解決を行わない) （デフォルト） <br>1 ... search (検索の時はエイリアス解決を行う)<br>2 ... find (検索ベースエントリに位置した時のみ参照/解決を行う)<br>3 ... always (常にエイリアス解決を行う)|
|attr|検索結果として受け取る属性<br>* ... 全てのユーザ属性<br>+ ... 全ての運用属性<br>1.1 ... 全属性を受け取らない<br>(値なし) ... 全てのユーザ属性|
|filter|検索フィルタ<br>(objectClass=*) ... 全エントリにマッチするフィルタ<br>|
|>RESULT||
|tag|オペレーションに対応する結果種別<br>97 ... BIND<br>101 ... SEARCH RESULT<br>103 ... MOD<br>105 ... ADD<br>107 ... DEL|
|err|エラー内容<br>0 ... 成功<br>4 ... Size limit exceeded<br>32 ... No such object<br>49 ... Invalid credentials|
|nentries|検索にマッチしたエントリ数|
|text|結果を示すテキスト（と思われる）<br>一部のエラーが発生した場合にテキストが出力されるところからの推測。|

コネクションフロー

1. ACCEPT
1. BIND
1. { SRCH / MOD / ADD / DEL }
1. UNBIND
1. closed

BIND は LDAP における認証を示す。DN では認証対象のユーザを指定する。
BIND が完了した後、この DN で指定したユーザに対して許可された操作が可能になる。
特定のユーザを指定しない匿名認証も可能。その場合は dn="" となる。

# Rocket.Chat ログの見方

1. ブラウザにて http://localhost:3000 へアクセスして Rocket.Chat の管理ユーザでログインする
1. 管理ユーザでログインしたら左ペインの最上部「：」をクリックしてプルダウンメニューから「管理」を選択する
1. 左ペインの「ログを表示」を選択する

# トラブルシューティング

## LDAP サーバ側にメールアドレスが未登録であった場合の LDAP 認証失敗

- Rocket.Chat のログイン画面時のエラーメッセージ
    - 「ユーザが見付からないか、パスワードが間違っています」
    - ![image.png](https://qiita-image-store.s3.amazonaws.com/0/206493/7d7063e4-6969-0463-7753-9a0e062c0fe1.png)

- Rocket.Chat のログ

```
[34mI20171217-10:44:24.733(0) rocketchat_logger rocketchat_logger.js:375 [31mLDAPSync ➔ error { [Error: LDAP Authentication succeded, there is no email to create an account. Have you tried setting your Default Domain in LDAP Settings? [LDAP-login-error]]   isClientSafe: true,   error: 'LDAP-login-error',   reason: 'LDAP Authentication succeded, there is no email to create an account. Have you tried setting your Default Domain in LDAP Settings?',   details: undefined,   message: 'LDAP Authentication succeded, there is no email to create an account. Have you tried setting your Default Domain in LDAP Settings? [LDAP-login-error]',   errorType: 'Meteor.Error' } 
[34mI20171217-10:46:10.552(0) rocketchat_logger rocketchat_logger.js:375 [31mLDAPHandler ➔ error [Error: User not Found]
```

- 原因
    - LDAP ユーザにメールアドレスが未登録のため、Rocket.Chat 側のアカウント作成に失敗
- 対策
    - LDAP ユーザにメールアドレス登録する

# 参考情報
- LDAP サーバの構築について
    - [OpenLDAPでユーザー認証までやってみる](https://qiita.com/toshihirock/items/fc6ba5b6be8040e69ac9)
- LDAP ログの見方について
    - [ものいわぬOpenLDAPサーバのログ管理・その2 (3/3)](http://www.atmarkit.co.jp/ait/articles/1004/15/news104_3.html)