@@ -5,12 +5,13 @@ import {
55 Role ,
66 ServicePrincipal ,
77} from 'aws-cdk-lib/aws-iam' ;
8- import { Stack , RemovalPolicy } from 'aws-cdk-lib' ;
8+ import { Stack , RemovalPolicy , Tags } from 'aws-cdk-lib' ;
99import {
1010 Bucket ,
1111 BlockPublicAccess ,
1212 BucketEncryption ,
1313} from 'aws-cdk-lib/aws-s3' ;
14+ import { Subnet , Vpc , SecurityGroup , IVpc , ISubnet } from 'aws-cdk-lib/aws-ec2' ;
1415import {
1516 Runtime ,
1617 RuntimeNetworkConfiguration ,
@@ -37,6 +38,10 @@ export interface GenericAgentCoreProps {
3738 env : string ;
3839 createGenericRuntime ?: boolean ;
3940 createAgentBuilderRuntime ?: boolean ;
41+ agentCoreNetworkType : 'PUBLIC' | 'PRIVATE' ;
42+ agentCoreVpcId ?: string | null ;
43+ agentCoreSubnetIds ?: string [ ] | null ;
44+ agentCoreEnvironmentVariables ?: Record < string , string > ;
4045}
4146
4247interface RuntimeResources {
@@ -51,13 +56,20 @@ export class GenericAgentCore extends Construct {
5156 private readonly agentBuilderRuntimeConfig : AgentCoreRuntimeConfig ;
5257 private readonly resources : RuntimeResources ;
5358
59+ // Security Group ID that requires manual cleanup after AgentCore Runtime deletion
60+ // Used for CloudFormation Output to remind users of manual cleanup tasks
61+ public readonly retainedSecurityGroupId ?: string ;
62+
5463 constructor ( scope : Construct , id : string , props : GenericAgentCoreProps ) {
5564 super ( scope , id ) ;
5665
5766 const {
5867 env,
5968 createGenericRuntime = false ,
6069 createAgentBuilderRuntime = false ,
70+ agentCoreNetworkType = 'PUBLIC' ,
71+ agentCoreVpcId = null ,
72+ agentCoreSubnetIds = null ,
6173 } = props ;
6274
6375 // Create bucket first
@@ -68,10 +80,56 @@ export class GenericAgentCore extends Construct {
6880 this . genericRuntimeConfig = configs . generic ;
6981 this . agentBuilderRuntimeConfig = configs . agentBuilder ;
7082
83+ // Create security group if VPC mode
84+ let securityGroup : SecurityGroup | undefined ;
85+ let vpc : IVpc | undefined ;
86+ let subnets : ISubnet [ ] | undefined ;
87+
88+ if (
89+ agentCoreNetworkType === 'PRIVATE' &&
90+ agentCoreVpcId &&
91+ agentCoreSubnetIds
92+ ) {
93+ vpc = Vpc . fromLookup ( this , 'AgentCoreVpc' , { vpcId : agentCoreVpcId } ) ;
94+ subnets = agentCoreSubnetIds . map ( ( subnetId , index ) =>
95+ Subnet . fromSubnetId ( this , `AgentCoreSubnet${ index } ` , subnetId )
96+ ) ;
97+ securityGroup = new SecurityGroup ( this , 'AgentCoreSecurityGroup' , {
98+ vpc,
99+ description : 'Security group for AgentCore Runtime' ,
100+ allowAllOutbound : true ,
101+ } ) ;
102+
103+ // Add tags for manual cleanup identification
104+ Tags . of ( securityGroup ) . add ( 'ManualCleanupRequired' , 'true' ) ;
105+ Tags . of ( securityGroup ) . add (
106+ 'CleanupReason' ,
107+ 'AgentCore-Managed-ENI-Dependency'
108+ ) ;
109+ Tags . of ( securityGroup ) . add ( 'CreatedBy' , `GenU-${ env } ` ) ;
110+
111+ // Retain security group to prevent deletion errors when changing PRIVATE->PUBLIC or removing AgentCore
112+ // AgentCore Runtime creates managed ENIs that reference this security group
113+ // CloudFormation cannot delete the SG while managed ENIs are using it (even though they're not manually deletable)
114+ // The managed ENIs are automatically cleaned up after AgentCore Runtime deletion, but with a time delay
115+ // Therefore, this SG must be retained and manually deleted after the managed ENIs are cleaned up
116+ //
117+ // Note: Custom Resource with ENI monitoring could solve this, but deletion can take up to 1 hour
118+ // Since security groups incur no cost, RETAIN is the practical solution for better user experience
119+ securityGroup . applyRemovalPolicy ( RemovalPolicy . RETAIN ) ;
120+
121+ // Store SG ID for output
122+ this . retainedSecurityGroupId = securityGroup . securityGroupId ;
123+ }
124+
71125 // Create all resources atomically
72126 this . resources = this . createResources (
73127 createGenericRuntime ,
74- createAgentBuilderRuntime
128+ createAgentBuilderRuntime ,
129+ agentCoreNetworkType ,
130+ vpc ,
131+ subnets ,
132+ securityGroup
75133 ) ;
76134 }
77135
@@ -125,7 +183,11 @@ export class GenericAgentCore extends Construct {
125183
126184 private createResources (
127185 createGeneric : boolean ,
128- createAgentBuilder : boolean
186+ createAgentBuilder : boolean ,
187+ agentCoreNetworkType : 'PUBLIC' | 'PRIVATE' ,
188+ vpc ?: IVpc ,
189+ subnets ?: ISubnet [ ] ,
190+ securityGroup ?: SecurityGroup
129191 ) : RuntimeResources {
130192 if ( ! createGeneric && ! createAgentBuilder ) {
131193 return { role : this . createExecutionRole ( ) } ;
@@ -138,15 +200,23 @@ export class GenericAgentCore extends Construct {
138200 resources . genericRuntime = this . createRuntime (
139201 'Generic' ,
140202 this . genericRuntimeConfig ,
141- role
203+ role ,
204+ agentCoreNetworkType ,
205+ vpc ,
206+ subnets ,
207+ securityGroup
142208 ) ;
143209 }
144210
145211 if ( createAgentBuilder ) {
146212 resources . agentBuilderRuntime = this . createRuntime (
147213 'AgentBuilder' ,
148214 this . agentBuilderRuntimeConfig ,
149- role
215+ role ,
216+ agentCoreNetworkType ,
217+ vpc ,
218+ subnets ,
219+ securityGroup
150220 ) ;
151221 }
152222
@@ -157,20 +227,54 @@ export class GenericAgentCore extends Construct {
157227 private createRuntime (
158228 type : string ,
159229 config : AgentCoreRuntimeConfig ,
160- role : Role
230+ role : Role ,
231+ agentCoreNetworkType : 'PUBLIC' | 'PRIVATE' ,
232+ vpc ?: IVpc ,
233+ subnets ?: ISubnet [ ] ,
234+ securityGroup ?: SecurityGroup
161235 ) : Runtime {
236+ const networkConfig = this . createNetworkConfiguration (
237+ agentCoreNetworkType ,
238+ vpc ,
239+ subnets ,
240+ securityGroup
241+ ) ;
242+
162243 return new Runtime ( this , `${ type } AgentCoreRuntime` , {
163244 runtimeName : config . name ,
164245 agentRuntimeArtifact : AgentRuntimeArtifact . fromAsset (
165246 path . join ( __dirname , `../../${ config . dockerPath } ` )
166247 ) ,
167248 executionRole : role ,
168- networkConfiguration : RuntimeNetworkConfiguration . usingPublicNetwork ( ) ,
249+ networkConfiguration : networkConfig ,
169250 protocolConfiguration : ProtocolType . HTTP ,
170251 environmentVariables : config . environmentVariables ,
171252 } ) ;
172253 }
173254
255+ private createNetworkConfiguration (
256+ agentCoreNetworkType : 'PUBLIC' | 'PRIVATE' ,
257+ vpc ?: IVpc ,
258+ subnets ?: ISubnet [ ] ,
259+ securityGroup ?: SecurityGroup
260+ ) : RuntimeNetworkConfiguration {
261+ if ( agentCoreNetworkType === 'PRIVATE' ) {
262+ if ( ! vpc || ! subnets ) {
263+ throw new Error (
264+ 'VPC and Subnets are required for PRIVATE network type'
265+ ) ;
266+ }
267+
268+ return RuntimeNetworkConfiguration . usingVpc ( this , {
269+ vpc,
270+ vpcSubnets : { subnets } ,
271+ securityGroups : securityGroup ? [ securityGroup ] : undefined ,
272+ } ) ;
273+ } else {
274+ return RuntimeNetworkConfiguration . usingPublicNetwork ( ) ;
275+ }
276+ }
277+
174278 private createExecutionRole ( ) : Role {
175279 const region = Stack . of ( this ) . region ;
176280 const accountId = Stack . of ( this ) . account ;
0 commit comments