@venturekit/infra 0.0.38 → 0.0.40
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.
- package/dist/cdk/app-stack.d.ts +37 -2
- package/dist/cdk/app-stack.d.ts.map +1 -1
- package/dist/cdk/app-stack.js +151 -40
- package/dist/cdk/app-stack.js.map +1 -1
- package/dist/cdk/data-stack.d.ts +36 -0
- package/dist/cdk/data-stack.d.ts.map +1 -1
- package/dist/cdk/data-stack.js +108 -12
- package/dist/cdk/data-stack.js.map +1 -1
- package/dist/cdk/identity-stack.d.ts.map +1 -1
- package/dist/cdk/identity-stack.js +81 -9
- package/dist/cdk/identity-stack.js.map +1 -1
- package/dist/cdk/shared/admin-routes.d.ts +34 -0
- package/dist/cdk/shared/admin-routes.d.ts.map +1 -0
- package/dist/cdk/shared/admin-routes.js +73 -0
- package/dist/cdk/shared/admin-routes.js.map +1 -0
- package/dist/cdk/shared/ssm-keys.d.ts +14 -0
- package/dist/cdk/shared/ssm-keys.d.ts.map +1 -1
- package/dist/cdk/shared/ssm-keys.js +7 -0
- package/dist/cdk/shared/ssm-keys.js.map +1 -1
- package/package.json +2 -2
package/dist/cdk/app-stack.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AAKH,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AACnC,OAAO,KAAK,GAAG,MAAM,qBAAqB,CAAC;AAC3C,OAAO,KAAK,GAAG,MAAM,qBAAqB,CAAC;AAC3C,OAAO,KAAK,MAAM,MAAM,wBAAwB,CAAC;AAQjD,OAAO,KAAK,YAAY,MAAM,8BAA8B,CAAC;AAM7D,OAAO,KAAK,cAAc,MAAM,gCAAgC,CAAC;AAEjE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"app-stack.d.ts","sourceRoot":"","sources":["../../src/cdk/app-stack.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AAKH,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AACnC,OAAO,KAAK,GAAG,MAAM,qBAAqB,CAAC;AAC3C,OAAO,KAAK,GAAG,MAAM,qBAAqB,CAAC;AAC3C,OAAO,KAAK,MAAM,MAAM,wBAAwB,CAAC;AAQjD,OAAO,KAAK,YAAY,MAAM,8BAA8B,CAAC;AAM7D,OAAO,KAAK,cAAc,MAAM,gCAAgC,CAAC;AAEjE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAC;AAoB1B,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,iCAAiC,CAAC;AA6CzC;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC;IAEf;;;;OAIG;IACH,GAAG,CAAC,EAAE,YAAY,CAAC,QAAQ,CAAC;IAE5B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE3C;;;;;;;;;;;;OAYG;IACH,mBAAmB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE1C;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE3C,sDAAsD;IACtD,2BAA2B,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAElE;;;;;;;;;;;;;;OAcG;IACH,8BAA8B,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAErD,4EAA4E;IAC5E,sBAAsB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE7C,mEAAmE;IACnE,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEpC;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAExC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEvC,wDAAwD;IACxD,yBAAyB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEhD,oEAAoE;IACpE,qBAAqB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE5C,uDAAuD;IACvD,+BAA+B,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEtD,6EAA6E;IAC7E,2BAA2B,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAElD;;;;OAIG;IACH,yBAAyB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEhD;;;;OAIG;IACH,iCAAiC,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAExD;;;;;;OAMG;IACH,qBAAqB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE5C;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAErC;;;;;OAKG;IACH,2BAA2B,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAElD,+CAA+C;IAC/C,uBAAuB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE9C;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,GAAG,CACzB,MAAM,EACN;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CACjE,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,uBAAuB,CAAC,EAAE,GAAG,CAC3B,MAAM,EACN;QAAE,eAAe,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAC1C,CAAC;CACH;AAED,MAAM,WAAW,oBAAqB,SAAQ,GAAG,CAAC,UAAU;IAC1D,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,SAAS,CAAC;IAErB,4EAA4E;IAC5E,cAAc,EAAE,aAAa,CAAC;IAE9B,yEAAyE;IACzE,OAAO,EAAE,eAAe,CAAC;IAEzB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;;;;;;;;OAUG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,eAAgB,SAAQ,GAAG,CAAC,KAAK;IAC5C;;;;;OAKG;IACH,SAAgB,UAAU,EAAE,GAAG,CAAC,IAAI,CAAC;IAErC;;;;;;;OAOG;IACH,SAAgB,aAAa,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC;IAEzC,iEAAiE;IACjE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAoB;IAEvD,yEAAyE;IACzE,SAAgB,mBAAmB,CAAC,EAAE,GAAG,CAAC,aAAa,CAAC;IAExD,oEAAoE;IACpE,SAAgB,kBAAkB,EAAE,cAAc,CAAC,MAAM,CAAC;IAE1D;;;;;;OAMG;IACH,SAAgB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEpD,mEAAmE;IACnE,SAAgB,GAAG,CAAC,EAAE,YAAY,CAAC,QAAQ,CAAC;IAE5C,kCAAkC;IAClC,SAAgB,GAAG,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC;IAE/B;;;;;;OAMG;IACH,SAAgB,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAa;IAEvE,oDAAoD;IACpD,SAAgB,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAa;IAEvE,sDAAsD;IACtD,SAAgB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAa;IAE1E,wEAAwE;IACxE,SAAgB,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAa;IAE1E,2DAA2D;IAC3D,SAAgB,uBAAuB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAa;IAElF,+DAA+D;IAC/D,SAAgB,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAa;IAE9E;;;;;OAKG;IACH,SAAgB,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAEhD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAU;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAE/B;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAkC;IACtE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAU;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAEhD;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAEpC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAGjC;IAEJ;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,sBAAsB,CAAK;IAEnD;;;OAGG;IACH,OAAO,CAAC,uBAAuB;gBAanB,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,oBAAoB;IAmyBrE;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAezB;;;;;;OAMG;IACH,OAAO,CAAC,mBAAmB;IAwG3B;;;;OAIG;IACH,OAAO,KAAK,cAAc,GAEzB;IAED;;;;OAIG;IACH,OAAO,KAAK,iBAAiB,GAE5B;IAED,+CAA+C;IAC/C,OAAO,CAAC,aAAa;IAIrB,4FAA4F;IAC5F,OAAO,CAAC,YAAY;IAOpB,mGAAmG;IACnG,OAAO,CAAC,eAAe;IAOvB;;;;;;;OAOG;IACH,OAAO,CAAC,WAAW;IAgDnB;;;;;;;;OAQG;IACH,OAAO,CAAC,mBAAmB;IAkD3B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,qBAAqB;IAuD7B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,wBAAwB;IA0BhC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,wBAAwB;IAqChC;;;;;;;;;OASG;IACH,OAAO,CAAC,eAAe;IA8DvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA+BG;IACH,OAAO,CAAC,gBAAgB;IA2DxB;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;IAuB9B;;;;;OAKG;IACH,OAAO,CAAC,uBAAuB;IAW/B;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,OAAO,CAAC,mBAAmB;IAyP3B;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,uBAAuB;CAsKhC"}
|
package/dist/cdk/app-stack.js
CHANGED
|
@@ -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
|
-
/**
|
|
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.
|
|
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
|
-
//
|
|
408
|
-
//
|
|
409
|
-
//
|
|
410
|
-
|
|
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.
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
}
|