GlassFish の LDAPレルム を利用する (ja)

既にシリーズ化してきた「GlassFishの****レルムを利用する」記事ですが、今回はいよいよLDAPに挑戦します。私がこれまで経験してきたシステムでも、特に大規模システムではLDAPによるユーザー認証を行っています。
もちろんLDAPだけで認証システムを構築するケースは多くなく、認可のところでRDBMSなどの既存システムを利用したり、シングル・サイン・オン製品と連携させるなどの工夫があります。
今回はGlassFishからLDAPのユーザー情報を使って認証・認可を試してみます。ノリとしては前回の「GlassFishのJDBCレルムを利用する」と同じです。なお、この記事を読む前に、以下の過去記事(特に最初のもの、ここに今回の要求事項がまとめています)に目を通して頂きたいと思います。
まず前提として、アプリケーションの要求は「GlassFishでBASIC認証を利用する(Servlet編)」と全く同じものとします。これまでの連載でServlet版とJAX-RS版のサンプルを作成しました(JAX-RS版は改良版サンプルも作成しました)が、同じ動きをするものですのでどちらのサンプルをテストに使用しても構いません。

LDAPレルムを使用するには、以下の2段階の準備が必要です。
  • LDAPにユーザー認証用のデータを作成する。
  • GlassFishにLDAPレルムを作成する(デフォルトでは作成されていないため)。
JDBCレルムを使用する場合と大きく異なるのは、LDAPの場合は既にユーザー検索用のディレクトリが構築されており、GlassFishの設定を現行のLDAPに合わせる必要が出てくる可能性があることです。大規模で複雑なLDAPディレクトリ構成の場合、一筋縄ではいかないかも知れませんが、今回の記事の内容を元に試行錯誤しながら実現できるかと思います。

さて、LDAPと言ってもその実態はLDAP実装やそれぞれのシステムによってデータの持ち方(スキーマ定義)が違います。今回は以下のLDAPサーバー製品で検証しました。
  • Oracle Directory Server Enterprise Edition -- 旧Sun Java Directory Server, Enterprise Edition、そのルーツはNetscape社が開発した最初期の商用LDAPサーバーまで遡ります。
  • Microsoft Active Directory -- Windows Serverのドメインコントローラーで、LDAP、DNS、Kerberos等のオープン規格を全面的に採用しているため、アクセスが比較的容易になっています(それでもGSSAPI/Kerberosは結構ハマりどころ)。
ちなみに、OpenLDAPについては検証していません。これは私の自宅システムがSolarisとWindows Serverで構築している上、Solarisの認証系ととOpenLDAPの相性が最悪のため「インストールするのも憚られる」という個人的な感情が理由です。

1. Directory Server Enterprise Edition 7 (DSEE7/11g) の場合
先に断っておきますが、最新のSolaris 11上でDSEE7/11gは動作しません(インストーラーが転けます)。日本オラクルのサポートに問い合わせたのですが、DSEEのCSIがないという理由だけで一時切り分けさえしてもらえませんでした(後日依頼されたアンケートでクレームを付けたのは言うまでもありません)。現状ではSolaris 10の環境を残してそちらにDSEE7/11gをインストールするのが正解です。
Solaris 10 8/11では最新のDSEE7/11gの初期設定が行えるのですが、それより前のバージョンではSunブランドのDSEE7はイントールできるがOracleブランドのDSEE11gがインストールできないという障害にぶつかります。対処方法は以下の通りです。
/usr/lib/ldap/idsconfig の以下の箇所について:

case "${IDS_MAJVER}" in
5|6|7)  : ;;
*)   ${ECHO} "ERROR: $PROG only works with JES DS version 5.x, 6.x or
7.x, not ${IDS_VER}."; exit 1;;

次のように修正すること:

case "${IDS_MAJVER}" in
5|6|7|11)  : ;;
*)   ${ECHO} "ERROR: $PROG only works with JES DS version 5.x, 6.x, 7.x or 11.x, not ${IDS_VER}."; exit 1;;

なお念を押しておきますが、Solaris 10 8/11ではこの不具合は修正済みなので触れる必要はありません。
さて、とりあえずDSEEをインストールしました。今回はほぼデフォルトの設定を踏襲しつつ、ディレクトリ構成を以下のように設定しました。この設定は私の手元の環境のもので、各自の環境に合わせて読み替えてください。

LDAPサーバー設定項目 (DSEE)
設定項目 設定内容
サーバー (ホスト名) eurydome.voyager.coppermine.jp
LDAPポート番号 389
サフィックス (BaseDN) dc=voyager,dc=coppermine,dc=jp
ユーザーのスキーマ inetOrgPerson
ユーザーデータのDN ou=people,dc=voyager,dc=coppermine,dc=jp
グループのスキーマ groupOfUniqueNames
グループのDN ou=group,dc=voyager,dc=coppermine,dc=jp

スキーマとグループのDNはidsconfigスクリプトを実行することで自動的にに作成されます。idsconfigはDSEEをSolarisのユーザー認証に素早く組み込むことができるよう、多くの作業を自動化してくれます。今回は実験的要素が強いためidsconfigを使用する意味は薄れてしまいますが、Solaris中心のネットワークにLDAPを導入する際には相当の効果を発揮します。
また、ここではスキーマとしてinetOrgPersonとgroupOfUniqueNamesを使用しましたが、SolarisのPAM認証と連携させるにはこれらのスキーマのobjectClassにposixAccount、shadowAccount、posixGroupなどを追加する必要があります。これらを追加した場合の差分情報については後述します。

前置きが長くなりましたが、データが作成できたら早速LDAPレルムを設定しましょう。

LDAPレルムの設定項目 (DSEE)
プロパティ設定値
レルム名任意 (例えば ldap など)
クラス名com.sun.enterprise.security.auth.realm.ldap.LDAPRealm
JAASコンテキストldapRealm
ディレクトリldap://eurydome.voyager.coppermine.jp:389/
ベースDNou=people,dc=voyager,dc=coppermine,dc=jp
search-filter uid=%s (デフォルト;省略可)
group-base-dnou=group,dc=voyager,dc=coppermine,dc=jp
group-search-filteruniqueMember=%d (デフォルト:省略可)
group-targetcn (デフォルト:省略可)
search-bind-dn(匿名でも読み取りだけ可能なら省略可)
search-bind-password(search-bind-dnを設定した時のみ必須)

注意したいのは「ベースDN」という項目です。名前から連想するベースDNは、この例では「dc=voyager,dc=coppermine,dc=jp」ですが、ここではユーザーのエントリーを含むレベル、つまり「ou=people,dc=voyager,dc=coppermne,dc=jp」まで指定する必要があります。また、DSEEではデフォルトでユーザーとグループのouを分けて管理するので、groupd-base-dnも別途指定する必要があります。

また、先述の理由でスキーマのobjectClassにposixAccount、shadowAccount、posixGroupなどを追加してUnix(POSIX)認証に対応させている場合は、上記プロパティ group-search-filter を「memberUid=%d」に変更する必要があるかも知れません(グループ管理をposixGroupに統一した場合など)。

これでLDAPレルムの設定は完了しましたが、実際に開発環境でテストをする際には、事前にGlassFishを再起動することをお勧めします。

結果は基本的にこれまでと同じものが得られるはずです。

2. Active Directory (Windows Server 2008 R2 SP1) の場合
Active Directoryは日々進化しているため(特にセキュリティ面で)いつまでこの情報が役に立つかはわかりませんが、当面の間は有効かと思われます。
Active Directory上の構成は、これまでの要件に準拠する形で構成しましたが、一点だけ異なる点があります。ユーザーを作成すると必ずグループに「Domain Users」が追加されますが、上位の「Domain Admins」に置き換えない限りはそのままにしておくのが無難です(最悪、ログインできなくなります)。検証用に使うグループはDomain Usersとは別に作成し、それぞれのユーザーに割り当てました。

LDAPサーバー設定項目 (DSEE)
設定項目 設定内容
サーバー (ホスト名) adrastea.pioneer.coppermine.jp
LDAPポート番号 389
サフィックス (BaseDN) dc=pioneer,dc=coppermine,dc=jp
ユーザーのスキーマ ユーザー (organizationalPerson)
ユーザーデータのDN cn=Users,dc=voyager,dc=coppermine,dc=jp
グループのスキーマ グループ (group)
グループのDN dc=Users,dc=pioneer,dc=coppermine,dc=jp

LDAPレルムの設定項目 (Active Directory)
プロパティ設定値
レルム名任意 (例えば ads など)
クラス名com.sun.enterprise.security.auth.realm.ldap.LDAPRealm
JAASコンテキストldapRealm
ディレクトリldap://adrastea.pioneer.coppermine.jp:389/
ベースDNcn=Users,dc=pioneer,dc=coppermine,dc=jp
search-filter cn=%s
group-base-dn(ベースDNと同じ値を使用するため省略可)
group-search-filtermember=%d
group-targetcn
search-bind-dncn=haruka,cn=Users,dc=pioneer,dc=coppermine,dc=jp
search-bind-password(harukaのパスワード)

Active Directoryを使用する場合、最大の注意点はsearch-bind-dnとsearch-bind-passwordの設定が必須になってくることでしょう。ネット上の多くの資料では、Active Directory側で匿名アクセスを許可して認証を省略する方法をよく見かけますが(むしろプログレベルではほぼそのやり方しか紹介していませんね)、本来であればActive Directoryの設定は変更せず(セキュリティ上の問題です)、正しい方法で認証して情報を利用するのが筋だと考えます。

実はCIツールとして有名なJenkinsでも過去にActive Directory認証で問題が起こった際、匿名認証を可能な限り避ける方向で問題を解決したという実績があります。つまり、匿名認証を許可しなくてもディレクトリの検索程度なら比較的実現しやすい方法で問題を解決できるというわけです。

参考まで、該当するJenkinsのIssue Trackerのチケットを紹介します。川口耕介さんが匿名認証より前に世紀の認証プロセスを試すのが美しいやり方だと判断したことを知ることができます。
なお、今回のActive Directory接続については、アクセス権限を持つ任意のユーザーとそのパスワードを指定することで、Read Onlyに限定する形でとりあえず解決しました。認証プロセスでLDAPの項目を意図的に書き換えることはまずしませんから、実用上はこれで問題ないかと思われます。

LDAPは自由度の高いディレクトリサービスですので、個々に紹介した以外にも様々な実現方法が考えられるかと思います。また、GlassFishではUnix/LinuxのPAM認証もサポートしているため、LDAPとPAMの連携によりGlassFish側からはLDAPを意識させない方法を採ることも可能です。PAMを使えば、例えばLDAP→NIS→/etc/passwdと検索することでどこかしらのネームサービスにユーザー情報があれば認証できるような仕組みも実現できるはずです。

次回はPAMレルムについて調査を進める予定です。PAMの仕組み上、すべてのネームサービスを使った疎通試験は省略するかも知れませんが、Unix/Linuxプラットフォームでは選択肢の1つとして考えても良いと思います。