Kongを使ってAPIへのアクセスを制御する　～APIキー＆ACL編～

# はじめに
オープンソースのAPIゲートウェイである[Kong](https://konghq.com/)を利用し、APIに対するアクセス制御方法を数記事に渡って紹介していきます。

Kongでは、作成したAPIに対して、プラグイン形式で様々な機能（ログ取得、流量制御など）を付与することが可能です。今回紹介するアクセス制御の仕組みもプラグイン形式で提供されており、[Pluginsのページ](https://konghq.com/plugins/)に記載の通り、APIに対して様々なアクセス制御を適用することができます（OpenID Connect RPやOAuth2.0 Introspectionなどの一部プラグインは有料版（エンタープライズ版）のみの提供となりますが、、）。

これにより、従来は各リソースにてそれぞれアクセス制御（認証・認可）や流量制御、ロギングなどの機能を実装していたところを、APIゲートウェイであるKongで一元管理できるようになります（個々のリソースにOAuth2や流量制御などの仕組みを実装せずともよくなります）。このあたりの利点は[Kongの公式サイト](https://getkong.org/)でも紹介されていますね。

![kong-arc_mod.png](https://qiita-image-store.s3.amazonaws.com/0/116164/c3dc4528-3647-a6d7-9654-f31f1694965c.png)
<font color="DarkGray">*出典：[https://getkong.org/#comparison](https://getkong.org/#comparison)*</font>


Kongにおけるアクセス制御実践のファーストステップとして、[Key Authenticationプラグイン](https://getkong.org/plugins/key-authentication/)と[ACLプラグイン](https://getkong.org/plugins/acl/)を組み合わせてAPIにアクセス制御を施す方法を本記事では紹介していきます。

# 本記事で実現する構成
本記事では、[Key Authenticationプラグイン](https://getkong.org/plugins/key-authentication/)と[ACLプラグイン](https://getkong.org/plugins/acl/)を組み合わせ、**「特定のユーザのみがアクセス可能なAPI」**を作成する手順を解説していきます。具体的には、APIにアクセス制御機能を適用することで**「ユーザaliceとユーザbobはアクセスできるが、ユーザcharlieはアクセスできないAPI」**を作成していきます。
![kong-keyauth-1.png](https://qiita-image-store.s3.amazonaws.com/0/116164/40e6e7ec-1592-39f2-decc-7efc1ca7bbfa.png)


また、APIをコールした際に呼び出すリソースサーバには、リクエスト情報をそのまま返してくれる[httpbin.org/anything](http://httpbin.org/anything)を利用することにします（Kongからどんなリクエストが投げられてるんだろう～などを確認したい際に結構便利なウェブサイトです）。

# Kongのインストール
（※既にKongをインストールされている方はこの節を飛ばしていただいて結構です）

Kongのインストール記事は多数あるので割愛:wink:と行きたいところですが、つい先日バージョン0.12.x系がリリースされたばかり（2018/2/19現在）なので、手順を示しておきます。環境はDockerを想定しています。公式ドキュメント（[Docker Installation](https://getkong.org/install/docker/)）にもある通り、基本的には以下の３コマンドをコピペ実行でOKです。

## ① Kongの設定を保存するDBの準備

```bash
$ docker run -d --name kong-database \
              -p 5432:5432 \
              -e "POSTGRES_USER=kong" \
              -e "POSTGRES_DB=kong" \
              postgres:9.4
```

## ② DBとKongの連携（マイグレーション）
```bash
$ docker run --rm \
    --link kong-database:kong-database \
    -e "KONG_DATABASE=postgres" \
    -e "KONG_PG_HOST=kong-database" \
    -e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \
    kong:latest kong migrations up
```

## ③ Kongの起動

```bash
$ docker run -d --name kong \
    --link kong-database:kong-database \
    -e "KONG_DATABASE=postgres" \
    -e "KONG_PG_HOST=kong-database" \
    -e "KONG_CASSANDRA_CONTACT_POINTS=kong-database" \
    -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
    -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \
    -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
    -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
    -e "KONG_ADMIN_LISTEN=0.0.0.0:8001" \
    -e "KONG_ADMIN_LISTEN_SSL=0.0.0.0:8444" \
    -p 8000:8000 \
    -p 8443:8443 \
    -p 8001:8001 \
    -p 8444:8444 \
    kong:latest
```

③について、公式ドキュメントではDocker起動時のオプションにアクセスログの出力先や管理者用ポートを指定していますが、他の設定項目を設定したい場合には[Configuration Reference](https://getkong.org/docs/0.12.x/configuration/)を参考にしてください。

## Kongの動作確認
①～③の手順を実施し、正常にKongを起動できていれば、以下のcurlを実行した際にKongの基本情報を表すJSONが返ってきます。

```bash:動作確認リクエスト
$ curl http://localhost:8001/
```

```json:レスポンス
{"version":"0.12.1","plugins":{"enabled_in_cluster":[], ～以下省略～ }
```

ちなみに、下表のように、Kongでは8001ポートを利用してAPIの作成・設定やプラグインの適用を行い、8000ポートを利用して作成したAPIをコールします。

|ポート|役割|
|:--|:--|
|8001|新規APIの作成、APIへのプラグインの適用、コンシューマの作成など（管理者用ポート）|
|8000|作成したAPIをクライアントがコールするためのエンドポイント（ユーザ用ポート）|

Kongを利用する準備が整ったので、まずはAPIを作成していきましょう。

# プレーンなAPIの作成
APIにアクセス制御機能を付与するにあたり、まずはベースとなるAPIを新規に作成していきます。この段階では、単純にAPIを作成するだけなので、誰でも作成したAPIをコールできる状態となります。

新規APIの作成は、`http://[ホスト]:8001/apis`に対してAPI名やAPIのアクセス先URLなどを指定し、POSTすることで行います。具体的には、以下のフォーマットに従ってリクエストを送ります。

```bash:プレーンなAPIを作成するフォーマット
$ curl -i -X POST \
  --url http://[ホスト]:8001/apis/ \
  --data 'name=[API名]' \
  --data 'uris=/[APIのURI]' \
  --data 'upstream_url=[APIアクセス時に呼び出すサービスのURI]'
```

上記リクエストのdata部は下表の意味を持ちます。

|設定|意味|
|:--|:--|
|name|APIの名称。このAPIに対してプラグインを適用するといった設定を行う際にこのnameを指定します。|
|uris|クライアントがこのAPIをコールする際に指定するURI。クライアントは`http://[ホスト]:8000/[uris]`を指定してこのAPIをコールします。|
|upstream_url|このAPIをコールした際に呼び出されるURL。（リソースサーバに相当します）|

早速、APIを作成するリクエストを送信してみましょう。今回は`sandbox-api`というAPIを作成してみます。urisはAPI名と同じ`/sandbox-api`とします。また、upstream_urlには、`http://httpbin.org/anything`を設定します。正常に処理が完了すれば、ステータスコード201と共にAPIを作成した旨のJSONが返されます。

```bash:sandbox-apiの作成
$ curl -i -X POST \
       --url http://localhost:8001/apis/ \
       --data 'name=sandbox-api' \
       --data 'uris=/sandbox-api' \
       --data 'upstream_url=http://httpbin.org/anything'

HTTP/1.1 201 Created
Date: Mon, 19 Feb 2018 09:32:32 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Access-Control-Allow-Origin: *
Server: kong/0.12.1

{"created_at":1519032752107,"strip_uri":true,"id":"287fa349-eb7f-456a-948c-0270b0fc4511","name":"sandbox-api","http_if_terminated":false,"preserve_host":false,"upstream_url":"http:\/\/httpbin.org\/anything","uris":["\/sandbox-api"],"upstream_connect_timeout":60000,"upstream_send_timeout":60000,"upstream_read_timeout":60000,"retries":5,"https_only":false}
```

正常にAPIを作成できたので、次に作成したAPIをコールして確認してみます。今回はKongと同環境で試すので、以下のようにホストにlocalhostを指定し、ホスト以降にはAPI作成時のurisを指定してコールします。

```bash:作成したAPIをテストするリクエスト
$ curl -i localhost:8000/sandbox-api
```

正常にAPIをコールできていれば、以下のように200ステータスと共にupstream_urlに設定したリソースを取得できます。X-Kong-Proxy-Latency以下のJSONがリソースサーバ（httpbin.org/anything）からのレスポンスとなります。（余談となりますが、X-Kong-Upstream-LatencyなどKongがリクエストヘッダに情報を付与していることも確認できますね）


```bash:テストしたAPIからのレスポンス
$ curl -i localhost:8000/sandbox-api

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 351
Connection: keep-alive
Server: meinheld/0.6.1
Date: Mon, 19 Feb 2018 09:41:45 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
X-Powered-By: Flask
X-Processed-Time: 0
Via: kong/0.12.1
X-Kong-Upstream-Latency: 30
X-Kong-Proxy-Latency: 0

{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Connection": "close", 
    "Host": "httpbin.org", 
    "User-Agent": "curl/7.55.1", 
    "X-Forwarded-Host": "localhost"
  }, 
  "json": null, 
  "method": "GET", 
  "origin": [mask], 
  "url": "http://localhost/anything"
}
```

さて、これでAPIの作成&動作確認ができました。

但し、現状はAPIに対して何のアクセス制御も施していないので、下図のように誰でもこのAPIをコールすることができる状態です。

![kong-keyauth-2.png](https://qiita-image-store.s3.amazonaws.com/0/116164/18e000f6-50e2-48a2-0601-4b93fef329ca.png)


このAPIに対して各種プラグインを適用していくことで、アクセス制御を適用したAPIを実現していきます。
まずは、Key Authenticationプラグインを適用していきましょう。

# Key Authenticationプラグインの適用 
冒頭で述べた、特定のユーザのみAPIにアクセスできる構成を実現するには、Key AuthenticationプラグインとACLプラグインの両方を適用する必要がありますが、まずはKey Authenticationプラグインを導入していきます。このプラグインを導入することで、APIに対するアクセスをキー所有ユーザに限定することができます。要するに、正しいキーが付与されていないリクエストを拒否するプラグインとなります。

キーを利用した動作イメージは以下のとおりです。図における`AliceSecretKey`、`BobSecretKey`、`CharlieSecretKey`がキーに相当します。

![kong-keyauth-4.png](https://qiita-image-store.s3.amazonaws.com/0/116164/3401547a-c368-bec5-89a8-ec6ca7dd13e6.png)


Key Authenticationプラグインの導入には、以下の３ステップを実施します。

* **① sandbox-apiにKey Authenticationプラグインを適用する**
* **② コンシューマ（ユーザ）を作成する**
* **③ コンシューマを識別するキーを設定する**

それでは、ステップごとに設定方法を解説します。

## ① APIにKey Authenticationプラグインを適用する
APIに対してプラグインを適用したい場合には、`http://[host]:8001/apis/[API名(name)]/plugins`に対してプラグイン名やプラグインの設定をPOSTします。Key Authenticationプラグインを適用したい場合には、以下をフォーマットに従って設定をPOSTします。

```bash:KeyAuthenticationプラグイン適用時のフォーマット 
$ curl -X POST localhost:8001/apis/[API名]/plugins \
    --data "name=key-auth" \
    --data "config.key_names=[キーを指定する際の属性名]"
```


|設定|意味|
|:--|:--|
|name|プラグインの名称。Key Authenticationプラグインの場合は`key-auth`を指定します。この後設定するACLプラグインでは`acl`を指定します。|
|config.key_names|Key Authenticationプラグインでは、クエリ文字列でユーザを識別します。config.key_namesはクエリ文字列の左側（属性名）を指定します。|

今回は、先ほど作成したsandbox-apiに対してKey Authenticationプラグインを適用し、`config.key_names`にはsandboxApiKeyを設定します。このリクエストをPOSTし、正常に処理が完了すると、プラグインの各設定が記述されたJSONがレスポンスとして返されます。

```bash:KeyAuthenticationプラグインの適用
$ curl -X POST localhost:8001/apis/sandbox-api/plugins \
       --data "name=key-auth" \
       --data "config.key_names=sandboxApiKey"
  
{"created_at":1519125441000,"config":{"key_names":["sandboxApiKey"],"key_in_body":false,"anonymous":"","run_on_preflight":true,"hide_credentials":false},"id":"3a342b9e-bf22-4b28-b3f2-ebc0ea54b0aa","name":"key-auth","api_id":"c9c6c0bb-445c-491e-9307-0157fe5d4e74","enabled":true}
```

これで、sandbox-apiに対してKey Authenticationプラグインを適用できました。この段階で、APIの末尾にクエリ文字列を付与して`http://localhost:8000/sandbox-api?sandboxApiKey=[ユーザのKey]`をコールできるようになります。

但し、現時点ではクエリ文字列のパラメータにあたる`[ユーザのKey]`の設定を行っていません（まだユーザを作成していません）。それでは、②でユーザに相当するものを作成していきましょう。

## ② コンシューマ（ユーザ）を作成する
ここで新たな概念であるConsumerが登場します。コンシューマについて、[公式ドキュメント](https://getkong.org/docs/0.12.x/admin-api/#consumer-object)によると以下の記載があります。

> The Consumer object represents a consumer - or a user - of an API. 
> 和訳）Consumerオブジェクトは、APIのコンシューマまたはユーザーを表します。

ドキュメントが指す意味としては、APIの消費者（利用者）といったところでしょうか。ニュアンスとしては、ユーザの集まり（組織）をコンシューマと定義しているように見えます。
※ちなみに、コンシューマはOAuth2プラグイン等でも利用する概念なので、OAuth2プラグインの利用を考えている場合には、是非公式ドキュメントを一読しておきたいところです。



今回はコンシューマをユーザとして捉え、aliceコンシューマ、bobコンシューマ、charlieコンシューマを作成します。作成は下記のフォーマットに従って行います。

```bash:コンシューマ作成フォーマット
$ curl -X POST localhost:8001/consumers/ \
    --data "username=<USERNAME>" \
    --data "custom_id=<CUSTOM_ID>"
```

aliceコンシューマを作成したい場合のリクエストは以下になります。

```bash:aliceコンシューマを作成
$ curl -X POST localhost:8001/consumers/ \
     --data "username=alice" \
     --data "custom_id=alice"
{"custom_id":"alice","created_at":1519125666000,"username":"alice","id":"7265a18a-3b2b-4a6a-a51d-18e6e6dea2c7"}
```

同様にして、bobコンシューマとcharlieコンシューマも作成します。

```bash:bobコンシューマを作成
$ curl -X POST localhost:8001/consumers/ \
     --data "username=bob" \
     --data "custom_id=bob"
{"custom_id":"bob","created_at":1519127073000,"username":"bob","id":"b4835592-b912-41a8-bfa2-88416db69de5"}
```

```bash:charlieコンシューマを作成
$ curl -X POST localhost:8001/consumers/ \
     --data "username=charlie" \
     --data "custom_id=charlie"
{"custom_id":"charlie","created_at":1519127112000,"username":"charlie","id":"49509fc9-d6a0-4d6e-887f-73d95a6d83dd"}
```

## ③コンシューマを識別するキーを設定する
コンシューマが作成できたら、次に作成したコンシューマに対してキーを設定します。ここで設定するキーが①で設定したクエリ文字列のパラメータ部分（`[ユーザのKey]`部分）に相当します。

下記フォーマットに従ってコンシューマにキーを設定します。

```bash:コンシューマにキーを設定する際のフォーマット
$ curl -X POST localhost:8001/consumers/[コンシューマ名]/key-auth \
    --data "key=[コンシューマのKey]"
```

今回、aliceコンシューマには、キーとして文字列"AliceSecretKey"を設定します。

```bash:aliceコンシューマにキーを設定
$ curl -X POST localhost:8001/consumers/alice/key-auth \
     --data "key=AliceSecretKey"
  
{"id":"fd26bf85-b73e-4eaa-b1bd-8c2b3dfe2d2b","created_at":1519125820000,"key":"AliceSecretKey","consumer_id":"7265a18a-3b2b-4a6a-a51d-18e6e6dea2c7"}
```

同様にして、bobコンシューマには"BobSecretKey"を、charlieコンシューマには"CharlieSecretKey"を設定します。

```bash:bobコンシューマにキーを設定
$ curl -X POST localhost:8001/consumers/bob/key-auth \
       --data "key=BobSecretKey"

{"id":"e504aada-25a3-4e59-a9a5-21e8121a0677","created_at":1519127653000,"key":"BobSecretKey","consumer_id":"b4835592-b912-41a8-bfa2-88416db69de5"}
```

```bash:charlieコンシューマにキーを設定
$ curl -X POST localhost:8001/consumers/charlie/key-auth \
       --data "key=CharlieSecretKey"

{"id":"51d48a78-a3e8-489b-9cc5-2108b69ce190","created_at":1519127695000,"key":"CharlieSecretKey","consumer_id":"49509fc9-d6a0-4d6e-887f-73d95a6d83dd"}
```

ここまでの手順で、プレーンなAPIに対してKey Authenticationプラグインを適用し、Key Authenticationで利用するクエリ文字列のパラメータの設定（Consumerの作成＆キー設定）が終わりました。

早速、クエリ文字列にパラメータを付与してAPIにアクセスしてみましょう。コンシューマのキーを正しく付与できていれば、正常にコンテンツを取得できるはずです。

```bash:aliceコンシューマのキーを付与してAPIにリクエストを送信
$ curl -i http://localhost:8000/sandbox-api?sandboxApiKey=AliceSecretKey

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 558
Connection: keep-alive
Server: meinheld/0.6.1
Date: Tue, 20 Feb 2018 11:26:50 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
X-Powered-By: Flask
X-Processed-Time: 0
Via: kong/0.12.1
X-Kong-Upstream-Latency: 28
X-Kong-Proxy-Latency: 0

{
  "args": {
    "sandboxApiKey": "AliceSecretKey"
  }, 
  "data": "", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Connection": "close", 
    "Host": "httpbin.org", 
    "User-Agent": "curl/7.55.1", 
    "X-Consumer-Custom-Id": "alice", 
    "X-Consumer-Id": "7265a18a-3b2b-4a6a-a51d-18e6e6dea2c7", 
    "X-Consumer-Username": "alice", 
    "X-Forwarded-Host": "localhost"
  }, 
  "json": null, 
  "method": "GET", 
  "origin": "172.17.0.1, 18.218.119.187", 
  "url": "http://localhost/anything?sandboxApiKey=AliceSecretKey"
}
```

正しくないキーを付与したり、そもそもクエリ文字列自体を付与しない場合には以下のようにエラーが返されます。

```bash:存在しないキーを付与した場合の挙動
$ curl -i http://localhost:8000/sandbox-api?sandboxApiKey=MichelSecretKey

HTTP/1.1 403 Forbidden
Date: Tue, 20 Feb 2018 11:29:16 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: kong/0.12.1

{"message":"Invalid authentication credentials"}
```

```bash:クエリ文字列を付与しなかった場合の挙動
$ curl -i http://localhost:8000/sandbox-api
HTTP/1.1 401 Unauthorized
Date: Tue, 20 Feb 2018 11:29:23 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
WWW-Authenticate: Key realm="kong"
Server: kong/0.12.1

{"message":"No API key found in request"}
```

これで、コンシューマ以外はAPIにアクセスできない設定を適用できました。

しかしながら、これはKongに存在するコンシューマであれば誰でもAPIにアクセスできてしまう状況でもあります。実際は「コンシューマなら誰でもアクセスOK！」なんてことはあまりなく、コンシューマ単位でもアクセスを制御したいケースが多いかと思います。

次に説明するACLプラグインを適用すれば、コンシューマ単位でアクセスを制御できるので、早速設定していきましょう。


# ACLプラグインの適用
Key Authenticationプラグインを適用した状態でACLプラグインを適用すれば、コンシューマ単位でAPIへのアクセスを制御することが可能になります。

具体的には、`各コンシューマに"ACLグループ"を設定`し、`ACLプラグインにてアクセスを許可・拒否するACLグループを指定`することでアクセス制御を実現します。アクセス許可・拒否するACLグループの設定は、ACLプラグインのホワイトリスト・ブラックリスト機能にて行います。

ACLプラグインを適用すると、下図の動作を実現できます。
![kong-keyauth-3.png](https://qiita-image-store.s3.amazonaws.com/0/116164/b45800cd-cc33-06f3-f4e8-a0284036ffbd.png)


ACLプラグインの導入には、以下の２ステップを実施します。

* **① コンシューマにACLグループを設定する**
* **② ACLプラグインを適用し、アクセスを許可・許可しないACLグループを設定する**

それでは、ステップごとに設定方法を解説します。

## ①コンシューマにACLグループを設定する
アクセス制御の単位となるACLグループをコンシューマに設定します。フォーマットは下記のようになります。

```bash:コンシューマへのACLグループの設定
$ curl -X POST http://[ホスト]:8001/consumers/[コンシューマ名]/acls \
    --data "group=[ACLグループ名]"
```

今回、各コンシューマに割り振るACLグループ名は下記にしました。aliceとbobは共にアクセスを許可したいので、同じACLグループを割り振っています。

|コンシューマ|ACLグループ|
|:--|:--|
|alice|allow-group|
|bob|allow-group|
|charlie|deny-group|

では、各コンシューマにACLグループを設定していきます。

```bash:aliceコンシューマにACLグループを設定
$ curl -X POST http://localhost:8001/consumers/alice/acls \
       --data "group=allow-group"

{"group":"allow-group","created_at":1519273998000,"id":"9f04008f-9914-420d-a3a2-edabf32d37ac","consumer_id":"82a61b1a-97b6-43a4-82ed-97040b108707"}
```


```bash:bobコンシューマにACLグループを設定
$ curl -X POST http://localhost:8001/consumers/bob/acls \
       --data "group=allow-group"

{"group":"allow-group","created_at":1519274058000,"id":"1c048e7a-99ab-4328-a431-a63e43a26aa3","consumer_id":"f6255716-6808-4229-90ce-d8b86c793b8a"}
```

```bash:charlieコンシューマにACLグループを設定
$ curl -X POST http://localhost:8001/consumers/charlie/acls \
       --data "group=deny-group"

{"group":"deny-group","created_at":1519274108000,"id":"0f820d10-9e94-495e-9637-c232bb058a79","consumer_id":"f8158211-be7a-47e9-8cc6-b9d4d1630d61"}
```

ACLグループのコンシューマへの設定はこれで終わりです。次に、作成したACLグループにアクセスを許可する・拒否する設定を行います。

## ②ACLプラグインを適用し、アクセスを許可・許可しないACLグループを設定する
sandbox-apiに対してACLプラグインを適用していきますが、適用する際には`config.whitelist`、または`config.blacklist`を設定する必要があります。
`config.whitelist`は文字通り、指定されたACLグループをホワイトリストとして登録する設定となります。ホワイトリストなので、指定されたACLグループのみアクセスを許可し、その他のACLグループのアクセスは拒否する動作となります。`config.blacklist`はブラックリストの設定のため、`config.whitelist`とは逆の動作となります（指定したACLグループのアクセスを拒否し、その他のACLグループのアクセスを許可する）。

設定のフォーマットは以下の通りです。

```bash:ACLプラグインの適用
$ curl -X POST http://[ホスト]:8001/apis/[API名]/plugins \
    --data "name=acl" \
    --data "config.whitelist=[ACLグループ]"
```

今回、aliceコンシューマとbobコンシューマの所属するACLグループ（allow-group）のアクセスを許可し、charlieコンシューマのACLグループ（deny-group）のアクセスを拒否したいので、`config.whitelist`に`allow-group`を指定します[^1]。

```bash:sandbox-apiにACLプラグインを適用し、whitelistにallow-groupを指定
$ curl -X POST http://localhost:8001/apis/sandbox-api/plugins \
       --data "name=acl" \
       --data "config.whitelist=allow-group"

{"created_at":1519276536000,"config":{"whitelist":["allow-group"]},"id":"79a8ee65-3290-4da8-a6e2-2113928503e1","name":"acl","api_id":"cdab454f-75ff-4761-9eb9-4aee1b33b262","enabled":true}
```

これで全ての設定が終わりました。動作確認してみましょう。

aliceコンシューマとbobコンシューマでAPIにアクセスします。

```bash:aliceコンシューマでAPIにアクセス
$ curl -i http://localhost:8000/sandbox-api?sandboxApiKey=AliceSecretKey

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 558
Connection: keep-alive
Server: meinheld/0.6.1
Date: Thu, 22 Feb 2018 05:11:53 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
X-Powered-By: Flask
X-Processed-Time: 0
Via: kong/0.12.1
X-Kong-Upstream-Latency: 29
X-Kong-Proxy-Latency: 19

{
  "args": {
    "sandboxApiKey": "AliceSecretKey"
  }, 
  "data": "", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Connection": "close", 
    "Host": "httpbin.org", 
    "User-Agent": "curl/7.55.1", 
    "X-Consumer-Custom-Id": "alice", 
    "X-Consumer-Id": "82a61b1a-97b6-43a4-82ed-97040b108707", 
    "X-Consumer-Username": "alice", 
    "X-Forwarded-Host": "localhost"
  }, 
  "json": null, 
  "method": "GET", 
  "origin": "172.17.0.1, 18.218.119.187", 
  "url": "http://localhost/anything?sandboxApiKey=AliceSecretKey"
}
```

```bash:bobコンシューマでAPIにアクセス
$ curl -i http://localhost:8000/sandbox-api?sandboxApiKey=BobSecretKey

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 550
Connection: keep-alive
Server: meinheld/0.6.1
Date: Thu, 22 Feb 2018 05:12:48 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
X-Powered-By: Flask
X-Processed-Time: 0
Via: kong/0.12.1
X-Kong-Upstream-Latency: 28
X-Kong-Proxy-Latency: 15

{
  "args": {
    "sandboxApiKey": "BobSecretKey"
  }, 
  "data": "", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Connection": "close", 
    "Host": "httpbin.org", 
    "User-Agent": "curl/7.55.1", 
    "X-Consumer-Custom-Id": "bob", 
    "X-Consumer-Id": "f6255716-6808-4229-90ce-d8b86c793b8a", 
    "X-Consumer-Username": "bob", 
    "X-Forwarded-Host": "localhost"
  }, 
  "json": null, 
  "method": "GET", 
  "origin": "172.17.0.1, 18.218.119.187", 
  "url": "http://localhost/anything?sandboxApiKey=BobSecretKey"
}
```

無事APIにアクセスできましたね。では、charlieコンシューマでAPIにアクセスします。

```bash:charlieでAPIにアクセス
$ curl -i http://localhost:8000/sandbox-api?sandboxApiKey=CharlieSecretKey

HTTP/1.1 403 Forbidden
Date: Thu, 22 Feb 2018 05:16:22 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: kong/0.12.1

{"message":"You cannot consume this service"}
```

こちらも想定通りアクセスが拒否されました。もちろん、存在しないキーを指定した場合やキーを付与しなかった場合にもアクセスが拒否されます。

```bash:存在しないキーを指定してAPIにアクセス
$ curl -i http://localhost:8000/sandbox-api?sandboxApiKey=MichelSecretKey

HTTP/1.1 403 Forbidden
Date: Thu, 22 Feb 2018 05:18:52 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: kong/0.12.1

{"message":"Invalid authentication credentials"}
```

```bash:キーを指定せずAPIにアクセス
$ curl -i http://localhost:8000/sandbox-api

HTTP/1.1 401 Unauthorized
Date: Thu, 22 Feb 2018 05:20:42 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
WWW-Authenticate: Key realm="kong"
Server: kong/0.12.1

{"message":"No API key found in request"}
```


これにて、最初想定していた構成を実現できました！


# おわりに
今回は、キーとACLを利用し、APIにアクセス制御機能を付与しました。但し、実際の利用シーンでは、ユーザ数が増えれば増えるほどKong単体でコンシューマ作成やキー発行を管理するのは難しくなりますし、ユーザの認証は既に存在するKeycloak[^2]などのIdP（アイデンティティ・プロバイダ）で実施したい要件もあるかと思います。

そのため、またの機会があればユーザの認証部分をKong外に配置し、Kongでは外部の認証情報を元にAPIのアクセス可否を検証するパターンを[OAuth2.0 Authenticationプラグイン](https://getkong.org/plugins/oauth2-authentication/)を利用して試してみたいと思います。

また、弊社の[OpenStandia](http://openstandia.jp/)では、様々なオープンソースの最新情報やサポートを提供しています。興味があれば、是非ご覧ください！

最後までご覧いただき、ありがとうございました。




[^1]: 今回はconfig.whitelistにallow-groupを指定することでアクセス制御を実現しましたが、同様のアクセス制御はconfig.blacklistにdeny-groupを指定することでも実現できます。

[^2]: Keycloakに興味がある方は、[Keycloak by OpenStandia Advent Calendar 2017](https://qiita.com/advent-calendar/2017/keycloak-by-openstandia)を是非ご覧ください！Keycloakのインストールから応用まで、多岐に渡るコンテンツが掲載されています。