CloudFront OAC+Lambda関数URLをTerraformで実装する
この記事は、Finatext Advent Calendar 2025の 6 日目の記事です。
株式会社 Finatext でバックエンドエンジニアをしている阿部です。
先日業務で AWS Lambda で作った API (Go + Echo) を CloudFront で公開する機会がありました。
Terraform で IaC 化するにあたりいくつかハマったポイントもあったのでこの記事でまとめます。
説明すること / 説明しないこと
説明すること
- Lambda Function URLs (Lambda 関数 URL)、CloudFront OAC とはなにか
- Terraform のコード例
- ハマったポイント
説明しないこと
- Lambda に Web サーバーをデプロイする方法
-
Lambda Web Adapterを使って Web サーバー(Go+echo)をデプロイする方法は以下の記事が参考になりました!
https://qiita.com/maooz4426/items/43c05f14cae0d4753719
-
Lambda Web Adapterを使って Web サーバー(Go+echo)をデプロイする方法は以下の記事が参考になりました!
前提知識・背景
Lambda Function URLs とは
Lambda Function URLs (Lambda 関数 URL)は、Lambda 関数に HTTP(S) エンドポイントを追加できる機能です。
関数 URL を作成すると、以下のような一意の URL エンドポイントが生成されます。
https://<url-id>.lambda-url.<region>.on.aws
認証タイプとして AWS_IAM(lambda:InvokeFunctionUrlとlambda:InvokeFunctionを持つ Principal のみアクセス可能)かNONE(パブリックにアクセス可能)が選択できます。
認証タイプにAWS_IAMが設定されている場合、各 HTTP リクエストにはAWS Signature Version 4 (SigV4) による署名が必要となります。
CloudFront 経由で関数 URL にアクセスする場合、後述する CloudFront OAC の設定によってリクエストに署名させることができます。
CloudFront OAC (Origin Access Control) とは
OAC は CloudFront がオリジンにアクセスする際の認証/認可を設定できる機能です。
従来の OAI(Origin Access Identity)の後継機能として、より細かな設定が行えるようになりました。
2024 年 4 月のアップデートで、Lambda Function URLs の実行がサポートされるようになりました。
CloudFront OAC + Lambda Function URLs で実現できること
以下の 2 つによって、Lambda Function URLs の直接アクセスを防ぎ、CloudFront 経由のみに制限することができます。
-
CloudFront ディストリビューションの「認証」
CloudFront が Lambda Function URLs にアクセスする際、CloudFront が各リクエストに対して SigV4 で署名を行い、Lambda 側で検証することで、「このリクエストは信頼できる CloudFront から来ている」と認証できる -
Lambda Function URLs への「認可」
Lambda のポリシーに OAC を設定することで、「認証タイプがAWS_IAMかつ指定した CloudFront ディストリビューションからの Lambda 実行のみを許可する」ことができる
アーキテクチャ概要

フロー:
- クライアントが CloudFront にリクエスト
- CloudFront が OAC を使用して AWS Signature V4 で署名
- Lambda Function URL が署名を検証
- 検証成功時のみ Lambda 関数を実行
Terraform による実装
以下は Terraform のコード例です。
Lambda Function の作成
まず、Lambda 関数と Function URL を作成します。
ポイントは authorization_type = "AWS_IAM" を指定することです。
# Lambda 関数 (コンテナイメージ使用)
resource "aws_lambda_function" "example" {
architectures = ["arm64"]
function_name = "example-function"
image_uri = "<account-id>.dkr.ecr.<region>.amazonaws.com/<ecr-name>:<tag>"
memory_size = 128
role = aws_iam_role.lambda_exec.arn
}
# Lambda Function URL
resource "aws_lambda_function_url" "example" {
function_name = aws_lambda_function.example.function_name
authorization_type = "AWS_IAM" # IAM 認証を有効化
}
# Lambda 実行ロール
resource "aws_iam_role" "lambda_exec" {
name = "lambda-exec-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}]
})
}
resource "aws_iam_role_policy_attachments_exclusive" "lambda_policy" {
role = aws_iam_role.lambda_exec.name
policy_arns = ["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"]
}
CloudFront Distribution の設定
次に CloudFront Distribution の作成 と OAC を設定を行います。
OAC の設定
resource "aws_cloudfront_origin_access_control" "example" {
name = "example-oac"
description = "OAC for Lambda Function URL origin"
origin_access_control_origin_type = "lambda"
signing_behavior = "always"
signing_protocol = "sigv4" # SigV4による署名
}
CloudFront Distribution の作成
Origin 設定で Function URL を指定します。
ここで注意が必要なのは、Function URL からプロトコル部分 (https://) と末尾の/を除いたドメイン名を指定することです。
resource "aws_cloudfront_distribution" "example" {
enabled = true
price_class = "PriceClass_All"
http_version = "http2and3"
origin {
domain_name = replace(replace(aws_lambda_function_url.example.function_url, "https://", ""), "/", "") # プロトコル部と末尾の/を削除
origin_id = aws_lambda_function_url.example.url_id
origin_access_control_id = aws_cloudfront_origin_access_control.example.id
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}
default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
cached_methods = ["GET", "HEAD"]
target_origin_id = aws_lambda_function_url.example.url_id
viewer_protocol_policy = "redirect-to-https"
cache_policy_id = data.aws_cloudfront_cache_policy.managed_cachingdisabled.id
origin_request_policy_id = data.aws_cloudfront_origin_request_policy.managed_allviewerexcepthost.id
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
data "aws_cloudfront_cache_policy" "managed_cachingdisabled" {
name = "Managed-CachingDisabled"
}
data "aws_cloudfront_origin_request_policy" "managed_allviewerexcepthost" {
name = "Managed-AllViewerExceptHostHeader"
}
Lambda のリソースポリシー設定
最後に Lambda に対してリソースポリシーの設定を行います。
CloudFront からの Lambda 呼び出しを許可するために、lambda:InvokeFunctionUrl と lambda:InvokeFunction の両方の権限が必要です。
resource "aws_lambda_permission" "cloudfront" {
statement_id = "AllowCloudFrontInvoke"
action = "lambda:InvokeFunctionUrl"
function_name = aws_lambda_function.example.function_name
function_url_auth_type = aws_lambda_function_url.example.authorization_type
principal = "cloudfront.amazonaws.com"
source_arn = aws_cloudfront_distribution.example.arn
}
resource "aws_lambda_permission" "cloudfront_invoke" {
statement_id = "AllowCloudFrontInvokeFunction"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.example.function_name
principal = "cloudfront.amazonaws.com"
source_arn = aws_cloudfront_distribution.example.arn
}
terraform apply 後、Lambda のアクセス権限 > リソースベースのポリシーステートメントでポリシーを表示すると以下のようになります。

{
"Version": "2012-10-17",
"Id": "default",
"Statement": [
{
"Sid": "AllowCloudFrontInvoke",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "lambda:InvokeFunctionUrl",
"Resource": "arn:aws:lambda:<region>:<account-id>:function:<function-name>",
"Condition": {
"StringEquals": {
"lambda:FunctionUrlAuthType": "AWS_IAM"
},
"ArnLike": {
"AWS:SourceArn": "arn:aws:cloudfront::<account-id>:distribution/<distribution-id>"
}
}
},
{
"Sid": "AllowCloudFrontInvokeFunction",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "lambda:InvokeFunction",
"Resource": "arn:aws:lambda:<region>:<account-id>:function:<function-name>",
"Condition": {
"ArnLike": {
"AWS:SourceArn": "arn:aws:cloudfront::<account-id>:distribution/<distribution-id>"
}
}
}
]
}
ハマったポイント
1. Lambda リソースポリシーの権限設定
InvokeFunction だけではだめ
初め、以下のように lambda:InvokeFunction だけを設定していました。
# ❌ これだけでは動かない
resource "aws_lambda_permission" "cloudfront" {
statement_id = "AllowCloudFrontInvoke"
action = "lambda:InvokeFunctionUrl"
function_name = aws_lambda_function.example.function_name
function_url_auth_type = aws_lambda_function_url.example.authorization_type
principal = "cloudfront.amazonaws.com"
source_arn = aws_cloudfront_distribution.example.arn
}
しかし、CloudFront 経由でアクセスするとエラーが発生します。
公式のドキュメントを参照すると、2025 年 10 月以降は lambda:InvokeFunctionUrlとlambda:InvokeFunctionの両方を設定する必要があると記載があります。
両方設定したところ、問題なくアクセスできるようになりました。
2. CloudFront Origin の設定
domain_name の指定方法
Function URL を Origin の domain_name に設定する際、最初は以下のように設定しましたが、アクセスできませんでした。
# ❌ これではダメ
origin {
domain_name = aws_lambda_function_url.example.function_url
origin_id = "lambda-function-url"
# ...
}
原因
aws_lambda_function_url.example.function_url は https:// を含む完全な URL を返します。
しかし、CloudFront の domain_name パラメータにはドメイン名のみを指定する必要があります。
解決策
replace 関数を使ってプロトコル部(https://)と末尾の/ を削除したものを domain_name に設定する必要があります。
# ✅ 正しい設定
origin {
domain_name = replace(replace(aws_lambda_function_url.example.function_url, "https://", ""), "/", "")
}
3. CloudFront ビヘイビアの設定
オリジンリクエストポリシー
オリジンリクエストポリシーに Managed Policy を設定する際、AllViewerは Terraform からは apply できますが、コンソールからは設定できないようになっています。

基本は Lambda Functions URLs 向けの推奨設定にするのが良さそうです。
- キャッシュポリシー:
CachingDisabled - オリジンリクエストポリシー:
AllViewerExceptHostHeader

4. その他の注意点
PUT メソッドと POST メソッド
PUT メソッドや POST メソッドを使用する際、リクエストボディのハッシュ値を SHA256 で計算したものをx-amz-content-sha256ヘッダーにセットする必要があります。
% curl -X POST \
-H 'x-amz-content-sha256: 0cf5950903541013d83c2f79171a7f4ef1868f6001fef37018a855bf2425cf21' \
-H 'Content-Type: application/json' \
-d '{"key": "value}' \
https://${CLOUDFRONT_DOMAIN}/example # 例: CLOUDFRONT_DOMAIN=xxxxxxxxx.cloudfront.net
動作確認
CloudFront URL 経由でのアクセス(成功)
# アクセステスト
% curl -w "%{http_code}" https://${CLOUDFRONT_DOMAIN}/health # 例: CLOUDFRONT_DOMAIN=xxxxxxxxx.cloudfront.net
# 出力例
200
Lambda Function URL への直接アクセス(失敗)
# 直接アクセスを試行
% curl https://<url-id>.lambda-url.<region>.on.aws
{"Message":"Forbidden"}
このようにして、Function URL への直接アクセスは拒否され、CloudFront 経由でのみアクセスできることが確認できました。
まとめ
CloudFront OAC を使って Lambda Function URLs へのアクセスを CloudFront に制限する方法を、Terraform を使った実装とともに解説しました。
今回の実装をベースに、以下のような拡張をするのも良いと思います!
- AWS WAF の追加:CloudFront に WAF を関連付けて、DDoS 対策や IP 制限を設定する
- カスタムドメインの使用:Route 53 を使った独自ドメイン設定
-
CloudFront Functions の活用:
x-amz-content-sha256にボディのハッシュを自動的にセット
Finatext グループでは、一緒に働いてくれる仲間を募集中です!
カジュアル面談も実施しておりますので、少しでも興味を持っていただけた方はお気軽にご応募ください!
Discussion