
Lambda Web Functions を CloudFront OAC で保護すると、POST / PUT の x-amz-content-sha256 が不要になる
はじめに
AWS Lambda に新しく Web Functions という関数タイプが追加されました。
Node.js の Web フレームワークで書いた HTTP サーバーを、そのまま Lambda で動かせる関数タイプです。HTTP サーバーを起動するフレームワークなら何でもよく、ドキュメントには例として Express、Next.js、Fastify、Koa が挙がっています。本記事では Hono(@hono/node-server)を使います。
触った第一印象は「Lambda Web Adapter(LWA)っぽい」でした。実際、公式ドキュメントにも LWA は不要と書かれています。
You don't need the AWS Lambda Web Adapter or another event-to-HTTP adapter. Your application handles HTTP requests directly using the framework's standard routing and middleware.
個人的に一番うれしかったのは、CloudFront の OAC(Origin Access Control)と組み合わせたときの、POST や PUT のような body を送るリクエストの扱いです。Lambda Function URL + OAC では body のハッシュをクライアント側で計算する必要があり、Lambda@Edge で補うのが定番でした。Web Functions ではこれが不要になっています。
本記事では、CloudFront + OAC の背後に Web Function を置く構成を CDK で作り、POST・Authorization ヘッダー・ストリーミングの挙動を確認します。
Lambda Web Functions とは
Lambda には現在、次の 3 つの種類があります。従来の Lambda 関数は Event Functions と呼ばれるようになりました。
| Event Functions | Web Functions | MicroVMs | |
|---|---|---|---|
| 向いている用途 | イベント駆動、リクエスト・レスポンス型の処理 | HTTP を直接受ける Web アプリ / API | ユーザーや AI が生成した信頼できないコードの実行環境 |
| プログラミングモデル | ハンドラー関数 | ポートで待ち受ける HTTP サーバー | 任意のアプリケーション |
| 実行時間 | 1 回の呼び出しで最大 15 分 | 1 リクエストで最大 15 分(デフォルト 30 秒) | 1 セッションで最大 8 時間 |
| ランタイム | マネージドランタイム + カスタムランタイム | マネージドの Node.js のみ | 自前の MicroVM イメージ |
| 同時実行 | 実行環境ごとに 1 リクエスト(デフォルト) | 実行環境ごとに複数リクエスト | MicroVM ごとに複数接続 |
| 課金 | リクエスト数 + GB 秒 | リクエスト数 + CPU を実際に使った時間 + 実行環境が存在する間のメモリ | 稼働中の秒単位 + 停止中のスナップショット保存 |
LWA との違い
LWA を使った構成と比べると、主に次の点が違います。
| LWA + Event Function | Web Functions | |
|---|---|---|
| 並列 | 1 つの実行環境で 1 リクエストずつ | 1 つの実行環境で複数リクエスト(maxConcurrencyPerEnvironment) |
| 入口 | API Gateway や Function URL を別に用意する | HTTPS エンドポイントが最初から付く |
| マルチリージョン | 自前で組む | endpoint の設定でレイテンシーベースのルーティングができる |
| ランタイム | 何でも動く(コンテナイメージも可) | Node.js 24(arm64)、.zip のみ |
| フレームワーク | HTTP を話すものなら言語を問わない(Express.js、Next.js、Flask、Spring Boot、ASP.NET、Laravel、Axum など) | Node.js の Web フレームワーク(Express、Hono、Next.js、Fastify、Koa、React Router など) |
| 待ち受けポート | アプリが決めたポートを AWS_LWA_PORT(デフォルト 8080)で LWA に伝える |
Lambda が AWS_LAMBDA_HTTP_ENDPOINT(host:port 形式)で指定し、アプリがそれに合わせる |
Next.js の standalone モードのように、待ち受けるアドレスを独自の環境変数から読むフレームワークもあります。その場合は、AWS_LAMBDA_HTTP_ENDPOINT の値をその環境変数に設定してからサーバーを起動する index.js を入口に置きます[1]。
制約
検証前に把握しておいた制約です。
- ランタイムは
nodejs24.xのみで、コードは S3 に置いた .zip でしか渡せません[2] - 実行環境のメモリは 2 GB 固定です[3]
- endpoint はカスタムドメインと WAF の直接関連付けに対応していません。必要なら CloudFront を前段に置きます[4]
- 2026-10 時点の対応リージョンは、バージニア北部、オハイオ、オレゴン、東京、アイルランドです
CloudFront OAC と body を送るリクエスト
Function URL と Web Functions で、OAC の署名がどう変わるかを先に図で示します。

Function URL + OAC の場合
Function URL を OAC で守る構成では、POST / PUT のときにクライアントが body の SHA256 を計算して x-amz-content-sha256 ヘッダーに入れる必要があります。CloudFront のドキュメントにも Important として書かれています。
If you use
PUTorPOSTmethods with your Lambda function URL, your users must compute the SHA256 of the body and include the payload hash value of the request body in thex-amz-content-sha256header when sending the request to CloudFront. Lambda doesn't support unsigned payloads.
ブラウザにハッシュを計算させるのは現実的ではないため、Lambda@Edge の origin request で body を読んでハッシュを付けるのが定番の回避策でした。
ただし origin request で Lambda@Edge が受け取れる body は 1 MB で切り詰められるため[5]、大きな body ではハッシュが合わなくなります。
Web Functions + OAC の場合
Web Functions では、PUT / POST / PATCH のとき CloudFront が UNSIGNED-PAYLOAD で署名します。クライアントが x-amz-content-sha256 を付ける必要はありません。
For
PUT,POST, andPATCHrequests, CloudFront signs the origin request with an unsigned payload (UNSIGNED-PAYLOAD). The request body is not covered by the CloudFront signature. (中略) You do not need to include anx-amz-content-sha256header in requests to CloudFront. If a viewer sends one, CloudFront does not use it when signing the origin request.
body は署名の対象外になるので、改ざんからの保護は CloudFront とエンドポイント間の HTTPS に任せることになります。
もう 1 つの変化が Authorization ヘッダーです。
従来の Function URL + OAC では、CloudFront が自分の SigV4 署名を Authorization ヘッダーに入れます。signing behavior を no-override にすると閲覧者の Authorization がそのまま渡りますが、今度は CloudFront の署名が付かないため、IAM 認証の Function URL を通りません[6]。つまり Authorization: Bearer と OAC は両立できませんでした。以前 CloudFront OAC と Function URL で認証付きのサイトを作ったときも、Bearer ヘッダーで API に送ると OAC の署名と競合するため、業務用の JWT は別のヘッダーに分けていました。
Web Function オリジンの OAC は signing behavior が always-amz-auth(コンソールでは「Always sign, preserve viewer authorization」)のみ対応しています。CloudFront は専用のヘッダーに署名を入れるため、閲覧者の Authorization ヘッダーはそのまま関数まで届きます。JWT を別のヘッダーに逃がさなくても、Authorization: Bearer のままアプリ側で検証できます。
一方で、Web Function を OAC 付きでオリジンにしたキャッシュビヘイビアには、Lambda@Edge の origin request / origin response トリガーを設定できません。
Lambda@Edge origin request and origin response triggers are not supported on cache behaviors that use a Lambda Web Function origin with OAC.
Function URL 構成で x-amz-content-sha256 を付けるために置いていた Lambda@Edge は、Web Functions では不要なので外せば済みます。困るのは、origin request / origin response の Lambda@Edge でパスの書き換えやレスポンスヘッダーの付与など、ほかの処理もしている場合です。Web Functions に移すときは、その処理を viewer request / viewer response のトリガーや CloudFront Functions、またはアプリ側に移す必要があります。
CDK の対応状況
aws-cdk-lib 2.273.0 で確認した状況です。
- Web Functions は L1 のみです。
CfnWebFunction、CfnWebFunctionRevision、CfnWebFunctionEndpoint、CfnNetworkConnector、CfnResourcePolicyがあります - CloudFront L2 の
SigningBehaviorはalways/never/no-overrideだけで、always-amz-authがありません。OAC はCfnOriginAccessControlで作ります。作った OAC はHttpOriginのoriginAccessControlIdで渡せます - Revision の
ServiceConfigにegressNetworkConnectorArnが無く、CloudFormation からは VPC 接続を設定できないようです cdk synthすると、CDK 組み込みのテンプレート検証がUnknown resource type 'AWS::Lambda::WebFunction'という警告を出します。警告なので synth 自体は通ります
構成

cf-lambda-web-functions/
├── app/src/index.ts # Hono の検証用アプリ
├── bin/app.ts # ap-northeast-1 に 1 スタック
└── lib/web-function-stack.ts # スタック本体
lib/web-function-stack.ts の全体
Web Functions のリソースを定義している行をハイライトしています。
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';
import * as cdk from 'aws-cdk-lib';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
import * as origins from 'aws-cdk-lib/aws-cloudfront-origins';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment';
import type { Construct } from 'constructs';
import { buildSync } from 'esbuild';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const appDir = path.resolve(__dirname, '../app');
export class WebFunctionStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const functionName = 'cf-lambda-web-functions';
// Web Functions のコードはバージョニング有効な S3 バケットの zip からしか読めず、
// バケットポリシーで lambda.amazonaws.com に読み取りを許可する必要がある。
// CDK bootstrap のバケットにはポリシーを足せないため専用バケットを用意する
const codeBucket = new s3.Bucket(this, 'CodeBucket', {
versioned: true,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
enforceSSL: true,
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
});
codeBucket.addToResourcePolicy(
new iam.PolicyStatement({
principals: [new iam.ServicePrincipal('lambda.amazonaws.com')],
actions: ['s3:GetObject', 's3:GetObjectVersion'],
resources: [codeBucket.arnForObjects('*')],
conditions: {
ArnLike: {
'aws:SourceArn': this.formatArn({
service: 'lambda',
resource: 'web-function',
resourceName: `${functionName}/revision/*`,
}),
},
},
}),
);
// esbuild で index.js 1ファイルにまとめ、zip のまま専用バケットへ置く。
// 稼働中の revision が参照するオブジェクトを消すと revision が Failed になるため prune しない
const code = new s3deploy.BucketDeployment(this, 'CodeDeployment', {
sources: [
s3deploy.Source.asset(appDir, {
assetHashType: cdk.AssetHashType.OUTPUT,
bundling: {
image: cdk.DockerImage.fromRegistry('public.ecr.aws/docker/library/node:24'),
local: {
tryBundle(outputDir) {
buildSync({
entryPoints: [path.join(appDir, 'src/index.ts')],
outfile: path.join(outputDir, 'index.js'),
bundle: true,
platform: 'node',
target: 'node24',
format: 'cjs',
minify: true,
});
return true;
},
},
},
}),
],
destinationBucket: codeBucket,
extract: false,
prune: false,
});
const executionRole = new iam.Role(this, 'ExecutionRole', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
],
});
+ // Web Functions は L2 が無いため L1 で組む
+ const webFunction = new lambda.CfnWebFunction(this, 'WebFunction', {
+ functionName,
+ });
+
+ // revision はイミュータブル。コードや設定が変わると CloudFormation が置換し、
+ // 新しい revision を作成 → endpoint の向き先を更新 → 古い revision を削除の順で進む
+ const revision = new lambda.CfnWebFunctionRevision(this, 'Revision', {
+ functionName: webFunction.attrFunctionArn,
+ buildConfig: {
+ codeConfig: {
+ s3Object: {
+ bucket: codeBucket.bucketName,
+ key: cdk.Fn.select(0, code.objectKeys),
+ versionId: cdk.Fn.select(0, code.objectVersionIds),
+ },
+ },
+ runtimeConfig: { runtime: 'nodejs24.x' },
+ },
+ serviceConfig: {
+ executionRoleArn: executionRole.roleArn,
+ timeoutSeconds: 30,
+ },
+ });
+ // revision 作成時に Lambda がバケットを読めるよう、バケットポリシーの反映を先に済ませる
+ revision.node.addDependency(codeBucket.policy!);
+
+ const endpoint = new lambda.CfnWebFunctionEndpoint(this, 'Endpoint', {
+ functionName: webFunction.attrFunctionArn,
+ endpointName: 'prod',
+ endpointType: 'HomeRegion',
+ // CloudFront OAC から署名付きで呼ばせ、直接アクセスは 403 にする
+ authType: 'IamAuth',
+ revisionWeights: [{ revisionId: revision.attrRevisionId, weight: 100 }],
+ });
// Web Function オリジンは always-amz-auth のみ対応。L2 の SigningBehavior には無いため L1 で作る
const oac = new cloudfront.CfnOriginAccessControl(this, 'OriginAccessControl', {
originAccessControlConfig: {
name: `${this.stackName}-web-function`,
originAccessControlOriginType: 'lambda',
signingBehavior: 'always-amz-auth',
signingProtocol: 'sigv4',
},
});
// GET/HEAD の Authorization はキャッシュキーに含めないと CloudFront に落とされる。
// TTL がすべて 0 だとキャッシュキーにヘッダーを含められないため maxTtl だけ 1 秒にする
const cachePolicy = new cloudfront.CachePolicy(this, 'CachePolicy', {
minTtl: cdk.Duration.seconds(0),
defaultTtl: cdk.Duration.seconds(0),
maxTtl: cdk.Duration.seconds(1),
headerBehavior: cloudfront.CacheHeaderBehavior.allowList('Authorization'),
queryStringBehavior: cloudfront.CacheQueryStringBehavior.all(),
cookieBehavior: cloudfront.CacheCookieBehavior.all(),
});
const distribution = new cloudfront.Distribution(this, 'Distribution', {
defaultBehavior: {
origin: new origins.HttpOrigin(endpoint.attrDomainName, {
protocolPolicy: cloudfront.OriginProtocolPolicy.HTTPS_ONLY,
originAccessControlId: oac.attrId,
}),
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
cachePolicy,
// Host を転送すると Web Function が自分宛てと判定できず 404 を返す
originRequestPolicy: cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,
},
});
+ // Web Functions は AddPermission 非対応。リソースポリシーを丸ごと設定する
+ new lambda.CfnResourcePolicy(this, 'ResourcePolicy', {
+ resourceArn: webFunction.attrFunctionArn,
+ policyDocument: new iam.PolicyDocument({
+ statements: [
+ new iam.PolicyStatement({
+ principals: [new iam.ServicePrincipal('cloudfront.amazonaws.com')],
+ actions: ['lambda:InvokeWebFunctionEndpoint'],
+ resources: [endpoint.attrEndpointArn],
+ conditions: {
+ StringEquals: { 'aws:SourceArn': distribution.distributionArn },
+ },
+ }),
+ ],
+ }),
+ });
new cdk.CfnOutput(this, 'DistributionUrl', {
value: `https://${distribution.distributionDomainName}`,
});
new cdk.CfnOutput(this, 'EndpointUrl', {
value: `https://${endpoint.attrDomainName}`,
});
}
}
実装
アプリ
Hono で検証用のルートを 4 つ用意しました。
import { createHash } from 'node:crypto';
import { serve } from '@hono/node-server';
import { Hono } from 'hono';
import { streamSSE } from 'hono/streaming';
const app = new Hono();
app.get('/', (c) =>
c.json({
message: 'Hello from Lambda Web Functions',
region: process.env.AWS_REGION ?? null,
requestId: c.req.header('lambda-web-request-id') ?? null,
}),
);
// CloudFront (OAC) 経由の POST で body がそのまま届くかを確認する。
// クライアントは x-amz-content-sha256 を付けずに送る想定
app.post('/echo', async (c) => {
const body = Buffer.from(await c.req.arrayBuffer());
return c.json({
bytes: body.length,
sha256: createHash('sha256').update(body).digest('hex'),
contentType: c.req.header('content-type') ?? null,
});
});
// viewer の Authorization ヘッダーが関数まで届くかを確認する
app.get('/headers', (c) => c.json(c.req.header()));
// レスポンスストリーミングを確認する
app.get('/stream', (c) =>
streamSSE(c, async (stream) => {
for (let i = 0; i < 5; i++) {
await stream.writeSSE({ data: JSON.stringify({ i, time: new Date().toISOString() }) });
await stream.sleep(1000);
}
}),
);
// Lambda が AWS_LAMBDA_HTTP_ENDPOINT (host:port) で待ち受け先を渡す。未設定はローカル実行時のみ
const [hostname, port] = (process.env.AWS_LAMBDA_HTTP_ENDPOINT ?? 'localhost:3000').split(':');
serve({ fetch: app.fetch, hostname, port: Number(port) }, (info) => {
console.log(`listening on ${info.address}:${info.port}`);
});
コードの配置
Web Functions のコードは、バージョニングを有効にした S3 バケットの .zip からしか読めません。さらにバケットポリシーで lambda.amazonaws.com に s3:GetObject / s3:GetObjectVersion を許可する必要があります[7]。CDK bootstrap のバケットにはポリシーを足せないので、専用のバケットを作りました。
const codeBucket = new s3.Bucket(this, 'CodeBucket', {
versioned: true,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
enforceSSL: true,
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
});
codeBucket.addToResourcePolicy(
new iam.PolicyStatement({
principals: [new iam.ServicePrincipal('lambda.amazonaws.com')],
actions: ['s3:GetObject', 's3:GetObjectVersion'],
resources: [codeBucket.arnForObjects('*')],
conditions: {
ArnLike: {
'aws:SourceArn': this.formatArn({
service: 'lambda',
resource: 'web-function',
resourceName: `${functionName}/revision/*`,
}),
},
},
}),
);
アプリは esbuild で index.js 1 ファイルにまとめ、BucketDeployment の extract: false で zip のまま置きます。objectKeys と objectVersionIds から、Revision が参照するキーとバージョン ID を取り出せます。
使用中の Revision が参照するオブジェクトを消すと、その Revision は Failed になり、オブジェクトを戻しても復旧しません[8]。そのため prune: false にして古い zip を残しています。
const code = new s3deploy.BucketDeployment(this, 'CodeDeployment', {
sources: [
s3deploy.Source.asset(appDir, {
assetHashType: cdk.AssetHashType.OUTPUT,
bundling: {
image: cdk.DockerImage.fromRegistry('public.ecr.aws/docker/library/node:24'),
local: {
tryBundle(outputDir) {
buildSync({
entryPoints: [path.join(appDir, 'src/index.ts')],
outfile: path.join(outputDir, 'index.js'),
bundle: true,
platform: 'node',
target: 'node24',
format: 'cjs',
minify: true,
});
return true;
},
},
},
}),
],
destinationBucket: codeBucket,
extract: false,
prune: false,
});
Web Function / Revision / Endpoint
Web Function はリソースが 3 つに分かれています。
| リソース | 役割 |
|---|---|
AWS::Lambda::WebFunction |
関数そのもの。名前とタグだけを持つ |
AWS::Lambda::WebFunctionRevision |
コードと設定のスナップショット。イミュータブルで、変更すると置換される |
AWS::Lambda::WebFunctionEndpoint |
HTTPS エンドポイント。RevisionWeights で最大 2 つの revision に振り分ける |
const webFunction = new lambda.CfnWebFunction(this, 'WebFunction', {
functionName,
});
const revision = new lambda.CfnWebFunctionRevision(this, 'Revision', {
functionName: webFunction.attrFunctionArn,
buildConfig: {
codeConfig: {
s3Object: {
bucket: codeBucket.bucketName,
key: cdk.Fn.select(0, code.objectKeys),
versionId: cdk.Fn.select(0, code.objectVersionIds),
},
},
runtimeConfig: { runtime: 'nodejs24.x' },
},
serviceConfig: {
executionRoleArn: executionRole.roleArn,
timeoutSeconds: 30,
},
});
// revision 作成時に Lambda がバケットを読めるよう、バケットポリシーの反映を先に済ませる
revision.node.addDependency(codeBucket.policy!);
const endpoint = new lambda.CfnWebFunctionEndpoint(this, 'Endpoint', {
functionName: webFunction.attrFunctionArn,
endpointName: 'prod',
endpointType: 'HomeRegion',
// CloudFront OAC から署名付きで呼ばせ、直接アクセスは 403 にする
authType: 'IamAuth',
revisionWeights: [{ revisionId: revision.attrRevisionId, weight: 100 }],
});
実行ロールの信頼ポリシーは Event Functions と同じ lambda.amazonaws.com で、ログ用に AWSLambdaBasicExecutionRole を付けています。ログは /aws/lambda/web/<関数名> に出力されます[9]。
CloudFront
OAC は L1 で作ります。
const oac = new cloudfront.CfnOriginAccessControl(this, 'OriginAccessControl', {
originAccessControlConfig: {
name: `${this.stackName}-web-function`,
originAccessControlOriginType: 'lambda',
signingBehavior: 'always-amz-auth',
signingProtocol: 'sigv4',
},
});
ディストリビューションの設定では、次の 2 点に注意が必要です[10]。
- Host ヘッダーを転送すると endpoint が 404 を返すため、オリジンリクエストポリシーは
AllViewerExceptHostHeaderにします - GET / HEAD の
Authorizationヘッダーは、キャッシュポリシーに含めないと CloudFront が削除します
TTL をすべて 0 にするとキャッシュキーにヘッダーを含められないため、maxTtl だけ 1 秒にしたキャッシュポリシーを作っています。
const cachePolicy = new cloudfront.CachePolicy(this, 'CachePolicy', {
minTtl: cdk.Duration.seconds(0),
defaultTtl: cdk.Duration.seconds(0),
maxTtl: cdk.Duration.seconds(1),
headerBehavior: cloudfront.CacheHeaderBehavior.allowList('Authorization'),
queryStringBehavior: cloudfront.CacheQueryStringBehavior.all(),
cookieBehavior: cloudfront.CacheCookieBehavior.all(),
});
const distribution = new cloudfront.Distribution(this, 'Distribution', {
defaultBehavior: {
origin: new origins.HttpOrigin(endpoint.attrDomainName, {
protocolPolicy: cloudfront.OriginProtocolPolicy.HTTPS_ONLY,
originAccessControlId: oac.attrId,
}),
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
cachePolicy,
originRequestPolicy: cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER,
},
});
Web Functions は AddPermission に対応していないため、AWS::Lambda::ResourcePolicy でリソースポリシーを丸ごと設定します[11]。
new lambda.CfnResourcePolicy(this, 'ResourcePolicy', {
resourceArn: webFunction.attrFunctionArn,
policyDocument: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
principals: [new iam.ServicePrincipal('cloudfront.amazonaws.com')],
actions: ['lambda:InvokeWebFunctionEndpoint'],
resources: [endpoint.attrEndpointArn],
conditions: {
StringEquals: { 'aws:SourceArn': distribution.distributionArn },
},
}),
],
}),
});
検証
bun run deploy
[Warning] Template validation found issues in your templates (reported as warnings).
Set feature flag "@aws-cdk/core:validateAgainstDefaultRules" to true to turn these into errors.
lib/web-function-stack.ts:87:25
WARNING Unknown resource type 'AWS::Lambda::WebFunction' (CloudFormation Validate)
CfLambdaWebFunctions/WebFunction (WebFunction) aws-cdk-lib.aws_lambda.CfnWebFunction
Acknowledge with 'CloudFormation-Validate::F3006'
...
CfLambdaWebFunctions: deploying... [1/1]
CfLambdaWebFunctions | 2/20 | 11:00:04 AM | CREATE_COMPLETE | AWS::CloudFront::OriginAccessControl | OriginAccessControl
CfLambdaWebFunctions | 3/20 | 11:00:04 AM | CREATE_COMPLETE | AWS::CloudFront::CachePolicy | CachePolicy (CachePolicy26D8A535)
CfLambdaWebFunctions | 4/20 | 11:00:08 AM | CREATE_COMPLETE | AWS::Lambda::WebFunction | WebFunction
...
CfLambdaWebFunctions | 15/20 | 11:00:57 AM | CREATE_COMPLETE | Custom::CDKBucketDeployment | CodeDeployment/CustomResource/Default (CodeDeploymentCustomResource443F2B7D)
CfLambdaWebFunctions | 15/20 | 11:01:08 AM | CREATE_IN_PROGRESS | AWS::Lambda::WebFunctionRevision | Revision
CfLambdaWebFunctions | 16/20 | 11:01:13 AM | CREATE_COMPLETE | AWS::Lambda::WebFunctionRevision | Revision
CfLambdaWebFunctions | 16/20 | 11:01:14 AM | CREATE_IN_PROGRESS | AWS::Lambda::WebFunctionEndpoint | Endpoint
CfLambdaWebFunctions | 17/20 | 11:01:19 AM | CREATE_COMPLETE | AWS::Lambda::WebFunctionEndpoint | Endpoint
CfLambdaWebFunctions | 17/20 | 11:01:20 AM | CREATE_IN_PROGRESS | AWS::CloudFront::Distribution | Distribution (Distribution830FAC52)
CfLambdaWebFunctions | 17/20 | 11:01:25 AM | CREATE_IN_PROGRESS | AWS::Lambda::ResourcePolicy | ResourcePolicy
CfLambdaWebFunctions | 18/20 | 11:01:27 AM | CREATE_COMPLETE | AWS::Lambda::ResourcePolicy | ResourcePolicy
CfLambdaWebFunctions | 19/20 | 11:04:27 AM | CREATE_COMPLETE | AWS::CloudFront::Distribution | Distribution (Distribution830FAC52)
CfLambdaWebFunctions | 20/20 | 11:04:30 AM | CREATE_COMPLETE | AWS::CloudFormation::Stack | CfLambdaWebFunctions
✅ CfLambdaWebFunctions
✨ Deployment time: 271.46s
Outputs:
CfLambdaWebFunctions.DistributionUrl = https://dxxxxxxxxxxxxx.cloudfront.net
CfLambdaWebFunctions.EndpointUrl = https://xxxxxxxxxxxxxxxx.xxx.lambda-web.ap-northeast-1.on.aws
DISTRIBUTION_URL=https://dxxxxxxxxxxxxx.cloudfront.net
ENDPOINT_URL=https://xxxxxxxxxxxxxxxx.xxx.lambda-web.ap-northeast-1.on.aws
x-amz-content-sha256 なしで POST が通るか
head -c 2000000 /dev/urandom > /tmp/body.bin
shasum -a 256 /tmp/body.bin
curl -s -X POST --data-binary @/tmp/body.bin -H 'content-type: application/octet-stream' $DISTRIBUTION_URL/echo
1e97822a9d671d74e5b0a0190e20e2ce4b4e7ab0811fb76b955695a55d7b8a6f /tmp/body.bin
{"bytes":2000000,"sha256":"1e97822a9d671d74e5b0a0190e20e2ce4b4e7ab0811fb76b955695a55d7b8a6f","contentType":"application/octet-stream"}
Lambda@Edge の 1 MB 制限を超える 2 MB の body でも、bytes と sha256 がローカルの値と一致しました。
Authorization ヘッダーが関数まで届くか
curl -s -H 'Authorization: Bearer test' $DISTRIBUTION_URL/headers
{"accept":"*/*","authorization":"Bearer test","cloudfront-forwarded-proto":"https","cloudfront-is-android-viewer":"false","cloudfront-is-desktop-viewer":"true","cloudfront-is-ios-viewer":"false","cloudfront-is-mobile-viewer":"false","cloudfront-is-smarttv-viewer":"false","cloudfront-is-tablet-viewer":"false","cloudfront-viewer-address":"104.28.174.0:36845","cloudfront-viewer-asn":"13335","cloudfront-viewer-city":"Saitama","cloudfront-viewer-country":"JP","cloudfront-viewer-country-name":"Japan","cloudfront-viewer-country-region":"11","cloudfront-viewer-country-region-name":"Saitama","cloudfront-viewer-http-version":"2.0","cloudfront-viewer-latitude":"35.91040","cloudfront-viewer-longitude":"139.66060","cloudfront-viewer-postal-code":"330-0800","cloudfront-viewer-time-zone":"Asia/Tokyo","cloudfront-viewer-tls":"TLSv1.3:TLS_AES_128_GCM_SHA256:fullHandshake","connection":"close","host":"xxxxxxxxxxxxxxxx.xxx.lambda-web.ap-northeast-1.on.aws","lambda-web-endpoint-name":"prod","lambda-web-request-id":"PKW5RT3PAZYSCRUKNEYZSTLQIM","te":"trailers","user-agent":"curl/8.7.1","via":"2.0 9c8b15421bca36e4a3e74f62bbfbe1f8.cloudfront.net (CloudFront)","x-amz-cf-id":"TXoEgWLi1h8n5WLzfv4y7CHVXdTNs3gy22FDjWLL9XqEOjTSlKW0Jw==","x-forwarded-for":"104.28.174.0, 3.172.52.229","x-forwarded-port":"443","x-forwarded-proto":"https"}
authorization は Bearer test のまま届きました。CloudFront が付けた署名のヘッダーは関数には渡っておらず、x-amz- で始まるのは x-amz-cf-id だけでした。
レスポンスストリーミング
curl -sN $DISTRIBUTION_URL/stream
data: {"i":0,"time":"2026-10-13T02:26:56.729Z"}
data: {"i":1,"time":"2026-10-13T02:26:57.730Z"}
data: {"i":2,"time":"2026-10-13T02:26:58.729Z"}
data: {"i":3,"time":"2026-10-13T02:26:59.731Z"}
data: {"i":4,"time":"2026-10-13T02:27:00.732Z"}
data が 1 秒ごとに 1 件ずつ届きました。
CloudFront を通さずに Web Function を呼ぶと拒否されるか
curl -i $ENDPOINT_URL/
HTTP/1.1 403 Forbidden
content-type: text/html
connection: keep-alive
lambda-web-request-id: ERJTBYIYD2ZI33UPPXCYBK2XR4
content-length: 230
date: Tue, 13 Oct 2026 02:27:03 GMT
<!DOCTYPE html>
<html>
<head>
<title>HTTP Error 403</title>
</head>
<body>
<h1>403 Forbidden</h1>
<p>You are not authorized to access this endpoint.</p>
<p>Request ID: ERJTBYIYD2ZI33UPPXCYBK2XR4</p>
</body>
</html>
CloudFront を通さず、署名なしで Web Function の URL(ENDPOINT_URL)を直接呼ぶと 403 になりました。
料金
バージニア北部(Arm)の価格です。東京の価格は料金ページに載っていませんでした。
| 項目 | 価格 |
|---|---|
| リクエスト | $0.30 / 100 万リクエスト |
| CPU を実際に使った時間 | $0.0000276944 / vCPU 秒 |
| メモリ | $0.0000036667 / GB 秒 |
DB や外部 API を待っている間はほとんど CPU を使わないので、I/O 待ちが多い API では Event Functions より安くなる可能性があります。ただしメモリは実行環境が存在する間ずっと課金され、2 GB 固定です。アクセスがなくなってから実行環境が消えるまでの時間はドキュメントで見つけられなかったので、アクセスの少ないサイトでいくらになるかは実際に動かして確認したいところです。
さいご
以下のことがわかりました。
- CloudFront + OAC の背後に置くと、PUT / POST / PATCH で
x-amz-content-sha256を計算する必要がなく、Lambda@Edge が不要になる - 閲覧者の
Authorizationヘッダーを関数まで届けられる
Lambda Web Functions は LWA をプラットフォームに取り込んだような関数タイプで、1 つの実行環境で複数リクエストを処理でき、CPU を使った時間で課金されます。フレームワークは HTTP サーバーを起動するものなら何でもよく、縛りになっているのはランタイムのほうです。出たばかりなので、今のところランタイムは Node.js だけです。LWA ではすでに、特定ポートで HTTP を喋るサーバーなら言語を問わず動かせているので、Web Functions もそのうちほかの言語に対応されるといいなと思います!
https://docs.aws.amazon.com/lambda/latest/dg/web-functions-runtimes.html#web-functions-runtimes-frameworks ↩︎
https://docs.aws.amazon.com/lambda/latest/dg/web-functions.html / https://docs.aws.amazon.com/lambda/latest/dg/web-functions-code-s3.html ↩︎
https://docs.aws.amazon.com/lambda/latest/dg/web-functions-runtimes.html#web-functions-runtimes-memory ↩︎
https://docs.aws.amazon.com/lambda/latest/dg/web-functions-networking.html#web-functions-networking-front-door ↩︎
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-at-edge-function-restrictions.html#lambda-at-edge-restrictions-request-body ↩︎
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-lambda.html#oac-advanced-settings-lambda ↩︎
https://docs.aws.amazon.com/lambda/latest/dg/web-functions-code-s3.html#web-functions-code-s3-bucket-policy ↩︎
https://docs.aws.amazon.com/lambda/latest/dg/web-functions-code-s3.html#web-functions-code-s3-lifecycle ↩︎
https://docs.aws.amazon.com/lambda/latest/dg/web-functions-permissions.html#web-functions-permissions-execution-role-create ↩︎
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-lambda-web.html#oac-prerequisites-lambda-web ↩︎
https://docs.aws.amazon.com/lambda/latest/dg/web-functions-permissions.html#web-functions-permissions-resource-policy ↩︎







