403 エラーが返ってきた時に特定 URL にリダイレクトする Lambda@Edge を試してみた
こんにちは、岩城です。
CloudFront を利用している環境で、オリジンから 403 エラーがレスポンスされた場合、指定した URL にリダイレクトしたいことがありました。
試してみた結果を共有します。
まとめ
- CloudFront Functions はオリジンが 400 以上のエラーを返した場合、実行されない
- Lambda@Edge は 400 以上のエラーを返されても Lambda 関数を実行できる
- Lambda@Edge を使用してオリジンが 403 エラーを返した場合、指定したURLにリダイレクト Lambda 関数を実行させる
CloudFront Functions と Lambda@Edge の違い
CloudFront Functions と Lambda@Edge はどちらも CloudFront のイベントに応じてコードを実行できます。
CloudFront Functions は、軽量で実行時間の短い関数に最適ですが、Lambda@Edge は以下のユースケースに最適とされています[1]
- 完了までに数ミリ秒以上かかる関数
- 調整可能な CPU またはメモリを必要とする機能
- サードパーティライブラリに依存する関数 (他の AWS のサービスとの統合のため、AWS SDK を含む)
- 外部サービスを使用して処理するために、ネットワークアクセスを必要とする関数
- ファイルシステムへのアクセスまたは HTTP リクエストの本文へのアクセスを必要とする関数
さらに、CloudFront Functions は、オリジンが 400 以上の HTTP エラーを返した場合、CloudFront Functions は実行されません。
ということで、本エントリではオリジンが 403 を返したときにリダイレクトさせたかったため、Lambda@Edge を採用しました。
やってみた
構成図

CloudFront と S3 の静的ウェブサイトホスティングを利用します。
Origin response で 403 エラーが返ってきた時に、Lambda@Edge で特定の URL にリダイレクトさせます。
CloudFront と S3 の静的ウェブサイトホスティング
CloudFront と S3 の静的ウェブサイトホスティングは、特別なことは設定しておらず、基本的にはデフォルトの設定です。
大した設定ではないのでさらっと紹介します。
S3 の静的ウェブサイトホスティング
S3 バケットを作成して静的ウェブサイトホスティングを有効にしました。

作成した S3 バケットのルートに以下の index.html をアップロードしました。
favicon 起因の 403 エラーを避けるため、favicon.ico を定義しています。
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <link rel="icon" type="image/x-icon" href="https://dev.classmethod.jp/favicon.ico">
  </head>
  <body>
    <h1>
      こちらはIndex.html
    </h1>
  </body>
</html>
CloudFront
オリジンに上で作成・設定しておいた S3 バケットを指定します。

ビヘイビアも基本的にはデフォルト設定ですが、検証を容易にするためにキャッシュは無効化しておきました。

Lambda@Edge
まず Lambda@Edge で使用する実行ロールを作成します。
実行ロールは 公式ドキュメント を参考に以下のとおり設定しました。
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:*:*:*"
            ]
        }
    ]
}
信頼ポリシーは以下のとおりです。edgelambda.amazonaws.comを追加しないと権限不足になります。
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": [
                    "edgelambda.amazonaws.com",
                    "lambda.amazonaws.com"
                ]
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
つぎに、Lambda 関数を作成します。
CloudFront のリージョンに合わせる必要があるため、バージニア北部で以下のように設定しました。

exports is not defined in ES module scope のエラーが発生するので、ファイル名を index.mjs から index.js に変更します。

コードはあくまで サンプル なので参考程度に考えてください。
'use strict';
exports.handler = (event, context, callback) => {
    const response = event.Records[0].cf.response;
    const redirectUrl = `<リダイレクト先の URL>`;
    
    if (response.status == 403) {
        response.status = 302;
        response.statusDescription = 'Found';
        response.headers['location'] = [{ 'value': redirectUrl }];
    }
    callback(null, response);
};
アクションから Lambda@Edge へのデプロイ を行います。
CloudFront イベントはオリジンレスポンスを設定します。

デプロイすると CloudFront に反映されます。

動作確認
存在しない URL にアクセスしオリジンから 403 エラーが返り、Lambda@Edge で設定したとおり、ステータスコード 302 Found が返り、指定した URL にリダイレクトされることを確認できました。

おわりに
本エントリを執筆する動機は、403 エラーが返ってきた時にエラーページを表示させたくなく、403 エラーが返ってきた時にトップページへのリダイレクトを想定するものでした。
favicon の未設定含めて、403 エラーが返ってきたら何でもリダイレクトされてしまうので副作用は強めかも知れません。
本エントリがどなたかのお役に立てれば幸いです。









