55import software .amazon .awscdk .RemovalPolicy ;
66import software .amazon .awscdk .services .ecr .Repository ;
77import software .amazon .awscdk .services .ecr .TagMutability ;
8+ import software .amazon .awscdk .services .events .EventBus ;
89import software .amazon .awscdk .services .iam .*;
910import software .amazon .awscdk .services .lambda .Code ;
1011import software .amazon .awscdk .services .lambda .Function ;
2425 *
2526 * Contains:
2627 * - ECR repository (unicorn-store-spring)
28+ * - EventBus (unicorns)
2729 * - EKS Pod Identity role (unicornstore-eks-pod-role)
28- * - ECS task roles (unicornstore-ecs-task-role, unicornstore-ecs-task-execution-role)
30+ * - ECS Express Mode roles:
31+ * - Infrastructure role (unicornstore-ecs-infrastructure-role)
32+ * - Task execution role (unicornstore-ecs-task-execution-role)
33+ * - Task role (unicornstore-ecs-task-role)
2934 * - Database schema setup (unicorns table)
3035 */
3136public class Unicorn extends Construct {
3237
3338 // ECR Repository
3439 private Repository ecrRepository ;
3540
41+ // EventBus
42+ private EventBus eventBus ;
43+
3644 // EKS Roles
3745 private Role eksPodRole ;
3846
3947 // ECS Roles
48+ private Role ecsInfrastructureRole ;
4049 private Role ecsTaskRole ;
4150 private Role ecsTaskExecutionRole ;
4251
@@ -55,6 +64,11 @@ public Unicorn(final Construct scope, final String id, final UnicornProps props)
5564 .emptyOnDelete (true )
5665 .build ();
5766
67+ // === EVENTBUS ===
68+ this .eventBus = EventBus .Builder .create (this , "UnicornEventBus" )
69+ .eventBusName ("unicorns" )
70+ .build ();
71+
5872 // === EKS ROLES ===
5973 if (props .isEksRolesEnabled ()) {
6074 createEksRoles (props );
@@ -115,9 +129,9 @@ private String loadFile(String filePath) {
115129 * Creates EKS Pod Identity role with permissions for:
116130 * - X-Ray tracing
117131 * - CloudWatch metrics
118- * - Bedrock AI access
119132 * - S3 bucket access
120133 * - Database secrets access
134+ * - EventBridge PutEvents
121135 */
122136 private void createEksRoles (UnicornProps props ) {
123137 ServicePrincipal eksPods = ServicePrincipal .Builder .create ("pods.eks.amazonaws.com" )
@@ -149,9 +163,6 @@ private void createEksRoles(UnicornProps props) {
149163 // CloudWatch Agent
150164 eksPodRole .addManagedPolicy (ManagedPolicy .fromAwsManagedPolicyName ("CloudWatchAgentServerPolicy" ));
151165
152- // Bedrock AI access
153- eksPodRole .addManagedPolicy (ManagedPolicy .fromAwsManagedPolicyName ("AmazonBedrockLimitedAccess" ));
154-
155166 // S3 bucket access (if provided)
156167 if (props .getWorkshopBucket () != null ) {
157168 eksPodRole .addToPolicy (PolicyStatement .Builder .create ()
@@ -169,91 +180,91 @@ private void createEksRoles(UnicornProps props) {
169180 props .getDatabase ().getDatabaseSecret ().grantRead (eksPodRole );
170181 props .getDatabase ().getParamDBConnectionString ().grantRead (eksPodRole );
171182 }
183+
184+ // EventBridge access
185+ eventBus .grantPutEventsTo (eksPodRole );
172186 }
173187
174188 /**
175- * Creates ECS task roles with permissions for:
176- * - X-Ray tracing
177- * - CloudWatch logs
178- * - SSM parameter access
179- * - Database secrets access
189+ * Creates ECS roles for Express Mode (Fargate):
190+ * - Infrastructure role: manages ALB, security groups, auto scaling
191+ * - Task execution role: pulls images, writes logs, injects secrets
192+ * - Task role: app runtime permissions (X-Ray, EventBridge)
180193 */
181194 private void createEcsRoles (UnicornProps props ) {
182- ServicePrincipal ecsTasks = ServicePrincipal . Builder . create ( "ecs-tasks.amazonaws.com" )
183- .build ();
195+ // === ECS Infrastructure Role (for Express Mode) ===
196+ ServicePrincipal ecsService = ServicePrincipal . Builder . create ( "ecs.amazonaws.com" ) .build ();
184197
185- // OpenTelemetry policy for observability
186- PolicyStatement otelPolicy = PolicyStatement .Builder .create ()
187- .effect (Effect .ALLOW )
188- .actions (List .of (
189- "logs:PutLogEvents" , "logs:CreateLogGroup" , "logs:CreateLogStream" ,
190- "logs:DescribeLogStreams" , "logs:DescribeLogGroups" , "logs:PutRetentionPolicy" ,
191- "xray:PutTraceSegments" , "xray:PutTelemetryRecords" ,
192- "xray:GetSamplingRules" , "xray:GetSamplingTargets" , "xray:GetSamplingStatisticSummaries" ,
193- "cloudwatch:PutMetricData" , "ssm:GetParameters"
194- ))
195- .resources (List .of ("*" ))
198+ this .ecsInfrastructureRole = Role .Builder .create (this , "UnicornStoreEcsInfrastructureRole" )
199+ .roleName ("unicornstore-ecs-infrastructure-role" )
200+ .assumedBy (ecsService )
201+ .description ("ECS infrastructure role for Express Mode services" )
196202 .build ();
197203
198- // ECS Task Role
199- this .ecsTaskRole = Role .Builder .create (this , "UnicornStoreEcsTaskRole" )
200- .roleName ("unicornstore-ecs-task-role" )
204+ ecsInfrastructureRole .addManagedPolicy (ManagedPolicy .fromAwsManagedPolicyName (
205+ "service-role/AmazonECSInfrastructureRoleforExpressGatewayServices" ));
206+
207+ // === ECS Task Execution Role ===
208+ ServicePrincipal ecsTasks = ServicePrincipal .Builder .create ("ecs-tasks.amazonaws.com" ).build ();
209+
210+ this .ecsTaskExecutionRole = Role .Builder .create (this , "UnicornStoreEcsTaskExecutionRole" )
211+ .roleName ("unicornstore-ecs-task-execution-role" )
201212 .assumedBy (ecsTasks )
202- .description ("ECS task role for Unicorn Store application " )
213+ .description ("ECS task execution role for pulling images and injecting secrets " )
203214 .build ();
204215
205- ecsTaskRole .addToPolicy (PolicyStatement .Builder .create ()
216+ // Base permissions: ECR pull + CloudWatch logs
217+ ecsTaskExecutionRole .addManagedPolicy (ManagedPolicy .fromAwsManagedPolicyName (
218+ "service-role/AmazonECSTaskExecutionRolePolicy" ));
219+
220+ // Allow creating log groups (not in managed policy)
221+ ecsTaskExecutionRole .addToPolicy (PolicyStatement .Builder .create ()
206222 .effect (Effect .ALLOW )
207- .actions (List .of ("xray:PutTraceSegments " ))
223+ .actions (List .of ("logs:CreateLogGroup " ))
208224 .resources (List .of ("*" ))
209225 .build ());
210226
211- ecsTaskRole .addManagedPolicy (ManagedPolicy .fromAwsManagedPolicyName ("CloudWatchLogsFullAccess" ));
212- ecsTaskRole .addManagedPolicy (ManagedPolicy .fromAwsManagedPolicyName ("AmazonSSMReadOnlyAccess" ));
213- ecsTaskRole .addToPolicy (otelPolicy );
214-
215- // Database secrets access (if provided)
227+ // Database secrets injection at container startup (scoped)
216228 if (props .getDatabase () != null ) {
217- props .getDatabase ().getDatabaseSecret ().grantRead (ecsTaskRole );
218- props .getDatabase ().getSecretPassword ().grantRead (ecsTaskRole );
219- props .getDatabase ().getParamDBConnectionString ().grantRead (ecsTaskRole );
229+ props .getDatabase ().getDatabaseSecret ().grantRead (ecsTaskExecutionRole );
230+ props .getDatabase ().getParamDBConnectionString ().grantRead (ecsTaskExecutionRole );
220231 }
221232
222- // ECS Task Execution Role
223- this .ecsTaskExecutionRole = Role .Builder .create (this , "UnicornStoreEcsTaskExecutionRole " )
224- .roleName ("unicornstore-ecs-task-execution- role" )
233+ // === ECS Task Role (app runtime permissions) ===
234+ this .ecsTaskRole = Role .Builder .create (this , "UnicornStoreEcsTaskRole " )
235+ .roleName ("unicornstore-ecs-task-role" )
225236 .assumedBy (ecsTasks )
226- .description ("ECS task execution role for Unicorn Store application " )
237+ .description ("ECS task role for application runtime permissions " )
227238 .build ();
228239
229- ecsTaskExecutionRole .addToPolicy (PolicyStatement .Builder .create ()
240+ // X-Ray tracing
241+ ecsTaskRole .addToPolicy (PolicyStatement .Builder .create ()
230242 .effect (Effect .ALLOW )
231- .actions (List .of ("logs:CreateLogGroup " ))
243+ .actions (List .of ("xray:PutTraceSegments " ))
232244 .resources (List .of ("*" ))
233245 .build ());
234246
235- ecsTaskExecutionRole .addManagedPolicy (
236- ManagedPolicy .fromAwsManagedPolicyName ("service-role/AmazonECSTaskExecutionRolePolicy" ));
237- ecsTaskExecutionRole .addManagedPolicy (ManagedPolicy .fromAwsManagedPolicyName ("CloudWatchLogsFullAccess" ));
238- ecsTaskExecutionRole .addManagedPolicy (ManagedPolicy .fromAwsManagedPolicyName ("AmazonSSMReadOnlyAccess" ));
239- ecsTaskExecutionRole .addToPolicy (otelPolicy );
240-
241- // Database secrets access for execution role (if provided)
242- if (props .getDatabase () != null ) {
243- props .getDatabase ().getDatabaseSecret ().grantRead (ecsTaskExecutionRole );
244- props .getDatabase ().getSecretPassword ().grantRead (ecsTaskExecutionRole );
245- }
247+ // EventBridge access
248+ eventBus .grantPutEventsTo (ecsTaskRole );
246249 }
247250
248251 // Getters
249252 public Repository getEcrRepository () {
250253 return ecrRepository ;
251254 }
252255
256+ public EventBus getEventBus () {
257+ return eventBus ;
258+ }
259+
253260 public Role getEksPodRole () {
254261 return eksPodRole ;
255262 }
256263
264+ public Role getEcsInfrastructureRole () {
265+ return ecsInfrastructureRole ;
266+ }
267+
257268 public Role getEcsTaskRole () {
258269 return ecsTaskRole ;
259270 }
0 commit comments