【AWS】CloudFrontとは?FunctionsでS3と連携する方法

Cloud

前回の記事では、AWS S3を使ってindex.htmlを公開する方法を紹介しました。 しかし、S3単体での公開だけでは、柔軟なアクセス制御ができません。

そこで今回は、Amazon CloudFront を導入し、さらに CloudFront Functions を使って、「Basic認証」をかけたり、「簡易的な祝日API」を作ったりする方法を解説します。
AWS S3にindex.htmlファイルが保管できている事が前提となっていますので、まだS3を準備していない方は、前回のAWS S3をご覧ください。

この記事の内容
  • AWS S3とCloudFrontとの連携方法
  • AWS CloudFront Functionsの使い方
  • KeyValueStoreの使い方

S3だけでWebページ公開はダメ?

Webページを公開するにあたって、S3単体でページを公開する事は可能です。しかしこの行為は、以下のようにセキュリティー面で不安があります。

① 「うっかりミス」による情報漏洩のリスク

S3バケットを「公開」設定にすると、そのバケット内のオブジェクトは基本的に世界中から見られる状態になります(バケットポリシーで調整は可能ですが、設定が複雑になります)。 もし、開発者が誤って「顧客リストのCSV」や「APIキーを含んだ設定ファイル」をそのバケットにアップロードしてしまった場合、即座に世界中に流出します。 「CloudFront経由のみ許可(OAC)」にしておけば、万が一機密ファイルをアップロードしても、CloudFront側でパス制限をかけたり、そもそも公開用パスに置かなければ直接アクセスされることはないため、安全弁になります。

② AWSアカウント全体のセキュリティ基準(ベースライン)

現在、AWSでS3バケットを新規作成すると、デフォルトで「パブリックアクセスをすべてブロック」がON になっています。 これをOFFにするということは、AWSが推奨するセキュリティガードレールを意図的に外すことを意味します。組織のセキュリティ監査では、「S3が公開設定になっていないか」は必ずチェックされる項目です。ここをクリアにするためにも「S3は閉じる」が基本です。

③ DDoS攻撃とコストの直撃

S3を直接公開すると、悪意のある大量アクセス(DDoS攻撃など)が来た場合、S3に対して直接リクエストが飛びます。S3のリクエスト料金やデータ転送量は青天井で課金されるため、「高額請求 のリスクがあります。 CloudFrontを挟めば、AWS Shield(DDoS保護)が標準で適用されるほか、WAF(Web Application Firewall)を適用して攻撃をブロックすることが可能です。

④ 独自ドメインを使ったWebサイト公開でHTTPS化できない

S3には2種類のアクセス方法(エンドポイント)があります。
・REST API エンドポイント:HTTPS機能があるがindex.htmlの自動補完が効かない。
・Webサイト エンドポイント:HTTPS機能が無いがindex.htmlの自動補完はある。

通常、Webサイトとして公開するならindex.htmlの自動補完がほしいので、「Webサイトエンドポイント」を使うことになり、するとHTTPS機能が効かないことになります。
また、S3バケット単体にはサーバー証明書(SSL証明書)をインストールする機能がありませんので、独自ドメインを使うことができません。独自ドメインでHTTPS通信させるためには、S3の前段に証明書を扱える CloudFront(またはロードバランサー)が必須 となります。

※当記事では独自証明書の使い方は説明しません。

CloudFrontとS3を使ってWebページ公開する方法

Webページを公開する場合、一般的にはCloudFrontとS3を利用することになります。ここではこの2つのコンポーネントを使用して、Webページの公開方法を説明します。
また同時に、CloudFront Functions の説明も行います。

全体の構成

まず今回作成するAWSの全体構成を示します。

User -> (HTTPS) -> CloudFront (Functions + KeyValueStore) -> (OAC) -> S3 Bucket

  • CloudFront: コンテンツ配信と、Functionsによるロジック実行を担当。
  • KeyValueStore: 祝日データを保存するKVS(キーバリューストア)。Functionsからミリ秒単位で高速に読み出せます。
  • S3: 静的ファイル(HTMLなど)のストレージ。

手順1: CloudFrontディストリビューションの作成

AWSコンソール「CloudFront」から「ディストリビューションを作成」します。各ステップで次のように設定してください。

  1. Coose a plan: 静的コンテンツをS3から取得するだけのWebページを作る場合は、Freeプランを選択してよいでしょう。しかしFreeプランでは、この記事の後半で説明している「CloudFront KeyValueStore」(FunctionsでS3から辞書型の情報を取得する機能)が利用できません。よって今回はpay as you go プランを選択します。なおpay as you go プランでは、使用していなくても6ドル/月のWAFの費用が発生します。
  2. オリジンドメイン: 作成済みのS3バケットを選択。
    • 注意: 静的ウェブサイトホスティングのエンドポイントではなく、バケットそのものを選択。
  3. オリジンアクセス: 「Origin access control settings (recommended)」を選択し、「コントロール設定を作成」します。
  4. ビューワープロトコルポリシー: Redirect HTTP to HTTPS を選択。
  5. 設定 (WAF): 「セキュリティ保護を有効にしない」を選択(コスト節約のため)。
  6. デフォルトルートオブジェクト: index.html と入力。

作成後、画面上部の「ポリシーをコピー」ボタンを押し、S3バケットの「アクセス許可」→「バケットポリシー」に貼り付けて保存してください。

・・・

  • Distribution nameを入力
  • オリジンドメイン: 作成済みのS3バケットを選択。
    注意: 静的ウェブサイトホスティングのエンドポイントではなく、バケットそのものを選択。
  • デフォルトではWAFが自動的に有効となる。大した金額ではないので、このまま次に進む。

手順2: CloudFrontの動作確認

まず作成したディストリビューションを開き、「ディストリビューションドメイン名」をコピーしてください。い。

次に、ブラウザのアドレスバーに先ほどのディストリビューションドメイン名を貼り付け、S3で用意したファイルである/index.htmlを付けてアクセスしてください。ブラウザ上にindex.htmlの内容が現れたら成功です。

URL:https://<あなたのディストリビューションドメイン名(xxx.cloudfront.net)>/index.html
URL(今回の例):https://dyeznmdbh5ydy.cloudfront.net/index.html

ブラウザによるアクセス結果

前回記事のS3のオブジェクトURLにアクセスした時と、同じ内容が表示されましたね。

CloudFront Functionsの使い方

上でCloudFrontが動作しましたので、ここからはCloudFront Functionsの使い方を説明します。

(基本編)CloudFront FunctionsでBasic認証を追加する

まずは基本として、サイト全体にBasic認証をかける関数を作ってみましょう。

  1. CloudFrontメニュー「関数 (Functions)」→「関数を作成」。
  2. 今回の名前:BasicAuth
  3. 以下のコードを記述して「変更を保存」→「発行」。
function handler(event) {
    var request = event.request;
    var headers = request.headers;
    
    // "user:password" をBase64化した文字列
    // echo -n "user:password" | base64 で作成可能
    var authString = "Basic dXNlcjpwYXNzd29yZA==";

    if (
        typeof headers.authorization === "undefined" ||
        headers.authorization.value !== authString
    ) {
        return {
            statusCode: 401,
            statusDescription: "Unauthorized",
            headers: {
                "www-authenticate": { value: "Basic" }
            }
        };
    }
    return request;
}

コード解説: この関数は、すべてのリクエストに対して動作し、以下のロジックを実行します。

  1. 認証情報の定義: authString 変数に、許可したい「ユーザー名:パスワード」をBase64エンコードした文字列(Basic ...)を設定します。
  2. ヘッダーチェック: リクエストヘッダーに authorization が含まれていない、または値が設定した文字列と一致しないかを判定します。
  3. 認証要求: 条件に一致しない(未認証の)場合、ステータスコード 401 Unauthorizedwww-authenticate: Basic ヘッダーを返します。これにより、ブラウザはユーザーにIDとパスワードの入力を求めるダイアログを表示します。
  4. 通過: 認証情報が一致した場合は、return request; でリクエストをそのままCloudFront(そしてS3)へ通します。

最後に、ディストリビューションの「ビヘイビア (Behaviors)」タブから、「関数の関連付け」セクションで「ビューワーリクエスト(Viewer Request) 」にこの関数を関連付けます。関数タイプには「CloudFront Function」を、関数ARN/名前には先ほど作成した関数「BasicAuth 」を設定してください。

動作確認

先ほどのURLにアクセスすると、Basic認証画面が表示され、ユーザー名・パスワード(先のjavascriptソース参照)を入力する事で、ブラウザにindex.htmlが表示されました。

URL:https://<あなたのディストリビューションドメイン名(xxx.cloudfront.net)>/index.html
URL(今回の例):https://dyeznmdbh5ydy.cloudfront.net/index.html


(応用編) CloudFront KeyValueStoreで祝日APIを作る

ここからが本題の応用テクニックです。 大量のデータ(例:辞書データなど)を扱う場合や、コードとデータを分離して管理したい場合は、CloudFront KeyValueStore (KVS) を利用します。

今回は「YYYYMMDD」をキー、「祝日名」を値としてKVSに登録し、関数側で「指定された月のデータをまとめて高速取得」する構成を作ります。

1. データの準備(S3への登録)

まず、元データとなる祝日データを準備します。

データ取得元: 内閣府「国民の祝日について」 から国民の祝日 CSVファイルをダウンロードできます。

内閣府ページ

ダウンロードした国民の祝日 CSVの内容

国民の祝日・休日月日,国民の祝日・休日名称
1955/1/1,元日
1955/1/15,成人の日
・・・
2025/1/1,元日
2025/1/13,成人の日
2025/2/11,建国記念の日
2025/2/23,天皇誕生日
2025/2/24,休日
・・・

KVSにインポートするために、このCSVを日付YYYY/M/D (ゼロ埋めなし)をキー にしたJSON形式に変換し、ファイル(例: holidays_kvs_date.json)として保存してください。

{
  "data": [
    {
      "key": "2025/1/1",
      "value": "元日"
    },
    {
      "key": "2025/1/13",
      "value": "成人の日"
    },
    {
      "key": "2025/2/11",
      "value": "建国記念の日"
    },
    {
      "key": "2025/2/23",
      "value": "天皇誕生日"
    },
    {
      "key": "2025/2/24",
      "value": "休日"
    },
    // ... 他の日付も同様
  ]
}

このJSONファイルをS3バケットにアップロードしておきます。 アップロード先は、手順1で使用したWebサイト公開用のS3バケット(index.htmlと同じバケット)で問題ありません。
※このファイルはKVSへのインポート時に一度参照されるだけで、Webサイトの訪問者が直接アクセスするものではありません。管理しやすい場所にアップロードしてください。

2. KeyValueStoreの作成とインポート

  1. CloudFrontメニュー「関数 (Functions)」→「KeyValueStore」タブを選択し、「KeyValueStoreを作成」をクリック。
  2. 名前を入力(今回はHolidayKVSとします)。
  3. 作成後、「インポート」をクリックし、先ほどS3に上げたJSONファイルをソースとして指定してインポートを実行します。

4. 関数の作成

関数を作成します。今回は月ごとのデータを取得するために、「その月の1日から31日までの全パターンのキーを生成し、並列アクセスで一気に存在確認を行う」 というテクニックを使います。 CloudFront Functionsのランタイムはcloudfront-js-2.0 を選択してください。

KeyValueStoreの関連付け

関数エディタ画面の「Associated KeyValueStore」セクションで、「Associate existing KeyValueStore」ボタンを押下し、作成した HolidayKVS を選択して関連付けます。下図は関連付け後のイメージです。

API用関数のプログラムの作成

関数エディタ画面の構築タブ内で、関数のプログラムを作成します。
なお、関数エディタ画面のAssociated KeyValueStoreセクションでKeyValueStoreを関連付けると、親切にもExample functionとしてサンプルソースを表示してくれています(下図参照)。このサンプルソースを参考に関数を構築すると良いでしょう。

上記のExample functionを参考にしつつ、作成した関数のソースコードは次の通りです。

API用関数のソースコード:

import cf from 'cloudfront';

// KVSのハンドルを取得(関数に関連付けられたKVSを使用)
const kvsHandle = cf.kvs();

async function handler(event) {
    const request = event.request;
    const querystring = request.querystring;

    // 1. クエリパラメータの取得 (?year=2025&month=01)
    const targetYear = querystring.year ? querystring.year.value : null;
    const targetMonth = querystring.month ? querystring.month.value : null;

    // バリデーション
    if (!targetYear || !targetMonth) {
        return createResponse(400, { error: "Parameters 'year' and 'month' are required." });
    }

    try {
        const holidays = [];
        const promises = [];

        // 入力が "01" 等の場合に備えて数値化(ゼロサプレス)
        // CSV形式に合わせて "2025/1/1" のようなキーを作るため
        const yearNum = Number(targetYear);
        const monthNum = Number(targetMonth);

        // 2. その月の全日付(1日~31日)を走査するためのループ
        // 厳密な月末日は計算せず、最大31日まで全てチェックします(存在しない日はKVSエラーで無視するため)
        for (let d = 1; d <= 31; d++) {
            // キー生成: YYYY/M/D 形式 (例: 2025/1/1)
            const key = `${yearNum}/${monthNum}/${d}`;

            // KVSへのGetリクエストをPromiseとして配列に格納
            // そのままawaitすると遅いので、Promise.allで並列実行させる準備
            const p = kvsHandle.get(key)
                .then(value => {
                    // 取得成功時はリストに追加
                    return {
                        date: key, // YYYY/M/D 形式 (例: 2025/1/1)
                        name: value // KVSの値(祝日名)
                    };
                })
                .catch(err => {
                    // キーが存在しない(祝日ではない)場合はnullを返して無視
                    return null;
                });
            
            promises.push(p);
        }

        // 3. 全日付分を並列実行して待機
        const results = await Promise.all(promises);

        // null(祝日ではなかった日)を除外して結果リストを作成
        const validHolidays = results.filter(item => item !== null);

        return createResponse(200, validHolidays);

    } catch (err) {
        console.log("System Error: " + err);
        return createResponse(500, { error: "Internal Server Error" });
    }
}

// レスポンス生成用ヘルパー関数
function createResponse(statusCode, bodyObject) {
    return {
        statusCode: statusCode,
        statusDescription: statusCode === 200 ? "OK" : "Error",
        headers: {
            "content-type": { value: "application/json; charset=utf-8" },
            "access-control-allow-origin": { value: "*" }
        },
        body: {
            encoding: "text",
            data: JSON.stringify(bodyObject)
        }
    };
}

解説:
通常、データベースであれば「前方一致検索」を行いますが、KeyValueStoreは「完全一致検索」しかできません。 そこで、「月の日付は最大でも31個しかない」 点に着目し、forループで 20250101, 20250102… と31個分のキーを生成し、Promise.all を使って一斉にKVSへ問い合わせを行っています。これにより、日単位のキー設計でありながら、APIとしては月単位のリストを返すことが可能になります。

関数のテスト

画面上の「発行」タブを押し、関数のテストを行います。テスト時のパラメータ等は保存しておくことが可能です。


今回のテスト結果は次の通りです。コンピューティング使用率は、関数の最大許容時間の使用パーセンテージです。この値が71~100だと、スロットリング(実行制限)がかかる可能性がありますので、プログラムを調整するしてください。

発行

関数の発行: 画面上の「発行」タブを押し、「関数を発行」ボタンをクリックします。これでLive状態になります。


5. ビヘイビアへの関連付け

関数コードを書いただけでは今回のCloudfront Function動きません。以下の手順で公開設定を行ってください。
ビヘイビアの作成と関数の適用のために、CloudFrontディストリビューションの設定画面を開き、次の設定を行います。

  • ビヘイビア」タブ → 「ビヘイビアを作成」をクリック。
  • パスパターン: /api/holidays と入力(このURLでAPIにアクセスすることになります)。
  • オリジン: 祝日データのJSONfileを登録したS3を選択。
  • ビューワープロトコルポリシー: Redirect HTTP to HTTPS または HTTPS only
  • 関数の関連付け (最下部):
    • ビューワーリクエスト」の項目で、「CloudFront Functions」を選択。
    • 「関数を選択」のプルダウンから、今回作った関数を選びます。
  • 設定を保存: 「ビヘイビアを作成」ボタンを押して完了です

2026年1月時点では、ディストリビューションがFreeプランだと、ビヘイビアの登録時に次のエラーが発生します。KeyValueStoreを使用する場合は、ディストリビューションを有料プラン(例:pay as you go)で作り直してください。
<エラーメッセージ>
「You can’t associate this CloudFront Function to a distribution on a Free plan tier. Remove the key value store from your function or upgrade to a paid plan.」

ビヘイビアを登録すると、関数(Function)側のステータスが「更新中」になりますが、数分で「デプロイ済み」になり、関数が使えるようになります。

ビヘイビアを登録すると、自動的に関数画面の発行タブの「関連付けられているディストリビューション」が登録されます。

5. 動作確認(APIの呼び出し方)

APIの準備ができたら、ブラウザやプログラムからアクセスして動作を確認しましょう。 この関数は URLのクエリパラメータ yearmonth を読み取るように作られていますので、URLの後ろに付与して、ブラウザで開いてみてください。

APIのURL:
https://<あなたのディストリビューションドメイン名(xxx.cloudfront.net)>/api/holidays?year=2025&month=1
APIのURL(今回の例):
https://dyeznmdbh5ydy.cloudfront.net/api/holidays?year=2025&month=1

ブラウザでの表示結果(JSON): 以下のようなJSONが返ってくれば成功です。

[{"date":"2025/1/1","name":"元日"},{"date":"2025/1/13","name":"成人の日"}]

curlコマンドでの呼び出し例: ターミナルから確認する場合は以下のコマンドを使います。

curl "https://<あなたのディストリビューションドメイン名(xxx.cloudfront.net)>/api/holidays?year=2025&month=01"
(今回の例)
curl "https://dyeznmdbh5ydy.cloudfront.net/api/holidays?year=2025&month=01"

まとめ

CloudFrontは単なるCDN(キャッシュ)だけでなく、Functionsを組み合わせることで、「認証付きサイト」や「サーバーレスAPI」を簡単に構築できます。

  • 少量のデータ: コード埋め込み(手軽・最速)
  • 大量のデータ / 頻繁な更新: KeyValueStore(スケーラブル・管理分離)

用途に合わせて使い分けてみてください。

タイトルとURLをコピーしました