@venturekit/infra 0.0.37 → 0.0.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -331,8 +331,24 @@ export interface VentureAppStackProps extends cdk.StackProps {
331
331
  * declare only data + messaging.
332
332
  */
333
333
  export declare class VentureAppStack extends cdk.Stack {
334
- /** Shared execution role for every Lambda in this stack. */
334
+ /**
335
+ * Shared execution role for every Lambda in this stack — except, when
336
+ * an auth intent declares `adminRoutes`, the Lambdas that match: those
337
+ * run as {@link authAdminRole}. This role then carries NO Cognito
338
+ * admin grants.
339
+ */
335
340
  readonly lambdaRole: iam.Role;
341
+ /**
342
+ * Execution role carrying the Cognito admin grants
343
+ * (`AdminCreateUser`, `AdminSetUserPassword`, …) plus everything
344
+ * {@link lambdaRole} has. Created only when at least one auth intent
345
+ * declares `adminRoutes`; attached to the route / function Lambdas
346
+ * those patterns select. Undefined otherwise — the historical
347
+ * single-role layout, where the shared role holds the admin grants.
348
+ */
349
+ readonly authAdminRole?: iam.Role;
350
+ /** `adminRoutes` patterns collected across every auth intent. */
351
+ private readonly adminRoutePatterns;
336
352
  /** Shared SG when a VPC is imported; undefined for VPC-less projects. */
337
353
  readonly lambdaSecurityGroup?: ec2.SecurityGroup;
338
354
  /** Internal HMAC signing secret for Lambda-to-Lambda `invoke()`. */
@@ -453,12 +469,31 @@ export declare class VentureAppStack extends cdk.Stack {
453
469
  * cross-stack pattern works the same as in-stack.
454
470
  */
455
471
  private createRouteFunction;
472
+ /**
473
+ * Every execution role in this stack: the shared role, plus the
474
+ * auth-admin role when one exists. Baseline grants (secrets, S3,
475
+ * SES, rate-limit table, …) are applied to all of them.
476
+ */
477
+ private get executionRoles();
478
+ /**
479
+ * Roles that receive the Cognito admin grants: only the auth-admin
480
+ * role when `adminRoutes` are declared, otherwise the shared role
481
+ * (historical layout).
482
+ */
483
+ private get cognitoAdminRoles();
484
+ /** Apply one grant to every execution role. */
485
+ private grantEachRole;
486
+ /** The role a route Lambda runs as — auth-admin when its API path matches `adminRoutes`. */
487
+ private roleForRoute;
488
+ /** The role a standalone / queue / schedule Lambda runs as — auth-admin when `fn:<id>` matches. */
489
+ private roleForFunction;
456
490
  /**
457
491
  * Build a Lambda function with the standard app-stack properties
458
492
  * (shared role, VPC attachment, base env vars, log retention).
459
493
  * Used by `createRouteFunction` (now refactored), `createQueueConsumer`,
460
494
  * and `createScheduleHandler` to keep the per-Lambda invariants in
461
- * one place.
495
+ * one place. Pass `role` in `overrides` (see `roleForFunction`) to
496
+ * run as the auth-admin role.
462
497
  */
463
498
  private buildLambda;
464
499
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"app-stack.d.ts","sourceRoot":"","sources":["../../src/cdk/app-stack.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app-stack.d.ts","sourceRoot":"","sources":["../../src/cdk/app-stack.ts"],"names":[],"mappings":"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"}
@@ -87,6 +87,7 @@ import * as cloudwatchActions from 'aws-cdk-lib/aws-cloudwatch-actions';
87
87
  import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
88
88
  import * as logs from 'aws-cdk-lib/aws-logs';
89
89
  import { DATA_SAFETY_CONFIG, isExternalAuthIntent, } from '@venturekit/core';
90
+ import { collectAdminRoutePatterns, matchesAdminRoute } from './shared/admin-routes.js';
90
91
  import { readSsmString } from './shared/cross-stack-refs.js';
91
92
  import { grantCustomSenderInvoke, resolveCustomSender } from './shared/custom-sender.js';
92
93
  import { networkKeys, edgeKeys, identityKeys } from './shared/ssm-keys.js';
@@ -151,8 +152,24 @@ function fnv1a32(input) {
151
152
  * declare only data + messaging.
152
153
  */
153
154
  export class VentureAppStack extends cdk.Stack {
154
- /** Shared execution role for every Lambda in this stack. */
155
+ /**
156
+ * Shared execution role for every Lambda in this stack — except, when
157
+ * an auth intent declares `adminRoutes`, the Lambdas that match: those
158
+ * run as {@link authAdminRole}. This role then carries NO Cognito
159
+ * admin grants.
160
+ */
155
161
  lambdaRole;
162
+ /**
163
+ * Execution role carrying the Cognito admin grants
164
+ * (`AdminCreateUser`, `AdminSetUserPassword`, …) plus everything
165
+ * {@link lambdaRole} has. Created only when at least one auth intent
166
+ * declares `adminRoutes`; attached to the route / function Lambdas
167
+ * those patterns select. Undefined otherwise — the historical
168
+ * single-role layout, where the shared role holds the admin grants.
169
+ */
170
+ authAdminRole;
171
+ /** `adminRoutes` patterns collected across every auth intent. */
172
+ adminRoutePatterns;
156
173
  /** Shared SG when a VPC is imported; undefined for VPC-less projects. */
157
174
  lambdaSecurityGroup;
158
175
  /** Internal HMAC signing secret for Lambda-to-Lambda `invoke()`. */
@@ -307,11 +324,38 @@ export class VentureAppStack extends cdk.Stack {
307
324
  iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
308
325
  ],
309
326
  });
327
+ // ─── Auth-admin execution role (opt-in) ────────────────────
328
+ // Cognito admin actions (create users, set passwords, edit
329
+ // attributes) are the most powerful grants any route Lambda gets.
330
+ // On the single shared role, an SSRF / RCE in ANY public route
331
+ // yields them. When an auth intent names the Lambdas that need them
332
+ // (`adminRoutes`), they move to this second role and the shared
333
+ // role keeps everything else. Two roles, not one per Lambda —
334
+ // CFN's 500-resource limit on large projects is the reason the
335
+ // per-function layout was never adopted.
336
+ this.adminRoutePatterns = collectAdminRoutePatterns(infrastructure.auth ?? []);
337
+ if (this.adminRoutePatterns.length > 0) {
338
+ this.authAdminRole = new iam.Role(this, 'AuthAdminRole', {
339
+ assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
340
+ description: `Execution role for ${projectName}-${stage} Lambdas allowed to call Cognito admin APIs (auth[].adminRoutes)`,
341
+ managedPolicies: [
342
+ iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
343
+ ],
344
+ });
345
+ }
346
+ else if ((infrastructure.auth ?? []).length > 0) {
347
+ cdk.Annotations.of(this).addWarning(`[venturekit] auth intents declare no 'adminRoutes': every Lambda in ${projectName}/${stage} ` +
348
+ `shares one role that can create users, set passwords and edit attributes on the user pool. ` +
349
+ `List the routes that call @venturekit/auth/server admin flows (e.g. adminRoutes: ['/auth/**']) ` +
350
+ `to confine those grants to them.`);
351
+ }
310
352
  if (this.vpc) {
311
353
  // VPC-attached Lambdas need ENI-creation rights on cold start.
312
354
  // Bolted on conditionally so VPC-less projects don't carry the
313
355
  // unused managed policy.
314
- this.lambdaRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaVPCAccessExecutionRole'));
356
+ for (const role of this.executionRoles) {
357
+ role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaVPCAccessExecutionRole'));
358
+ }
315
359
  this.lambdaSecurityGroup = new ec2.SecurityGroup(this, 'LambdaSg', {
316
360
  vpc: this.vpc,
317
361
  description: `Shared SG for ${projectName}-${stage} Lambdas`,
@@ -404,10 +448,21 @@ export class VentureAppStack extends cdk.Stack {
404
448
  // UpdateItem is denied at runtime and every rate-limited route
405
449
  // fails closed.
406
450
  VENTURE_RATE_LIMIT_TABLE: `${projectName}-rate-limits-${stage}`,
407
- // Inline value, not ARN. Lambdas read it directly from
408
- // env without hitting Secrets Manager — zero cold-start cost
409
- // for HMAC verification.
410
- VENTURE_INTERNAL_HMAC_SECRET: this.internalHmacSecret.secretValue.unsafeUnwrap(),
451
+ // ARN, not the inline value. `@venturekit/runtime` fetches the
452
+ // key once per container (`primeInternalHmacSecret`) and caches
453
+ // it in memory — one Secrets Manager call per cold start, same
454
+ // as the DB credentials. Inlining the value (the former
455
+ // `secretValue.unsafeUnwrap()`) put the signing key in every
456
+ // function's environment block, readable by anyone with
457
+ // `lambda:GetFunctionConfiguration`. The matching
458
+ // `secretsmanager:GetSecretValue` grant is in applyImportedGrants.
459
+ VENTURE_INTERNAL_HMAC_SECRET_ARN: this.internalHmacSecret.secretArn,
460
+ // The runtime tables the data stack always provisions, named by
461
+ // the same `${project}-<suffix>-${stage}` convention the runtime
462
+ // reconstructs when the env var is absent. Injected explicitly so
463
+ // resolution never depends on the fallback.
464
+ VENTURE_IDEMPOTENCY_TABLE: `${projectName}-idempotency-${stage}`,
465
+ VENTURE_SAGA_TABLE: `${projectName}-sagas-${stage}`,
411
466
  // `--enable-source-maps` makes thrown errors point to the
412
467
  // .ts source instead of bundled .js. Free win in CloudWatch.
413
468
  NODE_OPTIONS: '--enable-source-maps',
@@ -1013,7 +1068,7 @@ export class VentureAppStack extends cdk.Stack {
1013
1068
  ? lambda.Tracing.ACTIVE
1014
1069
  : lambda.Tracing.DISABLED,
1015
1070
  reservedConcurrentExecutions: lambdaConfig.reservedConcurrency,
1016
- role: this.lambdaRole,
1071
+ role: this.roleForRoute(route.apiPath),
1017
1072
  // Attach to the VPC iff one exists. Route Lambdas without a VPC
1018
1073
  // can't reach RDS, but the free preset and DB-less projects
1019
1074
  // don't need one — paying ENI cold-start tax there would be
@@ -1043,12 +1098,48 @@ export class VentureAppStack extends cdk.Stack {
1043
1098
  integration: new apigatewayv2Integrations.HttpLambdaIntegration(`${resourceId}-integration`, fn),
1044
1099
  });
1045
1100
  }
1101
+ /**
1102
+ * Every execution role in this stack: the shared role, plus the
1103
+ * auth-admin role when one exists. Baseline grants (secrets, S3,
1104
+ * SES, rate-limit table, …) are applied to all of them.
1105
+ */
1106
+ get executionRoles() {
1107
+ return this.authAdminRole ? [this.lambdaRole, this.authAdminRole] : [this.lambdaRole];
1108
+ }
1109
+ /**
1110
+ * Roles that receive the Cognito admin grants: only the auth-admin
1111
+ * role when `adminRoutes` are declared, otherwise the shared role
1112
+ * (historical layout).
1113
+ */
1114
+ get cognitoAdminRoles() {
1115
+ return this.authAdminRole ? [this.authAdminRole] : [this.lambdaRole];
1116
+ }
1117
+ /** Apply one grant to every execution role. */
1118
+ grantEachRole(grant) {
1119
+ for (const role of this.executionRoles)
1120
+ grant(role);
1121
+ }
1122
+ /** The role a route Lambda runs as — auth-admin when its API path matches `adminRoutes`. */
1123
+ roleForRoute(apiPath) {
1124
+ if (this.authAdminRole && matchesAdminRoute(this.adminRoutePatterns, { apiPath })) {
1125
+ return this.authAdminRole;
1126
+ }
1127
+ return this.lambdaRole;
1128
+ }
1129
+ /** The role a standalone / queue / schedule Lambda runs as — auth-admin when `fn:<id>` matches. */
1130
+ roleForFunction(id) {
1131
+ if (this.authAdminRole && matchesAdminRoute(this.adminRoutePatterns, { functionId: id })) {
1132
+ return this.authAdminRole;
1133
+ }
1134
+ return this.lambdaRole;
1135
+ }
1046
1136
  /**
1047
1137
  * Build a Lambda function with the standard app-stack properties
1048
1138
  * (shared role, VPC attachment, base env vars, log retention).
1049
1139
  * Used by `createRouteFunction` (now refactored), `createQueueConsumer`,
1050
1140
  * and `createScheduleHandler` to keep the per-Lambda invariants in
1051
- * one place.
1141
+ * one place. Pass `role` in `overrides` (see `roleForFunction`) to
1142
+ * run as the auth-admin role.
1052
1143
  */
1053
1144
  buildLambda(resourceId, functionName, handlerFile, description, overrides) {
1054
1145
  const lambdaConfig = this.envConfig.lambda;
@@ -1114,6 +1205,7 @@ export class VentureAppStack extends cdk.Stack {
1114
1205
  const fn = this.buildLambda(resourceId, functionName, handlerFile, intent?.description ?? `Queue consumer: ${name}`, {
1115
1206
  memorySize: intent?.memorySize ?? this.envConfig.lambda.memoryMb,
1116
1207
  timeout: cdk.Duration.seconds(intent?.timeout ?? this.envConfig.lambda.timeoutSec),
1208
+ role: this.roleForFunction(name),
1117
1209
  });
1118
1210
  this.queueLambdas.set(name, fn);
1119
1211
  // `Queue.fromQueueArn` produces an `IQueue` that supports
@@ -1157,6 +1249,7 @@ export class VentureAppStack extends cdk.Stack {
1157
1249
  timeout: cdk.Duration.seconds(intent?.timeout ?? 60),
1158
1250
  deadLetterQueue: dlq,
1159
1251
  deadLetterQueueEnabled: true,
1252
+ role: this.roleForFunction(name),
1160
1253
  });
1161
1254
  this.scheduleLambdas.set(name, fn);
1162
1255
  // Optional EventBridge rule. When `intent` is undefined the
@@ -1195,6 +1288,7 @@ export class VentureAppStack extends cdk.Stack {
1195
1288
  const fn = this.buildLambda(resourceId, functionName, handlerFile, intent?.description ?? `Standalone function: ${name}`, {
1196
1289
  memorySize: intent?.memorySize ?? this.envConfig.lambda.memoryMb,
1197
1290
  timeout: cdk.Duration.seconds(intent?.timeout ?? this.envConfig.lambda.timeoutSec),
1291
+ role: this.roleForFunction(name),
1198
1292
  });
1199
1293
  this.functionLambdas.set(name, fn);
1200
1294
  }
@@ -1458,14 +1552,27 @@ export class VentureAppStack extends cdk.Stack {
1458
1552
  // `AccessDeniedException: not authorized to perform
1459
1553
  // dynamodb:UpdateItem` and every rate-limited route fails closed.
1460
1554
  const rateLimitTable = dynamodb.Table.fromTableName(this, 'imported-rate-limit-table', `${this.projectName}-rate-limits-${this.stage}`);
1461
- rateLimitTable.grantReadWriteData(this.lambdaRole);
1555
+ this.grantEachRole((role) => rateLimitTable.grantReadWriteData(role));
1556
+ // ── Idempotency + saga DDB tables (ALWAYS, same rationale) ─
1557
+ // Provisioned by the data stack beside the rate-limits table;
1558
+ // `VENTURE_IDEMPOTENCY_TABLE` / `VENTURE_SAGA_TABLE` in baseEnvVars
1559
+ // name them.
1560
+ const idempotencyTable = dynamodb.Table.fromTableName(this, 'imported-idempotency-table', `${this.projectName}-idempotency-${this.stage}`);
1561
+ this.grantEachRole((role) => idempotencyTable.grantReadWriteData(role));
1562
+ const sagaTable = dynamodb.Table.fromTableName(this, 'imported-saga-table', `${this.projectName}-sagas-${this.stage}`);
1563
+ this.grantEachRole((role) => sagaTable.grantReadWriteData(role));
1564
+ // ── Internal HMAC signing key (ALWAYS) ─────────────────────
1565
+ // `@venturekit/runtime` resolves `VENTURE_INTERNAL_HMAC_SECRET_ARN`
1566
+ // once per container; without read access it fails closed and
1567
+ // treats every call as external.
1568
+ this.grantEachRole((role) => this.internalHmacSecret.grantRead(role));
1462
1569
  // ── DB secrets ────────────────────────────────────────────
1463
1570
  if (imports.databaseEndpointsById) {
1464
1571
  for (const [id, db] of imports.databaseEndpointsById) {
1465
1572
  if (!db.secretArn)
1466
1573
  continue;
1467
1574
  const importedSecret = secretsmanager.Secret.fromSecretCompleteArn(this, `imported-db-secret-${id}`, db.secretArn);
1468
- importedSecret.grantRead(this.lambdaRole);
1575
+ this.grantEachRole((role) => importedSecret.grantRead(role));
1469
1576
  }
1470
1577
  }
1471
1578
  // ── Federated provider secrets ────────────────────────────
@@ -1473,7 +1580,7 @@ export class VentureAppStack extends cdk.Stack {
1473
1580
  for (const [authId, providers] of imports.federatedSecretArnsByAuthId) {
1474
1581
  for (const [provider, arn] of Object.entries(providers)) {
1475
1582
  const importedSecret = secretsmanager.Secret.fromSecretCompleteArn(this, `imported-fed-secret-${authId}-${provider}`, arn);
1476
- importedSecret.grantRead(this.lambdaRole);
1583
+ this.grantEachRole((role) => importedSecret.grantRead(role));
1477
1584
  }
1478
1585
  }
1479
1586
  }
@@ -1487,33 +1594,33 @@ export class VentureAppStack extends cdk.Stack {
1487
1594
  // import — an SSM-fallback ARN is a deploy-time token that renders
1488
1595
  // as the parameter's value in the resulting IAM statement.
1489
1596
  for (const [authId, keyArn] of this.customSenderKeyArns) {
1490
- kms.Key.fromKeyArn(this, `imported-custom-sender-key-${authId}`, keyArn)
1491
- .grantDecrypt(this.lambdaRole);
1597
+ const key = kms.Key.fromKeyArn(this, `imported-custom-sender-key-${authId}`, keyArn);
1598
+ this.grantEachRole((role) => key.grantDecrypt(role));
1492
1599
  }
1493
1600
  // ── WhatsApp token + webhook secrets ───────────────────────
1494
1601
  if (imports.whatsappTokenSecretArnsById) {
1495
1602
  for (const [id, arn] of imports.whatsappTokenSecretArnsById) {
1496
1603
  const importedSecret = secretsmanager.Secret.fromSecretCompleteArn(this, `imported-whatsapp-secret-${id}`, arn);
1497
- importedSecret.grantRead(this.lambdaRole);
1604
+ this.grantEachRole((role) => importedSecret.grantRead(role));
1498
1605
  }
1499
1606
  }
1500
1607
  if (imports.whatsappAppSecretArnsById) {
1501
1608
  for (const [id, arn] of imports.whatsappAppSecretArnsById) {
1502
1609
  const importedSecret = secretsmanager.Secret.fromSecretCompleteArn(this, `imported-whatsapp-app-secret-${id}`, arn);
1503
- importedSecret.grantRead(this.lambdaRole);
1610
+ this.grantEachRole((role) => importedSecret.grantRead(role));
1504
1611
  }
1505
1612
  }
1506
1613
  if (imports.whatsappVerifyTokenSecretArnsById) {
1507
1614
  for (const [id, arn] of imports.whatsappVerifyTokenSecretArnsById) {
1508
1615
  const importedSecret = secretsmanager.Secret.fromSecretCompleteArn(this, `imported-whatsapp-verify-token-${id}`, arn);
1509
- importedSecret.grantRead(this.lambdaRole);
1616
+ this.grantEachRole((role) => importedSecret.grantRead(role));
1510
1617
  }
1511
1618
  }
1512
1619
  // ── Billing provider credential secrets ───────────────
1513
1620
  if (imports.billingSecretArnsById) {
1514
1621
  for (const [id, arn] of imports.billingSecretArnsById) {
1515
1622
  const importedSecret = secretsmanager.Secret.fromSecretCompleteArn(this, `imported-billing-secret-${id}`, arn);
1516
- importedSecret.grantRead(this.lambdaRole);
1623
+ this.grantEachRole((role) => importedSecret.grantRead(role));
1517
1624
  }
1518
1625
  }
1519
1626
  // ── Operator-managed secrets (secrets intents) ────────
@@ -1523,38 +1630,42 @@ export class VentureAppStack extends cdk.Stack {
1523
1630
  if (imports.secretArnsById) {
1524
1631
  for (const [id, arn] of imports.secretArnsById) {
1525
1632
  const importedSecret = secretsmanager.Secret.fromSecretCompleteArn(this, `imported-config-secret-${id}`, arn);
1526
- importedSecret.grantRead(this.lambdaRole);
1633
+ this.grantEachRole((role) => importedSecret.grantRead(role));
1527
1634
  }
1528
1635
  }
1529
1636
  // ── S3 buckets (read+write) ──────────────────────────────
1530
1637
  if (imports.storageBucketNamesById) {
1531
1638
  for (const [id, bucketName] of imports.storageBucketNamesById) {
1532
1639
  const bucket = s3.Bucket.fromBucketName(this, `imported-bucket-${id}`, bucketName);
1533
- bucket.grantReadWrite(this.lambdaRole);
1640
+ this.grantEachRole((role) => bucket.grantReadWrite(role));
1534
1641
  }
1535
1642
  }
1536
1643
  // ── Cognito admin actions per user pool ──────────────────
1644
+ // Attached to the auth-admin role only when `adminRoutes` are
1645
+ // declared (see the constructor), otherwise to the shared role.
1537
1646
  if (imports.userPoolArnsByAuthId) {
1538
1647
  for (const [, poolArn] of imports.userPoolArnsByAuthId) {
1539
1648
  // Scope to specific Cognito admin actions used by
1540
1649
  // `@venturekit/auth/server`'s federated sign-in path.
1541
1650
  // Avoid `cognito-idp:*` — overly broad, includes pool
1542
1651
  // delete/update which Lambdas should never do.
1543
- this.lambdaRole.addToPrincipalPolicy(new iam.PolicyStatement({
1544
- effect: iam.Effect.ALLOW,
1545
- actions: [
1546
- 'cognito-idp:AdminCreateUser',
1547
- 'cognito-idp:AdminDeleteUser',
1548
- 'cognito-idp:AdminGetUser',
1549
- 'cognito-idp:AdminConfirmSignUp',
1550
- 'cognito-idp:AdminInitiateAuth',
1551
- 'cognito-idp:AdminSetUserPassword',
1552
- 'cognito-idp:AdminUpdateUserAttributes',
1553
- 'cognito-idp:AdminLinkProviderForUser',
1554
- 'cognito-idp:ListUsers',
1555
- ],
1556
- resources: [poolArn],
1557
- }));
1652
+ for (const role of this.cognitoAdminRoles) {
1653
+ role.addToPrincipalPolicy(new iam.PolicyStatement({
1654
+ effect: iam.Effect.ALLOW,
1655
+ actions: [
1656
+ 'cognito-idp:AdminCreateUser',
1657
+ 'cognito-idp:AdminDeleteUser',
1658
+ 'cognito-idp:AdminGetUser',
1659
+ 'cognito-idp:AdminConfirmSignUp',
1660
+ 'cognito-idp:AdminInitiateAuth',
1661
+ 'cognito-idp:AdminSetUserPassword',
1662
+ 'cognito-idp:AdminUpdateUserAttributes',
1663
+ 'cognito-idp:AdminLinkProviderForUser',
1664
+ 'cognito-idp:ListUsers',
1665
+ ],
1666
+ resources: [poolArn],
1667
+ }));
1668
+ }
1558
1669
  }
1559
1670
  }
1560
1671
  // ── SES send ─────────────────────────────────────────────
@@ -1564,11 +1675,11 @@ export class VentureAppStack extends cdk.Stack {
1564
1675
  // because SES doesn't support resource-level scoping for
1565
1676
  // SendEmail.
1566
1677
  if (imports.notifyConfigurationSetNamesById && imports.notifyConfigurationSetNamesById.size > 0) {
1567
- this.lambdaRole.addToPrincipalPolicy(new iam.PolicyStatement({
1678
+ this.grantEachRole((role) => role.addToPrincipalPolicy(new iam.PolicyStatement({
1568
1679
  effect: iam.Effect.ALLOW,
1569
1680
  actions: ['ses:SendEmail', 'ses:SendRawEmail'],
1570
1681
  resources: ['*'],
1571
- }));
1682
+ })));
1572
1683
  }
1573
1684
  // ── SNS publish on notify events topics ─────────────────
1574
1685
  // Used by the dispatcher to publish synthetic events when an
@@ -1579,11 +1690,11 @@ export class VentureAppStack extends cdk.Stack {
1579
1690
  if (imports.notifyEventsTopicArnsById) {
1580
1691
  const topicArns = [...imports.notifyEventsTopicArnsById.values()];
1581
1692
  if (topicArns.length > 0) {
1582
- this.lambdaRole.addToPrincipalPolicy(new iam.PolicyStatement({
1693
+ this.grantEachRole((role) => role.addToPrincipalPolicy(new iam.PolicyStatement({
1583
1694
  effect: iam.Effect.ALLOW,
1584
1695
  actions: ['sns:Publish'],
1585
1696
  resources: topicArns,
1586
- }));
1697
+ })));
1587
1698
  }
1588
1699
  }
1589
1700
  // ── EventBridge PutEvents on OWNED buses ─────────────────
@@ -1595,11 +1706,11 @@ export class VentureAppStack extends cdk.Stack {
1595
1706
  if (imports.eventBusArnsById) {
1596
1707
  const busArns = [...imports.eventBusArnsById.values()];
1597
1708
  if (busArns.length > 0) {
1598
- this.lambdaRole.addToPrincipalPolicy(new iam.PolicyStatement({
1709
+ this.grantEachRole((role) => role.addToPrincipalPolicy(new iam.PolicyStatement({
1599
1710
  effect: iam.Effect.ALLOW,
1600
1711
  actions: ['events:PutEvents'],
1601
1712
  resources: busArns,
1602
- }));
1713
+ })));
1603
1714
  }
1604
1715
  }
1605
1716
  }