LDAP
SAML
WebSphere
SSO
OpenAM

WebSphere+OpenAMのSAML SSO環境構築(1/3)(IdP構築編)

WebSphere(WAS)でのSAMLによるSSOの構成

SAMLによるSingle-Sign-On(SSO)をWebSphereでサポートしているということで、実際に試してみました。正直、SAMLという言葉を知ったのですらつい最近なので、認識相違等あればご指摘いただきたいです。

基本用語

いろいろな人がいろいろにまとめているので今更感はあるものの、基本用語を自分なりの解釈でまとめておきます。


SAML
"Security Assertion Markup Language"の略。"security assertion"とは直訳すればセキュリティ表明。この人(subject)はこういう認証を受けたよ、という情報を書くためのX509署名つきXML文書1。さらにSAML Attributeといわれる属性を持たせることができる(UID等の具体的なユーザ属性)。

IdP
"Identity Provider"の略。"identity"すなわち認証を司る。本人認証にはパスワード認証や生体認証、ICカードなどの所持による認証など様々だが、SAML的にはIdPがどんな認証スキームをとろうがどうでもいい。とにかく、本人認証してアサーションを投げる(ID Federationなどともいう)機能を持ったエンティティ(存在、サービス)。

SP
"Service Provider"の略。サービスを誰に提供するかといえばもちろんユーザ。IdP・SPはどちらかというとユーザ視点での見え方。IdPからのアサーションを受取り、認証("authentication")されたものとして扱い、認可("authorization")した上でサービスを提供するエンティティ(存在、サービス)2。もちろん、アサーションを検証する(正しい、信頼関係のあるIdPからの情報であることを確認する)というステップが不可欠。

TAI
"Trust Association Interceptor"の略。WASが提供する機能で、SAMLアサーション(署名つきXML文書)を解析して検証し、問題がなければ"javax.security.auth.Subject"オブジェクトを生成してくれる。SubjectにはWASのオブジェクトが格納され、その中にSAML属性情報も含まれる。

ACS
"Assertion Consumer Service"の略。WASでは、TAIが検証してOKとなったアサーションを消費して(要は実際に使って)ユーザにサービスを提供するエンティティ。WASにもACSアプリケーションが"<WAS_ROOT>/installableApps/WebSphereSamlSP.ear"として提供されている(詳しくはあとでみていく)。一般的には、TAIの機能もACSに含まれる(つまりアサーションを検証し、認可してユーザにロールを割り当てて実際に使用するサービス)。

LTPA
"Lightweight Third Party Authentication"の略。IBM社独自の規格である。WASインスタンスで認証したユーザに対し、特殊なクッキー(その名も"LTPAToken2")を発行して別のWASインスタンスでも認証を通す(SSOする)仕組み。

IdP-initiated SAML
IdPをリクエストの起点としたSSO。つまり、IdP側でSPへのリンクを持ったポータルサイトなどを作っておいて、ユーザがそのリンクをクリックしたら未認証であれば認証して、SAMLアサーションをつけてSP(ACS)へHTTPリクエストするようなやり方。

SP-initiated SAML
IdP-initiatedとは逆に、SP側からSAMLアサーションをIdPに対して要求する。未認証のユーザがSP(ACS)へアクセスしてきたら、正式なSAMLの要求文書(SAMLリクエスト)をIdPに投げて、これをIdP側が処理(ユーザ認証)した上でSPへHTTPリクエスト(アサーション)を投げ返すやり方。

SP-redirected SAML
SP-initiatedで「正式なSAMLの要求文書」と書いたのとは対照的に、未認証のユーザがSP(ACS)へアクセスしてきたら、とりあえずIdPのログインページ(たいていform loginのページ)へリダイレクトしてしまえ、その後ここ(SP)に戻ってこられるようにHTTPリクエストのパラメタ(RelayState)に自分のURLを書き込んでおく、というやり方。
現状、WASでは"IdP-initiated SAML"と"SP-redirected"方式をサポートしており、"SP-initiated"方式は怪しそうなので、前二つの方式で認証するところまでやってみようと思います。

必要環境

WAS/IHSは構築済みとして、実験にあたって以下の環境が必要になります。

  • ユーザリポジトリミドルウェア:具体的にはLDAP、今回はOpenDJを使用
  • IdPミドルウェア:OSS(?)のOpenAMを使用
  • 認証局(CA):証明書発行(特にIdP用とSSL用)、今回はOpenSSLを使用

いずれもMacOS上で動作可能なので、IdP機能は全てMacOS上に構築します。もう少し砕いたソフトウェアスタックは以下のようになります。
名称未設定.png
IdPとSPとでホスト名が一緒だと紛らわしいので、このような構成にしています。IdPはalto.mognet.netでMacのループバックアドレス127.0.0.1の別名としてhostsファイルに定義します。SP側は構築済みのdockerコンテナで、これらも実体はループバックアドレスへのポートフォワードでアクセスしますが、ループバックのエイリアスとして10.1.1.1-254を切っており、dockerも同じホストアドレスのサブネットを切って構築しています。

ホスト情報 ホスト名 IPアドレス
IdP alto.mognet.net 127.0.0.1
SP www.docker.mognet.net 10.1.1.10

ポイントがあるとすれば、SPはWEB(IHS)とAP(WAS)の2サーバで構成しますが、IdPから見たSPのホストがWebサーバになる、ということでしょうか。もし負荷分散装置(ロードバランサ)を導入していれば、そのIPアドレスに対応するホスト名がIdPから見たSPのホスト名になります。あとでWASからSPのメタデータ(IdPに渡すSPの情報)を出力するときに割と効いてくるので念のため。

認証局構築

まずはオレオレ認証局を構成します。格好良くいえば「プライベート認証局」ですね。昔、職場でVerySignという架空の認証局を、中間CA構成含めたあらゆる属性を真似て作ったことがありますが、このご時世なんとなく怖いので簡単にRoot認証局がEE証明書を発行する形でいきます。

MacOSにもopensslは導入されていますが、うまく動かないのでbrew installで持ってきます。

mac:$ brew update
mac:$ brew install openssl
mac:$ echo 'export PATH=/usr/local/opt/openssl/bin:${PATH}' >> ~/.bash_profile  # PATH設定
mac:$ . ~/.bash_profile
mac:$ which openssl
/usr/local/opt/openssl/bin/openssl
mac:$ openssl version
OpenSSL 1.0.2n  7 Dec 2017
mac:$ mkdir ~/mognetCA         # CAのルートディレクトリを作成
mac:$ cd ~/mognetCA
mac:$ cp /usr/local/etc/openssl/misc/CA.sh ./   # 構築スクリプト雛形をコピー
mac:$ cp /usr/local/etc/openssl/openssl.cnf ./  # 設定ファイル雛形をコピー

CA構築スクリプトを編集します。あまりこだわるところではないので、コマンドパスを明示、証明書有効期限を約30年に延長し、CAディレクトリを変更している程度です。

61,62c61
< if [ -z "$OPENSSL" ]; then OPENSSL=openssl; fi
< 
---
> OPENSSL=openssl
64c63
< CADAYS="-days 1095"   # 3 years
---
> CADAYS="-days 10950"  # 30 years
70,71c69
< 
< if [ -z "$CATOP" ] ; then CATOP=./demoCA ; fi
---
> CATOP=~/mognetCA

スクリプト実行。

mac$ ./CA.sh

設定ファイルを編集します。発行ポリシーをpolicy_anything(誰でもWelcome)、サブジェクトのデフォルト値を使いやすいように変更、KeyUsagensCertTypeをなんでもにしています。特に、サーバ証明書(SSL用)として使用する場合、KeyUsageKeyEnciperment(SSLハンドシェイクの鍵交換時に公開鍵暗号方式でマスターキーを暗号化して交換するため)、nsCertTypeserverがないとダメだったと思います。

8c8
< HOME          = .
---
> HOME          = ~/mognetCA
42c42
< dir       = ./demoCA      # Where everything is kept
---
> dir       = ~/mognetCA        # Where everything is kept
81c81
< policy        = policy_match
---
> policy        = policy_anything
129c129
< countryName_default       = AU
---
> countryName_default       = JP
134c134
< stateOrProvinceName_default   = Some-State
---
> stateOrProvinceName_default   = Tokyo
136a137
> localitiName_default      = Koutou-Ku
139c140
< 0.organizationName_default    = Internet Widgits Pty Ltd
---
> 0.organizationName_default    = mognet.net
179c180
< # nsCertType = objsign
---
>  nsCertType = objsign
185c186
< # nsCertType = client, email, objsign
---
>  nsCertType = server, client, email, objsign
188c189
< # keyUsage = nonRepudiation, digitalSignature, keyEncipherment
---
>  keyUsage = nonRepudiation, digitalSignature, keyEncipherment
191c192
< nsComment         = "OpenSSL Generated Certificate"
---
> #nsComment            = "OpenSSL Generated Certificate"
290c291
< # nsCertType = client, email, objsign
---
>  nsCertType = client, email, objsign
296c297
< nsComment         = "OpenSSL Generated Certificate"
---
> #nsComment            = "OpenSSL Generated Certificate"

opensslコマンドを発行するたびに-config ~/mognetCA/openssl.cnfを書くのは面倒なので、環境変数にしておきます。コマンドエイリアスまで切ってしまうと、サブコマンドを最初の引数に取るopensslコマンドの仕様上厳しいので(wrapperスクリプトを書けばいいんでしょうけど)、引数のみ。

mac:$ echo 'export CA="-config ~/mognetCA/openssl.cnf"' >> ~/.bash_profile
mac:$ . ~/.bash_profile

これで証明書発行時は、openssl ca ${CA} ...と実行すればOKです。では、一気に今回使用する鍵と証明書を生成していきます。

mac:$ mkdir -p ~/openAM/work; cd ~/openAM/work; pwd                       # ワークディレクトリ作成して移動
mac:$ openssl genrsa 2048 > opendj.key                                    # LDAPs用鍵生成
mac:$ openssl req ${CA} -new -key opendj.key -out opendj.req              # 同証明書発行要求生成(CN=alto.mognet.net)
mac:$ openssl ca ${CA} -in opendj.req -days 3650 -out opendj.crt          # 証明書発行(有効期限10年)
mac:$ cat opendj.key opendj.crt | openssl pkcs12 -export -out opendj.p12  # 鍵と証明書をPKCS12形式にエクスポート

これで1セットです。あとはforで回します。

mac$: for subject in idp apache ihs; do
  openssl genrsa 2040 > ${subject}.key
  openssl req ${CA} -new -key ${subject}.key -out ${subject}.req
  openssl ca ${CA} -in ${subject}.req -days 3650 -out ${subject}.crt
  cat ${subject}.key ${subject}.crt | openssl pkcs12 -export -out ${subject}.p12
done

対話型のところやパスワードは適宜設定します。注意するのはCN(Common Name)を自分のホスト名にすることくらいです。あと、apache用の鍵にはパスワードをかけられないことでしょうか(最近apache httpdを触っていないんですが、さすがにもう鍵パスワードかけられるのかな?)。

Apache httpdインストール

これもMacOSに付属していますが、古そうなのでソースからコンパイル、、、などという面倒な野暮なことはせずにbrewで持ってきます。

mac:$ brew update
mac:$ brew install httpd

httpd.confを修正します。

http.conf
52c52
< Listen 8080
---
> Listen 127.0.0.1:80
85,88c85,88
< #LoadModule auth_digest_module lib/httpd/modules/mod_auth_digest.so
< #LoadModule allowmethods_module lib/httpd/modules/mod_allowmethods.so
< #LoadModule file_cache_module lib/httpd/modules/mod_file_cache.so
< #LoadModule cache_module lib/httpd/modules/mod_cache.so
---
> LoadModule auth_digest_module lib/httpd/modules/mod_auth_digest.so
> LoadModule allowmethods_module lib/httpd/modules/mod_allowmethods.so
> LoadModule file_cache_module lib/httpd/modules/mod_file_cache.so
> LoadModule cache_module lib/httpd/modules/mod_cache.so
128c128
< #LoadModule proxy_module lib/httpd/modules/mod_proxy.so
---
> LoadModule proxy_module lib/httpd/modules/mod_proxy.so
136c136
< #LoadModule proxy_ajp_module lib/httpd/modules/mod_proxy_ajp.so
---
> LoadModule proxy_ajp_module lib/httpd/modules/mod_proxy_ajp.so
146c146
< #LoadModule ssl_module lib/httpd/modules/mod_ssl.so
---
> LoadModule ssl_module lib/httpd/modules/mod_ssl.so
188,189c188,189
< User _www
< Group _www
---
> User piro
> Group staff
210c210
< ServerAdmin you@example.com
---
> ServerAdmin dummy@mognet.net
219c219
< #ServerName www.example.com:8080
---
> ServerName www.alto.net
519c519
< #Include /usr/local/etc/httpd/extra/httpd-ssl.conf
---
> Include /usr/local/etc/httpd/extra/httpd-ssl.conf
529a530,531
> 
> ProxyPass /openam ajp://www.alto.net:8009/openam

先を見越してOpenAM on Tomcatとの連携設定もいれています(proxy_module, proxy_ajp_module, ProxyPass)。続いてhttpd-ssl.confを修正します。

httpd-ssl.conf
27c27
< #SSLRandomSeed startup file:/dev/urandom 512
---
> SSLRandomSeed startup file:/dev/urandom 512
29c29
< #SSLRandomSeed connect file:/dev/urandom 512
---
> SSLRandomSeed connect file:/dev/urandom 512
36c36
< Listen 8443
---
> Listen 127.0.0.1:443
92,93c92,93
< SSLSessionCache        "shmcb:/usr/local/var/run/httpd/ssl_scache(512000)"
< SSLSessionCacheTimeout  300
---
> #SSLSessionCache        "shmcb:/usr/local/var/run/httpd/ssl_scache(512000)"
> #SSLSessionCacheTimeout  300
121c121
< <VirtualHost _default_:8443>
---
> <VirtualHost _default_:443>
124,128c124
< DocumentRoot "/usr/local/var/www"
< ServerName www.example.com:8443
< ServerAdmin you@example.com
< ErrorLog "/usr/local/var/log/httpd/error_log"
< TransferLog "/usr/local/var/log/httpd/access_log"
---
> ServerName www.alto.net:443
144,146c140,142
< SSLCertificateFile "/usr/local/etc/httpd/server.crt"
< #SSLCertificateFile "/usr/local/etc/httpd/server-dsa.crt"
< #SSLCertificateFile "/usr/local/etc/httpd/server-ecc.crt"
---
>   SSLCertificateFile /Users/piro/openAM/work/apache.crt
>   SSLCertificateKeyFile /Users/piro/openAM/work/apache.key
>   SSLCertificateChainFile /Users/piro/mognetCA/cacert.cer
154c150
< SSLCertificateKeyFile "/usr/local/etc/httpd/server.key"
---
> #SSLCertificateKeyFile "/usr/local/etc/httpd/server.key"

特別な設定はいりません。さっき作った鍵と証明書のパスを指定してやるくらいです(ポートは443を使います)。

Apache Tomcatインストール

おとなしく公式サイトからダウンロードします。解凍したディレクトリごと、~/openAM/tomcatとして移動します。特にすることはありませんが、1点だけJavaのバージョンが1.8系であることだけ確認しておきます。

mac:$ java -version
java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)

OpenDJインストール

ForgeRock社の公式サイトからOpenDJをダウンロードします。リンク先からTRY NOWを辿ってダウンロードします(要ユーザ登録@無料)。ZIPを解凍したら、ディレクトリごと~/openAM/opendjとして移動します。setupコマンドを実行して対話型セットアップを実行します。セットアップスクリプトが存在するディレクトリをベースにして、データディレクトリ等が作成されるので、ご自分の環境に合ったディレクトリに配置してください。

mac:$ cd ~/openAM/opendj; pwd
mac:$ ./setup
〜省略〜
Do you accept the License Agreement? (yes / no) [no]: yes
Select the type of server to set up:

    1)  Directory Server
    2)  Proxy Server
    3)  Replication Server

Enter choice [1]: 1
〜省略〜
Provide the directory root user DN [cn=Directory Manager]: cn=admin
〜省略〜
Provide the server's fully qualified host name [alto.local]: www.alto.net
〜省略〜
Options to secure server connections:

    1)  自己署名付き証明書を生成します (テスト目的のみでの使用を推奨)
    2)  PKCS#12 鍵ストアに存在する既存の証明書を使用します
    3)  JCEKS 鍵ストアに存在する既存の証明書を使用します
    4)  Java 鍵ストア (JKS) に存在する既存の証明書を使用します
    5)  PKCS#11 トークン上の既存の証明書を使用します

Enter choice [1]: 2
PKCS#12 鍵ストアのパス: /Users/piro/openAM/work/opendj.p12
鍵ストアの PIN: 
〜省略〜
Enable LDAP? (yes / no) [yes]: 
LDAP port [1389]: 38900
Enable StartTLS on LDAP port? (yes / no) [yes]: 

Enable LDAPS? (yes / no) [yes]: 38901

Invalid response. Please enter "yes" or "no"

Enable LDAPS? (yes / no) [yes]: 
LDAPS port [1636]: 38901
〜省略〜
ディレクトリデータのベース DN を指定してください: [dc=example,dc=com]: dc=mognet,dc=net
〜省略〜
All Server Parameters
---------------------------------------------------------------------
Server instance path                   /Users/piro/openAM/opendj
ルートユーザー DN:                            cn=admin
Root user password                     ******
Hardened configuration for production  無効
Server's fully qualified host name     www.alto.net
管理コネクタポート:                             6444
Start server after setup               有効
Server security                        Use existing PKCS#12 keystore
Keystore file                          /Users/piro/openAM/work/opendj.p12
Keystore password                      ******
Certificate nickname(s) to use         opendj
LDAP (cleartext)                       Listening on port 38900
Start TLS (secure) for LDAP            有効
LDAPS (secure)                         Listening on port 38901
HTTP (cleartext)                       Listening on port 18080
HTTPS (secure)                         Listening on port 18081
Data storage option                    Generate 6000 sample entries
Base DN to create                      dc=mognet,dc=net
Data storage (backend) type            JE Backend
〜省略〜

サンプルデータを6000エントリ生成するオプションをつけましたがどちらでもよいです。ちなみにサンプルエントリは、オブジェクトクラスinetOrgPersonとして生成されます。
ただし、実際のユースケースでは、組織コードや社員番号等、LDAP標準スキーマをそのまま使って運用することは少ないと思います(というかそうしている例を知らない)。そこで、今回は一つのオブジェクトクラスに一つの属性を持ったスキーマを追加で作成します。
セットアップが正常に完了すれば、ディレクトリサーバが起動していますので、まずは以下のようにスキーマ定義ファイルを作成します。

addschema.ldif
1: # "mognetid"という名前で、文字列(UTF-8)を値に持つ属性(attributeTypes)を追加する
2: dn: cn=schema
3: changetype: modify
4: add: attributeTypes
5: attributeTypes: (
6:   1.1.2.1.1-myOwnSchema
7:   NAME 'mognetid'
8:   DESC 'mognet.netextended attribute'
9:   EQUALITY caseIgnoreOrderingMatch
a:   ORDERING caseIgnoreMatch
b:   SUBSTR caseIgnoreSubstringsMatch
c:   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 USAGE userApplications )
d: 
e: # "mognetobject"という名前で、inetOrgPersonオブジェクトクラスを拡張した、
f: # 属性"mognetid"を必須値に持つオブジェクトクラスを追加する
g: dn: cn=schema
h: changetype: modify
i: add: objectclasses
j: objectclasses: (
k:   1.1.2.2.1-myOwnSchema
l:   NAME 'mognetobject'
m:   DESC 'mognet extended objectclass'
n:   SUP inetOrgPersono
o:   STRUCTURAL
p:   MUST mognetid )
q: 

説明の都合で行番号(1~q:)を記載しています(ていうかこれ、マークダウンインタープリタで自動でやってくれないかなぁ。。。)。一応解説しておくと、まず2〜c行目までで一つの属性(属性名:mognetid)を追加しています(RDBでいうところのカラムに近いです)。6行目は当該属性のOID(Object IDentifier)2を記載します。本来であればIETFあたりが管理していますが(だったかな?)、検証用の独自属性なので適当なIDをいれておきます。9〜b行目は照合規則等の指定です。c行目でシンタックス、つまりこの属性の構文(データ型)を指定しています(RDBでいうところの、カラムの型に近いです)。この場合は、SYNTAXOID1.3.6.1.4.1.1466.115.121.1.15なので、Directory String syntaxになります(UTF-8の文字列だと思えばよいです)。

g行目からがオブジェクトクラスの追加です。既存の標準オブジェクトクラスに、追加した独自属性を持たせることも物理的には可能ですが、移植性等の問題からあまりというかほとんど禁じ手に近いです。普通は、このように別のオブジェクトクラスを作成します。今回はn行目にある通り、inetOrgPersonオブジェクトクラスを拡張し(Javaのクラスの拡張(extend)同様、子のオブジェクトクラスは暗黙的に親オブジェクトクラスの属性を継承します。is Aの関係です)、p行目でさきほど追加したmognetid属性をMUSTで持つ、l行目mognetobjectというオブジェクトクラスを追加します。

なお、LDIF形式はディレクトリサーバとの通信で使用するディレクトリ操作言語(SQLのような規格)ですが、スペースや空行の扱いが結構厳格です。基本形はaaa: bbbですが、ここでaaaが属性、bbbが値になります。値を複数行で書く場合は、2行目以降の先頭にスペース文字が一つ必要です。さらに、attributeTypes属性の構文として、たとえば(OIDの間には一つのスペースがなくていはいけない、などのルールもあります。なので、6〜c行目とk〜p行目ではあえて行頭に2個の半角スペースをいれています。

ひと昔まえによくldifを触っていましたが、そのときは各エントリ間は\n-\nで区切らなければいけなかった気がします。なぜかそれをいれたら弾かれたのでいれていません。もしかしたら他の構文エラーがあって弾かれたのを勘違いしただけかもしれません。上の表記でうまくいかない場合は、d行目の後ろに-だけの行ともう一つ空行をいれてください。さらにいうと、LDAPサーバには基本的に1操作単位でしかトランザクションの概念がありません(一つの変更に対してはほとんどの実装でATOMIC性があります)。このLDIFの場合、一つのエントリ(dn: cn=schemaエントリ)を2回modifyしますので、2トランザクションになります。一件ずつコミットされますので、同じLDIFを複数回実行することはできません(同じattributeTypesaddすることはできません)。
bash
mac:$ ./bin/ldapmodify -p 38900 -D cn=admin -w password -f ./addschema.ldif

無事に正常終了すればスキーマ拡張は完了です。このオブジェクトクラスを持つエントリー(ユーザ)を以下のldifで定義して同じように追加します。

alto.ldif
dn: uid=alto,ou=People,dc=mognet,dc=net
objectClass: mognetobject
mail: alto@mognet.net
departmentNumber: 0
carLicense: n/a
employeeNumber: 100
telephoneNumber: 0120123456
givenName: Alto
mognetid: alto1
displayName: alto the sweet
sn: Benjiamin
cn: Jiang Benjiamin Alto of Mountfield
businessCategory: sheep dog
description: lovely dog
description: hi! i'm alto the border collie
userPassword: password
l: Tokyo

ディレクトリに登録します。

mac:$ ./bin/ldapmodify -p 38900 -D cn=admin -w password -f ./alto.ldif
mac:$ ./bin/ldapsearch -p 38900 -D cn=admin -w password -b dc=mognet,dc=net objectclass=mognetobject

これでエントリの検索に一件ヒットすればOKです。今作ったユーザが、実際にシングルサインオンシナリオで使用するユーザなので、特にuiduserPasswordの値は手元に控えておきます。ついでに、検索した結果のuserPassword属性の値がハッシュ値になっていることも確認できます。多くのディレクトリサーバの実装では、設定値passwordStorageSchemeに則って、平文ではなくハッシュ値を格納します(逆にいえばこのスキームをtext-plainなどとすれば、平文で格納することができる実装もあります)。また、登録時にはmognetobjectオブジェクトクラスしか指定していなかったのに、検索すると親オブジェクトクラスであるinetOrgPersonなども追加されているのがわかります。

OpenAMインストール

OpenDJと同様に、ForgeRock社の公式サイトからOpenAMをダウンロードします。
スクリーンショット 2018-01-24 18.57.35.png
"Item..."の後ろにSのマークがついているものはサブスクリプション(有償)版です。今回は13.0.0を選択します。スクリーンショット 2018-01-24 18.58.36.pngTomcatにデプロイするので、war形式のファイルをダウンロードします。ダウンロードしたら、名前をopenam.warに変更して、先にインストールしたTomcatのドロップインディレクトリ(~/openAM/tomcat/webapps)に移動またはコピーします。

IdP環境構築

さて、ここからはIdPに必要なサービスを起動して、初期設定をやっていきます。openDJhttpdtomcatを一つずつ落としあげするのは面倒なので、以下のようなコントロールスクリプトを作っておきます(エラー処理等省略)。

/usr/local/bin/openamctl
#!/bin/sh

OPENAM_HOME=/Users/piro/openAM
export CATALINA_HOME=${OPENAM_HOME}/tomcat

case "${1}" in
    start) 
    ${OPENAM_HOME}/opendj/bin/start-ds
    ${CATALINA_HOME}/bin/startup.sh 
    sudo /usr/local/bin/apachectl start
    ;;
    stop)
    sudo /usr/local/bin/apachectl stop
    ${CATALINA_HOME}/bin/shutdown.sh
    ${OPENAM_HOME}/opendj/bin/stop-ds
    ;;
    *)
    echo "Usage: ${0} start|stop"
    ;;
esac

パスワードなしでsudoできるようsudo visudopiro ALL=NOPASSWD: /sbin/ifconfig,/usr/bin/vi,/usr/local/bin/apachectl
のように/usr/local/bin/apachectlをパスワードなしでsudo実行できるようにしておきます。それでは、IdPを起動します。

mac:$ openamctl start

起動できたらブラウザを立ち上げ、https://www.alto.net/openamにアクセスします。初期設定ウィザード画面になりますので、カスタム設定(スクショ撮り忘れているので項目名違うかも)で新規作成していきます。

設定項目 設定値 備考
adminユーザ名 amadmin
adminパスワード p@ssw0rd
サーバURL https://www.alto.net/
Cookieドメイン www.alto.net Tomcat 8以降のバージョンでは、.(ドット)から始まるCookieドメイン名は使用不可
プラットフォームロケール ja_JP
設定ディレクトリ /Users/piro/openAM/config 事前に作成不要。存在する場合は空ディレクトリでないとNG
設定データストア OpenAM OpenAM自体の情報を格納するディレクトリサーバ
ポート 52389 デフォルト
管理者ポート 7444 デフォルト
JMXポート 3689 デフォルト
暗号化キー [ランダム文字列]
ルートサフィックス dc=mognet,dc=net ユーザリポジトリLDAPサーバのルートDN
デフォルトポリシーエージェント(UrlAccessAgent) password adminパスワードと同じ文字列は不可

初期設定ができたらログイン画面が表示されます。ここから具体的な設定に入ります。amadminユーザでログインしてください。

LDAP属性設定

まずはOpenAMに今回使用するユーザリポジトリのスキーマ情報を定義します(初期設定ではinetOrgPersonの一部属性しか認識しません)。"Top Level Realm"に入ります(Realmとは抽象的な概念ですが、認証が通る境界内の名前空間といったニュアンスです。認証ドメインといってみてもいいかもしれません)。
スクリーンショット 2018-01-24 21.31.21.png
「Data Sources」に入ります。
スクリーンショット 2018-01-24 21.31.34.png
「OpenDJ」に入ります。
スクリーンショット 2018-01-24 21.31.42.png
「ユーザー設定」のセクションに、「LDAP ユーザーオブジェクトクラス」にmognetobjectオブジェクトクラスを追加します。また、同様に「LDAP ユーザー属性」に、以下の属性が登録されている状態にします。

userPassword
telephoneNumber
mail
displayName
givenName
mognetid
description
dn
cn
l
carLicense
employeeNumber
uid
departmentNumber
businessCategory
sn

スクリーンショット 2018-01-24 21.32.11.png
設定したらページ上部にある「保存」ボタンを押します。

IdP鍵と証明書の設定

割と前に作った鍵と証明書をOpenAMに登録します。PKCS12キーストアフォーマットには対応していないようなので、jrekeytoolコマンドを使って、まずはJKSタイプのキーストアを作成します(keytoolのインポートコマンドでPKCS12JKSに変換)。

mac:$ cd ~/openAM/config/openam; pwd
mac:$ mkdir private
mac:$ cd private; pwd
mac:$ keytool -importkeystore -srckeystore ~/openAM/work/idp.p12 \
>     -srcstoretype PKCS12 -destkeystore keystore.jks -deststoretype JKS \
>     -storepass changeit  # パスワードは適宜で設定
mac:$ keytool -changealias -alias 1 -keystore ./keystore.jsk -storepass changeit # キーストア内のエイリアス名を"idp"に変更
mac:$ keytool -list -v -keystore ./keystore.jks -storepass changeit              # 一覧出力

キーストアと秘密鍵にはパスワードがかかっているので、このままではOpenAMから鍵を使えません。そこで、同じディレクトリに暗号化したパスワード情報を格納しておきます。パスワードの暗号化は以下のURLで行います(amadminユーザでログイン済みでないとページを開けません)。
https://www.alto.net/openam/encode.jsp
フォームにパスワード平文を入力して送信すれば、暗号化されたパスワードが画面に出力されます。
スクリーンショット 2018-01-24 22.13.38.png
ちなみに、暗号化キーはセットアップ時にランダムで設定されます。

ファイルに書き出します。末尾に改行が入らないよう、echoではなくprintfをおすすめします。ファイル名は任意ですが、以降は記載のファイル名で作成した前提で進めます(今更ですが)。

mac:$ printf [出力されたキーストアパスワード暗号化文字列] > .storepass
mac:$ printf [出力された秘密鍵パスワード暗号化文字列]    > .keypass

Webコンソールに戻ります(セッションが切れている場合は再ログインします)。画面上部のナビメニューから「CONFIGURATION」に入ります。スクリーンショット 2018-01-24 22.17.46.png「サーバーおよびサイト」に入ります。スクリーンショット 2018-01-24 22.19.37.png「デフォルトのサーバー設定値」に入ります。スクリーンショット 2018-01-24 22.20.37.png「セキュリティー」に入ります。スクリーンショット 2018-01-24 22.24.05.png「キーストア」セクションにて、前の手順で作成したキーストアの情報を設定します。

設定項目 設定値
キーストアファイル %BASE_DIR%/%SERVER_URI%/private/keystore.jks
キーストアパスワードファイル %BASE_DIR%/%SERVER_URI%/private/.storepass
非公開鍵パスワードファイル %BASE_DIR%/%SERVER_URI%/private/.keypass
証明書エイリアス idp

ページトップの「保存」を押します。

IdPの作成

Webコンソールから、「Top Level Realm」に入ります。スクリーンショット 2018-01-24 21.31.21.png「Create SAMLv2 Providers」に入ります。スクリーンショット 2018-01-24 22.43.43.png「Create Hosted Identity Provider」に入ります。スクリーンショット 2018-01-24 22.44.52.png「このプロバイダのメタデータがありますか?」が「いいえ」になっていることを確認しつつ値を入れます。変更が要るのは「署名鍵:keytoolで指定したエイリアス名」と「新しいトラストサークル」くらいです。トラストサークル3は任意の名前でOKです。ここではmognettrustcircleとしています。スクリーンショット 2018-01-24 22.46.11.png
入力できたら「設定」を押します。

SAMLの設定

IdPがSPに渡すSAML文書に載せるユーザ属性(SAML属性、"SAML Attribute")を設定します。スクリーンショット 2018-01-24 23.00.09.pngコンソールトップから「FEDERATION」に入ります。エンティティプロバイダのIdPのURLを押します。スクリーンショット 2018-01-24 23.01.11.png「表明処理」に入ります。スクリーンショット 2018-01-24 23.03.03.png「属性マッパー」セクションの「属性マップ」を以下のように設定してください。なお、画面内に注記されている通り、SAML属性名=LDAP属性名という形式でマッピングしていきます。ここを設定しないとSAMLのアサーションに属性が載りません。

CommonName=cn
MognetId=mognetid
SirName=sn
BusinessCategory=businessCategory
CarLicense=carLicense
DepartmentNumber=departmentNumber
Description=description
DisplayName=displayName
EmployeeNumber=employeeNumber
GivenName=givenName
Locality=l
MailAddress=mail
TelephoneNumber=telephoneNumber
UserId=uid
UserPassword=userPassword

入力できたら「保存」します。ここで、敢えてuserPassword属性もSAML属性「UserPassword」として送ることにしていますが、実際はこんなことはしないでしょう。そもそもLDAP上はハッシュ値しか持っていませんし。今回は実験なので、このようにしていますが、本来SSOシナリオにおいてユーザの認証に必要なデータ(パスワードの情報)をSPに送るのはアンチパターンですね。

これでIdPとしての設定は完了です。次回はSP環境を構築していきます。

参考文献


  1. 正式なドキュメントはOASIS文書を参照 

  2. 実際に、認証されたユーザを具体的にどのロールにマッピングするかはSPに委ねられます。WASでは「特殊サブジェクト("special-subject")」として、「全員("EVERYONE")」、「全ての認証ユーザ("ALL_AUTHENTICATED_USERS")」、および「信頼されたレルム内で認証されたユーザ("ALL_AUTHENTICATED_USERS_IN_TRUSTED_REALMS")」に対してロールをマッピングできます。 

  3. トラストサークルは、OpenAM用語です。レルムのなかに存在する、信頼関係にあるIdPやSPといったエンティティの集まり(サークル)という解釈でよいと思います。