[実践]CloudFront + Lambda@Edgeによるドメインリダイレクト

![画像3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/221f4add-71bb-4493-9d5d-8665a08a18c4.png)

# 概要
こちらの記事でAWSサービスを使ったドメインリダイレクト手法についてまとめました。
その中でCloudFront関連の方法を試していこうと思い記事にします。
今回は「CloudFront+Lambda@Edge」編です。

https://qiita.com/tyskJ/items/b0070eeeaa42813c3867

# 実践
### 構成図
* CloudFrontに代替ドメインでアクセス
* ビューワーリクエストに対してLambda@Edgeで処理(後述)
* 特定のPathの場合にリダイレクト先のALBにリクエストを転送

![画像5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/b5f8b04d-6eb2-4036-b9d2-c723b6e6f27f.png)


### デプロイ
環境はGitHubで公開しています。
各コードとデプロイ方法を用意してますのでお試しください。

https://github.com/tyskJ/aws-cloudfront-lambda-edge-redirect

### 設定値
各ポイントとなる設定値を見ていきます。
#### CloudFront
##### ディストリビューションに代替ドメインを設定
![画像1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/3a69d891-a040-4890-94af-0ac13ed032a0.png)

##### オリジンはS3のリージョナルエンドポイント(REST API Endpoint)
![画像2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/a81045e6-f009-459e-993c-d336cb95b033.png)

##### S3オリジンにOAC(Origin Access Control)を設定
![画像3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/6afe4b36-7b2d-4388-b230-2977eaacbdf8.png)

##### デフォルトビヘイビアのビューワーリクエストにLambda@Edgeを適用
![画像4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3984127/fc2ea115-8778-4b3b-b380-e7838650a65f.png)


#### Lambda@Edge
##### コード
リクエストのURI(Path)が「/test1/」or「/test2/」の場合に、ALBに設定した独自ドメインにリダイレクトさせるコードです。
```python
def lambda_handler(event, context):
    request = event['Records'][0]['cf']['request']
    request_path = request['uri']

    # Pathチェック
    if request_path == '/test1/':
        redirect_url = 'https://リダイレクト先FQDN/test1/'
    elif request_path == '/test2/':
        redirect_url = 'https://リダイレクト先FQDN/test2/'
    else:
        # マッチしない場合は元のリクエストを返す
        return request

    # リダイレクトレスポンス
    response = {
        'status': '301',
        'statusDescription': 'MovedPermanently',
        'headers': {
            'location': [{
                'key': 'Location',
                'value': redirect_url
            }]
        }
    }

    return response
```

イベントは公式ドキュメントを参考にしています。

https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html

##### IAMロール
信頼ポリシーは以下の通りです。

```json:信頼ポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": [
                    "lambda.amazonaws.com",
                    "edgelambda.amazonaws.com"
                ]
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
```

##### IAMポリシーではCloudWatch Logs関連の許可を付与
* Lambda@Edge用のLambda関数はバージニア北部リージョンで作成する必要がありますが、実行時はリクエスト元の最寄りリージョン別エッジキャッシュにレプリカが作成され実行されます
* そのため、ロググループ作成許可のStatementではリージョンを`*`にしています
* Lambda@Edgeのロググループ名は`/aws/lambda/us-east-1.関数名`で作成されるため、<br>ログイベントは該当のロググループに出力できるように許可しています

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowCreateLogGroup",
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "arn:aws:logs:*:AccountId:*"
        },
        {
            "Sid": "AllowPutLogEvents",
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:AccoundId:log-group:/aws/lambda/us-east-1*"
        }
    ]
}
```

#### S3
##### バケットポリシーにCloudFront OACからのアクセス許可を追加
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowCloudFrontServicePrincipal",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudfront.amazonaws.com"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::バケット名/*",
            "Condition": {
                "StringEquals": {
                    "AWS:SourceArn": "arn:aws:cloudfront::アカウントID:distribution/ディストリビューションID"
                }
            }
        }
    ]
}

```


### 動作確認
まず、Lambda@Edgeコードの条件にマッチしないリクエストを呼び出してみます。
ディストリビューションに設定したデフォルトルートオブジェクト(S3バケットにホストしたindex.html)がレスポンスとして返ってきます。
```shell-session
$ curl -i https://ディストリビューション代替ドメイン/
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 316
Connection: keep-alive
x-amz-id-2: EvYdBiurBUqjXHMa0paHa/L+U+AwWgv5vIWT2sFbKV9mzIl+iirLDScFZ8QzMSSmROptIILLu3rP5Kr2mVGLsVuVcJNN8Xjnzw+h46KKf1A=
x-amz-request-id: FYHWE5MFR4EEV3HH
Date: Thu, 20 Mar 2025 15:54:46 GMT
Last-Modified: Thu, 20 Mar 2025 11:43:56 GMT
ETag: "0b51ba8caa5af4d214532a9912de1e23"
x-amz-server-side-encryption: AES256
Accept-Ranges: bytes
Server: AmazonS3
X-Cache: Miss from cloudfront
Via: 1.1 13c972ba4a9ffc82bb0bdabc148e3d20.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: KIX56-P1
X-Amz-Cf-Id: W_JjHiPfCH0riIOifgyVIJTClMWEotmlvo3kZLzGt_MF_rUp2DdyQg==

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>S3</title>
  </head>
  <body>
    <h1>S3 Default Page!</h1>
    <p>このページは S3 にホストされています。</p>
  </body>
</html>
```

続いて、Lambda@Edgeコードの条件分岐にマッチするリクエストを送信してみます。
```shell-session
$ curl -i -L https://ディストリビューション代替ドメイン/test1/
HTTP/1.1 301 MovedPermanently
Content-Length: 0
Connection: keep-alive
Server: CloudFront
Date: Thu, 20 Mar 2025 15:57:08 GMT
Location: https://リダイレクト先ドメイン/test1/
X-Cache: LambdaGeneratedResponse from cloudfront
Via: 1.1 a7d4728ae140bd74f2dfa727c541f5e6.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: KIX56-P1
X-Amz-Cf-Id: L2pFCzKmRqjXnoDiavY50-qP2ev3HibG7FP1SHztzKpDlQz-9DGKgg==

HTTP/1.1 200 OK
Date: Thu, 20 Mar 2025 15:57:08 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 232
Connection: keep-alive
Server: Apache/2.4.62 (Amazon Linux)
Last-Modified: Thu, 20 Mar 2025 11:13:29 GMT
ETag: "e8-630c43a3fe02c"
Accept-Ranges: bytes

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>EC2</title>
</head>
<body>
    <h1>Web Server Test1 Page!</h1>
</body>
</html>
```

# まとめ
今回は「CloudFront+Lambda@Edge」を試してみました。
「CloudFront Functions」と比較し、より柔軟な処理を実装できる点はメリットだなと感じました。

# 参考資料
### リファレンス
* [Amazon CloudFront デベロッパーガイド - Lambda@Edge を使用してエッジでカスタマイズする - ](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/lambda-at-the-edge.html)