
CloudFormation で Lambda Web Functions を CloudFront OAC の背後に置いてみた
はじめに
2026年10月12日、Lambda Web Functions の GA と同時に、CloudFront のオリジンアクセスコントロール(OAC)が Lambda Web Functions に対応しました。
OAC を利用すると、特定の CloudFront ディストリビューションを経由しないアクセスを拒否できるため、Web Function を直接公開するリスクへの対策として有効です。
今回、Lambda Web Functions、CloudFront、OAC を CloudFormation による IaC で構築する機会があったので、その手順を紹介します。
構成と進め方
Web Function のコードは S3 に置いた .zip でしか渡せないため、テンプレートを 2 本に分け、間にコードの配置を挟んだ 3 手順にしました。
| 手順 | 内容 | 方法 |
|---|---|---|
| 1 | コード置き場の S3 バケットの作成 | CloudFormation |
| 2 | コードの .zip の配置 | CLI |
| 3 | Web Function、OAC、Distribution、リソースポリシーの作成 | CloudFormation |
手順1: コード置き場の S3 バケットを作る
Web Function のコードを置くバケットは、バージョニングを有効にします。Lambda はオブジェクトのバージョンでリビジョンのコードを固定するため、バージョニングが必須です。
Lambda がバケットから .zip を読めるように、バケットポリシーで lambda.amazonaws.com に s3:GetObject と s3:GetObjectVersion を許可します。
- Sid: AllowLambdaWebFunctionCodeAccess
Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action:
- s3:GetObject
- s3:GetObjectVersion
Resource: !Sub ${CodeBucket.Arn}/*
Condition:
ArnLike:
aws:SourceArn: !Sub arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:web-function/*
aws:SourceArn はワイルドカードにして、同じアカウント・リージョンのすべての Web Function からの読み取りを許可しています。特定の Web Function に限定したい場合は、web-function/<関数名>/revision/* のように関数名を指定してください。
step1-code-bucket.yaml(全文)
AWSTemplateFormatVersion: '2010-09-09'
Description: Step 1 - S3 bucket for Lambda Web Function code (versioning + bucket policy for Lambda)
Resources:
CodeBucket:
Type: AWS::S3::Bucket
Properties:
VersioningConfiguration:
Status: Enabled
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
CodeBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref CodeBucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AllowLambdaWebFunctionCodeAccess
Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action:
- s3:GetObject
- s3:GetObjectVersion
Resource: !Sub ${CodeBucket.Arn}/*
Condition:
ArnLike:
aws:SourceArn: !Sub arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:web-function/*
Outputs:
CodeBucketName:
Value: !Ref CodeBucket
テンプレートを step1-code-bucket.yaml として保存し、スタックを作成します。スタックが CREATE_COMPLETE になったら、出力の CodeBucketName からバケット名を取得します。この値は手順2と手順3で使います。
aws cloudformation create-stack --stack-name hello-web-code --template-body file://step1-code-bucket.yaml
aws cloudformation describe-stacks --stack-name hello-web-code --query 'Stacks[0].Outputs'
手順2: コードを S3 に置く
アプリは、JSON を返すだけの Hello World です。index.js と package.json を用意します。index.js で import 構文(ES モジュール)を使うため、package.json に "type": "module" を指定しています。
index.js と package.json
index.js:
// Minimal AWS Lambda Web Function HTTP server.
//
// You can replace this with any code or framework that starts an HTTP server.
import http from 'node:http';
// Lambda sends requests to port 3000. Set LOCAL_PORT for local development.
const port = Number(process.env.LOCAL_PORT ?? 3000);
const server = http.createServer((req, res) => {
res.writeHead(200, { 'content-type': 'application/json' });
res.end(JSON.stringify({ message: 'Hello from Lambda Web!', path: req.url }));
});
server.listen(port, () => {
console.log(`listening on port ${port}`);
});
package.json:
{
"type": "module",
"main": "index.js"
}
index.js が .zip のルートに置かれるように圧縮し、手順1のバケットへ app/hello-web.zip としてアップロードします。
zip -r hello-web.zip index.js package.json
aws s3 cp hello-web.zip s3://<CodeBucketName>/app/hello-web.zip
手順3: Web Function と CloudFront を作る
step3-web-function.yaml(全文)
AWSTemplateFormatVersion: '2010-09-09'
Description: Step 3 - Lambda Web Function (IamAuth) behind CloudFront with OAC
Parameters:
FunctionName:
Type: String
Default: hello-web
CodeBucket:
Type: String
Description: Bucket created in step 1
CodeKey:
Type: String
Default: app/hello-web.zip
Description: Key of the .zip uploaded in step 2
Resources:
ExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
WebFunction:
Type: AWS::Lambda::WebFunction
Properties:
FunctionName: !Ref FunctionName
Revision:
Type: AWS::Lambda::WebFunctionRevision
Properties:
FunctionName: !Ref WebFunction
BuildConfig:
CodeConfig:
S3Object:
Bucket: !Ref CodeBucket
Key: !Ref CodeKey
RuntimeConfig:
Runtime: nodejs24.x
ServiceConfig:
ExecutionRoleArn: !GetAtt ExecutionRole.Arn
TimeoutSeconds: 30
Endpoint:
Type: AWS::Lambda::WebFunctionEndpoint
Properties:
FunctionName: !Ref WebFunction
EndpointName: main
EndpointType: HomeRegion
AuthType: IamAuth
RevisionWeights:
- RevisionId: !GetAtt Revision.RevisionId
Weight: 100
Oac:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
Name: !Sub ${FunctionName}-oac
OriginAccessControlOriginType: lambda
SigningBehavior: always-amz-auth
SigningProtocol: sigv4
Distribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
Comment: !Ref FunctionName
Origins:
- Id: web-function
DomainName: !GetAtt Endpoint.DomainName
OriginAccessControlId: !GetAtt Oac.Id
CustomOriginConfig:
OriginProtocolPolicy: https-only
DefaultCacheBehavior:
TargetOriginId: web-function
ViewerProtocolPolicy: redirect-to-https
AllowedMethods: [GET, HEAD]
# Managed-CachingDisabled
CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
# Managed-AllViewerExceptHostHeader
OriginRequestPolicyId: b689b0a8-53d0-40ab-baf2-68738e2966ac
ResourcePolicy:
Type: AWS::Lambda::ResourcePolicy
Properties:
ResourceArn: !GetAtt WebFunction.FunctionArn
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AllowCloudFrontOACAccess
Effect: Allow
Principal:
Service: cloudfront.amazonaws.com
Action: lambda:InvokeWebFunctionEndpoint
Resource: !GetAtt Endpoint.EndpointArn
Condition:
StringEquals:
aws:SourceArn: !Sub arn:${AWS::Partition}:cloudfront::${AWS::AccountId}:distribution/${Distribution}
Outputs:
DistributionDomain:
Value: !GetAtt Distribution.DomainName
EndpointDomain:
Value: !GetAtt Endpoint.DomainName
テンプレートを step3-web-function.yaml として保存し、スタックを作成します。CodeBucket には手順1で取得したバケット名を渡します。CodeKey は、既定値が手順2で置いたキー(app/hello-web.zip)と同じなので省略できます。IAM ロールを作成するため、--capabilities に CAPABILITY_IAM を指定します。
aws cloudformation create-stack \
--stack-name hello-web \
--template-body file://step3-web-function.yaml \
--parameters ParameterKey=CodeBucket,ParameterValue=<CodeBucketName> \
--capabilities CAPABILITY_IAM
実行ロール
信頼ポリシーで lambda.amazonaws.com にロールの引き受け(sts:AssumeRole)を許可し、CloudWatch Logs へログを書けるように AWSLambdaBasicExecutionRole を付けます。
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Revision と Endpoint
S3Object の VersionId を省略すると、リビジョン作成時点の最新バージョンのオブジェクトに固定されます。
BuildConfig:
CodeConfig:
S3Object:
Bucket: !Ref CodeBucket
Key: !Ref CodeKey
RuntimeConfig:
Runtime: nodejs24.x
ServiceConfig:
ExecutionRoleArn: !GetAtt ExecutionRole.Arn
TimeoutSeconds: 30
OAC を使うには IamAuth が必要なため、Endpoint の AuthType は IamAuth にします。
EndpointName: main
EndpointType: HomeRegion
AuthType: IamAuth
RevisionWeights:
- RevisionId: !GetAtt Revision.RevisionId
Weight: 100
OAC と Distribution
Web Function 向けの OAC では、OriginAccessControlOriginType を lambda、SigningBehavior を always-amz-auth にします。
OriginAccessControlConfig:
OriginAccessControlOriginType: lambda
SigningBehavior: always-amz-auth
SigningProtocol: sigv4
オリジンリクエストポリシーには、Host ヘッダーをオリジンへ転送しないマネージドポリシーの AllViewerExceptHostHeader を使います。
Origins:
- Id: web-function
DomainName: !GetAtt Endpoint.DomainName
OriginAccessControlId: !GetAtt Oac.Id
CustomOriginConfig:
OriginProtocolPolicy: https-only
DefaultCacheBehavior:
# Managed-CachingDisabled
CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
# Managed-AllViewerExceptHostHeader
OriginRequestPolicyId: b689b0a8-53d0-40ab-baf2-68738e2966ac
リソースポリシー
AWS::Lambda::ResourcePolicy で、cloudfront.amazonaws.com に lambda:InvokeWebFunctionEndpoint を許可します。ResourceArn にはポリシーを付ける Web Function の ARN、Resource には呼び出しを許可するエンドポイントの ARN を指定します。
ResourceArn: !GetAtt WebFunction.FunctionArn
PolicyDocument:
Statement:
- Sid: AllowCloudFrontOACAccess
Effect: Allow
Principal:
Service: cloudfront.amazonaws.com
Action: lambda:InvokeWebFunctionEndpoint
Resource: !GetAtt Endpoint.EndpointArn
Condition:
StringEquals:
aws:SourceArn: !Sub arn:${AWS::Partition}:cloudfront::${AWS::AccountId}:distribution/${Distribution}
aws:SourceArn にこのスタックで作るディストリビューションの ARN を指定し、呼び出し元をそのディストリビューションに限定しています。
動作確認
スタックが CREATE_COMPLETE になったら、出力から CloudFront のドメイン(DistributionDomain)とエンドポイントのドメイン(EndpointDomain)を取得します。
aws cloudformation describe-stacks --stack-name hello-web --query 'Stacks[0].Outputs'
CloudFront の標準ドメインへ GET /hello?x=1 を送ると、200 と JSON の本文が返りました。
$ curl -sS -i 'https://dxxxxxxxxxxxxx.cloudfront.net/hello?x=1'
HTTP/1.1 200 OK
Content-Type: application/json
X-Cache: Miss from cloudfront
{"message":"Hello from Lambda Web!","path":"/hello?x=1"}
エンドポイントのドメインへ直接同じリクエストを送ると、403 が返りました。
$ curl -sS -i 'https://xxxxxxxxxxxxxxxx.xxx.lambda-web.ap-northeast-1.on.aws/hello?x=1'
HTTP/1.1 403 Forbidden
まとめ
Lambda Web Functions から CloudFront、OAC の設定まで、CloudFormation で構築できることを確認しました。
直接公開に伴うリスクを避けるため、Lambda Web Functions の公開には OAC の利用をおすすめします。
今回の検証環境では省略しましたが、Lambda Web Functions を継続的に公開する場合は、CloudFront に適切なキャッシュポリシーとアクセスログを設定し、必要に応じて WAF による保護を有効にすることもご検討ください。







