Keycloakでリバプロ型構成を組んでみる（Security Proxy編）

# 今日やること

Keycloakアドベンドカレンダー7日目の今回は、KeycloakのSecurity Proxyを試してみます。

Security Proxyは、Javaで実装されたシンプルなリバースプロキシ(以後、リバプロ)サーバーの一種です。このリバプロサーバー自体がKeycloakのクライアント・アダプターの代わりとなり、プロキシ先のコンテンツの保護や、URLごとのアクセス制御を行うことが可能になります。
アプリケーションサーバーがOpenID ConnectやSAMLに対応していない場合や、クライアント・アダプターの導入が難しい場合、もしくはクライアント・アダプターが対応していない環境の場合の利用に適しています。

主な機能は以下のとおりです。

- HTTP(S)のリバプロ機能
- KeycloakサーバーとのOpenID Connect連携
- 特定パスごとのコンテンツ保護
    - 認証要求によるアクセス制御
    - 特定のHTTPメソッドの許可、拒否
    - 認証済みユーザーのロールによるアクセス制御
    - 特定URLのアクセスを拒否
    - 認証不要でアクセスできるURLの指定
- HTTPヘッダによる認証済みユーザーの情報連携

今回はTomcat 8の素のサンプルアプリに対して、Security Proxyで保護をかける際の手順を試してみます。

# 動作確認用のサーバー構成

今回のお試し環境はこのような感じです。
Keycloakサーバーには、`demo`レルムが作成されており([3日目の記事を参照](https://qiita.com/tamura__246/items/4290a1035e1adcab733b))、以下のようなロールをもつユーザーが存在していることが前提となります。

|FQDN|OS|JDK|構成|LISTEN ポート|
|:---|:---|:---|:---|:---|
|kc-server.example.com|CentOS 7.4.1708|OpenJDK 1.8.0.151|・Keycloakサーバー 3.3.0.CR2|8443|
|kc-proxy.example.com|CentOS 7.4.1708|OpenJDK 1.8.0.151|・Security Proxy 3.3.0.CR2<br>・Tomcat 8.5.23|・18443(Security Proxy)<br>・8080(Tomcat)<br>|

|ユーザー|ロール|
|:---|:---|
|user001|user|
|admin001|user,admin|

Tomcatに付属するexapmlesアプリケーションでは、以下のようなアクセス制御をかけることを想定します。

![proxy_acl01.jpg](https://qiita-image-store.s3.amazonaws.com/0/209129/c7a84b54-0ffa-045c-912b-e65157e44469.jpeg)

動作確認時の各サーバーへのアクセスポートは以下のとおりです。
![proxy_port01.png](https://qiita-image-store.s3.amazonaws.com/0/209129/b8a540bd-8c62-fa87-d67f-355961a1f6a2.png)



# Keycloakサーバー側の設定
## クライアントの追加・設定
1. Keycloakの管理コンソールにログインします。
1. 左メニューバーから、`demo`レルムを選択します。
1. 左メニューバーで `クライアント` をクリックします。クライアント一覧が表示されます。
2. 右上の `作成` ボタンを押下します。
3. 以下のようなクライアント設定値を入力し、`保存` ボタンを押下します
 - クライアントID: kc-proxy
 - クライアントプロトコル: openid-connect
 - ルートURL: https://kc-proxy.example.com:18443
![proxy01.png](https://qiita-image-store.s3.amazonaws.com/0/209129/b14fa4cd-8df4-b44a-597f-ad945f75ef04.png)
4. 引き続き kc-proxy の設定画面が表示されるので以下のような設定を行い、`保存` ボタンを押下します。
 - アクセスタイプ: confidential
 - 有効なリダイレクトURI(1つ目): https://kc-proxy.example.com:18443/
 - 有効なリダイレクトURI(2つ目): https://kc-proxy.example.com:18443/examples/websocket/echo.xhtml
![proxy02_opt.png](https://qiita-image-store.s3.amazonaws.com/0/209129/15af17f5-6ba8-6460-6e9f-125acd01838c.png)
:information_source: `有効なリダイレクトURI`は、Keycloak上ではワイルドカードによる指定も可能です。ただし、OIDCの仕様上では、redirect_uriと厳密に一致することが必須となっているため、ここではリダイレクト先となりえるURLはすべて指定しています。そのため、ここで指定していないURLへは未ログイン状態から直接遷移できません。
5. `クレデンシャル` タブを押下します。
6. `シークレット` として表示されている値を控えます。
![proxy03.png](https://qiita-image-store.s3.amazonaws.com/0/209129/1e5b45b3-64bb-e85c-556e-7d8004075bd0.png)



# Security Proxy側の設定と起動
## サンプルアプリケーションの導入
連携確認用のサンプルアプリケーションとして、Tomcat 8.0を導入して起動しておきます。

```bash
cd  /tmp
wget http://ftp.tsukuba.wide.ad.jp/software/apache/tomcat/tomcat-8/v8.5.23/bin/apache-tomcat-8.5.23.tar.gz
tar zxvf apache-tomcat-8.5.23.tar.gz -C /usr/local
cd /usr/local/apache-tomcat-8.5.23/
./bin/startup.sh
```

## Security Proxyのダウンロードと展開

Security Proxyをダウンロードして `/usr/local` に解凍し、展開したディレクトリに移動しておきます。

```bash
cd  /tmp
wget https://downloads.jboss.org/keycloak/3.3.0.CR2/keycloak-proxy-3.3.0.CR2.zip
unzip keycloak-proxy-3.3.0.CR2.zip -d /usr/local
cd /usr/local/keycloak-proxy-3.3.0.CR2/
```

## Security Proxyの設定ファイル作成

Security Proxyを動作させるために、以下のような設定ファイルを作成します。

```json:/usr/local/keycloak-proxy-3.3.0.CR2/proxy.json
{
    "target-url": "http://localhost:8080",
    "send-access-token": true,
    "bind-address": "{バインドするIPアドレス・DNS名}",
    "http-port": "18080",
    "https-port": "18443",
    "applications": [
        {
            "base-path": "/",
            "adapter-config": {
                "realm": "demo",
                "resource": "kc-proxy",
                "auth-server-url": "https://kc-server.example.com:8443/auth",
                "ssl-required" : "external",
                "credentials": {
                    "secret": "{Keycloakサーバー側のシークレットの値}"
                }
            },
            "constraints": [
                {
                    "pattern": "/*",
                    "authenticate": true
                },
                {
                    "pattern": "/examples/jsp/*",
                    "permit": true
                },
                {
                    "pattern": "/examples/websocket/*",
                    "roles-allowed": [
                        "admin"
                    ]
                }
            ]
        }
    ]
}
```

proxy.json内の設定パラメータの意味ついて、以下に記載します。

### 基本設定
|パラメータ名 |必須|  説明  |
|:-----------------|:-----------------:|:------------------|
| target-url       | ○ | リバプロ転送先URLを指定します。  |
| send-access-token  || KEYCLOAK_ACCESS_TOKENというヘッダ名でアクセストークンをプロキシ先に送信するかどうかを指定します。 |
| bind-address || プロキシサーバーのソケットにバインドするためのDNS名もしくはIPアドレスを指定します。外部から接続する場合は、ループバックアドレス以外を指定します。|
|http-port||HTTPとしてLISTENするポート番号を指定します。ただし、HTTPのアクセスが許可されるかどうかは、後述の`adapter-config`の`ssl-required`の設定値に依存します。|
|https-port||HTTPSとしてLISTENするポート番号を指定します。|
|keystore||HTTPSを受け付ける場合に利用されるキーストアを指定。キーストアを指定しなかった場合は自動生成された一時的な自己署名サーバー証明書が利用されます。|
|keystore-password||キーストアを指定した場合のキーストアパスワードを指定します。|
|key-password||利用されるサーバ証明書のキーパスワードを指定します。|
|buffer-size||HTTPサーバーのソケットバッファサイズを指定します。通常デフォルトのままで問題ありません。|
|buffers-per-region||HTTPサーバーのリージョンごとのソケットバッファ数を指定します。通常デフォルトのままで問題ありません。|
|io-threads||IOを処理するスレッド数を指定します。通常デフォルトのままで問題ありません。|
|worker-threads||リクエストを処理するスレッド数を指定します。通常デフォルトのままで問題ありません。|

### applicationsの設定
|パラメータ名 |必須|  説明  |
|:-----------------|:-----------------:|:------------------|
|base-path|○|アプリケーションのコンテキストルートを指定します。"/"で始まる必要があります。|
|error-page||エラーが発生した場合(アクセスを拒否した場合も含む)に表示するエラーページを指定します。base-pathからの相対パスを指定します。|
|adapter-config|○|後述します。|

### adapter-configの設定

|パラメータ名 |必須|  説明  |
|:-----------------|:-----------------:|:------------------|
|realm|○|Keycloak側のレルム名を指定します。|
|resource|○|Keycloak側のクライアント設定のクライアントID(client-id)を指定します。|
|auth-server-url|○|Keycloak側のベースURLを指定します。[^auth-server-url]|
|credentials|△|Keycloak側のアクセスタイプで"confidential"を指定した場合は必須です。|
|ssl-required||HTTPSでのアクセスを強制するかどうか指定します。"all", "external", "none" のいずれかを指定します。デフォルトは、"external"なので、外部からはHTTPのアクセスは許可されません。|

:information_source: adapter-configの設定は非常に多くあるため、動作確認に必要な最低限の項目のみ記載しています。その他のパラメータに関しては、[参考資料の"Java Adapter Config"](#参考資料)を参照ください。


### constraintの設定
|パラメータ名 |必須|  説明  |
|:-----------------|:-----------------:|:------------------|
|pattern|○|URLパターンを指定。"/"で始まる必要があり、ワイルドカード(*)は最後でのみ指定が可能です。<br>有効なパターン： /foo/bar/\* および /foo/\*.txt<br>無効なパターン： /\*/foo/\*|
|roles-allowed||特定のロールをもつユーザーのみアクセスを許可する設定です。配列で複数の指定可能です。|
|methods||許可するHTTPメソッド名を配列で指定します。|
|excluded-methods||拒否するHTTPメソッド名を配列で指定します。|
|deny||アクセスを拒否する場合にはtrueを設定します(Keycoak Security Proxy経由ではアクセスさせたくないURLなど)。|
|permit||認証不要でアクセス可能にする場合にはtrueを設定します。|
|permit-and-inject||認証不要でアクセス可能にする場合にはtrueを設定します。ただし、認証済みでアクセスされた場合はヘッダにユーザー情報を付加します。|
|authenticate||ロールに関係なく、認証のみでアクセス可能にする場合にはtrueを設定します。|


proxy.jsonを作成したら、以下のコマンドからSecurity Proxyが起動できます。

```bash
cd /usr/local/keycloak-proxy-3.3.0.CR2/
java -jar bin/launcher.jar proxy.json
```

設定ファイルに問題がなければ、以下のような起動メッセージが表示されます。

```
Home directory: /usr/local/keycloak-proxy-3.3.0.CR2
11 24, 2017 6:44:37 午後 org.keycloak.proxy.ProxyServerBuilder initConnections
WARN: Generating temporary SSL cert
11 24, 2017 6:44:45 午後 org.xnio.Xnio <clinit>
INFO: XNIO version 3.3.6.Final
11 24, 2017 6:44:45 午後 org.xnio.nio.NioXnio <clinit>
INFO: XNIO NIO Implementation Version 3.3.6.Final
```

# 動作確認
## 認証が必要なURL
1. 認証が必要な https://kc-proxy.example.com:18443/ にアクセスします。
2. Keycloakに未ログインであるため、ログイン画面にリダイレクトが発生します。
3. ID/PWでログインします。
4. Tomcatのトップ画面が表示されます。
5. https://kc-proxy.example.com:18443/examples/servlets/servlet/RequestHeaderExample にアクセスします。
6. HTTPヘッダにKeycloakのユーザー情報が連携されているのが分かります。
![proxy04.png](https://qiita-image-store.s3.amazonaws.com/0/209129/6a20bde7-392f-23b6-2585-3192e59ea615.png)



## 認証が不要なURL
1. 認証が不要な https://kc-proxy.example.com:18443/examples/jsp/ にアクセスします。
2. 認証していなくてもページを参照できるのが分かります。
![proxy05.png](https://qiita-image-store.s3.amazonaws.com/0/209129/d9f489f6-ee79-67f7-6223-785336d97a1d.png)



## admin ロールが必要なURL
1. adminロールが必要な https://kc-proxy.example.com:18443/examples/websocket/echo.xhtml にアクセスします。
3. adminロールを持つユーザーのID/PWでログインします。
1. https://kc-proxy.example.com:18443/examples/websocket/echo.xhtml が表示される。(admin ロールを持たないユーザーでログインした場合は403エラーとなります)
![proxy07.png](https://qiita-image-store.s3.amazonaws.com/0/209129/b5a6eae5-855b-55ad-dfaf-4a412d67953c.png)


# まとめ
今回はKeycloakとSecurity ProxyをID連携させる方法を確認しました。
基本的に設定用のjsonファイルを1つ配置するだけで、KeycloakとのID連携を確認できるため、リバプロ型の簡易的な動作確認としては有用かと思います。

ですが、Security Proxyを本格的にシステムに組み込む場合は、リバプロとして機能的に不足している点がいくつかあります。

 - プロキシ転送先が1サーバーしか指定できない
 - アクセスログがない
 - Apacheのような細かな制御(URL置換やヘッダ置換、ロードバランシングなどなど)ができない
 - HTTPヘッダとして連携できる項目が限られている

そのためリバプロ型構成を利用する場合は、8日目の記事で私が解説する「Keycloakでリバプロ型構成を組んでみる（mod_auth_openidc編）」を使用することにより、上記の不足点を補いつつ、リバプロとして柔軟な設定が利用可能となります。

# 参考資料
- [Keycloak Documentation](http://www.keycloak.org/documentation.html)
 - [10. Keycloak Security Proxy](http://www.keycloak.org/docs/latest/server_installation/index.html#_proxy)
 - [2.1.1. Java Adapter Config](http://www.keycloak.org/docs/latest/securing_apps/index.html#_java_adapter_config)
- [NRI OpenStandia Keycloak日本語ドキュメント](http://keycloak-documentation.openstandia.jp/master/ja_JP/server_installation/index.html#_proxy)

# 脚注

[^auth-server-url]: Keycloakサーバーとのssl接続で自己署名サーバー証明書を利用する場合にはSecurity Proxy側のJavaのtruststoreにサーバー証明書の取り込みが必要です。(Security Proxyが、Keycloakサーバーと直接SSL通信するため)