Amazon-CloudFront

CloudFront + S3 + その他諸々で特定のページにはカスタムエラーページが表示されないサイトを構築してみた

おはよう世界。どうも若林です。

最近読書にハマっていて通勤時間や休憩時間・お風呂上がりのスキンケア中や寝る前等の時間あるときはずっと本を読むようになりました。
といっても技術書や参考書みたいなものではなくて小説ですね。
今年に入ってから今日まで、学生時代に戻ったら夏休み30周分の読書感想文がかけるくらいの本を読んでました。その分お金もだいぶ飛んでいきましたが・・・。

最近読んだ本で面白かったのが、九条蓮作の『最後の夏は、きみが消えた世界』と有川ひろ作の『旅猫レポート』です。
感動系のお話が好きな方はぜひお手にとってみてはいかがでしょうか。

さて、今回は特定のエラーページを表示させるCloudFront + S3の構築をしていきたいと思います。
最近こちらの開発環境を構築しましたので備忘録的な感じで書いていきます。

やりたいこと

お客様からこんな要件がありました(という設定)


メインとなる通常ページと、403(権限なし)と404(存在しないパス)といったユーザーにとって「アクセスできない」状態を共通のエラーページで表示するようにしたいです。
一方で、APIリクエストからはHTTPステータスコードを正確に判定したいと考えています。
ですので、以下のように特定のパス以外からのアクセスにはエラーページを表示させます。ただしAPIについては403ステータスコードをそのまま返したいです。
ドメインは持っていますが、ディーネット側でDNS管理をしてほしいです。証明書の発行も同時にお願いします。

■通常ページ
https://example.com/
https://example.com/index.html/

■エラーページ
https://example.com/[存在しないページ]
https://example.com/404.html/

■デフォルトの403エラーページ
https://example.com/api

この要件ならCloudFront + S3をメインにして、Route53でドメイン管理をして、ACMでSSL証明書を発行します。
ただ、今回の要件だと関数を使わないと難しそうでしたので、Lambdaを利用します。
関数ならCloudFront Fanctionsでも良かったのですが、ログが欲しいという理由でLambdaを選びました。
LambdaならCloudWatch Logsで自動生成されて楽だからです。

次になぜLambdaが必要なのかですが、今回この構成でやりたいことは特定のページへアクセスすると通常のエラーページに飛びたいということです
単純に通常時はメインページ、403 / 404エラーが発生したらエラーページ(404.html)へリダイレクトみたいな設定でしたら、CloudFrontのディストリビューション内にある、エラーページという項目を設定したら実現します。
ただし、今回は/apiは通常の403エラーを表示させたいという要件があります。
先程のやり方だと/apiにもこちらで用意したエラーページ(404.html)へリダイレクトされちゃいますので、Lambdaを使ってリダイレクトさせない関数を作成しようというものになります。

構成図

今回の構成図はこのようになっています。

結構シンプルな構成で実現できますね。
1つ注意点がございますが、今回のリソースは基本全てバージニア北部(us-east-1)リージョンで作成してくだい。

これはCloudFrontがus-east-1で作成したSSL証明書やLmabdaじゃないとアタッチができないからです。それならリージョンは全て統一してもよろしいと思います。

それとS3の中に/apiというフォルダを作成していますが、アクセス権限がなくて通常の403エラーを確認できれば良いので中には何も入れなくても問題ございません。

S3を作成

最初にS3のバケットを作成していきます。
S3は今回のコンテンツ部分である通常ページやエラーページ・/apiをフォルダとして作成します。
今回ページとなるHTMLファイルですが、テストですので何でも構いません。通常ページとエラーページが差分化されていれば問題ないと思います。
私はチャッピーことChatGPTに全て任せました。
出力されたHTMLコードをVisual Studio Code等でHTMファイルとして保存してください。

S3バケットの作成

それでは実際にバケット作成まで進めていきたいと思います。
まずAWSコンソールにログインしていただいたら検索欄でS3と入力します。
S3のコンソールまで来たら▼ バケットから汎用バケットを選択し、バケットを作成を開いてください。
開いていただくとバケット作成画面に行くと思います。
バケットタイプを選択できますので汎用を選択してください。

選択後は名前を任意でつけていただき、その後はすべてデフォルトで問題ございません。
ただ、バケットのパブリックアクセスはブロックする設定になっているかを特に確認をしてください。

この設定をオフにすると、バケットポリシーやACLの設定次第でS3バケットがインターネットから直接アクセス可能になってしまう可能性があります
今回はCloudFront経由でアクセスしますし、セキュリティ的にもよろしくないので必ずパブリックアクセスをすべてブロックしてください。

その後作成内容を確認して、問題がなければバケットを作成してください。

S3ルート配下にHTMLファイルを設置

メインページとなるindex.htmlとエラーページとなる404.htmlを設置します。
バケットを開いていただくと右上に↑アップロードがありますのでクリックします。
すると作成したバケット内にコンテンツをアップロードできる上程になりますので、メインページ・エラーページとなるHTMLファイルをドラッグ&ドロップでアップロードします。

画面真ん中にあるファイルとフォルダの中にアップロードしたHTMLファイルが有るのを確認し、問題がなければファイル下にあるアップロードを押してください。

その後S3バケットのルート配下にアップロードしたHTMLファイルがあれば完了です。

apiフォルダの作成

次はS3内にAPI用のフォルダを作成します。
まずバケットの管理画面からフォルダの作成を選択してください。
フォルダ名をapi/と入力します。
暗号化キーは必要に応じて指定してください。今回は指定なしでいきます。
フォルダを作成し、管理画面が以下のようになっていれば完了です。

Route53のホストゾーンを作成

次はRoute53にホストゾーンを作成していきます。
こちら独自のドメインが必要になりますので、Route53で直接登録するかお名前.comなどでドメインの取得をしてください。
まず、AWSコンソールの検索欄でRoute53と入力します。
Route53のコンソールまで来たらホストゾーンを選択し、ホストゾーンの作成を開いてください。

開いていただくとまず初めにドメインを入力する欄がありますのでお持ちのドメインを入力してください。
タイプはパブリックホストゾーンを選択してください。
最後に設定内容の確認(特にドメイン部分は要チェック!)して、問題がなければホストゾーンを作成してください。
すると、ホストゾーンの管理画面に先程作成したドメインのホストゾーンがあると思います。
レコードを確認して、NSレコードとSOAレコードがあれば完了です。

これで一旦Route53は終了です。レコードの登録はこの後していきます。

ACMでSSL証明書を発行

次はSSLの証明書を発行していきます。
まず、AWSコンソールの検索欄でACMと入力します。
するとCertificate Managerっていうのが出ますので開いてください。

ACMのコンソールまで来たら証明書をリクエストを選択し、証明書のタイプをパブリック証明書を選択して次へ進んでください。

進んでいただくとパブリック証明書をリクエストするための画面に行きます。
ここで完全修飾ドメイン名を指定するのですが、独自ドメインを指定、サブドメインも使用予定なら「*.独自ドメイン」と入力してください。
今回の証明書はワイルドカードで発行していきます。何故かというと、AWSサービス使わないので、今回AWSで証明書を発行しました。
ACMは、無料でワイルドカードの証明書を発行できますので要件的には特に理由はないのですが、無料ならということで今回はワイルドカードで発行していきます。

エクスポートは無効にしてください。
ドメインの検証方法ですが、DNS検証Eメールアドレス検証があります。
どちらでも構いませんが、AWSが推奨してますし、早く終わりますので今回はDNS検証をしていきます。
キーアルゴリズムは今回RSA 2048で行きます。
最後に設定内容の確認(特にドメイン部分は要チェック!)して、問題なければ証明書をリクエストしていきます。

ホストゾーンに証明書のレコードを登録

次はDNS検証で証明書を発行すると選択しましたので、先程作成したホストゾーンに証明書のCNAMEレコードを登録していきます。
まずリクエスト中の証明書を開き、ステータスを確認します。
ステータスは保留中の検証になっていると思います。
確認ができれば次はRoute53でレコードを作成を選択します。

レコードを作成しましたら先程作ったホストゾーンを確認しに行きます。

レコード一覧を見て、先程にはなかったCNAMEがあればOKです。
確認し終えたら、もう一度証明書の方まで戻ります。
しばらく時間をおいて(数分~数時間)、ページを更新しますと、ステータスが発行済みに変わっていると思います。
ステータスが変わっているのが確認できたら証明書は完了です。

CloudFrontの作成

次はいよいよCloudFrontの作成を進めていきます。
リソースの構築はこれが最後ですので頑張っていきましょう!
まずはディストリビューションを作成していき、代替ドメインの設定SSL証明書の設置デフォルトルートオブジェクトの設定をしていきます。

ディストリビューションを作成

最初にAWSコンソールで検索欄でCloudFrontと入力します。
左メニューからディストリビューションを選択し、ディストリビューションを作成を開きます。
Choose a plan(プランを選択)で要件にあったプランを選択していくのですが、今回はPay as you go(従量課金)を選択してきます。

こちらは作成したディストリビューションを使用した分だけ料金が請求されるプランとなっております。
次はDistribution name(配布名)です。こちらは名前ですので自分の環境に合わせてつけてください。
Distribution type(配布タイプ)ですが、今回はSingle website or app(単一のWebサイトまたはアプリ)を選択してください。
その下にあるDomain(ドメイン)は先程作成したホストゾーンと同じドメインを入力してください。(サブドメインもある場合はサブドメインも含めて入力してください)

横にあるCheck domainをクリックして利用可能なドメインということも確認してください。

次はCloudFrontで使用するオリジンを選んでいきます。
Origin type(オリジンタイプ)からAmazon S3を選択してください。
するとS3オリジンを選択できるようになりますので、S3のバケットURLを選択してください。
選択方法ですが、横にあるBrowse S3から先程作成したS3のバケットを選んでいただくか、以下のように記述すれば良いです。

[バケット名].s3.[リージョン名].amazonaws.com

Browse S3から選んでいただくほうが確実ですのでそちらを推奨します。
その他はデフォルトで問題ございません。
下にあるオリジン設定やキャッシュ設定は基本的には作成時の推奨設定がデフォルトで選択されている状態だと思います。
カスタマイズする要件がない限りはそのままで問題ございません。

次はEnable security(セキュリティを有効化)ですが、こちらはWAFを有無を選びます。
コストが掛かりますので検証などでしたらご注意ください。
こちらはご自身の用途に分けてください。

  • 検証目的:セキュリティ保護無効化
  • 本番環境:事前にWAFでACLを作成・もしくはCloudFrontが自動的に設定するセキュリティ保護機能を利用する。

事前にWAFでACLを作成した方がどんなセキュリティ保護を利用しているか把握できますので作成することをおすすめします。

次はTLS証明書を選択する画面になります。
こちらは最初に代替ドメインを入力していただきました。そのドメインのホストゾーン内にある先程登録した証明書のCNAMEレコードがあれば表示されるはずです。(私の環境では以前に東京リージョンで証明書を発行してしまってるので2つありますが、本来は一つのはずです)

最後に確認画面に行きますので、設定内容に問題がなければディストリビューションを作成してください。
作成後、CloudFrontのコンソールに先ほど作成したディストリビューションがあれば完了です。

オリジンの設定

次はオリジンの設定をしていきます。
最初にディストリビューションを作成したときにS3の指定をしましたのでそこまでやることはないのですが、アクセス許可+設定内容確認をしていきます。

まず先程作成したディストリビューションの管理画面を開いていただき、オリジンの画面まで進めてください。
オリジンドメインを作成したS3バケットになっていることを確認してください。
次にオリジンアクセスですが、こちらはOrigin access control settings (recommended)を選択してください。
Origin access control「oac-[S3バケット名]-OAC ID」を選んでください。
選択していただいたら、ポリシーをコピーと表示されますので、クリックしていただいてコピーしてください。※コピーしたポリシーはメモ帳などで一時保管してください。
コピーしたら、そのままS3バケットアクセスへ移動してください。

(下の方にある赤い線から飛べます)

S3を開いていただいたら、バケット内にあるアクセス許可を開いてください。
その中にあるバケットポリシーを編集していただき、先程コピーした内容をそのまま貼り付けてください。
エラー表示が出てないのを確認して保存できれば完了です。
この設定でCloudFrontがS3を参照するための許可ポリシーを設定しました。

先程のオリジン設定の画面まで戻っていただいて、変更を保存してください。
無事変更できれば完了です。

デフォルトルートオブジェクトの設定

デフォルトルートオブジェクトの設定を行います。
こちらはCloudFrontでパスなしのアクセスをしたときに、メインページとなるindex.htmlが正しく表示されるために設定をしていきます。

■デフォルトルートオブジェクト設定無し
https://example.com/
→ 404 / 403エラーが発生

■デフォルトルートオブジェクト設定あり
https://example.com/
→ メインページが表示

このようにCloudFrontは明示的に設定しないと返されないので設定をしていきます。
まず、先程作成したディストリビューションの管理画面を開きます。
設定の編集をしていきます。
Default root object - optionalデフォルトページのHTMLファイル名(index.html)を入力します。
変更を保存していただき、最初の管理画面にデフォルトルートオブジェクトが表示されていれば完了です。

デフォルトページを確認

ここで一旦デフォルトページが開くか確認します。
ページ内容を確認するというよりは、CloudFrontがきちんとS3へアクセスできるのか確認したいからです。
まず、examle.comを独自のドメインに書き換えて、テストしてみます。

https://example.com/

では開いてみます。

無事表示されていますね。(ブックマークが見えちゃって恥ずかしいですが。。)
チャッピー(Chat-GPT)が出してくれたテストページが表示されましたので成功です。

Lambda@Edgeを作成

次はLambda@Edgeを作成していきます。
まず左メニューから関数を選択し、リージョンを米国(バージニア北部) us-east-1にしてください。
切り替え完了いたしましたらAWSコンソールの検索欄でLambdaと入力します。
Lambdaの管理画面まで来たら関数の作成を選択してください。

まずオプションは一から作成を選択してください。
次に関数名は任意で決めていただき、ランタイムをPythonに設定してください。

Pythonのバージョンですが、ランタイムのメニューを開いていただくと最新のサポート対象っていう項目が表示されますのでそちらにあるバージョンを選択してください。(今回はPython 3.14で構築します)

永続実行はどちらでも構いません。アーキテクチャはx86_64を選択してください。

次に実行ロールですが、こちらは基本的なLambdaアクセス権限で新しいロールを作成を選択してください。
こちらを選ぶとCloudWatch Logsに書き込み権限があるロールが自動生成されます。
私が今回Lambdaを選んだ理由ですね。何かエラーがあったら怖かったのでログを取得しやすいLambdaを選びました。自動生成だと楽ですので()

余談ですが、ロール名は一度作成してしまうと改名が出来ませんので、本番環境で命名規則がある場合は手動で作成することをおすすめします。

話を戻しまして、設定内容を確認して問題なければ作成してください。

信頼ポリシーの編集

次は信頼ポリシーですが、こちら編集する必要がございます。
IAMポリシーを開いていただき、信頼関係から信頼ポリシーを編集をクリックしてください。
開きましたら以下を貼り付けてください。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": [
          "lambda.amazonaws.com",
          "edgelambda.amazonaws.com"
        ]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

その後保存していただき、変更が反映されていれば完了です。

関数の設定

次は動作に必要な関数を設定していきます。
以下の関数をそのまま貼り付けてください。

import base64
import boto3

s3_client = boto3.client('s3')

BUCKET_NAME = 'バケット名' #バケット名をご自身の環境に合わせて書き換えください
ERROR_PAGE_KEY = '404.html' #ファイル名をご自身の環境に合わせて書き換えてください

def lambda_handler(event, context):
    record = event['Records'][0]['cf']
    request = record['request']
    response = record['response']
    uri = request['uri'].rstrip('/')

    if uri == '/api' or uri.startswith('/api/'): #フォルダ名をご自身の環境に合わせて書き換えてください
        return response

    try:
        status_code = int(response['status'])
    except:
        status_code = 0

    if 400 <= status_code < 600:
        try:
            obj = s3_client.get_object(Bucket=BUCKET_NAME, Key=ERROR_PAGE_KEY)
            content = obj['Body'].read()
            content_base64 = base64.b64encode(content).decode('utf-8')

            response['body'] = content_base64
            response['bodyEncoding'] = 'base64'
            response['headers']['content-type'] = [{'key': 'Content-Type', 'value': 'text/html; charset=utf-8'}]

        except Exception as e:
            print("Error fetching 404.html from S3:", e)
            return response

    return response

何点かご自身の環境に合わせた内容に変更する必要がございます。
貼り付け・修正が完了いたしましたら左側にあるDeployを押してください。
無地デプロイができれば完了です。

IAMポリシーを追加

現在Lambdaに付いているIAMロールにS3読み取り権限を付与していきます。
作成したLambdaの管理画面から設定→アクセス権限を選択してください。

そうすると実行ロールに先程自動的に作成されたIAMロールが付与されていますので開いてください。
許可ポリシーが現在CloudWatch LogsのものだけになっていますのでS3の権限も付与していきます。

アクセス許可を追加を選択してインラインポリシーを作成を選択→サービスはS3を選択
読み取りを開き、GetObjectにチェックします。

リソースはすべてを選択してください。
任意のポリシー名を作成し、確認をして問題がなければポリシーを作成してください。
その後許可ポリシーの中に先程作成したポリシーがあれば完了です。

$LATEST からバージョンを発行

作成したLambdaをCloudFrontにアタッチするための設定をしていきます。
まず最初に先程作成したLambdaからアクション ▼を押して新しいバージョンを発行を選択してください。
そのまま発行を選択し、下の方にある関数のARNが更新されて末尾にバージョンが書かれているのを確認したらコピーしてメモ帳などに一時保管してください。

CloudFrontの設定の続き

ここからはCloudFrontの続きです。
ディストリビューション内の設定をしていきます。

ビヘイビアの設定

次はビヘイビアの設定を進めていきます。
こちらではビューワーからのアクセスに対してどのように動作するかを設定します。
今回はDefault/api/のビヘイビアを設定していきます。

まず、先程作成したディストリビューションの管理画面を開いていただき、ビヘイビアの画面まで進めてください。
するとデフォルトというビヘイビアがあると思います。こちらを編集していきます。

基本的にはデフォルトで問題ございませんが、以下だけ変更してください。
ビューワープロトコルポリシーはRedirect HTTP to HTTPSを選択してください。
キャッシュポリシーは無効化(CachingDisabled)にしてください。
次に、関数の関連付け - オプションオリジンレスポンスがあります。
オリジンレスポンスにLambda@Edgeを選んで頂き、ARNに先程コピーしたLambdaのARNを記載してください。
最後に設定内容を確認して問題なければ保存をしてください。

次はAPI用のビヘイビアを作成します。
先程のビヘイビアの管理画面からビヘイビアを作成を開いてください。
まず、パスパターンは/api/を選択してください。
オリジンとオリジングループはデフォルトと同じものを選択してください。
あとはデフォルトのビヘイビアと設定は同じです。しかし、Lambda@Edgeは何もデプロイしないでください。

最後に設定内容を確認し、問題がなければ作成してください。
その後以下のようになっていればOKです。

(オリジン名がEC2になっているのは私が手順を間違えただけですのでお気になさらないでください)

Route53にレコードを登録

最後にRoute53のホストゾーンにAレコードを登録します。
これをしなければサイトが見れませんので必ず設定してください。(私はすっかり忘れててサイトを見に行ったら見れなくて焦りました・・・)

まずRoute53を開いていただき、先程作成したホストゾーンを開いてください。
次にレコードの作成をしていきます。
まずレコード名ですがサブドメインがなければ空白で問題ございません。

次にレコードタイプはAを選択してください。
そうしたら値を入力せず、エイリアスをオンにします。
そうするとエンドポイントを選べるようになりますので、CloudFront ディストリビューションへのエイリアスを選択してください。

次にディストリビューションを選択します。こちらはおそらく先程作成したディストリビューションしか選択できませんので、ご自身が作成したディストリビューションであることを確認して選択してください。

その後レコード内容を確認して、問題なければレコードを作成してください。

構築した結果を確認

こちらですべて完了です。
最後に構築したページの結果を確認します。

まずおさらいになりますが、テストページは以下になります。

■通常ページ
https://example.com/
https://example.com/index.html/

■エラーページ
https://example.com/[存在しないページ]
https://example.com/404.html/

■デフォルトの403エラーページ
https://example.com/api

example.comの部分をご自身のドメインに書き換えていただいて、テストしてみましょう。
まずは先程もテストしましたが、再びindex.htmlを確認してみます。

無事表示されていますね。
次はエラーページを開いてみます。

適当に末尾に/hogehogeと入力しました。
そうしたら無事エラーページが表示できましたね。これにてエラーページも完了です。
最後に末尾に/apiを入れて試してみます。

無事に通常の403エラーページが表示されました。
これにて全ての構築が完了いたしました。お疲れ様でした。

最後に

いかがだったでしょうか。
思ったよりも長くなって驚きました。。
結構駆け足になってしまいましたが、分かりづらくてすみません。
次は頑張って見やすいブログが書けるように頑張ります。

それではまたどこかでお会いしましょう。

返信を残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA