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 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を付与して返却する処理にしています
解説
S3作成
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の設定
CloudFront Functionsを定義しています
関数の指定方法はいくつかありましたが、fileのpathを指定する方法をとりました
cf_function = cloudfront.Function(self, "CloudFrontFunction",
code=cloudfront.FunctionCode.from_file(file_path="../lambda/src/cloudfront_function/index.js"))
behaviorの設定
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'}
}
}
}