Serverless FrameworkでAmazon API Gatewayの裏でLambda関数を実行している。この関数でTLSハンドシェイク情報を取得したくて試したメモ。
[やりたいこと]
Serverless Frameworkを使って、こんな感じのserverless.ymlでAPI Gateway経由でLambda関数を呼び出せるようにしている。
service: cloudfront-apigw frameworkVersion: '3' provider: name: aws runtime: nodejs18.x region: ap-northeast-1 functions: api: handler: index.handler events: - httpApi: path: / method: get
このLambda関数へのリクエストについて、API Gatewayに接続しているクライアントのTLSハンドシェイク情報を取得する必要が出てきた。
過去に似たような要件ではALBを利用しており、ALBのアクセスログを後で回収することで、TLSハンドシェイク情報を取得できた。ssl_cipherとかssl_protocolのフィールドがそれだ。
API Gatewayでも似たような機能はないか、と調べてみたものの、下記のようなStackoverflowの書き込みが見つかっただけ。2019年時点ではできない、と書かれていた。
その後のAPI Gatewayの機能リリース情報や、マッピングテンプレートに関するドキュメントを探してみたものの、目ぼしいものは見つからず。
なんとかならないか、と思っていた時に、このリリースを思い出した。CloudFrontを通せばTLSハンドシェイク情報を取得できるのでは...?
[CloudFrontディストリビューションを被せる]
ということで、早速API Gatewayの前段にCloudFrontディストリビューションを実装してみる。以下を参考に必要最小限でディストリビューションを追加した。
何度か試行錯誤しつつ、最終的に先のserverless.ymlに以下を追加すると、CloudFrontディストリビューションを追加できた。
resources:
Resources:
MyDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
Origins:
- Id: MyApiGateway
DomainName: !Join
- "."
- - !Ref HttpApi
- execute-api
- !Ref AWS::Region
- amazonaws.com
CustomOriginConfig:
OriginProtocolPolicy: https-only
DefaultCacheBehavior:
TargetOriginId: MyApiGateway
ViewerProtocolPolicy: https-only
ForwardedValues:
QueryString: false
# https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html#managed-cache-policy-caching-disabled
# CachingDisabled
CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
# https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-origin-request-policies.html
# AllViewerExceptHostHeader
OriginRequestPolicyId: b689b0a8-53d0-40ab-baf2-68738e2966ac
CachePolicyIdとOriginRequestPolicyIdは、何かしら指定しないといけなそうだったので、CachingDisabledポリシーとAllViewerExceptHostHeaderポリシーをそれぞれ指定した。API Gatewayが動的なレスポンスを返すため、キャッシュは不要とした。AllViewerExceptHostHeaderは2023年2月にリリースされた管理ポリシーで、同じくAPI Gateway向けのものである。

マネジメントコンソール上では、どちらもRecommended for API Gateway (API Gatewayでの利用推奨)と書かれており、わかりやすかった。

serverless deployコマンドを実行すると、無事CloudFrontディストリビューションが作成された。
デプロイしているLambda関数は以下のようなコードとなっている。受け取ったイベントをほぼそのままレスポンスとして返すだけのシンプルな内容だ。
module.exports.handler = async (event) => {
return {
statusCode: 200,
body: JSON.stringify(
{
message: "Go Serverless v3.0! Your function executed successfully!",
input: event,
},
null,
2
),
};
};
作成されたCloudFrontディストリビューションのURLをブラウザで開くと、次のように表示された。
event.headersの中に"cloudfront-viewer-tls"というキーでTLSハンドシェイク情報が入っている。今回の場合は、"TLSv1.3:TLS_AES_128_GCM_SHA256:fullHandshake"なので、TLSバージョンはTLS1.3、暗号スイートはTLS_AES_128_GCM_SHA256、初回の旧ハンドシェイクが実行されたことが分かる。

[今後の課題]
これでやりたいことはほぼ実現できたが、今回の実装にはまだいくつか課題がある。
- API Gatewayにインターネットから直接アクセスできないようにする
- CloudFrontディストリビューションのTLSセキュリティポリシー(利用可能な暗号スイート)をAPI Gatewayと揃える
- CloudFrontディストリビューションのドメインをカスタマイズして独自ドメインにする
API GatewayのリージョンAPIエンドポイントやプライベートAPIエンドポイントでは2023年3月現在ではTLS1.3を利用できない*1が、先に見たようにCloudFrontではTLS1.3を利用できる。
加えて、API GatewayのリージョンAPIエンドポイントやプライベートAPIエンドポイントでは前方秘匿性のない暗号スイートを無効化できないが、CloudFrontではTLSv1.2_2019またはTLSv1.2_2021のポリシーを利用することで前方秘匿性に準拠できる*2。
セキュリティ的にもAPI Gatewayを単独で使うのではなく、CloudFrontディストリビューションと組み合わせる構成の方が良さそうだ。