
Cognito認証 + CloudFront VPCオリジン経由でAgentCore RuntimeのWebSocket配信環境を構築してみた
はじめに
Amazon Bedrock AgentCore Runtimeのインタラクティブシェルに、Cognito認証とCloudFront署名付きCookieによる認可を導入しました。ウェブブラウザーから安全にアクセスできる構成をCDKで構築しました。
先行記事3本でAgentCore Runtimeのシェル機能・WebSocketプロトコル・Kiro CLI連携を段階的に検証してきました。本記事はそれらに認証認可を組み込み、ネットワーク・配信をAWSマネージドサービスで一通り揃えた構成を示します。通信経路は以下のとおりです。
ブラウザー → CloudFront → VPC Origin → Internal ALB → ECS Fargate(WebShell中継) → NAT GW → AgentCore Runtime Shell API
構成の特徴は以下のとおりです。
- アプリケーション本体の外部公開エンドポイントをCloudFrontに集約
- Cognito認証 → CloudFront署名付きCookieでWebSocketを含むアプリケーション向けリクエストを保護(認証用パスを除く)
- CloudFront VPC OriginのWebSocketサポート(2026年5月GA)により、ブラウザーからのWSS接続をCloudFrontで受けてInternal ALBへ中継
構成図

アーキテクチャのポイント解説
AgentCore Runtime インタラクティブシェル
AgentCore Runtimeのインタラクティブシェルについては先行記事で詳しく解説しています。本記事では「このWebSocketベースのシェルをCloudFront経由で安全に公開する」構成に焦点を当てます。
Cognito + Lambda で CloudFront 署名付き Cookie を発行
CloudFrontのデフォルトビヘイビアをTrusted Key Groupで保護し、認証済みユーザーのみがアクセスできるようにしています。署名付きCookieの発行は、Cognito User PoolとLambda関数URLの組み合わせで実現しました。
認証フローは以下のように動作します。
- ブラウザーが
/loginにアクセスすると、LambdaがCognito Hosted UIのauthorizeエンドポイントへ302リダイレクト - ユーザーがCognitoでログインすると、
/callback?code=xxxへリダイレクト - Callback Lambdaがauthorization codeをtokenへ交換し、CloudFront署名付きCookie(Policy/Signature/Key-Pair-Id)を発行
- 以降のリクエストはCookie付きで送信され、CloudFrontのTrusted Key Groupで検証される
この方式によりLambda@Edgeを使わず、リージョナルなLambda関数のみで認証フローを完結させています。
署名鍵ペアの管理にはカスタムリソースを使いました。デプロイ時にRSA 2048bit鍵ペアを生成します。秘密鍵はSSM Parameter Store(SecureString)に保存し、公開鍵はCloudFrontのPublic Key + Key Groupとして登録します。
// lambda-stack.ts — 署名鍵カスタムリソース
const signingKeyResource = new cdk.CustomResource(this, 'SigningKey', {
serviceToken: new cr.Provider(this, 'SigningKeyProvider', {
onEventHandler: signingKeyFn,
}).serviceToken,
properties: {
SsmParameterName: ssmPrivateKeyName,
KeyName: 'agentcore-webshell-cf-key',
SsmRegion: this.region,
},
});
this.keyPairId = signingKeyResource.getAttString('PublicKeyId');
this.keyGroupId = signingKeyResource.getAttString('KeyGroupId');
Callback LambdaではSSMから秘密鍵を取得し、@aws-sdk/cloudfront-signer で署名付きCookieを生成しています。
// callback/index.mjs — 署名付き Cookie 発行
const privateKey = await getPrivateKey();
const expires = new Date(Date.now() + 8 * 60 * 60 * 1000);
const policy = JSON.stringify({
Statement: [{
Resource: `https://${cookieDomain}/*`,
Condition: { DateLessThan: { 'AWS:EpochTime': Math.floor(expires.getTime() / 1000) } },
}],
});
const cookies = getSignedCookies({ policy, privateKey, keyPairId });
const cookieAttrs = `Domain=${cookieDomain}; Path=/; Secure; HttpOnly; SameSite=Lax`;
const setCookies = [
`CloudFront-Policy=${cookies['CloudFront-Policy']}; ${cookieAttrs}`,
`CloudFront-Signature=${cookies['CloudFront-Signature']}; ${cookieAttrs}`,
`CloudFront-Key-Pair-Id=${cookies['CloudFront-Key-Pair-Id']}; ${cookieAttrs}`,
];
カスタムポリシーで Resource にワイルドカード(https://ドメイン/*)を指定しています。Cookie 1セットで / 以下の全パス(WebSocketハンドシェイクを含む)をカバーできます。
Lambda 関数 URL の OAC 保護
/login と /callback のLambda関数URLは、2024年4月にGAとなったCloudFrontのOrigin Access Control(OAC)で保護しています。
OACを有効にすると、CloudFrontがリクエストにSigV4署名を付与してオリジンのLambda関数URLに送信します。Lambda側は AuthType: AWS_IAM を設定しているため、有効なSigV4署名を持たないリクエスト(CloudFrontを経由しない単純な直接アクセスなど)を拒否します。
CDKでは FunctionUrlOrigin.withOriginAccessControl() を呼ぶだけでOACの設定とリソースベースポリシーの付与が完了します。
// lambda-stack.ts — AuthType を AWS_IAM に設定
this.loginFunctionUrl = this.loginFn.addFunctionUrl({
authType: lambda.FunctionUrlAuthType.AWS_IAM,
});
// cloudfront-stack.ts — OAC 付きオリジン
const loginOrigin = origins.FunctionUrlOrigin.withOriginAccessControl(props.loginFunctionUrl);
const callbackOrigin = origins.FunctionUrlOrigin.withOriginAccessControl(props.callbackFunctionUrl);
なお、/login と /callback のビヘイビアには trustedKeyGroups を設定していません。これらは認証前にアクセスするパスであり、署名付きCookieなしでリクエストを受け付ける必要があるためです。
CloudFront VPC Origin(WebSocket + プライベートオリジン)
2026年5月にGAとなったCloudFront VPC OriginのWebSocketサポートにより、WebSocketを使うアプリケーションでもオリジンをプライベートサブネットに配置できます。
本構成ではECS Fargate上のWebShell中継サーバーをInternal ALBの背後に配置し、VPC Origin経由でCloudFrontに接続しています。VPC OriginはCloudFrontがマネージドENIをVPC内に作成し、そのENI経由でプライベートサブネットのALBにアクセスする仕組みです。CloudFront・VPC Origin側にWebSocket固有の追加設定は不要で、HTTP Upgradeリクエストを透過的に中継します。ALB・バックエンド側のWebSocket対応は別途必要です。
デフォルトビヘイビアの設定がポイントです。
// cloudfront-stack.ts — VPC Origin + デフォルトビヘイビア
const vpcOrigin = origins.VpcOrigin.withApplicationLoadBalancer(props.alb, {
httpPort: 80,
protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY,
});
const distribution = new cloudfront.Distribution(this, 'Distribution', {
defaultBehavior: {
origin: vpcOrigin,
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED,
originRequestPolicy: cloudfront.OriginRequestPolicy.ALL_VIEWER,
allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
trustedKeyGroups: [keyGroup],
},
});
本構成のデフォルトビヘイビアでは、WebSocketとアプリケーションAPIを同じ経路で扱うため、以下の3点を設定しています。
CachePolicy.CACHING_DISABLED— WebSocketのハンドシェイクや動的レスポンスはキャッシュさせたくないため、キャッシュを無効化して全リクエストをオリジンに転送するOriginRequestPolicy.ALL_VIEWER— Cookie、ヘッダー、クエリ文字列をすべてオリジンに転送する。署名付きCookieやアプリケーションが必要とするヘッダーの転送に使用しているAllowedMethods.ALLOW_ALL— GET/HEAD以外のメソッド(POST、PUT、DELETE等)を許可する。WebSocketハンドシェイクはGETだが、アプリケーション全体のAPIリクエストを通すために必要
パブリックサブネットレス構成(Regional NAT Gateway)
本構成のVPCはプライベートサブネットのみで構成しています。ECSタスクからAgentCore Runtime Shell APIやECRへのアウトバウンド通信には、2025年11月にGAとなったRegional NAT Gatewayを使用しました。
インターネット向けの従来のpublic NAT Gatewayはサブネット単位で配置する必要がありました。Regional NAT GatewayはVPCレベルで動作し、従来必要だったパブリックサブネットへの配置(subnetId 指定)が不要です。代わりにAZとEIPの割り当てを指定して作成します。
// network-stack.ts — VPC(プライベートサブネットのみ)
this.vpc = new ec2.Vpc(this, 'Vpc', {
ipAddresses: ec2.IpAddresses.cidr('192.168.0.0/18'),
maxAzs: 3,
natGateways: 0, // Regional NAT GW を手動作成
subnetConfiguration: [
{
cidrMask: 20,
name: 'Private',
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
},
],
});
// network-stack.ts — Regional NAT Gateway
const natGw = new ec2.CfnNatGateway(this, 'RegionalNatGw', {
connectivityType: 'public',
vpcId: this.vpc.vpcId,
availabilityMode: 'regional',
availabilityZoneAddresses: [{
availabilityZone: 'ap-northeast-1a',
allocationIds: [eip.attrAllocationId],
}],
});
natGw.addDependency(igwAttach);
CDKのL2 VpcコンストラクトはRegional NAT Gatewayに対応していないため、CfnNatGateway(L1)で作成しています。従来のzonal NAT Gatewayとの違いとして subnetId が不要になり、vpcId を直接指定します。
各プライベートサブネットのルートテーブルにデフォルトルート(0.0.0.0/0 → NAT Gateway)を追加します。これによりサブネットからのアウトバウンド通信がRegional NAT Gatewayを経由します。CDK上は PRIVATE_ISOLATED として作成していますが、ルート追加後はNAT経由のアウトバウンド経路を持つプライベートサブネットとして動作します。L2コンストラクトがRegional NAT Gatewayに非対応のため、この方式を採用しています。
動作確認
構築した環境の動作を確認します。
ログイン
ブラウザーで https://ドメイン/login にアクセスすると、Cognito Hosted UIにリダイレクトされました。

ログインに成功すると、署名付きCookieが発行され、ダッシュボードにリダイレクトされました。

WebSocket 接続
ダッシュボードから「New Session」で新規セッションを作成しました。CloudFront → VPC Origin → Internal ALB → ECS → AgentCore Runtime Shell APIの経路でWebSocket接続が確立されました。

Kiro CLI の実行
ターミナル上でKiro CLIを実行しました。AgentCore RuntimeのmicroVM上でAIエージェントが正常に動作しています。

署名付きCookieが未設定の状態でダッシュボードやWebSocketエンドポイントにアクセスすると、CloudFrontが403を返すことも確認しました。署名付きCookieで保護されたパス(デフォルトビヘイビア配下)へのアクセスは、認証を経由しない限りブロックされます。
セッションへの再接続
ECS Fargate上の中継サーバーは、AgentCore Runtime Shell APIへのWebSocket中継に加え、簡易ダッシュボードとセッション管理機能を追加しています。サーバー側でアクティブなセッション(shellId)を保持しており、既存セッションは一覧表示から「Connect」で再接続できます。

まとめ
Cognito認証とCloudFront署名付きCookieを組み合わせ、認証用パスを除くアプリケーション向けリクエストを、WebSocketを含めて認証済みユーザーのみに制限しました。Lambda関数URLはOACとAWS_IAM認証で保護し、CloudFrontを経由しない未署名の直接アクセスを拒否しています。ネットワーク面ではCloudFront VPC OriginのWebSocketサポートとRegional NAT Gatewayを活用しました。パブリックサブネットを持たないVPCで、外部公開エンドポイントをCloudFrontに集約した構成です。






