この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
先日のアップデートにより、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 で素直に構築できないか、アーキテクチャーを検討したほうが良いかもしれません。
それでは。