前振り
私が担当したシステムでは、KeycloakのIdentity Broker
機能を使用し、外部Identity Provider(以下外部IdP)を経由したログインを実施しています。
Identity Broker機能とは、KeycloakがService Provider(以下SP)となることで、OpenID Connect
、SAML
、OAuth
といった各種プロトコルに対応した外部IdPのアカウント情報を利用して、Keycloakと連携しているアプリケーションにSSOするという機能です。
本記事では、これらプロトコルのうち、SAMLを題材としています。
今回、このシステムを改修し、KeycloakがSAMLレスポンスを受け取った後のコールバックメソッドをカスタムして独自の処理を実行することになりました。
具体的には、以下図のNo.8
の箇所、外部IdPからレスポンスを受け取った後の処理です。
ここでは、レスポンスの内容を基にKeycloakの内部的な認証処理を実施しています。
このタイミングでユーザーの認証情報に対し、特定の属性をアタッチするよう修正を加えました。
各フローの具体的内容はこちらのドキュメントをご確認ください。
本題
さて、本改修の動作確認をローカル環境で実施するにあたり、SAMLレスポンスを返してくれる外部IdPが必要です。
本番環境では、ADFSなどの外部IdPを使用していますが、ローカルでの動作確認用に同等のものを用意するのは難しいです。
そこで、今回は簡単に用意できるSAMLのIdPについて考えてみます。
前提条件
- SP(Keycloak)にSAMLでログインできること
- SP上(Keycloak上)の既存のユーザー「
test@example.com
」とアカウントリンクできること- アカウントリンクは、SPのユーザー名 = IdPのユーザー名でのマッピングとする
- JITプロビジョニングは行わない。SP、IdPに同名のユーザーが存在することを前提とする
- 本システムでは、SPに同名のユーザーが存在しない場合
エラーとしているため
- 本システムでは、SPに同名のユーザーが存在しない場合
備考
- SP(Keycloak)のレルムは
main
レルムとする - Keycloakのバージョンは、本記事公開時点で最新の
11.0.3
を使用している
解その1 Keycloak(同一インスタンス上)を使う
Keycloak自身を外部IdPとみなす方法です。
Keycloakでは、レルム
という単位でアカウント情報、セッション情報、連携先アプリケーションの情報、認証方法などを管理しています。
レルムが異なると、同一のインスタンスにおいても別のIdPとみなすことが可能です。
一応、同一のレルムでもIdPとして設定可能ですが、既にログイン済みである旨の警告メッセージが表示されてしまいます。
手順
1.1 外部IdP用レルム作成(外部IdP)
まずは別レルムを作成します。
今回はtest
レルムと名付けました。
1.2 matadata取得(外部IdP)
レルム作成後、以下のURLからIdPのmetadataを取得します。
https://<Keycloak-host>/auth/realms/test/protocol/saml/descriptor
ブラウザから取得するより、curlで取得するほうが容易です。
$ curl https://<Keycloak-host>/auth/realms/test/protocol/saml/descriptor > metadata.xml
1.3 外部IdPの定義作成(SP)
SPとするmain
レルムの作業となります。
手順1.2で取得したmetadata.xmlをSPにインポートし、外部IdPの定義を作成します。
インポート手順ですが、Identity ProvidersにSAML
を選択し、metadata.xml
をインポートします。
すると、metadata.xmlの定義内容で設定が作成されます。
1.3.1 外部IdP定義の設定について
ここで、以下の設定を確認し、必要に応じて変更します。
前提条件で述べた通り、SPのユーザー名とIdPのユーザー名でマッピングを行いたいのでNameID Policy Format
には、Unspecified
を設定します。
設定名 | 設定内容 |
---|---|
NameID Policy Format | Unspecified |
次に、以下はSAMLリクエストの署名と検証です。
この設定は、本番環境の外部IdPと設定を揃えたい場合には設定が必要となります。
(ローカル環境での動作確認では、必ずしも必要な設定ではありません)
設定名 | 意味 |
ON となるケース |
---|---|---|
Want Assertions Signed | SAMLリクエストの署名有無 | 外部IdP側の設定によっては、リクエストの発行を信頼済みのSPにのみ許可するケースがあり、その場合、この設定はON にする必要があります。 |
Want Assertions Encrypted | SAMLリクエストの暗号化 | 何等かの秘匿データをSAMLリクエストに含める場合、この設定はON にする必要があります。 |
Validate Signature | SAMLレスポンスの署名検証 | レスポンスの署名検証を行わないと、アサーションの改ざんを検知できなくなり容易になりすましログインが可能となるため、この設定はON となっているべきです。 |
これらの設定が完了したら、保存します。
1.4 matadata取得(SP)
exportタブから、SPのmetadata(keycloak.txt
)をダウンロードします。
SP側の準備はこれで完了です!
1.5 クライアント(SPの定義)作成(外部IdP)
test
レルムに戻り、手順1.4でダウンロードしたSPのmetadata(keycloak.txt
)をインポートして、クライアントを作成します。
これが、IdPに対するSPの定義となります。
1.6 SPに連携するユーザーを作成(外部IdP)
test
レルム上にユーザーの作成と、パスワードの設定を実施します。
前提条件で述べた通り、SPのユーザー名と同一となるtest@example.com
とします。
これでIdP側の準備も完了です!
ログインしてみる
ログイン先は、Keycloakのユーザー用管理コンソールとします。
まず、https://<Keycloak-host>/auth/realms/main/account
にアクセスし、画面右側の外部IdP用ログインリンクを選択します。
外部IdPであるtest
レルムのログイン画面にリダイレクトするので、test@example.com
でログインします。
test@example.com
としてSPへのログインが成功し、連携先アプリケーション(管理コンソール)にリダイレクトしました。
思うところ
ここまでの手順で、1つのKeycloakインスタンスで外部IdPを使用したSP(Keycloak)へのログインの動作確認が可能となりました。
なのですが、この環境には以下の制約が存在します。
- ユーザーをSP、外部IdPのそれぞれに都度作成しなければならない
- 手間なので、片方のみの作成としたい
- ただし、ユーザー・ストレージ・フェデレーション機能を使用しているのであれば、SP、外部IdPで同一の設定を使用すればよいのでその限りではない
- KeycloakデフォルトのBrowser Flowでは、ログイン時、ログインIDとパスワードを入力する必要がある
そこで、前提条件を満たせつつ、IdP側のユーザ管理が不要で、かつ使いやすいモジュールを探したところ、saml-idpを発見しました。
解その2 saml-idpを使う
saml-idpは、nodeで書かれたSAML IdPとして動作するモジュールです。
Dockerfileがあらかじめ用意されているため、導入が容易で、設定もシンプルです。
手順
本手順では、予め用意されたDockerfileを使用して、saml-idpを構築します。
2.1 クローン
まずはgit clone
します。
# git clone https://github.com/mcguinness/saml-idp
Cloning into 'saml-idp'...
remote: Enumerating objects: 12, done.
remote: Counting objects: 100% (12/12), done.
remote: Compressing objects: 100% (9/9), done.
remote: Total 481 (delta 4), reused 6 (delta 3), pack-reused 469
Receiving objects: 100% (481/481), 1.43 MiB | 1.37 MiB/s, done.
Resolving deltas: 100% (249/249), done.
このようなファイル構成となっています。
# cd saml-idp/
# ll -rt
合計 112
-rw-r--r-- 1 root root 1448 7月 2 12:25 config.js
drwxr-xr-x 2 root root 20 7月 2 12:25 bin
-rw-r--r-- 1 root root 26281 7月 2 12:25 app.js
-rw-r--r-- 1 root root 10597 7月 2 12:25 README.md
-rw-r--r-- 1 root root 1082 7月 2 12:25 LICENSE
-rw-r--r-- 1 root root 473 7月 2 12:25 Dockerfile
drwxr-xr-x 4 root root 29 7月 2 12:25 public
-rw-r--r-- 1 root root 919 7月 2 12:25 package.json
-rw-r--r-- 1 root root 41871 7月 2 12:25 package-lock.json
drwxr-xr-x 2 root root 36 7月 2 12:25 lib
-rw-r--r-- 1 root root 1184 7月 2 12:25 idp-public-cert.pem
-rw-r--r-- 1 root root 1679 7月 2 12:25 idp-private-key.pem
-rw-r--r-- 1 root root 65 7月 2 12:25 docker-compose.yml
drwxr-xr-x 2 root root 101 7月 2 12:25 views
drwxr-xr-x 2 root root 108 7月 2 12:25 test
2.2 設定修正
必要に応じてconfig.js
を修正します。
今回修正したのは以下の設定です。
設定名 | 設定値 | 備考 |
---|---|---|
userName | test@example.com |
SP(Keycloak)上のユーザーを設定 |
nameIdFormat | urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified |
SPのユーザー名とIdPのユーザー名でマッピングを行いたいのでUnspecified とする |
/**
* User Profile
*/
var profile = {
userName: 'test@example.com',
nameIdFormat: 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
firstName: 'Saml',
lastName: 'Jackson',
displayName: 'saml jackson',
email: 'saml.jackson@example.com',
mobilePhone: '+1-415-555-5141',
groups: 'Simple IdP Users, West Coast Users, Cloud Users'
}
2.3 Dockerfileの修正、ビルド
Dockerfileを修正し、nodeのバージョンを固定並びに実行時引数を追加、修正します。
FROM node:12.18.1-buster
ADD ./package.json package.json
RUN npm install
EXPOSE 7000 7000
# ADD ./node_modules node_modules
ADD ./lib lib
ADD ./views views
ADD ./app.js app.js
ADD ./config.js config.js
ADD ./idp-public-cert.pem idp-public-cert.pem
ADD ./idp-private-key.pem idp-private-key.pem
ADD ./public public
ENTRYPOINT [ "node", "app.js", "--acs", "https://<Keycloak-host>/auth/realms/main/broker/saml/endpoint", "--aud", "https://<Keycloak-host>/auth/realms/main", "--host", "0.0.0.0"]
ビルドします。
docker build . -t saml-idp
2.4 起動
docker-composeで起動します。
デフォルトの設定では、コンテナ側のポートは7000を使用しています。
saml-idp:
container_name: saml-idp
image: saml-idp
ports:
- 7000:7000
network_mode: bridge
extra_hosts:
- keycloak.path.local:192.168.2.13
- hoka.nanika.areba.path.local:192.168.2.14
# docker-compose up -d saml-idp
以下のログが出力されていれば起動完了です。
# docker logs saml-idp
-- 省略 --
IdP server ready at
http://f6cdd171af63:7000
2.5 リバースプロキシ作成
saml-idpにアクセスするためのリバースプロキシを設定します。
以下はApacheの場合の設定例となります。
<VirtualHost _default_:443>
ServerName test.saml.local
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Port 443
SSLEngine on
SSLProxyEngine On
SSLCertificateFile /root/certs/server.crt
SSLCertificateKeyFile /root/certs/server.key
ProxyPreserveHost On
ProxyPass / http://localhost:7000/
ProxyPassReverse / http://localhost:7000/
</VirtualHost>
2.6 metadata(外部IdP)取得
以下のURLからmetadataを取得します。
https://<saml-idp-host>/metadata
$ curl https://<saml-idp-host>/metadata > metadata.xml
これで、saml-idp側の準備は完了です。
なお、SP(Keycloak)のmetadataをsaml-idpにインポートする必要はありません。
2.7 外部IdPの定義作成(SP)
SP(Keycloak)側で外部IdP(saml-idp)の定義を作成します。
設定については、手順1.5と同様にmetadata.xml
をインポートして外部IdP定義を作成すればよいので省略します。
ログインしてみる
ログイン先は、Keycloakのユーザー用管理コンソールとします。
https://<Keycloak-host>/auth/realms/main/account
にアクセスし、画面右側の外部IdP用ログインリンクを選択します。
外部IdPであるsaml-idpにリダイレクトするので、右下のSign in
を押してログインします。
SPにログインが成功し、連携先アプリケーション(管理コンソール)にリダイレクトしました。
なお、Subject NameID
を変更すると、別ユーザーとしてログインすることができます。
終わりに
SAMLのテスト用IdPは、今回紹介した二つに限らず、数多く存在します。
例えば、テスト用IdPのオンラインツールが存在します。
自身のユースケースに沿ったテストツールを探してみると面白いかもしれません。