📝

CloudFront Functions でアクセス元の国によってリダイレクトされるソリューションを試してみた

2025/01/10に公開

amazon-cloudfront-functions/redirect-based-on-country at main · aws-samples/amazon-cloudfront-functions · GitHub
上記ソリューションを試してみました。

AWS CLI の実行環境は CloudShell です。

1. AWS CLI のインストール

CloudShell にはプリインストールされているためスキップします。

2. リポジトリのクローン

$ git clone https://github.com/aws-samples/amazon-cloudfront-functions.git
Cloning into 'amazon-cloudfront-functions'...
remote: Enumerating objects: 326, done.
remote: Counting objects: 100% (150/150), done.
remote: Compressing objects: 100% (119/119), done.
remote: Total 326 (delta 71), reused 43 (delta 29), pack-reused 176 (from 2)
Receiving objects: 100% (326/326), 105.58 KiB | 2.78 MiB/s, done.
Resolving deltas: 100% (138/138), done.

3. カレントディレクトリの変更

$ cd amazon-cloudfront-functions/

4. CloudFront Functions の作成

$ aws cloudfront create-function \
--name redirect-based-on-country \
--function-config Comment="redirect-based-on-country",Runtime=cloudfront-js-1.0 \
--function-code fileb://redirect-based-on-country/index.js
レスポンス
{
    "Location": "https://cloudfront.amazonaws.com/2020-05-31/function/arn:aws:cloudfront::012345678901:function/redirect-based-on-country",
    "ETag": "ETVPDKIKX0DER",
    "FunctionSummary": {
        "Name": "redirect-based-on-country",
        "Status": "UNPUBLISHED",
        "FunctionConfig": {
            "Comment": "redirect-based-on-country",
            "Runtime": "cloudfront-js-1.0"
        },
        "FunctionMetadata": {
            "FunctionARN": "arn:aws:cloudfront::012345678901:function/redirect-based-on-country",
            "Stage": "DEVELOPMENT",
            "CreatedTime": "2025-01-09T10:36:32.096000+00:00",
            "LastModifiedTime": "2025-01-09T10:36:32.096000+00:00"
        }
    }
}

5. CloudFront Functions のテスト

エラーパターン

$ aws cloudfront test-function \
--name redirect-based-on-country \
--if-match ETVPDKIKX0DER \
--event-object fileb://redirect-based-on-country/test-objects/country-de.json
レスポンス
{
    "TestResult": {
        "FunctionSummary": {
            "Name": "redirect-based-on-country",
            "Status": "UNPUBLISHED",
            "FunctionConfig": {
                "Comment": "redirect-based-on-country",
                "Runtime": "cloudfront-js-1.0"
            },
            "FunctionMetadata": {
                "FunctionARN": "arn:aws:cloudfront::012345678901:function/redirect-based-on-country",
                "Stage": "DEVELOPMENT",
                "CreatedTime": "2025-01-09T10:36:32.096000+00:00",
                "LastModifiedTime": "2025-01-09T10:36:32.125000+00:00"
            }
        },
        "ComputeUtilization": "0",
        "FunctionExecutionLogs": [],
        "FunctionErrorMessage": "The CloudFront function associated with the CloudFront distribution is invalid or could not run. SyntaxError: Token \"async\" not supported in this version in 1",
        "FunctionOutput": "{}"
    }
}

ここで、上述の通り async が JavaScript ランタイム 1.0 ではサポートされていない旨のエラーが発生しました。
Use async and await - Amazon CloudFront

You must use JavaScript runtime 2.0 for the following code samples.

いったん CloudFront Functions のコンソール上で async を削除しましたが、他にも以下の対応方法があります。

  • リポジトリをクローンした時点でコードから async を削除する
  • async を使用する場合、Runtimecloudfront-js-2.0 を指定する

今回のように関数作成後にコードを更新した場合、 --if-match オプションで指定する ETag も変わるため、関数更新後に describe-function コマンドを実行して新しい ETag を確認する必要があります。

$ aws cloudfront describe-function --name redirect-based-on-country
レスポンス
{
    "ETag": "E3UN6WX5RRO2AG",
    "FunctionSummary": {
        "Name": "redirect-based-on-country",
        "Status": "UNPUBLISHED",
        "FunctionConfig": {
            "Comment": "redirect-based-on-country",
            "Runtime": "cloudfront-js-1.0"
        },
        "FunctionMetadata": {
            "FunctionARN": "arn:aws:cloudfront::012345678901:function/redirect-based-on-country",
            "Stage": "DEVELOPMENT",
            "CreatedTime": "2025-01-09T10:36:32.096000+00:00",
            "LastModifiedTime": "2025-01-09T10:40:18.277000+00:00"
        }
    }
}

成功パターン

再度 test-function コマンドを実行します。

なお、redirect-based-on-country のソリューションには 2 つのテストパターンがあるので両方試しました。

ドイツからのアクセス想定パターン
$ aws cloudfront test-function \
--name redirect-based-on-country \
--if-match E3UN6WX5RRO2AG \
--event-object fileb://redirect-based-on-country/test-objects/country-de.json
レスポンス
{
    "TestResult": {
        "FunctionSummary": {
            "Name": "redirect-based-on-country",
            "Status": "UNPUBLISHED",
            "FunctionConfig": {
                "Comment": "redirect-based-on-country",
                "Runtime": "cloudfront-js-1.0"
            },
            "FunctionMetadata": {
                "FunctionARN": "arn:aws:cloudfront::012345678901:function/redirect-based-on-country",
                "Stage": "DEVELOPMENT",
                "CreatedTime": "2025-01-09T10:36:32.096000+00:00",
                "LastModifiedTime": "2025-01-09T10:40:18.277000+00:00"
            }
        },
        "ComputeUtilization": "4",
        "FunctionExecutionLogs": [],
        "FunctionErrorMessage": "",
        "FunctionOutput": "{\"response\":{\"headers\":{\"location\":{\"value\":\"https://www.example.com/de/index.html\"}},\"statusDescription\":\"Found\",\"cookies\":{},\"statusCode\":302}}"
    }
}
ドイツ以外からのアクセス想定パターン
$ aws cloudfront test-function \
--name redirect-based-on-country \
--if-match E3UN6WX5RRO2AG \
--event-object fileb://redirect-based-on-country/test-objects/country-not-de.json
レスポンス
{
    "TestResult": {
        "FunctionSummary": {
            "Name": "redirect-based-on-country",
            "Status": "UNASSOCIATED",
            "FunctionConfig": {
                "Comment": "redirect-based-on-country",
                "Runtime": "cloudfront-js-1.0"
            },
            "FunctionMetadata": {
                "FunctionARN": "arn:aws:cloudfront::012345678901:function/redirect-based-on-country",
                "Stage": "DEVELOPMENT",
                "CreatedTime": "2025-01-09T10:36:32.096000+00:00",
                "LastModifiedTime": "2025-01-09T10:40:18.277000+00:00"
            }
        },
        "ComputeUtilization": "6",
        "FunctionExecutionLogs": [],
        "FunctionErrorMessage": "",
        "FunctionOutput": "{\"request\":{\"headers\":{\"cloudfront-viewer-country\":{\"value\":\"GB\"},\"host\":{\"value\":\"www.example.com\"},\"accept\":{\"value\":\"text/html\"}},\"method\":\"GET\",\"querystring\":{\"test\":{\"value\":\"true\"},\"arg\":{\"value\":\"val1\"}},\"uri\":\"/index.html\",\"cookies\":{\"loggedIn\":{\"value\":\"false\"},\"id\":{\"value\":\"CookeIdValue\"}}}}"
    }
}

6. CloudFront Functions の公開

$ aws cloudfront publish-function \
--name redirect-based-on-country \
--if-match E3UN6WX5RRO2AG
レスポンス
{
    "FunctionSummary": {
        "Name": "redirect-based-on-country",
        "Status": "UNASSOCIATED",
        "FunctionConfig": {
            "Comment": "redirect-based-on-country",
            "Runtime": "cloudfront-js-1.0"
        },
        "FunctionMetadata": {
            "FunctionARN": "arn:aws:cloudfront::012345678901:function/redirect-based-on-country",
            "Stage": "LIVE",
            "CreatedTime": "2025-01-09T10:52:54.031000+00:00",
            "LastModifiedTime": "2025-01-09T10:52:54.031000+00:00"
        }
    }
}

CloudFront ディストリビューションとの関連付け

$ aws cloudfront get-distribution-config \
--id E17MNC66XWD3RJ \
--output json > dist-cfg.json

上記コマンドで出力された dist-cfg.json で CloudFront Functions の紐づけを設定します。

dist-cfg.json
"FunctionAssociations": {
    "Quantity": 1,
    "Items": [
        {
            "EventType": "viewer-response",
            "FunctionARN": "arn:aws:cloudfront::012345678901:function/redirect-based-on-country"
        }
    ]
}

上記に加えて、ETag というパラメーターキーを IfMatch というキーに置換します。

dist-cfg.json
"IfMatch": "E7OXO5EJB76EN",

更新後の dist-cfg.json ファイルで CloudFront ディストリビューションを更新します。

$ aws cloudfront update-distribution \
--id E17MNC66XWD3RJ \
--cli-input-json fileb://dist-cfg.json
レスポンス
{
    "ETag": "E1TYLC352WFPP1",
    "Distribution": {
        "Id": "E17MNC66XWD3RJ",
        "ARN": "arn:aws:cloudfront::012345678901:distribution/E17MNC66XWD3RJ",
        "Status": "InProgress",
        "LastModifiedTime": "2025-01-09T11:09:11.591000+00:00",
        "InProgressInvalidationBatches": 0,
        "DomainName": "xxx.cloudfront.net",
        "ActiveTrustedSigners": {
            "Enabled": false,
            "Quantity": 0
        },
        "ActiveTrustedKeyGroups": {
            "Enabled": false,
            "Quantity": 0
        },
        "DistributionConfig": {
            "CallerReference": "fd568ddc-74d9-434c-979e-0f1e5af96afa",
            "Aliases": {
                "Quantity": 1,
                "Items": [
                    "xxx"
                ]
            },
            "DefaultRootObject": "index.html",
            "Origins": {
                "Quantity": 1,
                "Items": [
                    {
                        "Id": "xxx",
                        "DomainName": "xxx",
                        "OriginPath": "",
                        "CustomHeaders": {
                            "Quantity": 0
                        },
                        "S3OriginConfig": {
                            "OriginAccessIdentity": ""
                        },
                        "ConnectionAttempts": 3,
                        "ConnectionTimeout": 10,
                        "OriginShield": {
                            "Enabled": false
                        },
                        "OriginAccessControlId": "EW4HGERGH3D5T"
                    }
                ]
            },
            "OriginGroups": {
                "Quantity": 0
            },
            "DefaultCacheBehavior": {
                "TargetOriginId": "xxx",
                "TrustedSigners": {
                    "Enabled": false,
                    "Quantity": 0
                },
                "TrustedKeyGroups": {
                    "Enabled": false,
                    "Quantity": 0
                },
                "ViewerProtocolPolicy": "allow-all",
                "AllowedMethods": {
                    "Quantity": 2,
                    "Items": [
                        "HEAD",
                        "GET"
                    ],
                    "CachedMethods": {
                        "Quantity": 2,
                        "Items": [
                            "HEAD",
                            "GET"
                        ]
                    }
                },
                "SmoothStreaming": false,
                "Compress": true,
                "LambdaFunctionAssociations": {
                    "Quantity": 0
                },
                "FunctionAssociations": {
                    "Quantity": 1,
                    "Items": [
                        {
                            "FunctionARN": "arn:aws:cloudfront::012345678901:function/redirect-based-on-country",
                            "EventType": "viewer-response"
                        }
                    ]
                },
                "FieldLevelEncryptionId": "",
                "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
                "GrpcConfig": {
                    "Enabled": false
                }
            },
            "CacheBehaviors": {
                "Quantity": 0
            },
            "CustomErrorResponses": {
                "Quantity": 0
            },
            "Comment": "",
            "Logging": {
                "Enabled": false,
                "IncludeCookies": false,
                "Bucket": "",
                "Prefix": ""
            },
            "PriceClass": "PriceClass_All",
            "Enabled": true,
            "ViewerCertificate": {
                "CloudFrontDefaultCertificate": false,
                "ACMCertificateArn": "arn:aws:acm:us-east-1:012345678901:certificate/87075377-1557-47e8-8da6-88c234749fab",
                "SSLSupportMethod": "sni-only",
                "MinimumProtocolVersion": "TLSv1.2_2021",
                "Certificate": "arn:aws:acm:us-east-1:012345678901:certificate/87075377-1557-47e8-8da6-88c234749fab",
                "CertificateSource": "acm"
            },
            "Restrictions": {
                "GeoRestriction": {
                    "RestrictionType": "whitelist",
                    "Quantity": 1,
                    "Items": [
                        "JP"
                    ]
                }
            },
            "WebACLId": "",
            "HttpVersion": "http2",
            "IsIPV6Enabled": true,
            "ContinuousDeploymentPolicyId": "",
            "Staging": false
        },
        "AliasICPRecordals": [
            {
                "CNAME": "012345678901",
                "ICPRecordalStatus": "APPROVED"
            }
        ]
    }
}

以上で CloudFront Functions を紐づけた CloudFront ディストリビューションのデプロイ完了です。

まとめ

今回は CloudFront Functions でアクセス元の国によってリダイレクトされるソリューションを試してみました。
どなたかの参考になれば幸いです。

参考資料

Discussion