CloudFront Functionsを触ったのでついでにCDKも書いてみた

#AWS
#CDK
#CloudFront Functions

CloudFront FunctionsではCloudFront経由のリクエストに対して、Header、Cookie、URLの書き換えなどのシンプルな処理を安価に実行できるサービスです

似たようなサービスにLambda@Edgeがありますが、そちらに比べてRuntimeがJavaScript(ECMAScript 5.1)、最大実行時間1ms未満など安価で実行できる代わりに出来ることは限られます

 

また、Lambda@Edgeではus-east-1で作成したLambdaリソースを使用する必要がありましたが、CloudFront Functionsではリージョンは気にせずに実装できるようです

CloudFront アーキテクチャ図

 

CloudFront FunctionsのViewer Requestを使用してHeader、Cookie情報を変更してそのままレスポンスさせる処理をCDKで実装してみました

python/ ├ cdk/ │ ├ .venv │ ├ stacks/ │ │ ├ __init__.py │ │ └ cloudfront_stack.py │ ├ app.py │ └ requirements.txt ├ lambda/src/cloudfront_function/ │ └ index.js └ img/sample_image.png
from aws_cdk import ( Stack, RemovalPolicy, aws_cloudfront as cloudfront, aws_s3 as s3, aws_s3_deployment as s3_deployment, aws_iam as iam, ) from constructs import Construct class CloudFrontStack(Stack): def __init__(self, scope: Construct, id: str, **kwargs) -> None: super().__init__(scope, id, **kwargs) service_name = "test" contentsBucket = s3.Bucket(self, 'contentsBucket', server_access_logs_prefix='contentsBucketlogs', auto_delete_objects=True, removal_policy=RemovalPolicy.DESTROY ) contentsBucket.add_cors_rule( allowed_origins=['*'], allowed_methods=[s3.HttpMethods.HEAD, s3.HttpMethods.GET, s3.HttpMethods.PUT, s3.HttpMethods.POST, s3.HttpMethods.DELETE], max_age=3000, exposed_headers=[ 'x-amz-server-side-encryption', 'x-amz-request-id', 'x-amz-id-2', 'ETag' ], allowed_headers=['*'], ) oai = cloudfront.OriginAccessIdentity(self, 'oai', comment=f"{service_name} Frontend") contentsBucketPolicyStatemant = iam.PolicyStatement( effect=iam.Effect.ALLOW) contentsBucketPolicyStatemant.add_canonical_user_principal( oai.cloud_front_origin_access_identity_s3_canonical_user_id) contentsBucketPolicyStatemant.add_actions('s3:GetObject') contentsBucketPolicyStatemant.add_resources( contentsBucket.bucket_arn + '/*') contentsBucket.add_to_resource_policy(contentsBucketPolicyStatemant) cf_function = cloudfront.Function(self, "CloudFrontFunction", code=cloudfront.FunctionCode.from_file(file_path="../lambda/src/cloudfront_function/index.js")) distribution = cloudfront.CloudFrontWebDistribution(self, 'webdistribution', origin_configs=[ { 's3OriginSource': { 's3BucketSource': contentsBucket, 'originPath': '/dist', 'originAccessIdentity': oai }, 'behaviors': [cloudfront.Behavior(is_default_behavior=True)] }, { 's3OriginSource': { 's3BucketSource': contentsBucket, 'originAccessIdentity': oai }, 'behaviors': [cloudfront.Behavior( path_pattern='/function', function_associations=[cloudfront.FunctionAssociation( function=cf_function, event_type=cloudfront.FunctionEventType.VIEWER_REQUEST )] )] } ], price_class=cloudfront.PriceClass.PRICE_CLASS_200, error_configurations=[ { "errorCode": 403, "responsePagePath": "/index.html", "responseCode": 200, "errorCachingMinTTL": 300 }, { "errorCode": 404, "responsePagePath": "/index.html", "responseCode": 200, "errorCachingMinTTL": 300 } ] ) s3_deployment.BucketDeployment(self, "contents_deploy_memoryUp1024", destination_bucket=contentsBucket, memory_limit=1024, sources=[ s3_deployment.Source.asset( '../img') ], destination_key_prefix="dist", distribution=distribution, distribution_paths=["/*"] )
function handler(event) { var eventCookies = event.request.cookies var maxAge = 365 * 24 * 60 * 60 var attributes = `Max-Age=${maxAge}; Path=/; Domain=.sample.site; Secure` var cookiesToReturns = ['sample1', 'sample2'] var responseCookie = {} for (var i = 0; i < cookiesToReturns.length; ++i ) { var cr = cookiesToReturns[i] if (eventCookies[cr]) { responseCookie[cr] = eventCookies[cr] responseCookie[cr].attributes = attributes } } var response = { statusCode: 200, statusDescription: 'OK', headers: { 'cache-control': { value: 'no-cache, must-revalidate' } }, cookies: responseCookie }; return response; }

sample1, sample2という名前のCookieをつけてリクエストするとattributesを付与して返却する処理にしています

originとなるS3を作成しています

removal_policyをDESTROYにすることでスタック削除した際に消せるようにしています

contentsBucket = s3.Bucket(self, 'contentsBucket', server_access_logs_prefix='contentsBucketlogs', auto_delete_objects=True, removal_policy=RemovalPolicy.DESTROY)

 

指定したCloudFrontのOriginAccessIdentityからGetObjectできるようにしています

contentsBucketPolicyStatemant = iam.PolicyStatement( effect=iam.Effect.ALLOW) contentsBucketPolicyStatemant.add_canonical_user_principal( oai.cloud_front_origin_access_identity_s3_canonical_user_id) contentsBucketPolicyStatemant.add_actions('s3:GetObject') contentsBucketPolicyStatemant.add_resources( contentsBucket.bucket_arn + '/*') contentsBucket.add_to_resource_policy(contentsBucketPolicyStatemant)

 

CloudFront Functionsを定義しています

関数の指定方法はいくつかありましたが、fileのpathを指定する方法をとりました

cf_function = cloudfront.Function(self, "CloudFrontFunction", code=cloudfront.FunctionCode.from_file(file_path="../lambda/src/cloudfront_function/index.js"))

behaviorに「/function」を設定して、そこにfunctionをVIEWER_REQUESTで関連付けました

{ 's3OriginSource': { 's3BucketSource': contentsBucket, 'originAccessIdentity': oai }, 'behaviors': [cloudfront.Behavior( path_pattern='/function', function_associations=[cloudfront.FunctionAssociation( function=cf_function, event_type=cloudfront.FunctionEventType.VIEWER_REQUEST )] )] }
$ cdk deploy

curlコマンドのオプションIでヘッダ情報を表示します

cookiesをつける場合は、-bで'<name>=<value>'で指定します

$ curl -I \ -b 'sample1=test1; sample2=test2' \ https://<発行されたurl>.cloudfront.net/function

 

set-cookie: sample1=test1; Max-Age=31536000; Path=/; Domain=.sample.site; Secure set-cookie: sample2=test2; Max-Age=31536000; Path=/; Domain=.sample.site; Secure

返り値でattributeが追加されているのを確認できました

CroudFront Functions内のログは、

us-east-1リージョンの「/aws/cloudfront/function/<function名>」に保存されていました

console.log()を仕込むと自動的に送信されるようです

 

関数内のeventの中身を出力

{ version:'1.0', context:{ distributionDomainName:'xxxxxxxx.cloudfront.net', distributionId:'E2YEXXXXXXX', eventType:'viewer-request', requestId:'XXXXXXXXXXXXXXXXXXXXX==' }, viewer:{ ip:'XXX.XXX.XX.XX' }, request:{ method:'GET', uri:'/function/', querystring:{}, headers:{ host:{value:'xxxxxxxx.cloudfront.net'}, user-agent:{value:'curl/X.XX.X'}, accept:{value:'*/*'} }, cookies:{ sample1:{value:'test1'}, sample2:{value:'test2'} } } }