LDAP
openldap
docker

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 入門 に記載。

事前準備

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 で提供されているイメージを利用した。
(Docker Hub で Pull 数 +5M, GitHub Star 700超)'17/12/15時点

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 を参照のこと。

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

  1. osixia/ldapphpadminを起動する
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} >
  2. Userグループが無ければ以下に従って作成する(あれば 4. に進む)
  3. ou=Users (RDN=ou, objectClass=organizationalUnit) を作成する
    1. dc=localhost と同レベルの最下部項目 Create new entry here をクリックする
    2. Templates に Default を選択する
    3. ObjectClasses に organizationalUnit を 1 つだけ選択する
    4. RDN に ou を選択し、ou に 'Users' と入力する
    5. Create Object ボタン -> Commit ボタンを押す
  4. uid=usernameXX,ou=Users (RDN=uid, objectClass=inetOrgPerson&&posixAccount) のユーザを作成する
    1. ou=Users と同レベルの最下部項目 Create new entry here をクリックする
    2. Templates に Default を選択する
    3. ObjectClasses に inetOrgPersonposixAccount の 2 つ選択する
    4. 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 等
    5. Create Object ボタン -> Commit ボタンを押す

Rocket.Chat を起動する

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

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 を参照のこと。

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

  1. ブラウザにて http://localhost:3000 へアクセスして Rocket.Chat の管理ユーザを作成する。 (アカウント、パスワードは適宜設定する)
  2. 管理ユーザでログインしたら左ペインの最上部「:」をクリックしてプルダウンメニューから「管理」を選択する
  3. 検索ボックスに "LDAP" と入力して結果に出てきた「LDAP」を選択
  4. 次の設定内容を入力して 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
※エンティティ内に指定した属性が見つからない場合は、初ログイン時にユーザ名を指定できる
image.png
(LDAP - User Search)
Search Field uid

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

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

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

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

  • アカウント:ldapuser01
  • パスワード:hogehoge

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

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

コネクションフロー

  1. ACCEPT
  2. BIND
  3. { SRCH / MOD / ADD / DEL }
  4. UNBIND
  5. closed

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

Rocket.Chat ログの見方

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

トラブルシューティング

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

  • Rocket.Chat のログイン画面時のエラーメッセージ

    • 「ユーザが見付からないか、パスワードが間違っています」
    • image.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 ユーザにメールアドレス登録する

参考情報