CloudFront Functions で JWT を検証することはできますか
困っていた内容
CloudFront Functions で JWT を検証することはできますか。
どう対応すればいいの?
結論から言えば、 HMAC で署名された JWT であれば検証することが可能ですが、 RSA で署名された JWT を検証することは困難です。
理由についてですが、 CloudFront Functions には以下のような非常に厳しい制約があり、 RSA で署名された JWT を検証する計算リソースが不十分なためです。どうしても CDN レイヤーにおいて RSA で署名された JWT の検証を実行したい場合には、 Lambda@Edge の利用 をご検討ください。
- 関数のソースコードサイズ: 10KB 以内
- 最大メモリ使用量: 2MB 以内
- 実行時間: 1 ミリ秒以内
- 利用可能なモジュール: ビルトインモジュールのみ。
- HMAC 署名の検証用のモジュールは提供されていますが、 RSA 用は提供されていません。
やってみた
というわけで、実際に CloudFront Functions を利用して HMAC で署名された JWT を検証したいと思います。
構成
書くほど複雑なものでもないですが、構成図は以下のようになります。
ユーザーがアクセスすると、 CloudFront ディストリビューションのビューワーリクエストに紐づけられた CloudFront 関数が実行されます。関数内では KeyValueStore に保存された秘密鍵を取得し、これを用いて ユーザーから渡された JWT を検証します。検証が成功した場合にはオリジンに指定した S3 バケットにリクエストを通し、失敗した場合には 401 エラーを返すようにします。
ソースコード
ソースコードはサンプルコードが公式ドキュメントに記載されているので、これをそのまま利用してみます。
あまり機能的にカスタマイズの余地がないので、以下のポイントをおさえつつ、必要に応じて JWT ペイロードの検証を追加していただくと良いと思います。
- 署名の検証にはビルトインモジュールである Crypto を利用する
- HMAC 署名の秘密鍵は KeyValueStore から取得する
nbf
フィールドとexp
フィールドを確認し、 JWT が有効期限内であることを確認する
その他の設定
事前に、CloudFront ディストリビューションの向き先として S3 バケットを用意し、 index.html
に適当な内容を記入しておきます。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CloudFront Functions JWT検証テスト</title></head>
<body>
<h1>CloudFront Functions JWT検証テスト</h1>
<p>検証成功😎</p>
</body>
</html>
次に CloudFront ディストリビューションを作成します。オリジンを先程作成した S3 バケットとします。 OAC は有効化した上で、バケットポリシーを先程の S3 バケットにも反映しておきましょう。
それ以外の項目はスキップしてディストリビューションを作成します。
次に CloudFront Functions に関数を作成します。
後ほど秘密鍵を保存しておくための KeyValueStore を作成しますが、これを利用するにはランタイムバージョンを 2.0 に指定しておく必要があります。
ソースコードを関数に設定したら、関数を発行します。
さらに CloudFront KeyValueStore を作成します。
S3 上にアップロードした JSON から値を投入することもできますが、マネジメントコンソールからも投入できるのでスルーします。
KeyValueStore に HMAC 署名の秘密鍵を投入します。 Key の値は先程のソースコードで指定したキー名と一致するようにしましょう。
あとは作成した KeyValueStore を関数に関連付けします。
最後に CloudFront ディストリビューションのビヘイビアを編集し、ビューワーリクエストで先程作成した CloudFront Functions の関数を指定します。特定のパス配下でのみ実行したい場合には、適切なパスパターンに絞って指定しましょう。
動作確認
CloudFront ディストリビューションのデプロイが完了したら、 S3 バケットにアップロードした index.html
に CloudFront 経由でアクセスしてみます。
まずは JWT なしでアクセスしてみます。すると、関数内で指定したように 401 エラーが返されることが確認できます。
https://dxxxxxxxxxxxxx.cloudfront.net/index.html
次に、 JWT ありでアクセスしてみます。 AWS から提供されているサンプルコードに JWT を生成するシェルが提供されているので、これを実行して生成された JWT を利用してアクセスしてみます。
./generate-jwt.sh
eyJh...ciOiAiSFMyNTYiLCJ0eXAiOiAiSldUIn0.eyJz...IjogIjEyMzQ1Njc4OTAiLCJuYW1lIjogIkpvaG4gRG9lIiwiaWF0IjogMTUxNjIzOTAyMiwgIm5iZiIgOiAxNjE2MjM5MzIsImV4cCIgOjE3NDA5ODU5MTggfQ.oEWz_-aBcV...1h-0n91h0
https://dxxxxxxxxxxxxx.cloudfront.net/index.html?jwt=eyJh...ciOiAiSFMyNTYiLCJ0eXAiOiAiSldUIn0.eyJz...IjogIjEyMzQ1Njc4OTAiLCJuYW1lIjogIkpvaG4gRG9lIiwiaWF0IjogMTUxNjIzOTAyMiwgIm5iZiIgOiAxNjE2MjM5MzIsImV4cCIgOjE3NDA5ODU5MTggfQ.oEWz_-aBcV...1h-0n91h0
無事に JWT ありの場合でのみ index.html
にアクセスできることが確認できました。
あとは補足情報ですが、 CloudFront Functions の関数内に console.log
で出力した内容は、 CloudWatch Logs の /aws/cloudfront/function/${関数名}/
ロググループで確認することができます。