Web
LDAP
openldap
SSO
Keycloak

KeycloakでLDAP認証を試してみる

今日やること

Keycloakアドベンドカレンダー13日目の今回はKeycloakの機能として提供されている「ユーザーフェデレーション機能」を利用した、LDAPでの認証および情報の連携を試してみようと思います。

KeycloakでLDAP認証を有効にすると、LDAP上に登録されているユーザー情報を用いてSSOを実現することができます。これによって、元々はログイン時にLDAPサーバを参照していたというアプリケーションをKeycloakのSSO連携先として追加することが容易になります。

認証だけでなく、LDAPに登録されているユーザー情報をKeycloakへ一括登録したり、逆にKeycloakからLDAPにユーザー情報を登録したりといったこともついでに試してみました。

やることをまとめると以下の3つです。

  • LDAP上に登録されているユーザーでログインする
  • LDAPからKeycloakにユーザーを追加する
  • KeycloakからLDAPにユーザーを追加する

今回利用した環境

サーバ構成

FQDN OS プロダクト
SSOサーバ keycloak.example.com CentOS 7.4 (kernel 3.10.0-693) Keycloak 3.4.0.Final
LDAPサーバ ldap.example.com CentOS 7.4 (kernel 3.10.0-693) OpenLDAP 2.4.44

LDAPのディレクトリの階層構成

今回はベースディレクトリをdc=example,dc=comとして、ou=People,dc=example,dc=com配下にユーザーを作成していきます。

test0001test0002test0003というユーザーを用意しています。これらのユーザーのobjectClassにはtopaccountposixAccountshadowAccountを指定しています。
posixAccountを指定していることにより、cnuiduidNumbergidNumberhomeDirectoryが必須となっています。

Managerのパスワードは今回passwordに設定しています。

ldapsearch -x -b 'dc=example,dc=com'の結果は以下の通りです。

# extended LDIF
#
# LDAPv3
# base <dc=example,dc=com> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# example.com
dn: dc=example,dc=com
objectClass: dcObject
objectClass: organization
dc: example
o: Example Inc.

# People, example.com
dn: ou=People,dc=example,dc=com
objectClass: organizationalUnit
ou: People

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

# Manager, example.com
dn: cn=Manager,dc=example,dc=com
objectClass: organizationalRole
cn: Manager

# test0001, People, example.com
dn: uid=test0001,ou=People,dc=example,dc=com
uid: test0001
cn: test0001
objectClass: top
objectClass: account
objectClass: posixAccount
objectClass: shadowAccount
shadowLastChange: 0
shadowMin: 0
shadowMax: 99999
loginShell: /bin/bash
uidNumber: 1001
gidNumber: 1001
homeDirectory: /home/test0001

# test0002, People, example.com
dn: uid=test0002,ou=People,dc=example,dc=com
uid: test0002
cn: test0002
objectClass: top
objectClass: account
objectClass: posixAccount
objectClass: shadowAccount
shadowLastChange: 0
shadowMin: 0
shadowMax: 99999
loginShell: /bin/bash
uidNumber: 1001
gidNumber: 1001
homeDirectory: /home/test0002

# test0003, People, example.com
dn: uid=test0003,ou=People,dc=example,dc=com
uid: test0003
cn: test0003
objectClass: top
objectClass: account
objectClass: posixAccount
objectClass: shadowAccount
shadowLastChange: 0
shadowMin: 0
shadowMax: 99999
loginShell: /bin/bash
uidNumber: 1001
gidNumber: 1001
homeDirectory: /home/test0003

# search result
search: 2
result: 0 Success

# numResponses: 8
# numEntries: 7

LDAP上に登録されているユーザーでログインする

まずはすでにLDAPに保存されているユーザーを認証に利用する手順を見ていきます。

Keycloakの設定手順

ここではKeycloakのユーザーフェデレーションの設定を実施していきます。なお、今回使用するレルムはdemoレルムとします。

  1. Keycloakの管理コンソールにログインします。
  2. 左メニューバーから、demoレルムを選択します。
  3. 左メニューバーでユーザーフェデレーションをクリックします。
  4. 右上のプロバイダーを追加...からLDAPを選択します。
  5. 以下のように設定値を入力して、保存をクリックします。
    • Import Users:オフ
    • 編集モード:READ_ONLY
    • ベンダー:Other
    • UUID LDAP 属性:uid
    • ユーザーオブジェクトクラス:top,account,posixAccount,shadowAccount
    • 接続 URL:ldap://ldap.example.com
    • ユーザー DN:ou=People,dc=example,dc=com
    • Bind DN:cn=Manager,dc=example,dc=com
    • Bind のクレデンシャル:password
    • 上記以外はデフォルトのまま
    • デフォルトのままだとコンソール表示名はldapとなります

2017-12-11_154501.png

:information_source: この段階ではまだユーザーのインポートは実施しません。

動作確認

http://keycloak.example.com:8080/auth/realms/Demo/account にアクセスします。
2017-12-11_163934.png

ログイン画面が表示されるので、LDAPに登録されているユーザーでログインします。
2017-12-11_164043.png

ログインできました。この時のLDAPサーバ側のログには次のような出力がされています。

slapd[30280]: conn=1273 fd=24 ACCEPT from IP=XXX.XXX.XXX.XXX:XXXXX (IP=0.0.0.0:389)
slapd[30280]: conn=1273 op=0 BIND dn="cn=Manager,dc=example,dc=com" method=128
slapd[30280]: conn=1273 op=0 BIND dn="cn=Manager,dc=example,dc=com" mech=SIMPLE ssf=0
slapd[30280]: conn=1273 op=0 RESULT tag=97 err=0 text=
slapd[30280]: conn=1273 op=1 SRCH base="ou=People,dc=example,dc=com" scope=1 deref=3 filter="(&(objectClass=*)(uid=ユーザー名))"
slapd[30280]: conn=1273 op=1 SRCH attr=uid mail sn cn objectclass createTimestamp modifyTimestamp
slapd[30280]: <= bdb_equality_candidates: (uid) not indexed
slapd[30280]: conn=1273 op=1 SEARCH RESULT tag=101 err=0 nentries=1 text=
slapd[30280]: conn=1274 fd=25 ACCEPT from IP=XXX.XXX.XXX.XXX:XXXXX (IP=0.0.0.0:389)
slapd[30280]: conn=1274 op=0 BIND dn="uid=ユーザー名,ou=People,dc=example,dc=com" method=128
slapd[30280]: conn=1274 op=0 BIND dn="uid=ユーザー名,ou=People,dc=example,dc=com" mech=SIMPLE ssf=0
slapd[30280]: conn=1274 op=0 RESULT tag=97 err=0 text=
slapd[30280]: conn=1274 op=1 UNBIND
slapd[30280]: conn=1274 fd=25 closed

LDAPサーバでログインしようとしているユーザーの存在確認をManagerとして実行し、存在していれば認証処理を実行していることが分かります。

パスワードの変更を試みます。すると以下の画像のように変更ができません。これは編集モードをREAD_ONLYとしているためです。
2017-12-11_164118.png

:information_source: 編集モードをWRITABLEに変更すればKeycloakからLDAPへパスワードなどのユーザー情報を変更することが可能になります。また、編集モードをUNSYNCEDに変更してもユーザー情報の変更は可能ですが、後述のKeycloakDBには変更が適用される一方、LDAPには書き戻しがされません。

:warning: 編集モードをUNSYNCEDにした場合の挙動はこちらもご参照ください。

LDAPからKeycloakにユーザーを追加する

Keycloakの操作のみでLDAPに登録されているユーザーをKeycloakが持っているDB(KeycloakDB)に一括で追加することができます。

Keycloakの設定手順

  1. Keycloakの管理コンソールにログインします。
  2. 左メニューバーから、demoレルムを選択します。
  3. 左メニューバーでユーザーフェデレーションをクリックします。
  4. ldapをクリックします。
  5. import Usersをオンにします。
  6. 保存をクリックします。
  7. すべてのユーザーを同期をクリックします。

2017-12-11_164306.png

動作確認

左メニューバーでユーザーをクリックしたあと、ページ上部にある全てのユーザーを参照をクリックするとLDAPに登録されているユーザーが表示されます。この操作によって、LDAPに登録されているユーザーがKeycloakDBに反映されていることが確認できます。
2017-12-11_164418.png

なお、管理コンソールからユーザー情報の変更を試みても、編集モードがREAD_ONLYとなっているままでは変更に失敗します。先述の通り、編集モードをWRITABLEUNSYNCEDに変更すれば、Keycloakの画面でユーザー情報を変更することが可能です。

しかし、編集モードがREAD_ONLYのままユーザーの削除を試みると削除することが可能です。削除を実行した直後、Keycloakのログに下記のような出力がされます。

WARN  [org.keycloak.storage.ldap.LDAPStorageProvider] (default task-5) User 'ユーザー名' can't be deleted in LDAP as editMode is 'READ_ONLY'. Deleting user just from Keycloak DB, but he will be re-imported from LDAP again once searched in Keycloak

これはKeycloakDBからは削除されるものの、LDAPでは削除されないという旨を示しています。もう一度Keycloakからこの削除したユーザーを検索した場合(例えばこの削除したユーザーがログインをした場合など)は再度LDAPからKeycloakDBにインポートされます。

:information_source: 編集モードがUNSYNCEDの場合も同様の挙動を示しますが、WRITABLEの場合はKeycloakDBからもLDAPからも削除されます。

KeycloakからLDAPにユーザーを追加する

こちらもKeycloakの操作のみでKeycloakからLDAPへユーザー登録が可能になります。

ここまではマッパーの設定をしなくても使用することはできましたが、KeycloakからLDAPにユーザーを登録する場合はobjectClassの必須項目についてマッパーの設定を適切に行う必要があります。このマッパーの設定を行うことにより、Keycloak側とLDAP側のそれぞれの属性の対応関係を定義することができます。

Keycloakの設定手順

  1. Keycloakの管理コンソールにログインします。
  2. 左メニューバーから、demoレルムを選択します。
  3. 左メニューバーでユーザーフェデレーションをクリックします。
  4. ldapをクリックします。
  5. 以下のように設定値を入力して、保存をクリックします。

    • 編集モード:WRITABLE
    • 登録の同期:オン 2017-12-11_173643.png
  6. 画面上部のマッパーのタブをクリックします。

  7. 不要な項目の削除します。

    • first nameをクリックして画面上部のゴミ箱のアイコンをクリック
    • last nameをクリックして画面上部のゴミ箱のアイコンをクリック
    • emailをクリックして画面上部のゴミ箱のアイコンをクリック
    • この時点のマッパーの一覧は次の通りになっています。 2017-12-12_192324.png
  8. 作成ボタンをクリックして以下のように設定項目を変更します。

    • cnのマッピング追加
      • 作成ボタンをクリック
      • 以下項目を入力
        • 名前:cn(任意)
        • マッパータイプ:user-attribute-ldap-mapper
        • User Model Attribute:username
        • LDAP Attribute:cn
        • 上記以外は上記以外はデフォルトのまま
      • saveをクリック
    • uidNumberのマッピング追加
      • 作成ボタンをクリック
      • 以下項目を入力
        • 名前:uidNumber(任意)
        • マッパータイプ:hardcoded-ldap-attribute-mapper
        • LDAP Attribute Name:uidNumber
        • LDAP Attribute Value:1001(インクリメント機能などがあれば良いのですが、実装されていないようです。:cry:
        • 上記以外は上記以外はデフォルトのまま
      • saveをクリック
    • gidNumberのマッピング追加
      • 作成ボタンをクリック
      • 以下項目を入力
        • 名前:gidNumber(任意)
        • マッパータイプ:hardcoded-ldap-attribute-mapper
        • LDAP Attribute Name:gidNumber
        • LDAP Attribute Value:1001
        • 上記以外は上記以外はデフォルトのまま
      • saveをクリック
    • homeDirectoryのマッピング追加
      • 作成ボタンをクリック
      • 以下項目を入力
        • 名前:homeDirectory(任意)
        • マッパータイプ:hardcoded-ldap-attribute-mapper
        • LDAP Attribute Name:homeDirectory
        • LDAP Attribute Value:/home/${RANDOM}(いまのところ固定値以外を振りたい場合はランダムとする方法しかないようです。:cry:
        • 上記以外は上記以外はデフォルトのまま
      • saveをクリック
    • 例えばcnのマッピング情報は以下のようになっているはずです 2017-12-12_192045.png
    • 最終的にマッパーの一覧は次の通りになります。 2017-12-11_164846.png

動作確認

左メニューバーでユーザーをクリックしたあと画面右上のユーザーの追加をクリックします。ユーザー名を入力し、保存をクリックするとユーザーの登録が完了します。

今回はkctestというユーザーを作成しました。

まずKeycloakDB側ですが、左メニューバーでユーザーをクリックすると新たに追加されたユーザーを確認することができます。
2017-12-11_164943.png

次にLDAP側ですが、ldapsearch -x -b 'uid=kctest,ou=People,dc=example,dc=com'を実行すると対象のユーザーが確認することができます。

# extended LDIF
#
# LDAPv3
# base <uid=kctest,ou=People,dc=example,dc=com> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# kctest, People, example.com
dn: uid=kctest,ou=People,dc=example,dc=com
gidNumber: 1001
uidNumber: 1001
uid: kctest
objectClass: posixAccount
objectClass: top
objectClass: account
objectClass: shadowAccount
homeDirectory: /home/hjRHlStH8aOI0PdxjbmjCxbnK3cRMp
cn: kctest

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

このままではまだパスワードが設定されていないので、ログインする前にパスワードを設定するのを忘れないようご注意ください。パスワードを設定すればログインすることができます。

まとめ

今回はKeycloakのユーザーフェデレーション機能によって、LDAPに登録されているユーザーでログインをする方法や、Keycloak・LDAPそれぞれにユーザーを追加する方法を確認しました。一つのLDAPで設定を試しましたが、同じ手順を踏むことで複数のLDAPやADにも対応することができます。

ちなみに今回はあまり表立って触れませんでしたが、マッパーの設定を行うことによってLDAPに登録されているさまざまな属性をKeycloakへ連携できますのでぜひお試しください。

LDAPに登録されている情報を利用しているシステムであれば、Keycloakで認証をしているかのようにLDAPを利用したSSOを簡単に実現することができますので、この記事の内容ご参照頂いて身近に利用している既存システムへもご活用いただければと思います!!

補)不思議な現象

今回、途中に編集モードを変えた場合の挙動も解説しましたが、Import Usersをオフ、編集モードをUNSYNCEDに設定した状態でパスワードに変更を加えた場合に少し不思議だと思った現象が発生しましたので軽く紹介しようと思います。:alien:

Import UsersがオフであることからKeycloakDBにはユーザー情報が全くないものと考えられます。また、編集モードがUNSYNCEDなためLDAPへは変更内容が反映されず結果的に変更は一切加わらないと考えたのですが、その変更を実施した後では変更後のパスワードでないと認証が通りませんでした。

図1.png
図2.png

この時のLDAPのログを見るとユーザーの存在確認は行っているのですが、認証処理はLDAPで行っておらずKeycloak側で行っていました。

画面側には表示されないだけで、パスワードの変更を行った時点でKeycloakのDB側では何らかの形でユーザー情報が格納されているということなのでしょうか。この現象については引き続き調査したいと思っています。

このように編集モードをUNSYNCEDにすると若干直感的でない挙動を示すことがありそうなので注意が必要そうです。

参考資料