こんにちは!イーゴリです。
今日はAWS Private CAを使って、相互 TLS 認証 で サーバーレス API にアクセスする方法を紹介します。
構成図
ユースケース
- IoTデバイスからのAPI実行
- インターネットから特定のクライアント端末でのアクセス
前提条件
- 対象Lambda用のコードが存在していること
- ACM証明書が存在していること
相互 TLS 認証なしの構成
まずは相互 TLS 認証なしで動作確認を行います。アプリ動作確認後、相互 TLS 認証を追加します。
Lambda の作成(すでにある場合はスキップ)
[サービス] > [Lambda] > [関数の作成]をクリックします。
[一から作成]を選択し、他の設定を要件に合わせて入力します。
今回の記事では関数名を入力し、Pythonを選択し、[関数の作成]をクリックします。
必要なコードを入れて、[Deploy]をクリックします。
デプロイした後で、[Test]をクリックし、イベント名を入力し、[保存]をクリックします。
Lambda正常性を確認できました。
API Gateway の構築
API Gateway カスタムドメイン名の作成
[サービス] > [カスタムドメイン名] > [作成]をクリックします。
ドメイン名を入力し、将来的には[相互 TLS 認証]を選択しますが、まずは相互 TLS 認証なしでの動作の比較を見せたいため、一旦、[相互 TLS 認証]をスキップし、[エンドポイント設定]の[ACM 証明書]※を選択し、[ドメイン名を作成]をクリックします。
※これはブラウザーでアクセスする時のドメイン名になります。
ステータスが[利用可能]になったので、次に行きます。
API の作成
[サービス] > [API] > [REST API] > [構築]をクリックします。
[新しい API]を選択し、[API 名]を入力し、[API を作成]をクリックします。
[メソッドを作成]をクリックします。
メソッドタイプ:GET 統合タイプ:Lambda 関数
Lambda 関数:上記にて作成したLambda 関数を選択します。 [メソッドを作成]をクリックします。
なお、セキュリティ向上のため、お客様がクライアントの API へのアクセスにカスタムドメイン名のみを使用するように要求したいので、デフォルトの API エンドポイントを無効にします。
クライアントがカスタムドメイン名を使用した場合のみ API にアクセスできるようにするには、デフォルトの execute-api エンドポイントを無効にします。
[対象API] > [API 設定] > API詳細のところで[編集]をクリックします。
[デフォルトのエンドポイント] > [非アクティブ]を選択し、[変更を保存]をクリックします。
[API をデプロイ]をクリックします。
- ステージ:New Stage
- ステージ名:適切なステージ名を入力します。
[デプロイ]をクリックします。
APIをAPI Gatewayとマッピングする
[サービス] > [API Gateway] > [カスタムドメイン名] > 対象カスタムドメイン名 > [API マッピング] > [API マッピングを設定]をクリックします。
[新しいマッピングを追加] をクリックします。
適切なAPI及びステージを選択し、[保存]をクリックします。
Route53で新規レコードを登録
API Gatewayドメイン名をA (Alias)レコードとしてRoute53に登録します。
[サービス] > [Route 53] > 対象ホストゾーンを選択し、[レコードを作成]をクリックします。
適切なレコード名を入力※し、[エイリアス]をクリックし、[トラフィックのルーティング先]で、上記にて作成したAPI Gateway ドメイン名を選択し、[レコードを作成]をクリックします。
※今回は「secret.<ドメイン名>」にしたいため、「secret」を入力しました。
接続確認
ブラウザーでの確認
ブラウザーで「secret.<ドメイン名>」にアクセスします。
期待通りの結果でした。
コマンドでの確認
curl https://secret.<ドメイン名>
出力結果:
{"statusCode": 200, "body": "{\"Token\": \"x4JkouVV7V3FFtwlCiNmhxvQRSoc7jyMBP9mgBkK5Es\", \"Creation Date\": \"2023-12-26T05:52:38.496857\"}"}%
期待通りの結果でした。
相互 TLS 認証を追加
上記の構成だと、誰でもアクセスできてしまいます。特定のデバイスのみアクセスできるようにするには相互 TLS 認証を追加します。
AWS Private CA の作成
クライアント証明書を作るにはAWS Private CAを使いますが、普通のOpenSSLを使っても問題ないです。AWS Private CAのメリットとしてはマネージドサービスのため、プライベート証明書の管理/運用の手間がなくなります。
但し、「汎用モード」の場合、プライベート CA あたり月額 400 USD + 当月に発行された証明書の数の料金が発生するので、ご注意ください。
ダブル注意:今回の記事では、2個のプライベート証明書を発行するため、そのままプライベート証明書残してしまうと、月/800USDを請求されます。
詳しくは下記の料金ページをご参考ください。
証明書の階層構成の説明
SSL/TLSのベストプラクティス的には、SSL/TLS証明書は通常CAの「ルート証明書」ではなく中間証明書によって署名されるため、ルート証明書を作成し、その配下に「下位証明書」を作成し、「下位証明書」を使用します。
ルート証明書の作成
[サービス] > [AWS Private Certificate Authority]を検索し、選択します。
[プライベート認証機関] > [プライベート CA を作成]をクリックします。
- モードオプション:平凡
- CA タイプのオプション:ルート
[サブジェクトの識別名のオプション]:必要な情報を入力します。
プライベート証明書はすべての情報が公開されないため、すべての項目を記載しなくても問題ないですが、少なくとも 1 つの識別名を入力する必要があるため、[共通名 (CN)] (=Common Name)だけ入力します。
次に行きます。
「このアカウントでリクエストされた証明書を更新するための ACM アクセスを承認します。」のところにチェックが入っていることを確認し、「料金」の「このプライベート CA のサービス料金を確認してください。」の注意事項を確認し、チェックを入れて、[CA を 作成]をクリックします。
ちなみに、「料金」の「このプライベート CA のサービス料金を確認してください。」の注意事項にチェックを入れないと「続行するには、サービスの料金を確認してください。」という表示がでます。さすが高いサービスだけあって、注意を促してくれるのはありがたいですね!
CAのルート証明書が正常に作成されました。
但し、ステータスが[保留中の証明書]のため、CA 証明書をインストールする必要があります。
証明書の一覧に戻り、対象証明書を選択し、[アクション] > [CA 証明書をインストール]をクリックします。
[ルート CA 証明書をインストール]の画面では証明書のデフォルト有効期限が10年となっておりますが、カレンダーマークをクリックすることで、有効期限の変更が可能です。
問題なければ、[確認してインストール]をクリックします。
ルート証明書のステータスが[アクティブ]になりましたので、次に進みます。
ACMでプライベート証明書をリクエスト
[サービス] > [AWS Certificate Manager (ACM)] > [リクエスト]をクリックします。
[プライベート証明書をリクエスト]※を選択し、[次へ]をクリックします。
証明機関:上記にて作成したルート証明書 ドメイン名:private.<ドメイン名>※
※ドメイン名は任意の名前で問題ないです
[証明書の更新許可]の「私は、ACM に必要な許可がなければ、この認証機関からのプライベート証明書の発行を更新できないことを理解しています。」を選択し、[リクエスト]をクリックします。
ステータスが[発行済み]になりましたら、次へ進みます。
同様の手順を繰り返して、2個目の証明書を発行します(FQDNは「private2.<ドメイン名>」にします)。
下位証明書の作成
[サービス] > [AWS Private Certificate Authority]を検索し、選択します。
[プライベート認証機関] > [プライベート CA を作成]をクリックします。
- モードオプション:平凡
- CA タイプのオプション:下位
[サブジェクトの識別名のオプション]:必要な情報を入力します。
何も書かなくても問題ないですが、少なくとも 1 つの識別名を入力する必要があるため、[共通名 (CN)] (=Common Name)だけ入力します。「sub.<ルート証明書名>」として記載します。
「このアカウントでリクエストされた証明書を更新するための ACM アクセスを承認します。」にチェックが入っていることを確認し、「料金」の「このプライベート CA のサービス料金を確認してください。」の注意事項を確認しチェックを入れて、[CA を作成]をクリックします。
ステータスが[保留中の証明書]のため、CA 証明書をインストールする必要があります。
証明書の一覧に戻り、対象証明書を選択し、[アクション] > [CA 証明書をインストール]をクリックします。
[ルート CA 証明書をインストール]の画面では証明書の最短の有効期限としてルート証明書の期限(今回は10年)+1日となっています。但し、カレンダーマークをクリックすることで、有効期限を伸ばすことができます。
期限などはそのまま残して[確認してインストール]をクリックします。
CAの下位証明書が正常に作成されました。
トラストストア(S3バケット)に下位証明書をアップロード
S3を作成し、名前以外の設定は不要ですが、追加要件次第で他の項目を設定してください。
名前:ca-auth-test123-bucket-<アカウントID>
AWS Private Certificate Authorityで下位証明書を選択し、[アクション] > [CA 証明書の取得]をクリックします。
ローカル端末で[証明書本文](上)>[中間証明書](下)の順番で内容をファイルに入れます。
ファイルを「.pem」として保存します。
上記のファイルをトラストストア(S3バケット)にアップロードします。
アップロードしたファイルのS3 URIをコピーします。
API Gatewayで相互 TLS 認証の設定
[API Gateway] > [カスタムドメイン名] > 対象ドメイン名を選択 > [編集]をクリックします。
相互 TLS 認証を選択します。
トラストストアURI:sub.test123.pemのS3 URIを貼り付けます。
[保存]ボタンをクリックします。
[更新中]のステータスになり、[利用可能]になるまで待機します。
ここからクライアント証明書がないとアクセスできなくなります。
接続確認
下記のコマンドにはクライアント証明書の指定がないため、接続エラーが発生しています。
curl https://secret.<ドメイン名>.jp
curl: (35) Recv failure: Connection reset by peer
クライアント証明書を端末にエクスポート
ACMにある対象証明書をクリックする。
今回は「private2.<ドメイン名>」を使用しますので、「private2.<ドメイン名>」をクリックします。
[エクスポート]をクリックします。
[暗号化の詳細]にある[パスフレーズ]欄にパスフレーズを設定し、[PEM エンコーディングを生成]をクリックします。
ローカル端末で[証明書本文](上)>[中間証明書](下)の順番で内容をファイルに入れて、crt.pemとして保存し、証明書のプライベートキーの内容をファイルに入れてprivate.pemとして保存します。
接続確認
下記のコマンドで実行すると成功です。
curl -i --key private.pem --cert crt.pem https://secret.<ドメイン名>.jp
HTTP/2 200 x-amzn-requestid: e7fbcebe-12fd-4d64-b45a-4584d6a756b7 x-amz-apigw-id: QiyYhF-kNjMFa4Q= x-amzn-trace-id: Root=1-658a9769-4416a089186f5675156db77b;Sampled=0;lineage=f7bd93b1:0 content-type: application/json content-length: 142 date: Tue, 26 Dec 2023 09:05:45 GMT {"statusCode": 200, "body": "{\"Token\": \"_Yr5VhoALESWua-R3GjqPSINrql2iMZ7RnWfjMr-Ogs\", \"Creation Date\": \"2023-12-26T09:05:45.777936\"}"}%
成功しました!
補足
curl: (58) unable to set private key file: type PEMのエラーが出た場合
.pemを.keyで保存してしまいますと、curlコマンド実施時に下記のエラーが発生します。
curl: (58) unable to set private key file: 'private.key' type PEM
PEMタイプに保存し直すために下記のコマンドを実行します。
openssl rsa -in 現在の秘密鍵名.pem -out 保存したい秘密鍵ファイル名.pem
※上記のコマンドの場合、クライアント証明書がPWなしで保存されます。
クライアント証明書をPWなしで設定したい場合
ACMからダウンロードしたクライアント証明書がPW付きのため、コマンドを実行する際にPWを入力する必要があります。クライアント証明書をPWなしで保存したい場合、上記と同じく下記のコマンドを実行します。
openssl rsa -in 現在のPW付きの秘密鍵名.pem -out 保存したいPWなしの秘密鍵ファイル名.pem 例)openssl rsa -in private.pem -out nonpw_private.pem
試しに下記のコマンドを実行します。
curl -i --key nonpw_private.pem --cert crt.pem https://secret.<ドメイン名>.jp
想定通りPWなしで実行できました。
以上、御一読ありがとうございました。
本田 イーゴリ (記事一覧)
カスタマーサクセス部
・2024 Japan AWS Top Engineers (Security)
・AWS SAP, DOP, SCS, DBS, SAA, DVA, CLF
・Azure AZ-900
・EC-Council CCSE
趣味:日本国内旅行(47都道府県制覇)・ドライブ・音楽