diff --git a/docs/en/DEPLOY_OPTION.md b/docs/en/DEPLOY_OPTION.md index c717fa429..ade372295 100644 --- a/docs/en/DEPLOY_OPTION.md +++ b/docs/en/DEPLOY_OPTION.md @@ -754,6 +754,67 @@ const envs: Record> = { } ``` +> [!NOTE] +> After enabling AgentCore use case settings, if you want to disable them again, you can disable the AgentCore use case by setting `createGenericAgentCoreRuntime: false` and redeploying, but the `AgentCoreStack` itself will remain. You can completely remove it by opening the Management Console and deleting the `AgentCoreStack` stack from CloudFormation in the `agentCoreRegion`. + +#### AgentCore Runtime Network Configuration + +AgentCore Runtime can operate in the following network modes: + +- `PUBLIC` (default): Operates on public network +- `PRIVATE`: Operates on private network within VPC + +Network settings apply to both Generic Runtime and AgentBuilder Runtime. + +**Use cases for VPC mode**: + +- When AgentCore Runtime needs to access internal systems or private databases +- For example, when you want to communicate directly with other AWS services (RDS, ElastiCache, etc.) within the VPC + +When using VPC mode, configure the following parameters: + +- `agentCoreVpcId`: VPC ID to use +- `agentCoreSubnetIds`: List of subnet IDs to use + +> [!NOTE] +> When both `agentCoreVpcId` and `agentCoreSubnetIds` are configured, AgentCore Runtime will be deployed in private network mode. If both are left unset (`null`), it will be deployed in public network mode. + +> [!IMPORTANT] +> **Availability Zone (AZ) Support**: AgentCore Runtime has limited supported AZs per region. Subnets must be placed within supported AZs. For details, please refer to the [AWS official documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agentcore-vpc.html#agentcore-supported-azs). +> +> **Internet Access**: AgentCore Runtime requires internet access for MCP server installation. If connecting to private subnets, configure routes to NAT Gateway. + +**Edit [parameter.ts](/packages/cdk/parameter.ts)** + +```typescript +// parameter.ts +const envs: Record> = { + dev: { + createGenericAgentCoreRuntime: true, + agentBuilderEnabled: true, + agentCoreVpcId: 'vpc-xxxxxxxxx', + agentCoreSubnetIds: ['subnet-xxxxxxxxx', 'subnet-yyyyyyyyy'], + }, +}; +``` + +**Edit [packages/cdk/cdk.json](/packages/cdk/cdk.json)** + +```json +// cdk.json +{ + "context": { + "createGenericAgentCoreRuntime": true, + "agentBuilderEnabled": true, + "agentCoreVpcId": "vpc-xxxxxxxxx", + "agentCoreSubnetIds": ["subnet-xxxxxxxxx", "subnet-yyyyyyyyy"] + } +} +``` + +> [!WARNING] +> When using VPC mode, security groups are not automatically deleted when AgentCore Runtime is deleted due to changes such as from PRIVATE to PUBLIC. This is because AWS-managed ENIs created by AgentCore Runtime reference the security groups, making them undeletable by CloudFormation. After deleting AgentCore Runtime, wait for the managed ENIs to be automatically deleted, then manually delete the security groups. The security group IDs that need to be deleted are displayed in the CloudFormation outputs. + ### Enabling Voice Chat Use Case > [!NOTE] diff --git a/docs/ja/DEPLOY_OPTION.md b/docs/ja/DEPLOY_OPTION.md index fff669479..99ef5f450 100644 --- a/docs/ja/DEPLOY_OPTION.md +++ b/docs/ja/DEPLOY_OPTION.md @@ -769,6 +769,67 @@ const envs: Record> = { } ``` +> [!NOTE] +> AgentCore ユースケースの設定を有効後に、再度無効化する場合は、`createGenericAgentCoreRuntime: false` にして再デプロイすればAgentCore ユースケースは無効化されますが、`AgentCoreStack` 自体は残ります。マネージメントコンソールを開き、`agentCoreRegion` の CloudFormation から `AgentCoreStack` というスタックを削除することで完全に消去ができます。 + +#### AgentCore Runtime のネットワーク設定 + +AgentCore Runtime は以下のネットワークモードで動作できます: + +- `PUBLIC` (デフォルト): パブリックネットワークで動作 +- `PRIVATE`: VPC内のプライベートネットワークで動作 + +ネットワーク設定は、Generic Runtime と AgentBuilder Runtime の両方に適用されます。 + +**VPCモードの使用場面**: + +- AgentCore Runtime から社内システムやプライベートデータベースにアクセスする必要がある場合 +- 例えば、VPC内の他のAWSサービス(RDS、ElastiCache等)と直接通信したい場合 + +VPC モードを使用する場合は、以下のパラメータを設定してください: + +- `agentCoreVpcId`: 使用するVPCのID +- `agentCoreSubnetIds`: 使用するサブネットのIDリスト + +> [!NOTE] +> `agentCoreVpcId`と`agentCoreSubnetIds`を両方設定すると、AgentCore Runtimeはプライベートネットワークモードでデプロイされます。両方とも未設定(`null`)の場合は、パブリックネットワークモードでデプロイされます。 + +> [!IMPORTANT] +> **Availability Zone (AZ) サポート**: AgentCore RuntimeはリージョンごとにサポートされているAZが限定されています。サブネットは必ずサポートされているAZ内に配置してください。詳細は[AWS公式ドキュメント](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agentcore-vpc.html#agentcore-supported-azs)をご確認ください。 +> +> **インターネットアクセス**: AgentCore Runtime で MCP サーバーのインストールにはインターネットアクセスが必要です。プライベートサブネットが接続先の場合には NAT Gateway への経路を設定してください。 + +**[parameter.ts](/packages/cdk/parameter.ts) を編集** + +```typescript +// parameter.ts +const envs: Record> = { + dev: { + createGenericAgentCoreRuntime: true, + agentBuilderEnabled: true, + agentCoreVpcId: 'vpc-xxxxxxxxx', + agentCoreSubnetIds: ['subnet-xxxxxxxxx', 'subnet-yyyyyyyyy'], + }, +}; +``` + +**[packages/cdk/cdk.json](/packages/cdk/cdk.json) を編集** + +```json +// cdk.json +{ + "context": { + "createGenericAgentCoreRuntime": true, + "agentBuilderEnabled": true, + "agentCoreVpcId": "vpc-xxxxxxxxx", + "agentCoreSubnetIds": ["subnet-xxxxxxxxx", "subnet-yyyyyyyyy"] + } +} +``` + +> [!WARNING] +> VPC モードを使用する場合、例えば PRIVATE から PUBLIC への変更により AgentCore Runtime 削除時にセキュリティグループが自動削除されません。AgentCore Runtime が作成する AWS マネージドな ENI がセキュリティグループを参照するため、CloudFormation では削除できません。AgentCore Runtime 削除後、マネージド ENI が自動削除されるまで待ってから、手動でセキュリティグループを削除してください。削除が必要なセキュリティグループ ID は CloudFormation の出力に表示されます。 + ### 音声チャットユースケースの有効化 > [!NOTE] diff --git a/docs/ko/DEPLOY_OPTION.md b/docs/ko/DEPLOY_OPTION.md index 06caa0b54..2e6a547cf 100644 --- a/docs/ko/DEPLOY_OPTION.md +++ b/docs/ko/DEPLOY_OPTION.md @@ -753,6 +753,67 @@ const envs: Record> = { } ``` +> [!NOTE] +> AgentCore 사용 사례 설정을 활성화한 후 다시 비활성화하려면 `createGenericAgentCoreRuntime: false`로 설정하고 재배포하면 AgentCore 사용 사례가 비활성화되지만 `AgentCoreStack` 자체는 남아있습니다. 관리 콘솔을 열고 `agentCoreRegion`의 CloudFormation에서 `AgentCoreStack` 스택을 삭제하여 완전히 제거할 수 있습니다. + +#### AgentCore Runtime 네트워크 설정 + +AgentCore Runtime은 다음 네트워크 모드에서 작동할 수 있습니다: + +- `PUBLIC` (기본값): 퍼블릭 네트워크에서 작동 +- `PRIVATE`: VPC 내 프라이빗 네트워크에서 작동 + +네트워크 설정은 Generic Runtime과 AgentBuilder Runtime 모두에 적용됩니다. + +**VPC 모드 사용 사례**: + +- AgentCore Runtime에서 사내 시스템이나 프라이빗 데이터베이스에 액세스해야 하는 경우 +- 예를 들어, VPC 내의 다른 AWS 서비스(RDS, ElastiCache 등)와 직접 통신하고 싶은 경우 + +VPC 모드를 사용할 때는 다음 매개변수를 설정하세요: + +- `agentCoreVpcId`: 사용할 VPC ID +- `agentCoreSubnetIds`: 사용할 서브넷 ID 목록 + +> [!NOTE] +> `agentCoreVpcId`와 `agentCoreSubnetIds`를 모두 설정하면 AgentCore Runtime이 프라이빗 네트워크 모드로 배포됩니다. 둘 다 미설정(`null`)인 경우 퍼블릭 네트워크 모드로 배포됩니다. + +> [!IMPORTANT] +> **가용 영역(AZ) 지원**: AgentCore Runtime은 리전별로 지원되는 AZ가 제한되어 있습니다. 서브넷은 반드시 지원되는 AZ 내에 배치해야 합니다. 자세한 내용은 [AWS 공식 문서](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agentcore-vpc.html#agentcore-supported-azs)를 확인하세요. +> +> **인터넷 액세스**: AgentCore Runtime에서 MCP 서버 설치에는 인터넷 액세스가 필요합니다. 프라이빗 서브넷이 연결 대상인 경우 NAT Gateway로의 경로를 설정하세요. + +**[parameter.ts](/packages/cdk/parameter.ts) 편집** + +```typescript +// parameter.ts +const envs: Record> = { + dev: { + createGenericAgentCoreRuntime: true, + agentBuilderEnabled: true, + agentCoreVpcId: 'vpc-xxxxxxxxx', + agentCoreSubnetIds: ['subnet-xxxxxxxxx', 'subnet-yyyyyyyyy'], + }, +}; +``` + +**[packages/cdk/cdk.json](/packages/cdk/cdk.json) 편집** + +```json +// cdk.json +{ + "context": { + "createGenericAgentCoreRuntime": true, + "agentBuilderEnabled": true, + "agentCoreVpcId": "vpc-xxxxxxxxx", + "agentCoreSubnetIds": ["subnet-xxxxxxxxx", "subnet-yyyyyyyyy"] + } +} +``` + +> [!WARNING] +> VPC 모드를 사용할 때, 예를 들어 PRIVATE에서 PUBLIC으로의 변경으로 인해 AgentCore Runtime 삭제 시 보안 그룹이 자동 삭제되지 않습니다. AgentCore Runtime이 생성하는 AWS 관리형 ENI가 보안 그룹을 참조하기 때문에 CloudFormation에서는 삭제할 수 없습니다. AgentCore Runtime 삭제 후 관리형 ENI가 자동 삭제될 때까지 기다린 다음 수동으로 보안 그룹을 삭제하세요. 삭제가 필요한 보안 그룹 ID는 CloudFormation 출력에 표시됩니다. + ### Voice Chat 사용 사례 활성화 > [!NOTE] diff --git a/packages/cdk/cdk.json b/packages/cdk/cdk.json index a73e7dcf1..3a3bb26bb 100644 --- a/packages/cdk/cdk.json +++ b/packages/cdk/cdk.json @@ -68,6 +68,8 @@ "createGenericAgentCoreRuntime": false, "agentCoreRegion": null, "agentCoreExternalRuntimes": [], + "agentCoreVpcId": null, + "agentCoreSubnetIds": null, "allowedIpV4AddressRanges": null, "allowedIpV6AddressRanges": null, "allowedCountryCodes": null, diff --git a/packages/cdk/lib/agent-core-stack.ts b/packages/cdk/lib/agent-core-stack.ts index e319c50c5..b1b43586d 100644 --- a/packages/cdk/lib/agent-core-stack.ts +++ b/packages/cdk/lib/agent-core-stack.ts @@ -22,6 +22,9 @@ export class AgentCoreStack extends Stack { env: params.env, createGenericRuntime: params.createGenericAgentCoreRuntime, createAgentBuilderRuntime: params.agentBuilderEnabled, + isAgentCoreNetworkPrivate: params.isAgentCoreNetworkPrivate, + agentCoreVpcId: params.agentCoreVpcId, + agentCoreSubnetIds: params.agentCoreSubnetIds, }); // Export runtime info for cross-region access via cdk-remote-stack (only if values exist) @@ -40,6 +43,15 @@ export class AgentCoreStack extends Stack { }); } + // Output retained security group ID for manual cleanup + if (this.genericAgentCore.retainedSecurityGroupId) { + new CfnOutput(this, 'RetainedSecurityGroupId', { + value: this.genericAgentCore.retainedSecurityGroupId, + description: + 'MANUAL CLEANUP REQUIRED: Security Group ID to delete after AgentCore ENI cleanup (check tags: ManualCleanupRequired=true)', + }); + } + if ( params.agentBuilderEnabled && this.genericAgentCore.deployedAgentBuilderRuntimeArn diff --git a/packages/cdk/lib/construct/generic-agent-core.ts b/packages/cdk/lib/construct/generic-agent-core.ts index 8077d5b61..0b169813b 100644 --- a/packages/cdk/lib/construct/generic-agent-core.ts +++ b/packages/cdk/lib/construct/generic-agent-core.ts @@ -5,12 +5,13 @@ import { Role, ServicePrincipal, } from 'aws-cdk-lib/aws-iam'; -import { Stack, RemovalPolicy } from 'aws-cdk-lib'; +import { Stack, RemovalPolicy, Tags } from 'aws-cdk-lib'; import { Bucket, BlockPublicAccess, BucketEncryption, } from 'aws-cdk-lib/aws-s3'; +import { Subnet, Vpc, SecurityGroup, IVpc, ISubnet } from 'aws-cdk-lib/aws-ec2'; import { Runtime, RuntimeNetworkConfiguration, @@ -37,6 +38,10 @@ export interface GenericAgentCoreProps { env: string; createGenericRuntime?: boolean; createAgentBuilderRuntime?: boolean; + isAgentCoreNetworkPrivate?: boolean; + agentCoreVpcId?: string | null; + agentCoreSubnetIds?: string[] | null; + agentCoreEnvironmentVariables?: Record; } interface RuntimeResources { @@ -51,6 +56,10 @@ export class GenericAgentCore extends Construct { private readonly agentBuilderRuntimeConfig: AgentCoreRuntimeConfig; private readonly resources: RuntimeResources; + // Security Group ID that requires manual cleanup after AgentCore Runtime deletion + // Used for CloudFormation Output to remind users of manual cleanup tasks + public readonly retainedSecurityGroupId?: string; + constructor(scope: Construct, id: string, props: GenericAgentCoreProps) { super(scope, id); @@ -58,6 +67,9 @@ export class GenericAgentCore extends Construct { env, createGenericRuntime = false, createAgentBuilderRuntime = false, + isAgentCoreNetworkPrivate = false, + agentCoreVpcId = null, + agentCoreSubnetIds = null, } = props; // Create bucket first @@ -68,10 +80,52 @@ export class GenericAgentCore extends Construct { this.genericRuntimeConfig = configs.generic; this.agentBuilderRuntimeConfig = configs.agentBuilder; + // Create security group if VPC mode + let securityGroup: SecurityGroup | undefined; + let vpc: IVpc | undefined; + let subnets: ISubnet[] | undefined; + + if (isAgentCoreNetworkPrivate && agentCoreVpcId && agentCoreSubnetIds) { + vpc = Vpc.fromLookup(this, 'AgentCoreVpc', { vpcId: agentCoreVpcId }); + subnets = agentCoreSubnetIds.map((subnetId, index) => + Subnet.fromSubnetId(this, `AgentCoreSubnet${index}`, subnetId) + ); + securityGroup = new SecurityGroup(this, 'AgentCoreSecurityGroup', { + vpc, + description: 'Security group for AgentCore Runtime', + allowAllOutbound: true, + }); + + // Add tags for manual cleanup identification + Tags.of(securityGroup).add('ManualCleanupRequired', 'true'); + Tags.of(securityGroup).add( + 'CleanupReason', + 'AgentCore-Managed-ENI-Dependency' + ); + Tags.of(securityGroup).add('CreatedBy', `GenU-${env}`); + + // Retain security group to prevent deletion errors when changing PRIVATE->PUBLIC or removing AgentCore + // AgentCore Runtime creates managed ENIs that reference this security group + // CloudFormation cannot delete the SG while managed ENIs are using it (even though they're not manually deletable) + // The managed ENIs are automatically cleaned up after AgentCore Runtime deletion, but with a time delay + // Therefore, this SG must be retained and manually deleted after the managed ENIs are cleaned up + // + // Note: Custom Resource with ENI monitoring could solve this, but deletion can take up to 1 hour + // Since security groups incur no cost, RETAIN is the practical solution for better user experience + securityGroup.applyRemovalPolicy(RemovalPolicy.RETAIN); + + // Store SG ID for output + this.retainedSecurityGroupId = securityGroup.securityGroupId; + } + // Create all resources atomically this.resources = this.createResources( createGenericRuntime, - createAgentBuilderRuntime + createAgentBuilderRuntime, + isAgentCoreNetworkPrivate, + vpc, + subnets, + securityGroup ); } @@ -125,7 +179,11 @@ export class GenericAgentCore extends Construct { private createResources( createGeneric: boolean, - createAgentBuilder: boolean + createAgentBuilder: boolean, + isAgentCoreNetworkPrivate: boolean, + vpc?: IVpc, + subnets?: ISubnet[], + securityGroup?: SecurityGroup ): RuntimeResources { if (!createGeneric && !createAgentBuilder) { return { role: this.createExecutionRole() }; @@ -138,7 +196,11 @@ export class GenericAgentCore extends Construct { resources.genericRuntime = this.createRuntime( 'Generic', this.genericRuntimeConfig, - role + role, + isAgentCoreNetworkPrivate, + vpc, + subnets, + securityGroup ); } @@ -146,7 +208,11 @@ export class GenericAgentCore extends Construct { resources.agentBuilderRuntime = this.createRuntime( 'AgentBuilder', this.agentBuilderRuntimeConfig, - role + role, + isAgentCoreNetworkPrivate, + vpc, + subnets, + securityGroup ); } @@ -157,20 +223,54 @@ export class GenericAgentCore extends Construct { private createRuntime( type: string, config: AgentCoreRuntimeConfig, - role: Role + role: Role, + isAgentCoreNetworkPrivate: boolean, + vpc?: IVpc, + subnets?: ISubnet[], + securityGroup?: SecurityGroup ): Runtime { + const networkConfig = this.createNetworkConfiguration( + isAgentCoreNetworkPrivate, + vpc, + subnets, + securityGroup + ); + return new Runtime(this, `${type}AgentCoreRuntime`, { runtimeName: config.name, agentRuntimeArtifact: AgentRuntimeArtifact.fromAsset( path.join(__dirname, `../../${config.dockerPath}`) ), executionRole: role, - networkConfiguration: RuntimeNetworkConfiguration.usingPublicNetwork(), + networkConfiguration: networkConfig, protocolConfiguration: ProtocolType.HTTP, environmentVariables: config.environmentVariables, }); } + private createNetworkConfiguration( + isAgentCoreNetworkPrivate: boolean, + vpc?: IVpc, + subnets?: ISubnet[], + securityGroup?: SecurityGroup + ): RuntimeNetworkConfiguration { + if (isAgentCoreNetworkPrivate) { + if (!vpc || !subnets) { + throw new Error( + 'VPC and Subnets are required for private network configuration' + ); + } + + return RuntimeNetworkConfiguration.usingVpc(this, { + vpc, + vpcSubnets: { subnets }, + securityGroups: securityGroup ? [securityGroup] : undefined, + }); + } else { + return RuntimeNetworkConfiguration.usingPublicNetwork(); + } + } + private createExecutionRole(): Role { const region = Stack.of(this).region; const accountId = Stack.of(this).account; diff --git a/packages/cdk/lib/stack-input.ts b/packages/cdk/lib/stack-input.ts index 3c066d1eb..6946517a0 100644 --- a/packages/cdk/lib/stack-input.ts +++ b/packages/cdk/lib/stack-input.ts @@ -160,6 +160,9 @@ const baseStackInputSchema = z.object({ }) ) .default([]), + // Agent Core Network Configuration + agentCoreVpcId: z.string().nullish(), + agentCoreSubnetIds: z.array(z.string()).nullish(), // MCP mcpEnabled: z.boolean().default(false), // Guardrail @@ -202,19 +205,37 @@ const baseStackInputSchema = z.object({ }); // Common Validator with refine -export const stackInputSchema = baseStackInputSchema.refine( - (data) => { - // If searchApiKey is provided, searchEngine must also be provided - if (data.searchApiKey && !data.searchEngine) { - return false; +export const stackInputSchema = baseStackInputSchema + .refine( + (data) => { + // If searchApiKey is provided, searchEngine must also be provided + if (data.searchApiKey && !data.searchEngine) { + return false; + } + return true; + }, + { + message: 'searchEngine is required when searchApiKey is provided', + path: ['searchEngine'], } - return true; - }, - { - message: 'searchEngine is required when searchApiKey is provided', - path: ['searchEngine'], - } -); + ) + .refine( + (data) => { + // Validate AgentCore VPC configuration consistency + const hasVpcId = !!data.agentCoreVpcId; + const hasSubnetIds = !!( + data.agentCoreSubnetIds && data.agentCoreSubnetIds.length > 0 + ); + + // Both must be provided or both must be empty + return hasVpcId === hasSubnetIds; + }, + { + message: + 'Both VPC ID and Subnet IDs must be provided together for AgentCore network configuration', + path: ['agentCoreVpcId'], + } + ); // schema after conversion export const processedStackInputSchema = baseStackInputSchema.extend({ @@ -254,6 +275,8 @@ export const processedStackInputSchema = baseStackInputSchema.extend({ ), // Processed agentCoreRegion (null -> modelRegion) agentCoreRegion: z.string(), + // Computed from VPC configuration (computed in parameter.ts) + isAgentCoreNetworkPrivate: z.boolean().optional(), // Branding configuration brandingConfig: z .object({ diff --git a/packages/cdk/parameter.ts b/packages/cdk/parameter.ts index 8000e662a..152cef363 100644 --- a/packages/cdk/parameter.ts +++ b/packages/cdk/parameter.ts @@ -78,6 +78,12 @@ export const getParams = (app: cdk.App): ProcessedStackInput => { ), // Process agentCoreRegion: null -> modelRegion agentCoreRegion: params.agentCoreRegion || params.modelRegion, + // Compute isAgentCoreNetworkPrivate from VPC configuration + isAgentCoreNetworkPrivate: !!( + params.agentCoreVpcId && + params.agentCoreSubnetIds && + params.agentCoreSubnetIds.length > 0 + ), // Load branding configuration brandingConfig: loadBrandingConfig(), }; diff --git a/packages/cdk/test/__snapshots__/generative-ai-use-cases.test.ts.snap b/packages/cdk/test/__snapshots__/generative-ai-use-cases.test.ts.snap index a163de685..e5c11b73d 100644 --- a/packages/cdk/test/__snapshots__/generative-ai-use-cases.test.ts.snap +++ b/packages/cdk/test/__snapshots__/generative-ai-use-cases.test.ts.snap @@ -1,5 +1,524 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`GenerativeAiUseCases matches the snapshot (AgentCore with VPC) 1`] = ` +{ + "Outputs": { + "AgentBuilderAgentCoreRuntimeArn": { + "Export": { + "Name": "AgentCoreStack-AgentBuilderAgentCoreRuntimeArn", + }, + "Value": { + "Fn::GetAtt": [ + "GenericAgentCoreAgentBuilderAgentCoreRuntimeFEFD60E0", + "AgentRuntimeArn", + ], + }, + }, + "AgentBuilderAgentCoreRuntimeName": { + "Export": { + "Name": "AgentCoreStack-AgentBuilderAgentCoreRuntimeName", + }, + "Value": "GenUAgentBuilderRuntime", + }, + "ExportsOutputRefGenericAgentCoreAgentCoreFileBucket0430DA423298A79F": { + "Export": { + "Name": "AgentCoreStack:ExportsOutputRefGenericAgentCoreAgentCoreFileBucket0430DA423298A79F", + }, + "Value": { + "Ref": "GenericAgentCoreAgentCoreFileBucket0430DA42", + }, + }, + "FileBucketName": { + "Export": { + "Name": "AgentCoreStack-FileBucketName", + }, + "Value": { + "Ref": "GenericAgentCoreAgentCoreFileBucket0430DA42", + }, + }, + "GenericAgentCoreRuntimeArn": { + "Export": { + "Name": "AgentCoreStack-GenericAgentCoreRuntimeArn", + }, + "Value": { + "Fn::GetAtt": [ + "GenericAgentCoreGenericAgentCoreRuntime041F933D", + "AgentRuntimeArn", + ], + }, + }, + "GenericAgentCoreRuntimeName": { + "Export": { + "Name": "AgentCoreStack-GenericAgentCoreRuntimeName", + }, + "Value": "GenUGenericRuntime", + }, + "RetainedSecurityGroupId": { + "Description": "MANUAL CLEANUP REQUIRED: Security Group ID to delete after AgentCore ENI cleanup (check tags: ManualCleanupRequired=true)", + "Value": { + "Fn::GetAtt": [ + "GenericAgentCoreAgentCoreSecurityGroup288CC66C", + "GroupId", + ], + }, + }, + }, + "Parameters": { + "BootstrapVersion": { + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]", + "Type": "AWS::SSM::Parameter::Value", + }, + }, + "Resources": { + "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F": { + "DependsOn": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + ], + "Properties": { + "Code": { + "S3Bucket": "cdk-hnb659fds-assets-123456890123-us-east-1", + "S3Key": "HASH-REPLACED.zip", + }, + "Description": { + "Fn::Join": [ + "", + [ + "Lambda function for auto-deleting objects in ", + { + "Ref": "GenericAgentCoreAgentCoreFileBucket0430DA42", + }, + " S3 bucket.", + ], + ], + }, + "Handler": "index.handler", + "MemorySize": 128, + "Role": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn", + ], + }, + "Runtime": "nodejs22.x", + "Timeout": 900, + }, + "Type": "AWS::Lambda::Function", + }, + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "GenericAgentCoreAgentBuilderAgentCoreRuntimeFEFD60E0": { + "DependsOn": [ + "GenericAgentCoreAgentCoreRuntimeRoleDefaultPolicy4E847841", + "GenericAgentCoreAgentCoreRuntimeRoleF34B80EA", + ], + "Properties": { + "AgentRuntimeArtifact": { + "ContainerConfiguration": { + "ContainerUri": { + "Fn::Sub": "123456890123.dkr.ecr.us-east-1.\${AWS::URLSuffix}/cdk-hnb659fds-container-assets-123456890123-us-east-1:69eb434ee242daa7224dd9fb0f5cf9142a71fe81dac805352a11509e5ecff870", + }, + }, + }, + "AgentRuntimeName": "GenUAgentBuilderRuntime", + "EnvironmentVariables": { + "FILE_BUCKET": { + "Ref": "GenericAgentCoreAgentCoreFileBucket0430DA42", + }, + "MCP_SERVERS": "{"time":{"command":"uvx","args":["mcp-server-time"],"metadata":{"category":"Utility","description":"Provides current time and date functionality"}},"aws-knowledge-mcp-server":{"command":"npx","args":["mcp-remote","https://knowledge-mcp.global.api.aws"],"metadata":{"category":"AWS","description":"AWS Knowledge Base MCP server for enterprise knowledge access"}},"awslabs.aws-documentation-mcp-server":{"command":"uvx","args":["awslabs.aws-documentation-mcp-server@latest"],"metadata":{"category":"AWS","description":"Access AWS documentation and guides"}},"awslabs.cdk-mcp-server":{"command":"uvx","args":["awslabs.cdk-mcp-server@latest"],"metadata":{"category":"AWS","description":"AWS CDK code generation and assistance"}},"awslabs.aws-diagram-mcp-server":{"command":"uvx","args":["awslabs.aws-diagram-mcp-server@latest"],"metadata":{"category":"AWS","description":"Generate AWS architecture diagrams"}},"awslabs.nova-canvas-mcp-server":{"command":"uvx","args":["awslabs.nova-canvas-mcp-server@latest"],"env":{"AWS_REGION":"us-east-1"},"metadata":{"category":"AI/ML","description":"Amazon Nova Canvas image generation"}},"tavily-search":{"command":"npx","args":["-y","mcp-remote","https://mcp.tavily.com/mcp/?tavilyApiKey="],"metadata":{"category":"Search","description":"Web search and research capabilities powered by Tavily"}}}", + "SUPPORTED_CACHE_FIELDS": "{"anthropic.claude-opus-4-5-20251101-v1:0":["messages","system","tools"],"anthropic.claude-sonnet-4-5-20250929-v1:0":["messages","system","tools"],"anthropic.claude-haiku-4-5-20251001-v1:0":["messages","system","tools"],"anthropic.claude-opus-4-1-20250805-v1:0":["messages","system","tools"],"anthropic.claude-opus-4-20250514-v1:0":["messages","system","tools"],"anthropic.claude-sonnet-4-20250514-v1:0":["messages","system","tools"],"anthropic.claude-3-7-sonnet-20250219-v1:0":["messages","system","tools"],"anthropic.claude-3-5-haiku-20241022-v1:0":["messages","system","tools"],"amazon.nova-premier-v1:0":["messages","system"],"amazon.nova-pro-v1:0":["messages","system"],"amazon.nova-lite-v1:0":["messages","system"],"amazon.nova-micro-v1:0":["messages","system"]}", + }, + "NetworkConfiguration": { + "NetworkMode": "VPC", + "NetworkModeConfig": { + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "GenericAgentCoreAgentCoreSecurityGroup288CC66C", + "GroupId", + ], + }, + ], + "Subnets": [ + "subnet-12345678", + "subnet-87654321", + ], + }, + }, + "ProtocolConfiguration": "HTTP", + "RoleArn": { + "Fn::GetAtt": [ + "GenericAgentCoreAgentCoreRuntimeRoleF34B80EA", + "Arn", + ], + }, + }, + "Type": "AWS::BedrockAgentCore::Runtime", + }, + "GenericAgentCoreAgentCoreFileBucket0430DA42": { + "DeletionPolicy": "Delete", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "AES256", + }, + }, + ], + }, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true, + }, + "Tags": [ + { + "Key": "aws-cdk:auto-delete-objects", + "Value": "true", + }, + ], + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + }, + "GenericAgentCoreAgentCoreFileBucketAutoDeleteObjectsCustomResourceC1B4B54D": { + "DeletionPolicy": "Delete", + "DependsOn": [ + "GenericAgentCoreAgentCoreFileBucketPolicyB659200A", + ], + "Properties": { + "BucketName": { + "Ref": "GenericAgentCoreAgentCoreFileBucket0430DA42", + }, + "ServiceToken": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F", + "Arn", + ], + }, + }, + "Type": "Custom::S3AutoDeleteObjects", + "UpdateReplacePolicy": "Delete", + }, + "GenericAgentCoreAgentCoreFileBucketPolicyB659200A": { + "Properties": { + "Bucket": { + "Ref": "GenericAgentCoreAgentCoreFileBucket0430DA42", + }, + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:PutBucketPolicy", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn", + ], + }, + }, + "Resource": [ + { + "Fn::GetAtt": [ + "GenericAgentCoreAgentCoreFileBucket0430DA42", + "Arn", + ], + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "GenericAgentCoreAgentCoreFileBucket0430DA42", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::S3::BucketPolicy", + }, + "GenericAgentCoreAgentCoreRuntimeRoleDefaultPolicy4E847841": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "bedrock:InvokeModel", + "bedrock:InvokeModelWithResponseStream", + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "BedrockModelInvocation", + }, + { + "Action": "iam:CreateServiceLinkedRole", + "Condition": { + "StringEquals": { + "iam:AWSServiceName": "runtime-identity.bedrock-agentcore.amazonaws.com", + }, + }, + "Effect": "Allow", + "Resource": "arn:aws:iam::*:role/aws-service-role/runtime-identity.bedrock-agentcore.amazonaws.com/AWSServiceRoleForBedrockAgentCoreRuntimeIdentity", + "Sid": "CreateServiceLinkedRole", + }, + { + "Action": [ + "bedrock-agentcore:CreateCodeInterpreter", + "bedrock-agentcore:StartCodeInterpreterSession", + "bedrock-agentcore:InvokeCodeInterpreter", + "bedrock-agentcore:StopCodeInterpreterSession", + "bedrock-agentcore:DeleteCodeInterpreter", + "bedrock-agentcore:ListCodeInterpreters", + "bedrock-agentcore:GetCodeInterpreter", + "bedrock-agentcore:GetCodeInterpreterSession", + "bedrock-agentcore:ListCodeInterpreterSessions", + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "Tools", + }, + { + "Action": [ + "s3:DeleteObject*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging", + "s3:Abort*", + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "GenericAgentCoreAgentCoreFileBucket0430DA42", + "Arn", + ], + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "GenericAgentCoreAgentCoreFileBucket0430DA42", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + { + "Action": [ + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":ecr:us-east-1:123456890123:repository/cdk-hnb659fds-container-assets-123456890123-us-east-1", + ], + ], + }, + }, + { + "Action": "ecr:GetAuthorizationToken", + "Effect": "Allow", + "Resource": "*", + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "GenericAgentCoreAgentCoreRuntimeRoleDefaultPolicy4E847841", + "Roles": [ + { + "Ref": "GenericAgentCoreAgentCoreRuntimeRoleF34B80EA", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "GenericAgentCoreAgentCoreRuntimeRoleF34B80EA": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Condition": { + "ArnLike": { + "aws:SourceArn": "arn:aws:bedrock-agentcore:us-east-1:123456890123:*", + }, + "StringEquals": { + "aws:SourceAccount": "123456890123", + }, + }, + "Effect": "Allow", + "Principal": { + "Service": "bedrock-agentcore.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::IAM::Role", + }, + "GenericAgentCoreAgentCoreSecurityGroup288CC66C": { + "DeletionPolicy": "Retain", + "Properties": { + "GroupDescription": "Security group for AgentCore Runtime", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1", + }, + ], + "Tags": [ + { + "Key": "CleanupReason", + "Value": "AgentCore-Managed-ENI-Dependency", + }, + { + "Key": "CreatedBy", + "Value": "GenU-", + }, + { + "Key": "ManualCleanupRequired", + "Value": "true", + }, + ], + "VpcId": "vpc-12345", + }, + "Type": "AWS::EC2::SecurityGroup", + "UpdateReplacePolicy": "Retain", + }, + "GenericAgentCoreGenericAgentCoreRuntime041F933D": { + "DependsOn": [ + "GenericAgentCoreAgentCoreRuntimeRoleDefaultPolicy4E847841", + "GenericAgentCoreAgentCoreRuntimeRoleF34B80EA", + ], + "Properties": { + "AgentRuntimeArtifact": { + "ContainerConfiguration": { + "ContainerUri": { + "Fn::Sub": "123456890123.dkr.ecr.us-east-1.\${AWS::URLSuffix}/cdk-hnb659fds-container-assets-123456890123-us-east-1:69eb434ee242daa7224dd9fb0f5cf9142a71fe81dac805352a11509e5ecff870", + }, + }, + }, + "AgentRuntimeName": "GenUGenericRuntime", + "EnvironmentVariables": { + "FILE_BUCKET": { + "Ref": "GenericAgentCoreAgentCoreFileBucket0430DA42", + }, + "MCP_SERVERS": "{"time":{"command":"uvx","args":["mcp-server-time"],"metadata":{"category":"Utility","description":"Provides current time and date functionality"}},"aws-knowledge-mcp-server":{"command":"npx","args":["mcp-remote","https://knowledge-mcp.global.api.aws"],"metadata":{"category":"AWS","description":"AWS Knowledge Base MCP server for enterprise knowledge access"}},"awslabs.aws-documentation-mcp-server":{"command":"uvx","args":["awslabs.aws-documentation-mcp-server@latest"],"metadata":{"category":"AWS","description":"Access AWS documentation and guides"}},"awslabs.cdk-mcp-server":{"command":"uvx","args":["awslabs.cdk-mcp-server@latest"],"metadata":{"category":"AWS","description":"AWS CDK code generation and assistance"}},"awslabs.aws-diagram-mcp-server":{"command":"uvx","args":["awslabs.aws-diagram-mcp-server@latest"],"metadata":{"category":"AWS","description":"Generate AWS architecture diagrams"}},"awslabs.nova-canvas-mcp-server":{"command":"uvx","args":["awslabs.nova-canvas-mcp-server@latest"],"env":{"AWS_REGION":"us-east-1"},"metadata":{"category":"AI/ML","description":"Amazon Nova Canvas image generation"}},"tavily-search":{"command":"npx","args":["-y","mcp-remote","https://mcp.tavily.com/mcp/?tavilyApiKey="],"metadata":{"category":"Search","description":"Web search and research capabilities powered by Tavily"}}}", + "SUPPORTED_CACHE_FIELDS": "{"anthropic.claude-opus-4-5-20251101-v1:0":["messages","system","tools"],"anthropic.claude-sonnet-4-5-20250929-v1:0":["messages","system","tools"],"anthropic.claude-haiku-4-5-20251001-v1:0":["messages","system","tools"],"anthropic.claude-opus-4-1-20250805-v1:0":["messages","system","tools"],"anthropic.claude-opus-4-20250514-v1:0":["messages","system","tools"],"anthropic.claude-sonnet-4-20250514-v1:0":["messages","system","tools"],"anthropic.claude-3-7-sonnet-20250219-v1:0":["messages","system","tools"],"anthropic.claude-3-5-haiku-20241022-v1:0":["messages","system","tools"],"amazon.nova-premier-v1:0":["messages","system"],"amazon.nova-pro-v1:0":["messages","system"],"amazon.nova-lite-v1:0":["messages","system"],"amazon.nova-micro-v1:0":["messages","system"]}", + }, + "NetworkConfiguration": { + "NetworkMode": "VPC", + "NetworkModeConfig": { + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "GenericAgentCoreAgentCoreSecurityGroup288CC66C", + "GroupId", + ], + }, + ], + "Subnets": [ + "subnet-12345678", + "subnet-87654321", + ], + }, + }, + "ProtocolConfiguration": "HTTP", + "RoleArn": { + "Fn::GetAtt": [ + "GenericAgentCoreAgentCoreRuntimeRoleF34B80EA", + "Arn", + ], + }, + }, + "Type": "AWS::BedrockAgentCore::Runtime", + }, + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5", + ], + { + "Ref": "BootstrapVersion", + }, + ], + }, + ], + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.", + }, + ], + }, + }, +} +`; + exports[`GenerativeAiUseCases matches the snapshot (closed network mode) 1`] = ` { "Outputs": { diff --git a/packages/cdk/test/generative-ai-use-cases.test.ts b/packages/cdk/test/generative-ai-use-cases.test.ts index 7c90a802f..0f5686035 100644 --- a/packages/cdk/test/generative-ai-use-cases.test.ts +++ b/packages/cdk/test/generative-ai-use-cases.test.ts @@ -209,4 +209,42 @@ describe('GenerativeAiUseCases', () => { }); } }); + + test('matches the snapshot (AgentCore with VPC)', () => { + const app = new cdk.App(); + + const params = processedStackInputSchema.parse({ + ...stackInput, + agentCoreVpcId: 'vpc-12345678', + agentCoreSubnetIds: ['subnet-12345678', 'subnet-87654321'], + isAgentCoreNetworkPrivate: true, + }); + + const { + cloudFrontWafStack, + ragKnowledgeBaseStack, + agentStack, + agentCoreStack, + guardrail, + generativeAiUseCasesStack, + dashboardStack, + } = createStacks(app, params); + + // Create Templates + if ( + !cloudFrontWafStack || + !ragKnowledgeBaseStack || + !agentStack || + !agentCoreStack || + !guardrail || + !generativeAiUseCasesStack || + !dashboardStack + ) { + throw new Error('Not all stacks are created'); + } + const agentCoreTemplate = Template.fromStack(agentCoreStack); + + // Assert + expect(agentCoreTemplate.toJSON()).toMatchSnapshot(); + }); });