AWS Application Load Balancer (ALB) の認証機能をKeycloakと使う

# はじめに

2018/05/30に、丁度日本でのAWS Summit開催中にALBの組み込み認証機能がリリースされました。この機能を使うと、Amazon Cognito または OpenID Connect (以下、OIDC)に準拠したOpenID Provider (Identity Provider、IdP)と組み合わせることで、ALB配下のアプリケーションを保護することができます。詳しくは、本家のBlog記事の以下を参照されると良いでしょう。

 * [Simplify Login with Application Load Balancer Built-in Authentication](https://aws.amazon.com/jp/blogs/aws/built-in-authentication-in-alb/)
   * 翻訳バージョンはこちら→ [Application Load Balancer 組み込み認証によりログインを簡略化](https://aws.amazon.com/jp/blogs/news/built-in-authentication-in-alb/)

本記事では、この新機能をOIDCに準拠しているOSSのIdentity and Access Managementのソフトウェアである[Keycloak](https://www.keycloak.org/)と組み合わせて使う方法について説明します。丁度最近、[Keycloakのメーリングリストに動かなくて困っている人の投稿](http://lists.jboss.org/pipermail/keycloak-user/2018-July/014702.html)を目にしたので書き記しておきます。

なお、Keycloak以外をIdPとして使用してみた日本語記事が既にクラスメソッドさんにて書かれているので、GoogleやOneLoginをIdPとして使いたい方はこちらを参照すると良いでしょう。

 * [【新機能】AWS ELBのApplication Load Balancer(ALB)の認証機能でWebアプリにGoogle認証を追加する](https://dev.classmethod.jp/cloud/add-google-authenticate-your-webapp-on-alb/)
 * [OneLogin(OIDC)でALB認証機能を使ってみた](https://dev.classmethod.jp/cloud/aws/onelogin-oidc-alb-auth/)

# 最初に残念なお知らせ

* 現状はそのままでは残念ながらKeycloakと組み合わせることができない。
* 原因はAWS ALB側にある (フォーラムに投稿した[バグレポート](https://forums.aws.amazon.com/message.jspa?messageID=858769#858769))。 
  * ALBはToken Endpointのレスポンスに含まれる `token_type` が `Bearer` であることを期待している。
  * 一方、Keycloakはすべて小文字の `bearer` を返す (GitHubなんかも同様っぽい)
  * OAuth 2.0の仕様としては、[RFC 6749 の 4.2.2.  Access Token Response](https://tools.ietf.org/html/rfc6749#section-4.2.2)に記載の下記の通り、大文字小文字を区別はしないが、ALBは区別して処理してしまっている。結果、ALBにて500エラーが発生してしまう。

    >   token_type
    >         REQUIRED.  The type of the token issued as described in
    >         Section 7.1.  Value is case insensitive.

* 現状のALBをKeycloakと組み合わせるには、Keycloak本体を改修するか、フロントにApache HTTPD Serverなどを立ててレスポンスを書き換えてあげる必要あり。

**2017/07/30 追記: フォーラムに投稿した[バグレポート](https://forums.aws.amazon.com/message.jspa?messageID=858769#858769)にAWSから以下のレスポンスがあり、近々修正されるかもしれません。そうすると上記のワークアラウンドは不要になるはず。**

> We are already tracking this issue internally. Currently the service team is working on deploying the fix. 

# 設定方法

設定として必要な

* Keycloak側の設定
* Keycloak前段のApache設定 (現時点でのワークアラウンド例)
* AWS ALB側の設定

を説明していく。

## Keycloak側の設定

* 適当なレルムでOIDC用のクライントを適当なクライアントIDで作成する(下図では`oidctest`としている)。
* 下図のように`Access Type`を **confidential** としつつ、`Valid Redirect URIs`に保護するアプリケーションのURIを設定し、一度 `Save` ボタンをクリックして保存する。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/35233/d638b14e-ddc7-17ae-dfcf-3bae3891676a.png)

* また、`Credentials` タブを開いて、クライアントシークレットである `Secret` の値を控えておく (ALB側の設定で使用する)。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/35233/99b14891-fbd7-3c84-3673-12add7111f4a.png)

## Keycloak前段のApache設定 (現時点でのワークアラウンド例)

最初に述べたとおり、AWS ALB側がOAuth 2.0の仕様どおりとして動いていないため500エラーとなってしまう。これを回避するためのワークアラウンド例として、KeycloakのフロントにApache HTTPD Serverを立て、`Substitute`ディレクティブを設定して `Bearer` に置換している。

```
<VirtualHost *:80>
    ServerName    test-sso.･･･

    ProxyPass / http://localhost:8080/
    ProxyPassReverse / http://localhost:8080/
    ProxyErrorOverride off

    AddOutputFilterByType SUBSTITUTE application/json
    Substitute s/"token_type":"bearer"/"token_type":"Bearer"/n
</VirtualHost>
```

## ALB側の設定

ALBリスナールールのOIDC認証設定にて、Keycloakの情報を設定する。

* クライアントIDには、Keycloak側で作成したクライアントの`Client ID`を入力する。
* クライアントのシークレットには、控えたクライアントシークレット値を入力する。
* 発行者、認証エンドポイント、トークンエンドポイント、ユーザー情報エンドポイントについては、使用するKeycloakのレルムのOpenID Provider情報を参照して設定する。*https://\<Keycloakホスト>/realms/<使用するレルム名>/.well-known/openid-configuration* でJSONにて参照できるので、JSON中の下記項目をそれぞれ設定すれば良い。
  * 発行者: `"issuer"` の値
  * 認証エンドポイント: `"authorization_endpoint"` の値
  * トークンエンドポイント `"token_endpoint"` の値
  * ユーザー情報エンドポイント `"userinfo_endpoint"` の値

![image.png](https://qiita-image-store.s3.amazonaws.com/0/35233/4244a127-7e8d-d569-4425-00c39c097fde.png)

# 動作確認

アプリケーションにアクセスすると、Keycloakの該当レルムのログイン画面にリダイレクトされます。別途該当レルムにユーザーを作成しておきログインすると、無事にアプリケーションの画面にアクセスすることができるようになります。もし、Apacheにて置換処理を入れていない場合は残念ながら現状はALBが500エラーを返してしまいます。

# 余談

KeycloakはOIDC準拠だし余裕で連携できるでしょ、と試してみたものの全然動かなくて原因を切り分けるのに苦労しました。Keycloak側はToken Endpointを200ステータスで正常に返しているだけであり、ALB側のアクセスログを参照しても詳細なエラー情報は記録されていないので一体何が悪いのか当初はさっぱり分からず状態でした。
結局、GolangでダミーのオレオレOpenID Providerを実装してみてKeycloakと同じ様なレスポンスを返すようにしつつ、内容をちょっと変えてみながら動作確認していたところ`token_type` が `Bearer`でないとALBの認証機能が動作しないことが分かりました。