AWS LambdaをAmazon CloudFrontのオリジンに指定してみた
先日のアップデートにより、AWS LambdaにHTTPSエンドポイントを設定できるようになり、Amazon API GatewayやApplication Load Balancerなどを挟まずにLambdaを直接APIとして呼び出せるようになりました。
- [アップデート]LambdaがHTTPSエンドポイントから実行可能になる、AWS Lambda Function URLsの機能が追加されました! | DevelopersIO
- Announcing AWS Lambda Function URLs: Built-in HTTPS Endpoints for Single-Function Microservices | AWS News Blog
LambdaをHTTPSで呼び出せるということは、Amazon CloudFrontのオリジンにLambdaを指定できることを意味し、CDNレイヤーでキャッシュしたりWAFを挟むことも可能です。
やってみた
実際に、Lambda を CloudFront から呼び出してみましょう。
オリジンに指定した Lambda から、リクエストのパス・クエリーストリングを取得できることを確認します。
1. Lambda関数を作成
まずはリクエストを処理する Lambda 関数を作成します。
利用者の多いリージョンに作成すると、CDNのキャッシュミス時のレイテンシーが小さくなります。
import json def lambda_handler(event, context): print(event) path = event['rawPath'] # /foo param = event.get('queryStringParameters', '') # {"bar" : "baz"} body = { 'path' : path, 'param' : param, } return { 'statusCode': '200', 'body': json.dumps(body), 'headers': { 'Content-Type': 'application/json', } }
リクエストのURIパス、クエリーストリングを返しているだけです。
2. エンドポイントURLを払い出す
Lambda 関数の Configuration -> Function URL からエンドポイントURLを払い出します。
CloudFrontから認証なしで呼び出せるよう、Auth Type : NONE
にします。
3. Lambdaエンドポイントの動作確認
LambdaをURLから呼び出せることを確認します。
$ URL=https://xxx.lambda-url.ap-northeast-1.on.aws/ $ curl $URL {"path": "/", "param": ""} $ curl $URL/foo {"path": "/foo", "param": ""} $ curl $URL/foo\?bar=baz {"path": "/foo", "param": {"bar": "baz"}}
4. Lambda用CloudFrontオリジンリクエストポリシーを作成
オリジンのLambdaでクエリーストリングを処理したいため、専用のオリジンリクエストポリシーを作成します。
Origin request settingsにおいて Query strings を All にします。
Cookieやリクエストヘッダーもオリジンに渡したい場合は、適宜修正してください。
なお、Lambda URLは Host
リクエストヘッダーが Lambda のエンドポイントと異なると、 403 エラーを返します。
$ curl -I \ -H "Host: dummy.example.com" \ https://xxx.lambda-url.ap-northeast-1.on.aws/ HTTP/1.1 403 Forbidden Date: Sun, 17 Apr 2022 09:49:23 GMT Content-Type: application/json Content-Length: 16 Connection: keep-alive x-amzn-RequestId: 47afb71d-36cb-4e76-a36f-8bdf5d1eb510 x-amzn-ErrorType: AccessDeniedException
そのため、全てのリクエストヘッダーをオリジンに転送するマネージドポリシーの AllViewer
を適用すると、CloudFront のホストヘッダーで Lambda にリクエストするため、同じ 403 Forbidden が発生します。
$ curl -I https://xxx.cloudfront.net HTTP/2 403 content-type: application/json content-length: 16 date: Sun, 17 Apr 2022 09:50:07 GMT x-amzn-requestid: d6d3bd79-656a-4e66-afda-25ae79da0238 x-amzn-errortype: AccessDeniedException x-cache: Error from cloudfront via: 1.1 xxx.cloudfront.net (CloudFront) x-amz-cf-pop: TXL50-P1 x-amz-cf-id: 1rQ7z8kkQwE0PWiKEeUo3tmTUAlFqpWvyfCmItzEqMxZcPGJ7Ri1oA==
リクエストヘッダーの転送が必要な場合、カスタムリクエストヘッダーの作成や Legacy cache settings でカスタマイズし、HOST
は含めないでください。
5. CloudFrontディストリビューションを作成
CloudFront ディストリビューションを作成します。
Origin
Origin の設定画面において
- Origin domain : Lambda の Function URL
- Protocol : HTTPS only
- Minimum origin SSL protocol : TLSv1.2
- Name : Lambda
とします。
Default cache behavior
behavior 設定画面において
- Origin request policy : lambda-endpoint(#4 で作成したポリシー)
を設定します。
6. CloudFront 経由の動作確認
最後に、CloudFront ディストリビューションのドメイン経由で Lambda を呼び出します。
$ curl -D - https://xxx.cloudfront.net/foo\?a\=b HTTP/2 200 content-type: application/json content-length: 37 date: Fri, 15 Apr 2022 08:31:36 GMT x-amzn-requestid: 805d10c9-39ac-4753-a882-117b73aac1e3 x-amzn-trace-id: root=1-62592d68-2962e3481bf1f7f30994284b;sampled=0 x-cache: Miss from cloudfront via: 1.1 xxx.cloudfront.net (CloudFront) x-amz-cf-pop: DUS51-P1 x-amz-cf-id: 6ulq_7myYfNMkqeh71aNspQ8A26hkzY96nqXCzrDF3EByD_rPU2AUw== server-timing: cdn-upstream-layer;desc="REC",cdn-upstream-dns;dur=0,cdn-upstream-connect;dur=676,cdn-upstream-fbl;dur=965,cdn-cache-miss,cdn-pop;desc="DUS51-P1",cdn-rid;desc="6ulq_7myYfNMkqeh71aNspQ8A26hkzY96nqXCzrDF3EByD_rPU2AUw==" {"path": "/foo", "param": {"a": "b"}} $ curl -D - https://xxx.cloudfront.net/foo\?a\=b HTTP/2 200 content-type: application/json content-length: 37 date: Fri, 15 Apr 2022 08:31:36 GMT x-amzn-requestid: 805d10c9-39ac-4753-a882-117b73aac1e3 x-amzn-trace-id: root=1-62592d68-2962e3481bf1f7f30994284b;sampled=0 x-cache: Hit from cloudfront via: 1.1 xxx.cloudfront.net (CloudFront) x-amz-cf-pop: DUS51-P1 x-amz-cf-id: 9D3LpWjDbIkRkOmLe7yp1HYE4jbm2PwmweHb-e8QVM9EYG_9xz8O5g== age: 3 server-timing: cdn-cache-hit,cdn-pop;desc="DUS51-P1",cdn-rid;desc="9D3LpWjDbIkRkOmLe7yp1HYE4jbm2PwmweHb-e8QVM9EYG_9xz8O5g==",cdn-hit-layer;desc="REC" {"path": "/foo", "param": {"a": "b"}}
1回目の呼び出しではCDNがキャッシュミスし、2回目の呼び出しではCDNがキャッシュヒットしています。
API Gateway と CloudFront の使い分け
CloudFront に複数の Behavior を設定し、Path に応じて Lambda を切り分ければ、CloudFront を API Gateway のように使うことも可能です。
とはいえ、CloudFront はあくまでも簡易的に Lambda を保護・キャッシュするためだけに用い、複雑なルーティングをしたい場合は、素直に API Gateway を使ったほうが良いでしょう。
Lambda リクエストペイロードへの影響
LambdaをURLで呼び出したときのリクエストペイロードは以下の形をしています。
参考 https://docs.aws.amazon.com/lambda/latest/dg/urls-invocation.html#urls-payloads
{ "version": "2.0", "routeKey": "$default", "rawPath": "/my/path", "rawQueryString": "parameter1=value1¶meter1=value2¶meter2=value", "cookies": [ "cookie1", "cookie2" ], "headers": { "header1": "value1", "header2": "value1,value2" }, "queryStringParameters": { "parameter1": "value1,value2", "parameter2": "value" }, "requestContext": { "accountId": "123456789012", "apiId": "<urlid>", "authentication": null, "authorizer": { "iam": { "accessKey": "AKIA...", "accountId": "111122223333", "callerId": "AIDA...", "cognitoIdentity": null, "principalOrgId": null, "userArn": "arn:aws:iam::111122223333:user/example-user", "userId": "AIDA..." } }, "domainName": "<url-id>.lambda-url.us-west-2.on.aws", "domainPrefix": "<url-id>", "http": { "method": "POST", "path": "/my/path", "protocol": "HTTP/1.1", "sourceIp": "123.123.123.123", "userAgent": "agent" }, "requestId": "id", "routeKey": "$default", "stage": "$default", "time": "12/Mar/2020:19:03:58 +0000", "timeEpoch": 1583348638390 }, "body": "Hello from client!", "pathParameters": null, "isBase64Encoded": false, "stageVariables": null }
CloudFront 経由で呼び出されると、requestContext-> http にあるクライアント情報が CloudFront のものに置き換わります。
具体的には、以下の通りです。
"http": { "method": "POST", "path": "/my/path", "protocol": "HTTP/1.1", "sourceIp": "Amazon CloudFrontのIPアドレス", "userAgent": "Amazon CloudFront" },
CloudFront アクセスログへの影響
CloudFrontのアクセスログは、ビューワー - CloudFront 間のリクエストが対象です。 オリジンは関係ありません。
最後に
HTTPS エンドポイント対応した Lambda を Amazon CloudFront から呼び出す方法を紹介しました。
CloudFront レイヤーでキャッシュやWAF連携することで、Lambda の呼び出し回数を抑えたり、Lambdaを保護することができます。
とはいえ、AWS Lambda の Function URLは 煩雑な API Gateway 連携をシンプルに解決するためのものです。 ルーティングやアクセスコントロールを CloudFront であれこれ頑張るのであれば、本当に Function URL を使うのが適切なのか、API Gateway で素直に構築できないか、アーキテクチャーを検討したほうが良いかもしれません。
それでは。