> ## Documentation Index
> Fetch the complete documentation index at: https://developers.authlete.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 金融グレード Amazon API Gateway

> 証明書に紐付いたアクセストークン (RFC 8705) を活用して Amazon API Gateway 上に構築した API をこれまで以上に安全に保護する方法を説明するチュートリアルです。

# はじめに

このチュートリアルでは、『**証明書に紐付いたアクセストークン**』を活用して **[Amazon API Gateway][APIGW]** 上に構築した API をこれまで以上に安全に保護する方法を紹介します。

OAuth アクセストークンが一度漏洩すると、攻撃者はそのアクセストークンをもって API にアクセスできます。従来のアクセストークンは電車の切符のようなもので、一度盗まれたら誰でも使えてしまいます。

この脆弱性はアクセストークンと同時にアクセストークンの正当な保有者である証拠も併せて提示することを API 呼出者に要求することで軽減することができます。その証拠は **proof of possession** と呼ばれ、よく **PoP** と短縮されます。使用時に PoP を必要とするアクセストークンは、搭乗時にパスポートの提示も併せて要求される国際線の航空券に似ています。

**[RFC 8705][8705]** (**OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens**) は PoP の仕組みを標準化しました。OAuth コミュニティーではその仕組みは **MTLS** と呼ばれていますが、混乱を避けるため個人的には『**証明書バインディング**』と呼んでいます。いずれにしても、簡潔に述べますと、その仕組みは、トークンエンドポイントからアクセストークンの発行を受ける際に使用した \*\*[X.509 証明書][X509]\*\*と同じ証明書をアクセストークンと併せて提示することを API 呼出者に要求します。次の図は証明書バインディングの概念を示しています。

<img alt="証明書バインディング" class="mx-auto d-block shadow" src="https://mintcdn.com/authlete/YFJcSy4uVerokBPD/protocols-and-flows/compliance-profiles/certificate-binding_ja.png?fit=max&auto=format&n=YFJcSy4uVerokBPD&q=85&s=567c31cccab371bfef4ff5ffbc329016" width="1600" height="900" data-path="protocols-and-flows/compliance-profiles/certificate-binding_ja.png" />

**金融グレード API** (英語名称 **[Financial-grade API][FAPI]**)、通称 **FAPI** (ファピ) をご存知でしょうか。FAPI は、より高度な API セキュリティのために **OAuth 2.0** と **[OpenID Connect][OIDC]** の上に作られた技術仕様です。[英国オープンバンキング][UKOB]はオープンバンキングプロファイルの基盤として FAPI を採用し、今では他国も追随しつつあります。ここで注目していただきたいのは、証明書バインディングが FAPI の必須技術要素となっている点です。

証明書バイディングの大前提は、API とクライアントアプリケーション間の接続が**相互 TLS** (**Mutual TLS**) を用いて確立されることです。相互 TLS では、TLS ハンドシェイク中にクライアントアプリケーション側も X.509 **クライアント証明書**の提示を要求されます。2020 年 9 月 17 日、AWS は *"[Introducing mutual TLS authentication for Amazon API Gateway][MTLSIntro]"* と題したブログで Amazon API Gateway が相互 TLS に対応したことを発表しました。Amazon API Gateway は金融グレード API セキュリティへの扉を開けました。

# オーソライザーの役割

Amazon API Gateway は、API 保護の独自ロジックを開発者自ら実装する手段として \*\*[Lambda オーソライザー][LAUTH]\*\*という仕組みを提供しています。OAuth ベースの API 保護を提供する Lambda オーソライザーは、API コールからアクセストークンとクライアント証明書を取り出し、次の事項を確認します。

1. 提示されたアクセストークンが存在し、
2. 有効期限が切れておらず、
3. リソースアクセスに要求されるスコープを持ち、
4. 無効化されておらず、
5. 提示されたクライアント証明書に紐付いている。 (**証明書バインディング**)

その後、オーソライザーは確認結果に応じて次のいずれかの処理をおこないます。

* リソースアクセスを許可する IAM ポリシーを返す。
* リソースアクセスを拒否する IAM ポリシーを返す。
* HTTP ステータスコード "401 Unauthorized" を用いてリソースアクセスを拒否するように Amazon API Gateway に指示するため、エラーメッセージ `'Unauthorized'` を持つ例外を投げる。
* HTTP ステータスコード "500 Internal Server Error" を用いてリソースアクセスを拒否するように Amazon API Gateway に指示するため、他のエラーメッセージを持つ例外を投げる。

<Note>
  Lambda オーソライザーの応答フォーマットに 2.0 を選択し、IAM ポリシーよりも簡素な応答を返すこともできます。詳細は『[Lambda オーソライザーのレスポンス形式](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/http-api-lambda-authorizer.html#http-api-lambda-authorizer.payload-format-response)』を参照してください。
</Note>

<Note>
  Amazon API Gateway に "401 Unauthorized" 応答をさせるのに、例外を投げる以外の方法はあるのでしょうか？ご存知であれば教えてください。[RFC 6750](https://tools.ietf.org/html/rfc6750) (The OAuth 2.0 Authorization Framework: Bearer Token Usage) に準拠するためには、Lambda オーソライザーで `WWW-Authenticate` ヘッダーの値を指定して Amazon API Gateway に使わせることができなければなりません。
</Note>

# オーソライザーの実装

この後すぐにお見せすることになる Lambda オーソライザーの実装では、アクセストークンの検証処理を **Authlete** (オースリート) のイントロスペクション API (`/api/auth/introspection`) に委譲します。そのため、そのオーソライザーの実装は複雑なロジックを含みません。次の図は Amazon API Gateway、Lambda オーソライザー、Authlete の関係を示しています。

<img alt="Amazon API Gateway、Lambda オーソライザー、Authlete の関係" class="mx-auto d-block shadow" src="https://mintcdn.com/authlete/oZd1VUaYFwHO2MBx/protocols-and-flows/compliance-profiles/apigateway-authorizer-authlete_ja.png?fit=max&auto=format&n=oZd1VUaYFwHO2MBx&q=85&s=629e47c8fb47caf2f2ae69acfb122a54" width="1600" height="900" data-path="protocols-and-flows/compliance-profiles/apigateway-authorizer-authlete_ja.png" />

これに加え、[Authlete の Python ライブラリ][AuthletePython]内にある [Authorizer][Authorizer] クラスが必要な作業のほとんどを行うため、実装は非常に小さくできます。実際に、次のコードは証明書バインディングをサポートする Lambda オーソライザーの完全な実装例です。

```python theme={null}
from authlete.aws.apigateway.authorizer import Authorizer

authorizer = Authorizer()

def lambda_handler(event, context):
    return authorizer.handle(event, context)
```

## リソースアクセスに要求されるスコープ

しかしながら、実際には、どのリソースがどのスコープを要求するかを設定する必要が出てくるでしょう。これは、（１）`Authorizer` の `handle()` メソッドに関数を渡す、もしくは（２）`Authorizer` のサブクラスを作り `determine_scopes()` メソッドをオーバーライドする、という方法で実現することができます。

関数とメソッドの双方とも、下表に挙げられている 4 つの引数を取り、リソースアクセスに必要となるスコープの名前のリストを返すことが求められます。

| 引数        | 説明                  |
| :-------- | :------------------ |
| `event`   | オーソライザーに渡されたイベント    |
| `context` | オーソライザーに渡されたコンテキスト  |
| `method`  | リソースアクセスの HTTP メソッド |
| `path`    | リソースのパス             |

次の 2 つの例はどちらも同じ効果を持ち、`time` リソースに HTTP `GET` メソッドでアクセスする際には `time:query` スコープが必要であると言っています。

```python theme={null}
from authlete.aws.apigateway.authorizer import Authorizer

authorizer = Authorizer()

def determine_scopes(event, context, method, path):
    if method == 'GET' and path == 'time':
        return ['time:query']

    return None

def lambda_handler(event, context):
    return authorizer.handle(event, context, determine_scopes)
```

```python theme={null}
from authlete.aws.apigateway.authorizer import Authorizer

class CustomAuthorizer(Authorizer):
    def determine_scopes(self, event, context, method, path):
        if method == 'GET' and path == 'time':
            return ['time:query']

        return None

authorizer = CustomAuthorizer()

def lambda_handler(event, context):
    return authorizer.handle(event, context)
```

## ポリシーまたは例外

OAuth 2.0 関連の仕様に準拠するためには、場合によっては (例えば提示されたアクセストークンの有効期限が切れているとき) "401 Unauthorized" を API 呼出者に返すよう、Lambda オーソライザーから Amazon API Gateway に伝えなければなりません。AWS の技術文書やサンプルプログラムによれば、これを行うためにオーソライザーは `'Unauthorized'` というメッセージを持つ例外を投げなければなりません。しかし、このような簡素な例外は "Unauthorized" レスポンスに関する全ての貴重な情報を捨ててしまい、デバッグ作業をとても難しくしてしまいます。

そこで、デフォルトでは、別の言い方をすると `policy` プロパティーが `True` (デフォルト) の場合、`Authorizer` の `handle()` メソッドは、`'Unauthorized'` 例外を投げるべきである場合を含め、常に許可 ("Allow") もしくは拒否 ("Deny") を示す IAM ポリシーを返します。

もしも ”Unauthorized" や "Internal Server Error" の際に `Authorizer` に例外を投げさせたければ、`policy` プロパティーに `False` を設定してください。`Authorizer` のコンストラクターに `policy=False` を渡すことで実現できます。

```python theme={null}
authorizer = Authorizer(policy=False)
```

## ポリシーのコンテキスト

`Authorizer` の `handle()` メソッドは IAM ポリシーを表す `dict` インスタンスを返します。その辞書内には `context` があり、その値も辞書となっています。`Authorizer` はそこに幾つかの情報を埋め込みます。下表は、`context` 辞書に含まれる可能性のあるプロパティーの一覧です。

| プロパティー                    | 説明                                                             |
| :------------------------ | :------------------------------------------------------------- |
| `introspection_request`   | Authlete のイントロスペクション API へのリクエストを表す JSON 文字列                   |
| `introspection_response`  | Authlete のイントロスペクション API からのレスポンスを表す JSON 文字列                  |
| `introspection_exception` | Authlete のイントロスペクション API コール時に発生した例外を表す文字列                     |
| `scope`                   | 提示されたアクセストークンがカバーするスコープ群をスペース区切りで列挙した文字列                       |
| `client_id`               | アクセストークンの発行対象のクライアントアプリケーションのクライアント ID                         |
| `sub`                     | クライアントアプリケーションへのアクセストークンの発行を許可したリソースオーナーのサブジェクト (主体識別子) を表す文字列 |
| `exp`                     | Unix エポック (1970 年 1 月 1 日) からの経過秒数で表現した、アクセストークンの有効期限終了日時      |
| `challenge`               | エラー時に `WWW-Authenticate` HTTP ヘッダーの値として用いるべき値                  |
| `action`                  | Authlete のイントロスペクション API のレスポンスに含まれる `action` の値               |
| `resultMessage`           | Authlete のイントロスペクション API のレスポンスに含まれる `resultMessage` の値        |

`Authorizer` のサブクラスで `update_policy_context()` をオーバーライドすることにより、`context` にエントリーを追加することができます。ただし、値として JSON オブジェクトや配列は使えないので注意してください。これは AWS の技術的制限事項です。

```python theme={null}
class CustomAuthorizer(Authorizer):
    def update_policy_context(self, event, context, request, response, exception, ctx):
        ctx.update({
            'my_key': 'my_value'
        })
```

Lambda オーソライザーが返すポリシーの `context` に含まれるエントリー群は、後ほど他の場所で使うことができます。詳細は『[Amazon API Gateway Lambda オーソライザーからの出力][LAUTHOUT]』を参照してください。

## フック

`Authorizer` クラスはサブクラスの実装のために幾つかのフックを提供します。必要に応じてオーバーライドしてください。

| メソッド                         | 説明                                       |
| :--------------------------- | :--------------------------------------- |
| `determine_scopes()`         | リソースアクセスに要求されるスコープ群を決定する                 |
| `update_policy_context()`    | ポリシーに埋め込む `context` を更新する                |
| `on_enter()`                 | `handle()` メソッド開始時に呼ばれる                  |
| `on_introspection_error()`   | Authlete イントロスペクション API 失敗時に呼ばれる         |
| `on_introspection()`         | Authlete イントロスペクション API 成功時に呼ばれる         |
| `on_allow()`                 | 許可 (Allow) を表すポリシー生成時に呼ばれる               |
| `on_deny()`                  | 拒否 (Deny) を表すポリシー生成時に呼ばれる                |
| `on_unauthorized()`          | "Unauthorized" 用に例外が投げられる際に呼ばれる          |
| `on_internal_server_error()` | "Internal Server Error" 用に例外が投げられる際に呼ばれる |

# オーソライザーの設定

## Lambda イベントペイロード

Lambda オーソライザーを作成するにあたり最も重要な点は、**Lambda イベントペイロードで『リクエスト』を選ぶ**ことです。さもないと、オーソライザーはクライアント証明書の情報にアクセスできません。それはアクセストークンがクライアント証明書と紐付いているかどうかをオーソライザーがチェックできなくなることを意味します。

<img alt="オーソライザーの作成（Lambda イベントペイロードで「リクエスト」を選択）" class="mx-auto d-block shadow" src="https://mintcdn.com/authlete/WGQFc11Y4wUplGrc/ja/protocols-and-flows/compliance-profiles/create-authorizer_ja.png?fit=max&auto=format&n=WGQFc11Y4wUplGrc&q=85&s=b214d32683a1e91f2d8bec9960d8c9b6" width="561" height="789" data-path="ja/protocols-and-flows/compliance-profiles/create-authorizer_ja.png" />

Lambda イベントペイロードの選択によりオーソライザーへの入力がどのように変化するかの詳細については『[Amazon API Gateway Lambda オーソライザーへの入力][LAUTHIN]』を参照してください。

## 環境変数

[AuthleteApi][AuthleteApi] のインスタンスが渡されない場合、`Authorizer` のコンストラクターは内部で `AuthleteApiImpl(AuthleteEnvConfiguration())` を実行してインスタンスを生成し、それを Authlete API へのアクセス時に使用します。そこで使用されている [AuthleteEnvConfiguration][AuthleteEnvConfiguration] が Authlete の設定情報を環境変数経由で取得できることを想定しているので、オーソライザーの実装として用いる Lambda 関数に次の環境変数群を設定しておく必要があります。

| 環境変数                         | 説明                      |
| :--------------------------- | :---------------------- |
| `AUTHLETE_BASE_URL`          | Authlete サーバーのベース URL   |
| `AUTHLETE_SERVICE_APIKEY`    | サービスに割り当てられた API キー     |
| `AUTHLETE_SERVICE_APISECRET` | サービスに割り当てられた API シークレット |

<img alt="Lambda 関数の環境変数" class="mx-auto d-block shadow" src="https://mintcdn.com/authlete/oZd1VUaYFwHO2MBx/protocols-and-flows/compliance-profiles/authorizer-environment-variables_ja.png?fit=max&auto=format&n=oZd1VUaYFwHO2MBx&q=85&s=99e77837ac1893b62a7308c63bc0311e" width="808" height="313" data-path="protocols-and-flows/compliance-profiles/authorizer-environment-variables_ja.png" />

## タイムアウト

Lambda 関数のタイムアウトをデフォルト値よりも大きくしておくことをお勧めします。様々な条件が重なることにより、Authlete のイントロスペクション API の応答に時間がかかることがありうるためです。

<img alt="Lambda 関数のタイムアウト" class="mx-auto d-block shadow" src="https://mintcdn.com/authlete/oZd1VUaYFwHO2MBx/protocols-and-flows/compliance-profiles/authorizer-timeout_ja.png?fit=max&auto=format&n=oZd1VUaYFwHO2MBx&q=85&s=b88617a8b0a614b3f3328bd59e426c22" width="715" height="330" data-path="protocols-and-flows/compliance-profiles/authorizer-timeout_ja.png" />

# オーソライザーのパッケージング

Lambda 関数の ZIP パッケージの生成とアップロードの方法は『[Python の AWS Lambda デプロイパッケージ][LPKG]』の『[追加の依存関係を使用して関数を更新する][LPKG_DEP]』で説明されています。

下記は、[authlete][AuthletePython] パッケージを含む Lambda オーソライザーの ZIP ファイルを生成してアップロードする例です。

```
~$ mkdir authorizer
~$ cd authorizer
~/authorizer$ vi lambda_function.py
~/authorizer$ pip install --target ./package authlete
~/authorizer$ (cd package; zip -r9 ../function.zip .)
~/authorizer$ zip -g function.zip lambda_function.py
~/authorizer$ aws lambda update-function-code --function-name authorizer --zip-file fileb://function.zip
```

# テスト

このチュートリアルにおいて、テストが一番大変な箇所かもしれません。というのは、下図に示すテスト環境を構築するために多くの手順が必要だからです。

<img alt="証明書バインディングをテストするため構成要素" class="mx-auto d-block shadow" src="https://mintcdn.com/authlete/YFJcSy4uVerokBPD/protocols-and-flows/compliance-profiles/certificate-binding-components_ja.png?fit=max&auto=format&n=YFJcSy4uVerokBPD&q=85&s=e9cd045af6dd50786bb9fc7e5e82fdb1" width="1600" height="900" data-path="protocols-and-flows/compliance-profiles/certificate-binding-components_ja.png" />

次の手順を一つずつ一緒に踏んでいきましょう。

1. 証明書バインディングをサポートする**認可サーバー**を用意する。
2. 認可サーバー用の**サーバー証明書**を用意する。
3. 認可サーバーのトークンエンドポイントの相互 TLS のために**リバースプロキシ**を設定する。
4. 証明書バインディング用に設定された**クライアントアプリケーション**を用意する。
5. クライアントアプリケーション用の**クライアント証明書**を用意する。
6. Amazon API Gateway 用の**カスタムドメイン**を用意する。
7. **証明書に紐付くアクセストークン**を取得する。
8. **リソースアクセス** (API コール) をおこなう。

## 認可サーバー

認可サーバーとして [java-oauth-server][java-oauth-server] を使うことにしましょう。これは、Authlete をバックエンドサービスとして用いる Java で書かれた認可サーバーです。

```
$ git clone https://github.com/authlete/java-oauth-server
$ cd java-oauth-server
$ vi authlete.properties
$ docker-compose up
```

上記のコマンド群を実行すると、ローカルマシン上の `http://localhost:8080` で認可サーバー (java-oauth-server) が起動します。

それから、[管理者コンソール][SO]にログインし、認可サーバーに対応するサービスの設定を証明書バインディングをサポートするように変更します。

<img alt="証明書バインディング用のサービス設定" class="mx-auto d-block shadow" src="https://mintcdn.com/authlete/YFJcSy4uVerokBPD/protocols-and-flows/compliance-profiles/certificate-binding-server-configuration_ja.png?fit=max&auto=format&n=YFJcSy4uVerokBPD&q=85&s=1ebd0e5c76c8b54350bf38339c7c166f" width="649" height="527" data-path="protocols-and-flows/compliance-profiles/certificate-binding-server-configuration_ja.png" />

## サーバー証明書

認可サーバー用の秘密鍵および自己署名証明書を作成します。

```
$ openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256    > server_private_key.pem
$ openssl req -x509 -key server_private_key.pem -subj /CN=localhost > server_certificate.pem
```

このチュートリアルで使用している `openssl` コマンドは OpenSSL のものであることに注意してください。High Sierra 以降、macOS にインストールされている `openssl` は LibraSSL のものなので、このチュートリアルのコマンドラインをそのまま試すためには OpenSSL の `openssl` をインストールする必要があります。

```
$ /usr/bin/openssl version -a
LibreSSL 2.6.5
......
$ brew install openssl
$ /usr/local/opt/openssl/bin/openssl version -a
OpenSSL 1.1.1g  21 Apr 2020
......
```

## リバースプロキシ

java-oauth-server 自身は自分のエンドポイント群を TLS で保護していないので、TLS 接続を受けるためには java-oauth-server の前にリバースプロキシを置く必要があります。次のものは [Nginx][NGINX] をリバースプロキシとして動かすための設定ファイルの例です。

```nginx theme={null}
events {}
http {
  server {
    # TSL 接続をポート番号 8443 で受け付けます。
    listen 8443 ssl;

    # PEM フォーマットのサーバー証明書
    ssl_certificate /path/to/server_certificate.pem;

    # PEM フォーマットのサーバー秘密鍵
    ssl_certificate_key /path/to/server_private_key.pem;

    # 相互 TLS を有効にします。optional_no_ca はクライアント証明書を要求するものの、
    # それが信頼済み CA 証明書で署名されていることを要求はしません。このチュートリアルの
    # 用途としてはこれで十分です。詳細については ngx_http_ssl_module のドキュメントを
    # 参照してください: http://nginx.org/en/docs/http/ngx_http_ssl_module.html
    ssl_verify_client optional_no_ca;

    # 相互 TLS 接続のクライアント証明書を、背後にあるサーバー (java-oauth-server) に
    # 'X-Ssl-Cert' HTTP ヘッダーの値として渡します。背後にあるサーバーのこの HTTP
    # ヘッダーを認識できる必要があり、java-oauth-server は認識できます。正確には、
    # java-oauth-server が利用している authlete-java-jaxrs ライブラリが認識します。
    proxy_set_header X-Ssl-Cert $ssl_client_escaped_cert;

    # Nginx が 'https://localhost:8443/token' で受けたリクエストを
    # 'http://localhost:8080/api/token' (java-oauth-server の '/api/token') に
    # 転送します。TLS はここで終端されます。
    location = /token {
      proxy_pass http://localhost:8080/api/token;
    }
  }
}
```

この設定により、Nginx は `https://localhost:8443` で起動し、`/token` で受け付けたリクエストを `http://localhost:8080/api/token` に転送します。

この設定ファイルの名前が `nginx.conf` の場合、次のコマンドにより Nginx を起動することができます。

```
$ nginx -c $PWD/nginx.conf
```

Nginx を止めるときは次のコマンドを入力してください。

```
$ nginx -s stop
```

## クライアントアプリケーション

[開発者コンソール][DC]にログインし、テストで使用する予定のクライアントアプリケーションの証明書バインディングの設定を有効にしてください。

<img alt="証明書バインディング用のクライアント設定" class="mx-auto d-block shadow" src="https://mintcdn.com/authlete/YFJcSy4uVerokBPD/protocols-and-flows/compliance-profiles/certificate-binding-client-configuration_ja.png?fit=max&auto=format&n=YFJcSy4uVerokBPD&q=85&s=f199d4a78a0c072d18fc265ca04c1a49" width="543" height="201" data-path="protocols-and-flows/compliance-profiles/certificate-binding-client-configuration_ja.png" />

## クライアント証明書

クライアントアプリケーション用の秘密鍵および自己署名証明書を作成します。

```
$ openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 > client_private_key.pem
$ openssl req -x509 -key client_private_key.pem -subj /CN=client.example.com > client_certificate.pem
```

後ほどテストで使うので、秘密鍵と証明書をもう一組作成してください。コモンネーム (`/CN=`) の値には別の値を指定するようにしてください。というのは、次のセクションで説明しますが、Amazon API Gateway のカスタムドメインの設定が同じサブジェクトの証明書を複数含むトラストストアを拒否するからです。

```
$ openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 > client_private_key_2.pem
$ openssl req -x509 -key client_private_key_2.pem -subj /CN=client2.example.com > client_certificate_2.pem
```

## カスタムドメイン

本記事執筆時点では、Amazon API Gateway で相互 TLS を有効にするためには API に**カスタムドメイン** (例 `api.example.com`) を割り当てなければなりません。

Amazon API Gateway のコンソールには『カスタムドメイン名』というメニューがあります。そこでのカスタムドメイン設定をスムーズにおこなうため、事前に次のものを用意しておいたほうがよいでしょう。

1. カスタムドメイン用の**サーバー証明書**
2. **トラストストア** (信頼するクライアント証明書群を含むファイル)

### カスタムドメイン用のサーバー証明書

Amazon API Gateway のカスタムドメイン用のサーバ証明書は **[AWS Certificate Manager][ACM]** (ACM) の管理下に置かなければなりません。[ACM コンソール](https://console.aws.amazon.com/acm/home)では、既存の証明書をインポートしたり新しく証明書を作成したりすることができます。しかし、相互 TLS が有効になっている場合、インポートした証明書は Amazon API Gateway のカスタムドメイン用としては使えないので、新しく作成してください。

### トラストストア

相互 TLS を設定する際、信頼するクライアント証明書群を含むファイルの場所を入力するよう求められます。そのファイルは**トラストストア**と呼ばれます。Amazon API Gateway の相互 TLS の実装は、TLS ハンドシェイク中に提示されたクライアント証明書がトラストストア内に含まれているかチェックします。もしも含まれていなければ、Amazon API Gateway は Lambda オーソライザーを呼ぶことなく、その接続を拒否します。

トラストファイルは、PEM フォーマットのクライアント証明書群を下記のように列挙するテキストファイルです。

```PEM theme={null}
-----BEGIN CERTIFICATE-----
<証明書の内容>
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
<証明書の内容>
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
<証明書の内容>
-----END CERTIFICATE-----
...
```

<Note>
  PEM フォーマットの詳細については『[図解 X.509 証明書](https://qiita.com/TakahikoKawasaki/items/4c35ac38c52978805c69)』を参照してください。
</Note>

このチュートリアル用のトラストストアは次のコマンドで作成できます。

```
$ cat client_certificate.pem   >  truststore.pem
$ cat client_certificate_2.pem >> truststore.pem
```

Amazon API Gateway が参照できるよう、トラストストアは **[S3][S3]** にアップロードする必要があります。

```
$ aws s3 cp truststore.pem s3://{あなたのS3バケット}
```

上記手順は『[REST API の相互 TLS 認証の設定][MTLSConfig]』に記載されています。詳細はそちらのドキュメントを参照してください。

### カスタムドメイン設定

Amazon API Gateway にカスタムドメインを登録するための準備が整いました。

『ドメインの詳細』で**相互 TLS 認証**を有効にしてください。そうすると、トラストストア URI を入力するフィールドが表示されます。あなたのトラストストアの S3 URI をそのフィールドに入力してください。

<img alt="カスタムドメインの詳細" class="mx-auto d-block shadow" src="https://mintcdn.com/authlete/g-UM9YXVfm3jeKy0/protocols-and-flows/compliance-profiles/custom-domain-details_ja.png?fit=max&auto=format&n=g-UM9YXVfm3jeKy0&q=85&s=f4da3cfbedf5600f4d6d250a5d949f77" width="839" height="756" data-path="protocols-and-flows/compliance-profiles/custom-domain-details_ja.png" />

それから、『エンドポイント設定』でカスタムドメイン用のサーバ証明書を選択してください。

<img alt="カスタムドメインのエンドポイント設定" class="mx-auto d-block shadow" src="https://mintcdn.com/authlete/g-UM9YXVfm3jeKy0/protocols-and-flows/compliance-profiles/custom-domain-endpoint-configuration_ja.png?fit=max&auto=format&n=g-UM9YXVfm3jeKy0&q=85&s=e96e9505c84e228ba657b74659a35684" width="840" height="423" data-path="protocols-and-flows/compliance-profiles/custom-domain-endpoint-configuration_ja.png" />

カスタムドメイン設定後、APIとステージの組をカスタムドメイン下のパスにどのようにマッピングするかを設定することができます。次のスクリーンショットでは、"Example" API の `dev` ステージをカスタムドメインのパス `dev` にマッピングしています。

<img alt="API マッピングを設定" class="mx-auto d-block shadow" src="https://mintcdn.com/authlete/YFJcSy4uVerokBPD/protocols-and-flows/compliance-profiles/configure-api-mappings_ja.png?fit=max&auto=format&n=YFJcSy4uVerokBPD&q=85&s=663655eecf369364018af25ecca3f86c" width="983" height="445" data-path="protocols-and-flows/compliance-profiles/configure-api-mappings_ja.png" />

### ルーティング

カスタムドメイン設定の最後の手順は、カスタムドメインが『API Gateway ドメイン名』にルーティングされるよう、DNS サーバーの CNAME レコードを追加することです。

<img alt="API Gateway domain name" class="mx-auto d-block shadow" src="https://mintcdn.com/authlete/oZd1VUaYFwHO2MBx/protocols-and-flows/compliance-profiles/api-gateway-domain-name_ja.png?fit=max&auto=format&n=oZd1VUaYFwHO2MBx&q=85&s=8c00da4df8e78b063fa5926f2fdca519" width="920" height="403" data-path="protocols-and-flows/compliance-profiles/api-gateway-domain-name_ja.png" />

## 証明書に紐付くアクセストークン

全ての準備が済みました。**[認可コードフロー][6749_41]**を用いて**証明書に紐付くアクセストークン**を発行してみましょう。

### 認可リクエスト

認可コードフローの最初のステップは、認可サーバーの\*\*[認可エンドポイント][6749_31]**に Web ブラウザ経由で**認可リクエスト\*\*を送ることです。このチュートリアルでは、認可エンドポイントは java-oauth-server が提供する `http://localhost:8080/api/authorization` です。認可リクエストを表す下記の URL の `${クライアントID}` をあなたのクライアントアプリケーションの実際のクライアント ID で置き換え、Web ブラウザを使ってその URL にアクセスしてください。

```
http://localhost:8080/api/authorization?response_type=code&client_id=${クライアントID}&scope=profile+email&state=123
```

Web ブラウザには認可サーバーが生成した**認可ページ**が表示されます。次のように見えるでしょう。

<img alt="認可コードフローの認可ページ" class="mx-auto d-block shadow" src="https://mintcdn.com/authlete/oZd1VUaYFwHO2MBx/protocols-and-flows/compliance-profiles/authorization-page-in-authorization-code-flow.png?fit=max&auto=format&n=oZd1VUaYFwHO2MBx&q=85&s=347de94ef024bf68ec8568c3d3cc2417" width="792" height="853" data-path="protocols-and-flows/compliance-profiles/authorization-page-in-authorization-code-flow.png" />

ページにはログイン ID とパスワードを入力するフィールドがあります。そこに `john` と `john` を入力し Authorize ボタンを押してください。Web ブラウザはあなたのクライアントアプリケーションのリダクレクトエンドポイントにリダイレクトされます。

<Note>
  `john` と `john` は java-oauth-server の擬似データベースにハードコードされている値です。
</Note>

<Note>
  認可エンドポイント再訪時に入力フィールドを再表示させたいときは、認可リクエストの末尾に `&prompt=login` を追加してください。`prompt` パラメーターについては [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html) の [3.1.2.1. Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest) を参照してください。
</Note>

クライアントアプリケーションの**リダイレクト URI** をデフォルト値から変更していなければ、リダイレクトエンドポイントの URL は `https://{Authleteサーバー}//api/mock/redirection/{サービスAPIキー}` です。

ブラウザのアドレスバーに表示されているリダイレクトエンドポイントの URL には、次のように `code` レスポンスパラメーターが含まれています。

```
https://{Authleteサーバー}/api/mock/redirection/{サービスAPIキー}?state=123&code=RwRq2Lp0bJVMiLPKAFz4qB1hxieBD1X5HKuv8EPkJeM
```

`code` レスポンスパラメーターの値は、あなたのクライアントアプリケーションに対して認可サーバーから発行された**認可コード**です。この認可コードはクライアントアプリケーションがトークンリクエストを投げる際に必要となります。

### トークンリクエスト

認可コード取得後、クライアントアプリケーションは認可サーバーの\*\*[トークンエンドポイント][6749_32]**に**トークンリクエスト\*\*を投げます。このチュートリアルでは、トークンエンドポイントは Nginx が提供する `https://localhost:8443/token` です。

トークンリクエストはシェル端末で `curl` コマンドを用いて投げることができます。下記はトークンリクエストの例です。タイプする前に、`${クライアントID}` と `${認可コード}` を実際のクライアント ID と認可コードに忘れずに置き換えてください。

```
$ curl -k --key client_private_key.pem --cert client_certificate.pem https://localhost:8443/token -d grant_type=authorization_code -d client_id=${クライアントID} -d code=${認可コード}
```

| 引数                                 | 説明                                                        |
| :--------------------------------- | :-------------------------------------------------------- |
| `-k`                               | サーバー証明書の検証をしない。このチュートリアルでは自己署名サーバー証明書を使っているので、このオプションが必要。 |
| `--key client_private_key.pem`     | クライアントの秘密鍵を指定する。                                          |
| `--cert client_certificate.pem`    | クライアントの証明書を指定する。                                          |
| `https://localhost:8443/token`     | トークンエンドポイントの URL。                                         |
| `-d grant_type=authorization_code` | 認可コードフローであることを示す。                                         |
| `-d client_id=${クライアントID}`         | クライアント ID を指定する。`${クライアントID}` を実際のクライアント ID で置き換えること。     |
| `-d code=${認可コード}`                 | 認可コードを指定する。`${認可コード}` を実際の認可コードで置き換えること。                  |

ここでポイントとなるのは、`--key` オプションと `--cert` オプションを用いてクライアントの秘密鍵と証明書を `curl` コマンドに渡すことです。トークンエンドポイントが相互 TLS を要求するため、すなわち TLS ハンドシェイク中にクライアント証明書を要求するためです。**トークンエンドポイントから発行されるアクセストークンは、トークンリクエストで使用されたクライアント証明書に紐付けられます。**

トークンリクエストが成功すると、トークンエンドポイントは `access_token` を含む JSON を返します。

```json theme={null}
{
  "access_token":  "b5qgqkXpzObRyceBqKeGPDCT9NX9GGXSt_oSYBSj7GQ",
  "refresh_token": "1iSpvpeznTzwdUJzwRbt-abqE4znWn_yhN5PbBKV9zw",
  "scope":         "email profile",
  "token_type":    "Bearer",
  "expires_in":    86400
}
```

`access_token` プロパティーの値が発行されたアクセストークンです。クライアントアプリケーションは API コール時にこのアクセストークンを使います。

## リソースアクセス (API コール)

ついに、証明書に紐付くアクセストークンで保護される Amazon API Gateway 上のリソース (API) にアクセスする準備が整いました。

最初に、アクセストークンと正しいクライアント証明書でリソースにアクセスしてください。下記の例の `${アクセストークン}`、`${カスタムドメイン}`、`${リソース}` は実際の値で置き換えてください。全てが全て正しく設定されていれば、ブロックされることなくリソースの取得に成功するでしょう。

```
$ curl --key client_private_key.pem --cert client_certificate.pem -H "Authorization: Bearer ${アクセストークン}" https://${カスタムドメイン}/${リソース}
```

次に、同じアクセストークンと不正なクライアント証明書 (このチュートリアルでは `client_certificate_2.pem`) でリソースにアクセスしてください。

```
$ curl --key client_private_key_2.pem --cert client_certificate_2.pem -H "Authorization: Bearer ${アクセストークン}" https://${カスタムドメイン}/${リソース}
```

次のエラーレスポンスを受け取ることでしょう。

```json theme={null}
{"Message":"User is not authorized to access this resource with an explicit deny"}
```

これは Amazon API Gateway がリソースアクセスを拒否したことを示しています。ここで最も重要なのは、「**同時に提示したクライアント証明書がアクセストークンに紐付くものとは異なるため、アクセストークンが正当であるにもかかわらずリソースアクセスが拒否された**」ということです。これが証明書バインディングです。

# おめでとうございます！

あなたはこのチュートリアルを完了し、証明書バインディング ([RFC 8705][8705]) を活用してこれまで以上に安全に Amazon API Gateway 上の API を保護することができるようになりました！

サポートが必要であれば\*\*[お問い合わせ][CONTACT]\*\*ください。いつでも歓迎します!

[6749_31]: https://tools.ietf.org/html/rfc6749#section-3.1

[6749_32]: https://tools.ietf.org/html/rfc6749#section-3.2

[6749_41]: https://tools.ietf.org/html/rfc6749#section-4.1

[8705]: https://www.rfc-editor.org/rfc/rfc8705.html

[ACM]: https://aws.amazon.com/certificate-manager/

[ACMConsole]: https://console.aws.amazon.com/acm/home

[APIGW]: https://aws.amazon.com/api-gateway/

[AuthleteApi]: https://github.com/authlete/authlete-python/blob/master/authlete/api/authlete_api.py

[AuthleteEnvConfiguration]: https://github.com/authlete/authlete-python/blob/master/authlete/conf/authlete_env_configuration.py

[AuthletePython]: https://github.com/authlete/authlete-python

[Authorizer]: https://github.com/authlete/authlete-python/blob/master/authlete/aws/apigateway/authorizer.py

[CONTACT]: https://www.authlete.com/ja/contact/

[DC]: https://www.authlete.com/ja/developers/cd_console/

[FAPI]: https://openid.net/wg/fapi/

[java-oauth-server]: https://github.com/authlete/java-oauth-server

[LAUTH]: https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html

[LAUTHIN]: https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-lambda-authorizer-input.html

[LAUTHOUT]: https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-lambda-authorizer-output.html

[LPKG]: https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/python-package.html

[LPKG_DEP]: https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/python-package.html#python-package-dependencies

[MTLSConfig]: https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/rest-api-mutual-tls.html

[MTLSIntro]: https://aws.amazon.com/blogs/compute/introducing-mutual-tls-authentication-for-amazon-api-gateway/

[NGINX]: https://nginx.org/

[OIDC]: https://openid.net/connect/

[S3]: https://aws.amazon.com/s3/

[SO]: https://www.authlete.com/ja/developers/so_console/

[UKOB]: https://www.openbanking.org.uk/

[X509]: https://qiita.com/TakahikoKawasaki/items/4c35ac38c52978805c69
