【CloudFront】カスタムエラーレスポンスを設定し、ログインページに飛ばしてみよう

#1. はじめに
　以下の画像が何かわかりますでしょうか？
![スクリーンショット 2021-11-12 162819.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674265/ebe6b740-0eb4-98c9-e863-136dea6fcbe9.png)
　そうです。署名付きクッキーで制限したCloudFrontにアクセスした際、クッキーが正しく設定でいていない時に表示されるエラーです。わかりましたか？
　さて、署名付きクッキーがない時に毎回この画面が表示されたんじゃ中々ばつが悪いことがあります。特に、「ログインページからログインして（署名付きクッキーを設定して）ページに飛べば正常通りに表示されるのに、直接URLで入ったらこんな画面が表示された。。。」なんて思われてしまうかも。。。ちなみに、署名付きクッキーとは[公式ではこういったもの](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-cookies.html)です。

#2. カスタムエラーレスポンスの設定
　カスタムエラーレスポンスとは何でしょうか。簡単に言うと、CloudFrontディストリビューションにアクセスした際に上記のようなエラー画面ではなく、自分で設定した画面を表示する処理を行う設定になります。[公式の説明](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/GeneratingCustomErrorResponses.html)はこちらへ。

　ではでは、カスタムエラーレスポンスの設定を行いましょう.
　まず前提として、エラーが発生した場合に飛ばすログイン画面は、S3の'/login/inedx.html'に格納されているとしましょう。署名付きクッキーでディストリビューションへのアクセスを制限している場合、パス'/login/*'への制限がないようにビヘイビアを編集する必要があります。
###やり方１　直接ログインページを表示する
　こちらは、エラーが発生したページで直接ログインページを開くやり方になります。下記とは違い、エラーが発生したページのURLのまま、ログインページを開くことになります。
　手順としては、任意のディストリビューションを選択し、エラーページのカスタムエラーレスポンスの作成を選択します。今回対象となるエラーは認証情報が足りない場合の403エラーになるため、エラーコードで403を選択します。最小TTLのキャッシュエラーは、変に残っていると再度アクセスした際に認証情報が足りててもログインページに飛ぶことがあるため0にしました。レスポンスページのパスでは表示したい'/login/index.html'を選択します。HTTPレスポンスコードはHTTPエラーコードと同様の403を選択しましょう。200を選択した場合、ログインページがそのURLにキャッシュとして残り、何度アクセスしてもログインページが表示されるようになります。
　カスタムエラーレスポンスの作成をクリックして作成しましょう。これで403エラーが発生した時に/login/inedx.htmlが表示されるようになりました。
![スクリーンショット 2021-11-12 165958.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674265/47045c21-b880-b595-a57e-4115a6d91a0f.png)
　しかし、先ほども言ったようにこのやり方はエラーが発生したページのURLのまま、ログインページを表示します。/login/inedx.htmlで他の画像などを参照していた場合、参照元URLが変わるため、相対パスで参照していた場合うまく他の情報を参照できなくなります。そこで、/login/inedx.htmlの参照先を絶対パスに変更しておきましょう。
###やり方２　ログインページにリダイレクトさせる
　こちらは、エラーが発生した際にそのURLで別のページを開いて、ログインページにリダイレクトさせるやり方になります。上記とは違い、どのページでエラーが発生しても、ログインページのURLでログインページが表示されます。
　やり方は手順は1とほとんど同じです。ただし、一度別のページを開く必要があるため、S3'/error/error.html'にログインページにログイン画面に飛ばすよう仕込んでおく必要があります。
　まずは、カスタムエラーレスポンスの設定を行いましょう。下記の画像のように設定すればOKです。
![スクリーンショット 2021-11-12 170038.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674265/c4e1153d-d667-1611-d2b9-df6e31534b29.png)
　次に、パス'/error/*'への制限がないようにビヘイビアを編集します。最後にS3/error/'の中に下記のように書きerror.htmlを格納しましょう。

error.html

```html
<!DOCTYPE html>
<html lang="ja">
  <title>403</title>
  <script>location.href='https://~~~~/login/index.html'</script>
```
　これで、403エラー時に/error/error.htmlにアクセスし、'/login/index.html'に飛ばされるようになりました。
　ちなみに、もしもIEでこの動作を行いたい場合は注意が必要です。IEでは、設定されているエラーページが500バイト以下の場合に、IEで標準に設定されているエラーページを表示するという仕様があります。なので、もしIEでも使用される場合は、error.htmlを下記のように訂正します。

error.html

```html
<!DOCTYPE html>
<html lang="ja">
  <title>403</title>
  <script>location.href='https://~~~~/login/index.html'</script>
  <a>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</a>
```
　上記のerror.htmlをS3に格納した時に500バイトを超えるまで「a」を適当に増やしてください。

#3. CloudFormationでも設定方法
　CloudFormationのテンプレートを用いて、カスタムエラーレスポンスの設定を行ってみました。手順1の場合、AWS::CloudFront::DistributionプロパティのDistributionConfigに下記のように設定を追加すればOKです。

```yaml
CustomErrorResponses:
  - ErrorCachingMinTTL: 0
    ErrorCode: 403
    ResponseCode: 403
    ResponsePagePath: /login/index.html
```


せっかくなんで、CloudFront全体のテンプレートファイルを公開します。すでにS3は作成済、バケットポリシーは何も記入無し、ACMに証明書登録済という前提で行います。やり方は手順1、アクセス制限は署名付きクッキーで行います。また、他の設定項目はおおむね適当です。すきに設定してください。

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  Create CloudFront Distribution 

Resources:
  #S3とCloudFrontを繋ぐようのOAI
  CloudFrontOriginAccessIdentity:
    Type: "AWS::CloudFront::CloudFrontOriginAccessIdentity"
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: access-identity-<S3バケット名>

  #S3に追記するバケットポリシー
  BucketPolicy:
    Type: "AWS::S3::BucketPolicy"
    Properties:
      Bucket: <S3バケット名>
      PolicyDocument:
        Statement:
        - Action: "s3:GetObject"
          Effect: Allow
          Resource: arn:aws:s3:::<S3バケット名>/*
          Principal:
            CanonicalUser: !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId

  #クラウドフロントディストリビューション作成
  CloudFrontDistribution:
    Type: "AWS::CloudFront::Distribution"
    Properties:
      DistributionConfig:
        Comment: <コメント>
        #証明書関係
        ViewerCertificate: 
          AcmCertificateArn: <証明書のarm>
          MinimumProtocolVersion: TLSv1.2_2019
          SslSupportMethod: sni-only
        PriceClass: PriceClass_All
        #オリジンの設定 S3との接続の設定
        Origins:
          - DomainName: <S3バケット名>.s3.amazonaws.com
            Id: S3origin-<S3バケット名>
            S3OriginConfig:
              OriginAccessIdentity: !Sub "origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}"
        #Behaviorsの設定　著名付クッキー等の設定
        DefaultCacheBehavior: #デフォルトの接続の設定
          TargetOriginId: S3origin-<S3バケット名>
          ViewerProtocolPolicy: redirect-to-https
          AllowedMethods:
            - GET
            - HEAD
          CachedMethods:
            - GET
            - HEAD
          DefaultTTL: 3600
          MaxTTL: 86400
          MinTTL: 60
          Compress: true
          ForwardedValues:
            QueryString: false
          TrustedKeyGroups: 
            - <署名付きクッキーのキー>
        CacheBehaviors:
          - TargetOriginId: S3origin-<S3バケット名> #login/*の接続の設定 
            ViewerProtocolPolicy: redirect-to-https
            AllowedMethods:
              - GET
              - HEAD
            CachedMethods:
              - GET
              - HEAD
            DefaultTTL: 3600
            MaxTTL: 86400
            MinTTL: 60
            Compress: true
            ForwardedValues:
              QueryString: false
            PathPattern: login/* 
        #カスタムエラーレスポンスの設定
        CustomErrorResponses:
          - ErrorCachingMinTTL: 0
            ErrorCode: 403
            ResponseCode: 403
            ResponsePagePath: /login/index.html
        HttpVersion: http2
        Enabled: true
```
#4. 終わりに
　いかがだったでしょうか。カスタムエラーレスポンスの設定よりも、CloudFrontのテンプレートファイルの方が需要があるかもしれませんね。。。今回紹介しているカスタムエラーレスポンスでは、署名付きクッキーがない際の403エラーを例に挙げていますが、他にもWAFによるエラー等も使用できると思われますので、ぜひ使用してみてください。