CloudFront のオリジンカスタムヘッダーで Lambda 関数 URL への直接アクセスを制御してみた
はじめに
テクニカルサポートの 片方 です。
オリジン側でその値を検証すると、検証用ヘッダーが一致しない Lambda 関数 URL へのリクエストを拒否できます。
本ブログでは、Lambda 関数 URL を CloudFront のオリジンに設定し、X-Origin-Verify ヘッダーを利用したアクセス制御を検証します。以下の結果を確認します。
- CloudFront 経由のアクセス: 200 OK
- Lambda 関数 URL への直接アクセス: 403 Forbidden
この構成では、Lambda 関数 URL を知っているだけのリクエストを拒否し、CloudFront が付与する検証用ヘッダーを持つリクエストだけを Lambda 関数で受け付けられます。
ただし、この方式は固定値を使ったリクエスト検証です。Lambda 関数 URL を非公開にするものではありません。関数 URL と検証用ヘッダーの値が漏えいした場合、第三者が CloudFront を経由せずに Lambda 関数 URL を呼び出せます。
本番環境で CloudFront 以外からのアクセスをより厳格に制御したい場合は、Origin Access Control(OAC)の利用を検討してください。Lambda 関数 URL で OAC を使用する場合は、認証タイプに AWS_IAM を指定し、CloudFront ディストリビューションからの呼び出しだけを許可するリソースベースポリシーを設定します。
検証したこと
CloudFront のオリジンカスタムヘッダーを利用して、CloudFront から Lambda 関数 URL へ送信されるリクエストに、次の固定ヘッダーを追加します。
X-Origin-Verify: <ランダムに生成した値>
Lambda 関数では、受信した X-Origin-Verify ヘッダーと環境変数に設定した値を比較します。
値が一致した場合は 200 OK、一致しない場合は 403 Forbidden を返します。検証内容は以下のとおりです。
| アクセス先 | クライアントが送信するヘッダー | 期待結果 |
|---|---|---|
| CloudFront | なし | 200 OK |
| Lambda 関数 URL | なし | 403 Forbidden |
| Lambda 関数 URL | X-Origin-Verify: invalid | 403 Forbidden |
| CloudFront | X-Origin-Verify: invalid | 200 OK |
最後のパターンでは、クライアントが X-Origin-Verify: invalid を送信しても、CloudFront がオリジンカスタムヘッダーの設定値で上書きしてオリジンへ転送することを確認します。
CloudFront は、同名のヘッダーがビューワーリクエストに含まれている場合、オリジンカスタムヘッダーに設定した値で上書きします。
前提条件
本ブログでは、AWS マネジメントコンソールを使用してリソースを作成します。検証条件は以下のとおりです。
| 項目 | 設定 |
|---|---|
| AWS リージョン | アジアパシフィック(東京) ap-northeast-1 |
| Lambda ランタイム | Node.js 22.x |
| Lambda アーキテクチャ | arm64 |
| Lambda 関数 URL の認証タイプ | NONE |
| CloudFront のキャッシュ | 無効 |
| 独自ドメイン | 使用しない |
| AWS WAF | 使用しない |
| 動作確認 | AWS CloudShell の curl コマンド |
CloudFront はグローバルサービスですが、オリジンとなる Lambda 関数は東京リージョンに作成します。
認証タイプに NONE を指定した Lambda 関数 URL では、IAM 認証は行われません。ただし、呼び出しにはリソースベースポリシーによる許可が必要です。
AWS マネジメントコンソールで認証タイプ NONE の関数 URL を作成した場合、Lambda は公開アクセスを許可するリソースベースポリシーを自動作成します。
そのため、関数 URL と検証用ヘッダーの値の両方を知る第三者は、CloudFront を経由せずにリクエストできる点に注意してください。
本ブログでは、Lambda 関数内でカスタムヘッダーを検証し、値が一致しないリクエストに 403 Forbidden を返します。
作成する主なリソースは以下のとおりです。
- Lambda 関数
- Lambda 関数 URL
- Lambda 実行ロール
- CloudFront ディストリビューション
- Lambda 関数を初回実行した後、CloudWatch Logs のロググループも自動作成されます。
検証用ヘッダー値の生成と動作確認には、AWS マネジメントコンソールから起動できる AWS CloudShell を使用します。
やってみた
まず、CloudFront と Lambda 関数で共有する検証用ヘッダー値を生成します。
AWS CloudShell で次のコマンドを実行しました。
openssl rand -hex 32
出力された値は、以降の手順で CloudFront のオリジンカスタムヘッダーと Lambda 関数の環境変数に設定します。

Lambda 関数の作成
AWS マネジメントコンソールで Lambda 関数を作成します。
| 項目 | 設定値 |
|---|---|
| 関数名 | cloudfront-origin-header-sample |
| ランタイム | Node.js 22.x |
| アーキテクチャ | arm64 |
| 実行ロール | 基本的な Lambda アクセス権限で新しいロールを作成 |
関数の作成後、コードを以下に置き換えます。
import { timingSafeEqual } from "node:crypto";
const HEADER_NAME = "x-origin-verify";
function secureCompare(actual, expected) {
if (typeof actual !== "string" || typeof expected !== "string") {
return false;
}
const actualBuffer = Buffer.from(actual, "utf8");
const expectedBuffer = Buffer.from(expected, "utf8");
if (actualBuffer.length !== expectedBuffer.length) {
return false;
}
return timingSafeEqual(actualBuffer, expectedBuffer);
}
function jsonResponse(statusCode, body) {
return {
statusCode,
headers: {
"content-type": "application/json; charset=utf-8",
"cache-control": "no-store",
},
body: JSON.stringify(body),
};
}
export const handler = async (event) => {
const expectedSecret = process.env.ORIGIN_VERIFY_SECRET;
if (!expectedSecret) {
// 設定ミスの詳細やシークレットはレスポンスに含めない
return jsonResponse(500, {
message: "Internal Server Error",
});
}
const headers = Object.fromEntries(
Object.entries(event.headers ?? {}).map(([key, value]) => [
key.toLowerCase(),
value,
]),
);
const providedSecret = headers[HEADER_NAME];
if (!secureCompare(providedSecret, expectedSecret)) {
return jsonResponse(403, {
message: "Forbidden",
});
}
return jsonResponse(200, {
message: "Accessed through CloudFront",
path: event.rawPath ?? "/",
method: event.requestContext?.http?.method ?? "UNKNOWN",
});
};
このコードでは、以下を実施しています。
- 環境変数 ORIGIN_VERIFY_SECRET から期待値を取得する
- リクエストヘッダーから x-origin-verify を取得する
- 値が一致しない場合は 403 Forbidden を返す
- 値が一致する場合は 200 OK を返す
- 値の比較には timingSafeEqual を使用する
- 検証用ヘッダーの値や内部設定の詳細をレスポンスへ含めない
続いて、[設定]タブの[環境変数]から以下を追加します。
| キー | 値 |
|---|---|
| ORIGIN_VERIFY_VALUE | CloudShell で生成した検証用ヘッダー値 |

Lambda 関数 URL の作成
Lambda 関数の[設定]タブから[関数 URL]を開き、[関数 URL を作成]を選択します。
設定は以下のとおりです。
| 項目 | 設定値 |
|---|---|
| 認証タイプ | NONE |
| CORS | 未設定 |
NONE を選択すると、Lambda 関数 URL に対する IAM 認証は行われません。今回は Lambda 関数内で X-Origin-Verify ヘッダーを検証するため、この設定を使用します。
関数 URL を作成したら、URL を控えます。以降では、CloudShell 上で環境変数に設定します。
export FUNCTION_URL='https://xxxxxxxxxxxxxxxx.lambda-url.ap-northeast-1.on.aws/'

Function URL への直接アクセスを確認
CloudShell から、ヘッダーなしで Lambda 関数 URL にアクセスします。
curl -i "${FUNCTION_URL}"
以下のように 403 Forbidden が返りました。
HTTP/1.1 403 Forbidden
Date: Mon, 03 Aug 2026 04:17:52 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 23
Connection: keep-alive
x-amzn-RequestId: xxxxxxxxxxxxxxxxxxxxx
cache-control: no-store
X-Amzn-Trace-Id: Root=1-xxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx;Parent=6xxxxxxxxxxxxe;Sampled=0;Lineage=1:3312f90a:0
{"message":"Forbidden"}~
続いて、不正な X-Origin-Verify ヘッダーを付与してアクセスします。
curl -i \
-H 'X-Origin-Verify: invalid' \
"${FUNCTION_URL}"
こちらも 403 Forbidden となりました。
HTTP/1.1 403 Forbidden
Date: Mon, 03 Aug 2026 04:18:11 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 23
Connection: keep-alive
x-amzn-RequestId: xxxxxxxxxxxxxxxxxxxxx
cache-control: no-store
X-Amzn-Trace-Id: Root=1-xxxxxx-xxxxxxxxxxxxxxxxxxxxxxx;Parent=6xxxxxxxxxxxxxx9;Sampled=0;Lineage=1:3312f90a:0
{"message":"Forbidden"}~
この時点では、CloudFront から付与される検証用ヘッダーがないため、Lambda 関数 URL へのリクエストは拒否されます。

CloudFront ディストリビューションの作成
CloudFront コンソールで[ディストリビューションを作成]を選択します。
オリジンには、先ほど作成した Lambda 関数 URL を設定します。https:// は含めず、ホスト名のみを指定します。
xxxxxxxxxxxxxxxx.lambda-url.ap-northeast-1.on.aws
主な設定は以下のとおりです。
| 項目 | 設定値 |
|---|---|
| オリジン | Lambda 関数 URL のホスト名 |
| プロトコル | HTTPS のみ |
| Origin access control | 設定しない |
| ビューワープロトコルポリシー | HTTP から HTTPS へリダイレクト |
| 許可された HTTP メソッド | GET、HEAD |
| キャッシュポリシー | CachingDisabled |
今回はリクエストごとの挙動を確認するため、AWS マネージドキャッシュポリシーの CachingDisabled を使用します。このポリシーでは最小 TTL、最大 TTL、デフォルト TTL がいずれも 0 秒であり、CloudFront のキャッシュは無効です。
ディストリビューションを作成後、ステータスが 有効 になり、デプロイが完了するまで待機します。


Origin Custom Header の設定
作成したディストリビューションの[オリジン]タブで、Lambda 関数 URL のオリジンを編集します。
[カスタムヘッダーを追加]から、以下を設定します。
| ヘッダー名 | 値 |
|---|---|
| X-Origin-Verify | CloudShell で生成した検証用ヘッダー値 |
設定を保存し、ディストリビューションのデプロイ完了を待ちます。
CloudFront は、オリジンカスタムヘッダーとして設定したヘッダーをオリジンリクエストへ追加します。
また、ビューワーリクエストに同名のヘッダーが含まれている場合は、CloudFront が設定値で上書きしてオリジンへ転送します。

確認してみた
CloudFront ディストリビューションのドメイン名を CloudShell の環境変数に設定します。
export CLOUDFRONT_DOMAIN='dxxxxxxxxxxxx.cloudfront.net'
まず、ヘッダーを指定せずに CloudFront 経由でアクセスします。
curl -i "https://${CLOUDFRONT_DOMAIN}/"
以下のように 200 OK が返りました。
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
cache-control: no-store
{"message":"OK"}
次に、クライアントから不正な X-Origin-Verify ヘッダーを指定して CloudFront へアクセスします。
curl -i \
-H 'X-Origin-Verify: invalid' \
"https://${CLOUDFRONT_DOMAIN}/"
この場合も 200 OK が返りました。
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
cache-control: no-store
{"message":"OK"}

Lambda 関数 URL へ直接アクセスした場合、不正なヘッダーでは 403 Forbidden でした。
一方、CloudFront 経由では、クライアントが送信した X-Origin-Verify: invalid が CloudFront のオリジンカスタムヘッダー設定値で上書きされます。そのため、Lambda 関数の検証を通過して 200 OK が返ります。
検証結果は以下のとおりです。
| アクセス先 | クライアントが送信するヘッダー | 結果 |
|---|---|---|
| CloudFront | なし | 200 OK |
| Lambda 関数 URL | なし | 403 Forbidden |
| Lambda 関数 URL | X-Origin-Verify: invalid | 403 Forbidden |
| CloudFront | X-Origin-Verify: invalid | 200 OK |
カスタムヘッダー方式の制約
今回の方式では、検証用ヘッダーが一致しない Lambda 関数 URL へのリクエストを拒否できます。
一方で、固定のカスタムヘッダーを使う方式には以下の制約があります。
- 検証用ヘッダー値は共有シークレットである
- Lambda 関数 URL と検証用ヘッダー値の両方が漏えいすると、CloudFront を経由しない直接アクセスが可能である
- 固定値を使用するため、漏えい時には CloudFront と Lambda 環境変数の両方を更新する必要がある
- Lambda 関数 URL の認証タイプが NONE であるため、URL 自体は公開エンドポイントである
- この方式だけで、リクエスト元が CloudFront であることを暗号学的に証明することはできない
CloudFront 経由のアクセスだけを許可したい場合は、Lambda 関数 URL の認証タイプを AWS_IAM に変更し、OAC を使用する方式を検討してください。
OAC を使用すると、CloudFront は Lambda 関数 URL へのオリジンリクエストに SigV4 署名を付与できます。
また、Lambda 関数 URL のリソースベースポリシーでは、CloudFront サービスプリンシパルに対して、対象の CloudFront ディストリビューション ARN を AWS:SourceArn 条件で指定します。
なお、CloudFront のオリジンカスタムヘッダーには設定できないヘッダーがあります。
たとえば、Cache-Control、Cookie、Host、X-Amz- で始まるヘッダー、X-Edge- で始まるヘッダーなどは設定できません。
今回使用した X-Origin-Verify は、これらの制限には該当しません。
まとめ
CloudFront の Origin Custom Header を使用し、Lambda 関数 URL に固定の検証用ヘッダーを付与してアクセス制御を試しました。結果は以下のとおりです。
- CloudFront 経由のリクエストは 200 OK
- 検証用ヘッダーを持たない Lambda 関数 URL への直接アクセスは 403 Forbidden
- CloudFront は、クライアントが送信した同名ヘッダーをオリジンカスタムヘッダーの設定値で上書きする
- 固定ヘッダー方式は共有シークレットに依存するため、Lambda 関数 URL への直接アクセスを完全に防ぐ用途には適さない
- 本番環境で CloudFront 以外からのアクセスをより厳格に制御する場合は、AWS_IAM と OAC の組み合わせを検討する
検証後は、不要な Lambda 関数 URL と CloudFront ディストリビューションを削除してください。特に NONE の Lambda 関数 URL を残したままにしないよう注意します。Lambda 関数 URL を CloudFront のオリジンとして利用する際の参考になれば幸いです。
参考資料
- Add custom headers to origin requests - Amazon CloudFront
- Restrict access to an AWS Lambda function URL origin - Amazon CloudFront
クラスメソッドオペレーションズ株式会社について
クラスメソッドグループのオペレーション企業です。
運用・保守開発・サポート・情シス・バックオフィスの専門チームが、IT・AIをフル活用した「しくみ」を通じて、お客様の業務代行から課題解決や高付加価値サービスまでを提供するエキスパート集団です。
当社は様々な職種でメンバーを募集しています。
「オペレーション・エクセレンス」と「らしく働く、らしく生きる」を共に実現するカルチャー・しくみ・働き方にご興味がある方は、クラスメソッドオペレーションズ株式会社 コーポレートサイト をぜひご覧ください。※2026年1月 アノテーション㈱から社名変更しました







