独自 CA で発行したサーバ証明書を使用して API Gateway の mTLS を設定してみた

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

テントの中から失礼します、IoT 事業部のてんとタカハシです!

API Gateway では mTLS による認証をサポートしています。元々は、ACM で発行したサーバ証明書のみに限定されていましたが、昨年(2021年)の8月から、独自 CA で発行したサーバ証明書についても mTLS で使用することが可能になりました。

今回は、独自 CA で発行したサーバ証明書を使用して、API Gateway で mTLS を設定する手順を試してみます。せっかくなので、AWS の環境上に何もリソースを作成していない状態からの手順を記載していきます。

尚、本記事を作成するにあたり、下記の記事を参考にしました。

手順

ドメインを取得する

API Gateway で mTLS を設定するためには、カスタムドメインを作成する必要があります。となると、まずはカスタムドメインを作成するためのドメインを用意しておく必要があります。

今回は、Freenom で無料のドメイン(.tk)を取得しました。Freenom については、下記の記事を参考にしてください。

Route53 でパブリックホストゾーンを作成する

取得したドメインで Route53 のパブリックホストゾーンを作成していきます。

ホストゾーンの一覧画面から「ホストゾーンの作成」をクリックします。

ドメイン名の入力欄に、今回取得したドメインを入力して、「ホストゾーンの作成」をクリックします。

ホストゾーンが作成されると、NS レコードが表示されます。これらを全て Freenom 側に登録して、権限委譲を行います。

Freenom 側の画面は、こんな感じになります。NS レコードの値を貼り付けたら、「Change Nameservers」をクリックします。

下記のように dig コマンドを実行して、設定した NS レコードの値が表示されていれば OK です。

$ dig <取得したドメイン> NS

...

;; ANSWER SECTION:
<取得したドメイン>.	21600	IN	NS	ns-1148.awsdns-15.org.
<取得したドメイン>.	21600	IN	NS	ns-120.awsdns-15.com.
<取得したドメイン>.	21600	IN	NS	ns-1967.awsdns-53.co.uk.
<取得したドメイン>.	21600	IN	NS	ns-744.awsdns-29.net.

...

Route53 でドメインの権限移譲を行う方法については、下記の記事にも記載がありますので、参考にしてください。

各証明書を作成する

今回使用する証明書を順に作成していきます。Common Name の値については、適宜変更してください。

ルート CA 証明書

まずはルート CA 証明書の作成から。こちらは、後々作成する S3 バケットにファイルごとアップロードします。

$ openssl version
LibreSSL 2.8.3

$ openssl genrsa -out RootCA.key 4096
$ openssl req -new -x509 -days 36500 -key RootCA.key -out RootCA.pem -subj "/C=JP/ST=Tokyo/O=Classmethod/CN=tent-takahashi ca"
$ ls
RootCA.key	RootCA.pem

クライアント証明書

本手順で完成した API に対して正常なリクエストを行う際に使用します。

$ openssl genrsa -out client.key 2048
$ openssl req -new -key client.key -out client.csr -subj "/C=JP/ST=Tokyo/O=Classmethod/CN=tent-takahashi client"
$ openssl x509 -req -in client.csr -CA RootCA.pem -CAkey RootCA.key -set_serial 01 -out client.pem -days 36500 -sha256
$ ls
RootCA.key	RootCA.pem	client.csr	client.key	client.pem

中間証明書

こちらは後々、ACM に内容をインポートします。

$ openssl genrsa -out inca.key 2048
$ openssl req -new -key inca.key -out inca.csr -subj "/C=JP/ST=Tokyo/O=Classmethod/CN=tent-takahashi inca"
$ openssl x509 -req -in inca.csr -CA RootCA.pem -CAkey RootCA.key -set_serial 01 -out inca.pem -days 36500 -sha256
$ ls
RootCA.key	client.csr	client.pem	inca.key
RootCA.pem	client.key	inca.csr	inca.pem

サーバー証明書

こちらも ACM に内容をインポートします。Common Name の値は、取得したドメインを設定することに注意してください。

$ openssl genrsa -out server.key 2048
$ openssl req -new -key server.key -out server.csr -subj "/C=JP/ST=Tokyo/O=Classmethod/CN=<取得したドメイン>"
$ openssl x509 -req -in server.csr -CA inca.pem -CAkey inca.key -set_serial 01 -out server.pem -days 36500 -sha256
$ ls
RootCA.key	client.csr	client.pem	inca.key	server.csr	server.pem
RootCA.pem	client.key	inca.csr	inca.pem	server.key

ルート CA 証明書を S3 バケットに格納する

API Gateway で mTLS を設定するためには、予めルート CA 証明書を S3 バケットに格納しておく必要があります。クライアントからのリクエストに含まれるクライアント証明書の妥当性を検証するために使用されます。

ということで、S3 バケットを作成していきます。バケットの一覧画面から「バケットを作成」をクリックしてください。

バケット名にはグローバルで一意になる名前を入力してください。ここではtrust-store-[アカウントID]としています。

画面を下にスクロールして、「バケットのバージョニング」を有効に切り替えてください。後々、API Gateway で mTLS を設定する際に、ルート CA 証明書のバージョンを入力します。

なぜバージョンを入力するかについてですが、ルート CA 証明書自体を更新したい場合に、バケット上のファイルを上書きするだけだと、mTLS の設定には更新が反映されません。反映させる場合は、mTLS の設定に予め入力していたルート CA 証明書のバージョンを上書きしてあげる必要があります。わりかし躓きポイントなので、覚えておくと良いことがありそうです。

有効に切り替えた後は、「バケットを作成」をクリックします。

作成したバケットにルート CA 証明書をアップロードしていきます。作成したバケットの名前をクリックしてください。

「アップロード」をクリックしてください。

「ファイルを追加」をクリックして、ファイルダイアログからルート CA 証明書(RootCA.pem)を選択してください。その後、「アップロード」をクリックします。

ルート CA 証明書のバージョンを確認します。ファイル名をクリックしてください。

タブ「バージョン」をクリックしてください。

バージョン ID が表示されます。こちらの値は後ほど使用しますので、どこかにメモしておいてください。

ACM にサーバ証明書をインポートする

独自 CA で発行したサーバ証明書を ACM にインポートしていきます。

ACM の証明書一覧画面から「インポート」をクリックします。

各入力欄の中身は下記のとおりです。入力したら「次へ」をクリックします。

  • 証明書本文
    • サーバ証明書(server.pem)の中身を全て入力する
  • 証明書のプライベートキー
    • サーバ証明書の秘密鍵(server.key)の中身を全て入力する
  • 証明書チェーン
    • 中間証明書(inca.pem)の中身を全て入力する

タグは必要に応じて設定してください。「次へ」をクリックします。

内容に問題なければ、「インポート」をクリックします。

ACM で所有権検証証明書を発行する

独自 CA で発行したサーバ証明書を使用して mTLS を設定する場合は、別途、所有権検証証明書というものが必要になります。これはなんぞや?と言う話ですが、AWS のドキュメントには下記の記載があります。

ACM にインポートされた証明書、または相互 TLS を用いた AWS Certificate Manager Private Certificate Authority からの証明書を使用するには、API Gateway には ACM が発行した ownershipVerificationCertificate が必要です。この所有権証明書は、ドメイン名を使用する許可を持っていることを確認するためにのみ使用されます。TLS ハンドシェイクには使用されません。

要は、設定されているドメインの所有者ですよ〜ということを確認するために、ACM が発行したパブリック証明書が必要だということみたいです。あくまでも所有権の検証で使用されるものであり、実際の mTLS による認証を行う際には使用されません。

ということで、ACM で所有権検証証明書を発行していきます。証明書の一覧画面から「リクエスト」をクリックしてください。

「次へ」をクリックしてください。

ドメイン名の入力欄には、取得したドメインを入力してください。検証方法は「DNS 検証」のままで OK です。「リクエスト」をクリックします。

証明書の一覧画面に戻り、ステータスが「保留中の検証」となっている証明書 ID をクリックします。

「Route 53でレコードを作成」をクリックします。DNS 検証を通すためには、先ほど作成した Route53 のパブリックホストゾーンに CNAME レコードを作成してあげる必要があります。

「レコードを作成」をクリックします。

先ほど作成した Route53 のパブリックホストゾーンを確認すると、CNAME レコードが作成されていることを確認できます。

その後、少し待つと、ACM でリクエストした証明書のステータスが「発行済み」に変わります。

API Gateway にて REST API を構築する

ここでは、AWS が用意しているサンプルの REST API を構築していきます。

API の一覧から「API を作成」をクリックします。

REST API の枠内にある「構築」をクリックします。

新しい API の作成にて「API の例」を選択します。すると、PetStore という名前のサンプル API を構築することができます。そのまま「インポート」をクリックします。

画面左側のメニューから「設定」をクリックします。

デフォルトのエンドポイントを「無効」に切り替えます。mTLS を設定しても、デフォルトのエンドポイントについては、クライアント証明書無しでアクセスできてしまうため、必ず無効に切り替えることを忘れないようにしましょう。切り替えたら、「変更の保存」をクリックします。

画面左側のメニューから「リソース」に戻ります。「アクション」から「API のデプロイ」をクリックします。

デプロイされるステージを「新しいステージ」にして、ステージ名を入力してください。ここでは prod にしています。「デプロイ」をクリックします。

カスタムドメインを設定 & mTLS を有効にする

色々と準備してきましたが、漸くカスタムドメインと mTLS の設定を行う段階に来ました。

画面左側のメニューから「カスタムドメイン名」をクリックします。

「作成」をクリックします。

各入力欄を下記の通りにしてください。その後、「ドメイン名を作成」をクリックします。

  • ドメイン名
    • 取得したドメインを入力する
  • TLS の最小バージョン
    • TLS 1.2(推奨)
  • 相互 TLS 認証
    • 有効!
  • トラストストア URI
    • ルート CA 証明書の格納先 URI を入力
    • s3://trust-store-[アカウントID]/RootCA.pem
  • Truststore version
    • (先ほどメモった)ルート CA 証明書のバージョンを入力
    • ※ ルート CA 証明書自体を更新した場合は、その際に発行された新しいバージョンでここを上書きすることを忘れずに
  • エンドポイントタイプ
    • リージョン
  • 証明書タイプ
    • インポートされた証明書またはプライベート証明書
  • 証明書
    • ACM にインポートした証明書を選択(選択時に表示される証明書 ID で判別してください)
  • 所有権検証証明書
    • ACM で発行した証明書を選択(選択時に表示される証明書 ID で判別してください)

ステータスが「利用可能」となっていることを確認したら、タグ「マッピング」をクリックします。

「API マッピングを設定」をクリックします。

カスタムドメインと紐づける API を設定します。

「新しいマッピングを追加」をクリックした後、APIを「PetStore」、ステージを「prod」に設定してください。その後、「保存」をクリックします。

Route53 のパブリックホストゾーンにAレコードを追加する

最後の手順になります。最初に作成したパブリックホストゾーンに A レコードを追加して、ドメイン名から API に対して名前解決できるようにします。

パブリックホストゾーンの画面から「レコードを作成」をクリックします。

「次へ」をクリックします。

「シンプルなレコードを定義」をクリックします。

レコードタイプを「A -...」、値/トラフィックのルーティング先を順に「API Gateway API へのエイリアス」、「アジアパシフィック(東京)」、「作成したAPI Gatewayのエンドポイント」として設定してください。その後、「シンプルなレコードを定義」をクリックします。

「レコードを作成」をクリックします。

これで手順は終了となります。長かった。。お疲れ様でした。

リクエストを投げてみる

クライアント証明書を使用して、カスタムドメインに向けてリクエストを投げてみます。PetStore API なので、ペットの情報が返ってくることを確認することができます。

$ curl --include --insecure --key client.key --cert client.pem https://<取得したドメイン>/pets
HTTP/2 200 
x-amzn-requestid: b43bc7a3-1ede-4042-9d63-e5834fdcde9c
access-control-allow-origin: *
x-amz-apigw-id: Tj_y9Ha0NjMFW2w=
x-amzn-trace-id: Root=1-62a4acdf-09133ef55914986ed911c0ff
content-type: application/json
content-length: 184
date: Sat, 11 Jun 2022 14:55:27 GMT

[
  {
    "id": 1,
    "type": "dog",
    "price": 249.99
  },
  {
    "id": 2,
    "type": "cat",
    "price": 124.99
  },
  {
    "id": 3,
    "type": "fish",
    "price": 0.99
  }
]

クライアント証明書無しでリクエストを投げてみると、エラーが発生することを確認できます。

$ curl --include --insecure https://<取得したドメイン>/pets
curl: (35) LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to <取得したドメイン>

おわりに

独自 CA で発行したサーバ証明書を使用して mTLS を設定できるようになったことで、更に幅広い要件を API Gateway で満たすことができるようになったのではないでしょうか。

手順がそこそこ長くなってしまったので、CDK や CloudFormation を活用して、同じ環境を即デプロイできるようにチャレンジしてみようかなと思いました。

今回は以上になります。最後まで読んで頂きありがとうございました!