@venturekit/infra 0.0.0-dev.20260514025219 → 0.0.0-dev.20260515022321
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 +330 -0
- package/dist/cdk/app-stack.d.ts.map +1 -0
- package/dist/cdk/app-stack.js +875 -0
- package/dist/cdk/app-stack.js.map +1 -0
- package/dist/cdk/config-stack.d.ts +104 -0
- package/dist/cdk/config-stack.d.ts.map +1 -0
- package/dist/cdk/config-stack.js +161 -0
- package/dist/cdk/config-stack.js.map +1 -0
- package/dist/cdk/data-stack.d.ts +198 -0
- package/dist/cdk/data-stack.d.ts.map +1 -0
- package/dist/cdk/data-stack.js +511 -0
- package/dist/cdk/data-stack.js.map +1 -0
- package/dist/cdk/edge-stack.d.ts +91 -0
- package/dist/cdk/edge-stack.d.ts.map +1 -0
- package/dist/cdk/edge-stack.js +364 -0
- package/dist/cdk/edge-stack.js.map +1 -0
- package/dist/cdk/identity-stack.d.ts +98 -0
- package/dist/cdk/identity-stack.d.ts.map +1 -0
- package/dist/cdk/identity-stack.js +254 -0
- package/dist/cdk/identity-stack.js.map +1 -0
- package/dist/cdk/index.d.ts +16 -0
- package/dist/cdk/index.d.ts.map +1 -1
- package/dist/cdk/index.js +17 -0
- package/dist/cdk/index.js.map +1 -1
- package/dist/cdk/messaging-stack.d.ts +109 -0
- package/dist/cdk/messaging-stack.d.ts.map +1 -0
- package/dist/cdk/messaging-stack.js +320 -0
- package/dist/cdk/messaging-stack.js.map +1 -0
- package/dist/cdk/network-stack.d.ts +87 -0
- package/dist/cdk/network-stack.d.ts.map +1 -0
- package/dist/cdk/network-stack.js +265 -0
- package/dist/cdk/network-stack.js.map +1 -0
- package/dist/cdk/shared/cross-stack-refs.d.ts +278 -0
- package/dist/cdk/shared/cross-stack-refs.d.ts.map +1 -0
- package/dist/cdk/shared/cross-stack-refs.js +326 -0
- package/dist/cdk/shared/cross-stack-refs.js.map +1 -0
- package/dist/cdk/shared/lambda-helpers.d.ts +82 -0
- package/dist/cdk/shared/lambda-helpers.d.ts.map +1 -0
- package/dist/cdk/shared/lambda-helpers.js +201 -0
- package/dist/cdk/shared/lambda-helpers.js.map +1 -0
- package/dist/cdk/shared/ssm-keys.d.ts +460 -0
- package/dist/cdk/shared/ssm-keys.d.ts.map +1 -0
- package/dist/cdk/shared/ssm-keys.js +291 -0
- package/dist/cdk/shared/ssm-keys.js.map +1 -0
- package/dist/cdk/stack.d.ts +3 -11
- package/dist/cdk/stack.d.ts.map +1 -1
- package/dist/cdk/stack.js +184 -41
- package/dist/cdk/stack.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier 1 — Data stack.
|
|
3
|
+
*
|
|
4
|
+
* Owns every long-lived stateful resource in the deploy:
|
|
5
|
+
*
|
|
6
|
+
* - RDS instance(s) (Postgres / MySQL) + master credentials
|
|
7
|
+
* Secret + the database-side SecurityGroup
|
|
8
|
+
* - ElastiCache cluster(s) + ElastiCache SG
|
|
9
|
+
* - DynamoDB table(s): user-declared (free-tier) and the
|
|
10
|
+
* auto-provisioned rate-limits table (always provisioned —
|
|
11
|
+
* `createDefaultRateLimiter()` from `@venturekit/runtime` reads
|
|
12
|
+
* `VENTURE_RATE_LIMIT_TABLE` and the rate limiter must "just
|
|
13
|
+
* work" on first deploy regardless of preset)
|
|
14
|
+
* - S3 bucket(s) — bucket only; CloudFront distribution lives in
|
|
15
|
+
* the edge tier and imports the bucket via SSM
|
|
16
|
+
* - Internal HMAC Secret used by `@venturekit/auth` for state-
|
|
17
|
+
* token signing across Lambda-to-Lambda calls
|
|
18
|
+
*
|
|
19
|
+
* # Lifecycle position
|
|
20
|
+
*
|
|
21
|
+
* Updated quarterly-ish — RDS minor version, instance-size bumps,
|
|
22
|
+
* occasional new bucket. Survives stack delete because every
|
|
23
|
+
* stateful resource has both `DeletionPolicy: Retain` AND
|
|
24
|
+
* (where AWS supports it) the resource-native deletion-protection
|
|
25
|
+
* flag (RDS, DynamoDB).
|
|
26
|
+
*
|
|
27
|
+
* # Public surface (via SSM)
|
|
28
|
+
*
|
|
29
|
+
* - `dataKeys.rdsEndpoint` / `rdsPort` / `rdsDatabaseName` /
|
|
30
|
+
* `rdsSecretArn` / `rdsSecurityGroupId` per declared db id
|
|
31
|
+
* - `dataKeys.bucketName` / `bucketArn` per declared bucket id
|
|
32
|
+
* - `dataKeys.dynamoTableName` / `dynamoTableArn` per
|
|
33
|
+
* user-declared table (free tier) AND for the rate-limits table
|
|
34
|
+
* (id `'rate-limits'`)
|
|
35
|
+
* - `dataKeys.cacheEndpoint` / `cachePort` / `cacheSecurityGroupId`
|
|
36
|
+
* per cache id
|
|
37
|
+
* - `dataKeys.internalHmacSecretArn`
|
|
38
|
+
*
|
|
39
|
+
* Outputs (`cdk.CfnOutput`) are deliberately NOT used — see plan §3.
|
|
40
|
+
*
|
|
41
|
+
* # What this stack does NOT do
|
|
42
|
+
*
|
|
43
|
+
* - **No CloudFront distribution.** Edge stack (step 8) imports the
|
|
44
|
+
* bucket and creates the distribution with `S3BucketOrigin.with
|
|
45
|
+
* OriginAccessControl(bucket)`. The OAC's `aws:SourceArn`-scoped
|
|
46
|
+
* bucket policy lands in the edge stack via
|
|
47
|
+
* `bucket.addToResourcePolicy()` on the imported `IBucket`.
|
|
48
|
+
*
|
|
49
|
+
* - **No migration runner.** The CodeBuild-based migration runner is
|
|
50
|
+
* ephemeral wiring (delete-on-redeploy is fine; migrations are
|
|
51
|
+
* already applied to the persistent DB). It moves to the app stack
|
|
52
|
+
* in step 9, where it imports the RDS endpoint + secret via SSM.
|
|
53
|
+
*
|
|
54
|
+
* - **No Lambda permissions wiring.** Every IAM grant
|
|
55
|
+
* (`bucket.grantReadWrite(role)`, `secret.grantRead(role)`, etc.)
|
|
56
|
+
* produces an `AWS::IAM::Policy` resource attached to the role.
|
|
57
|
+
* The role lives in the app stack; therefore so does every grant.
|
|
58
|
+
* This is critical: putting grants here would create a circular
|
|
59
|
+
* stack dependency (data → app for the role; app → data for the
|
|
60
|
+
* bucket).
|
|
61
|
+
*/
|
|
62
|
+
import * as cdk from 'aws-cdk-lib';
|
|
63
|
+
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
|
64
|
+
import * as rds from 'aws-cdk-lib/aws-rds';
|
|
65
|
+
import * as s3 from 'aws-cdk-lib/aws-s3';
|
|
66
|
+
import * as elasticache from 'aws-cdk-lib/aws-elasticache';
|
|
67
|
+
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
|
|
68
|
+
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
|
|
69
|
+
import * as iam from 'aws-cdk-lib/aws-iam';
|
|
70
|
+
import { DATA_SAFETY_CONFIG } from '@venturekit/core';
|
|
71
|
+
import * as path from 'path';
|
|
72
|
+
import { writeSsm } from './shared/cross-stack-refs.js';
|
|
73
|
+
import { dataKeys } from './shared/ssm-keys.js';
|
|
74
|
+
import { MigrationRunner, hasMigrations } from './migration-runner.js';
|
|
75
|
+
/**
|
|
76
|
+
* Map a database `intent.size` to an RDS instance type. Graviton
|
|
77
|
+
* (T4G / R6G) for cost. Mirrors `VentureStack.sizeToInstanceType`.
|
|
78
|
+
*/
|
|
79
|
+
function sizeToInstanceType(size) {
|
|
80
|
+
switch (size) {
|
|
81
|
+
case 'xlarge':
|
|
82
|
+
return ec2.InstanceType.of(ec2.InstanceClass.R6G, ec2.InstanceSize.LARGE);
|
|
83
|
+
case 'large':
|
|
84
|
+
return ec2.InstanceType.of(ec2.InstanceClass.R6G, ec2.InstanceSize.MEDIUM);
|
|
85
|
+
case 'medium':
|
|
86
|
+
return ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize.SMALL);
|
|
87
|
+
default: // 'small'
|
|
88
|
+
return ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize.MICRO);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function sizeToCacheNodeType(size) {
|
|
92
|
+
switch (size) {
|
|
93
|
+
case 'xlarge':
|
|
94
|
+
return 'cache.r6g.large';
|
|
95
|
+
case 'large':
|
|
96
|
+
return 'cache.r6g.medium';
|
|
97
|
+
case 'medium':
|
|
98
|
+
return 'cache.t4g.small';
|
|
99
|
+
default: // 'small'
|
|
100
|
+
return 'cache.t4g.micro';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export class VentureDataStack extends cdk.Stack {
|
|
104
|
+
/**
|
|
105
|
+
* In-process accessors for sibling stacks (app stack mostly) that
|
|
106
|
+
* are deployed in the same `cdk.App`. Same rationale as
|
|
107
|
+
* `VentureNetworkStack.vpc`: direct CDK references give the
|
|
108
|
+
* consumer fully-typed access; cross-deploy consumers go through
|
|
109
|
+
* SSM + `import…` helpers.
|
|
110
|
+
*/
|
|
111
|
+
databases = new Map();
|
|
112
|
+
buckets = new Map();
|
|
113
|
+
dynamoTables = new Map();
|
|
114
|
+
/**
|
|
115
|
+
* The auto-provisioned rate-limits table. Always populated, even
|
|
116
|
+
* when no `databases` intent is declared.
|
|
117
|
+
*/
|
|
118
|
+
rateLimitTable;
|
|
119
|
+
/** Internal HMAC secret used by `@venturekit/auth/runtime`. */
|
|
120
|
+
internalHmacSecret;
|
|
121
|
+
/**
|
|
122
|
+
* Captured once in the constructor and reused by every private
|
|
123
|
+
* `create…` method. Avoids re-deriving from `envConfig.dataSafety`
|
|
124
|
+
* at five separate call sites and keeps the retention semantics
|
|
125
|
+
* consistent across all data-tier resources within a single stack.
|
|
126
|
+
*/
|
|
127
|
+
isStrictRetain;
|
|
128
|
+
/**
|
|
129
|
+
* Native deletion-protection flag for resources that expose one
|
|
130
|
+
* (RDS, DynamoDB). Mirrors `dataSafetyConfig.deletionProtection`.
|
|
131
|
+
*/
|
|
132
|
+
nativeDeletionProtection;
|
|
133
|
+
/**
|
|
134
|
+
* Migration runner config captured from props for use by
|
|
135
|
+
* `createDatabase`. `undefined` skips runner provisioning;
|
|
136
|
+
* the runner is also skipped per-DB when `hasMigrations` is
|
|
137
|
+
* false or the engine isn't Postgres.
|
|
138
|
+
*/
|
|
139
|
+
migrationsConfig;
|
|
140
|
+
/** Captured for migration runner log retention. */
|
|
141
|
+
logRetentionDays;
|
|
142
|
+
/** Captured for migration runner NAT-availability gating. */
|
|
143
|
+
natType;
|
|
144
|
+
constructor(scope, id, props) {
|
|
145
|
+
super(scope, id, props);
|
|
146
|
+
const { projectName, stage, envConfig, infrastructure, vpc, isFreeTier } = props;
|
|
147
|
+
const dataSafetyConfig = DATA_SAFETY_CONFIG[envConfig.dataSafety];
|
|
148
|
+
this.isStrictRetain = dataSafetyConfig.removalPolicy === 'retain';
|
|
149
|
+
this.nativeDeletionProtection = dataSafetyConfig.deletionProtection;
|
|
150
|
+
this.migrationsConfig = props.migrations;
|
|
151
|
+
this.logRetentionDays = envConfig.observability.logs.retentionDays;
|
|
152
|
+
this.natType = envConfig.vpc.natType;
|
|
153
|
+
const isStrictRetain = this.isStrictRetain;
|
|
154
|
+
// ─── Internal HMAC secret ────────────────────────────────────
|
|
155
|
+
// CloudFormation auto-generates a 64-char alphanumeric value on
|
|
156
|
+
// first deploy and preserves it across stack updates. The value
|
|
157
|
+
// is consumed by `@venturekit/auth/runtime` for signing internal
|
|
158
|
+
// Lambda-to-Lambda state tokens; the app stack reads the secret's
|
|
159
|
+
// ARN via SSM and either fetches the value at cold start (default)
|
|
160
|
+
// or — for stages where the SM round-trip latency matters —
|
|
161
|
+
// injects the value as a Lambda env var directly.
|
|
162
|
+
this.internalHmacSecret = new secretsmanager.Secret(this, 'internalHmacSecret', {
|
|
163
|
+
secretName: `venturekit/${projectName}/${stage}/internal-hmac`,
|
|
164
|
+
description: 'VentureKit internal Lambda-to-Lambda HMAC signing key. Auto-generated.',
|
|
165
|
+
generateSecretString: {
|
|
166
|
+
passwordLength: 64,
|
|
167
|
+
excludePunctuation: true,
|
|
168
|
+
excludeCharacters: '"\'\\/',
|
|
169
|
+
requireEachIncludedType: false,
|
|
170
|
+
includeSpace: false,
|
|
171
|
+
},
|
|
172
|
+
removalPolicy: isStrictRetain ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY,
|
|
173
|
+
});
|
|
174
|
+
writeSsm(this, dataKeys.internalHmacSecretArn(projectName, stage), this.internalHmacSecret.secretArn, { description: 'Internal HMAC secret ARN' });
|
|
175
|
+
// ─── Databases ───────────────────────────────────────────────
|
|
176
|
+
if (infrastructure.databases) {
|
|
177
|
+
for (const intent of infrastructure.databases) {
|
|
178
|
+
if (isFreeTier) {
|
|
179
|
+
// Free tier swaps RDS for DynamoDB. Cheaper, fully
|
|
180
|
+
// serverless, no VPC required. The `databases` intent
|
|
181
|
+
// surface stays the same so vk.config.ts is unchanged.
|
|
182
|
+
this.createDynamoDBTable(intent, projectName, stage);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
if (!vpc) {
|
|
186
|
+
throw new Error(`VentureDataStack: database '${intent.id}' requires a VPC. ` +
|
|
187
|
+
`Pass the VentureNetworkStack's vpc to VentureDataStackProps.vpc, ` +
|
|
188
|
+
`or use the free preset which routes databases through DynamoDB.`);
|
|
189
|
+
}
|
|
190
|
+
this.createDatabase(intent, projectName, stage, vpc);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// ─── Built-in rate-limits DDB ────────────────────────────────
|
|
195
|
+
// Always created — the runtime rate limiter expects the table
|
|
196
|
+
// to exist regardless of preset. Per-stack name avoids cross-
|
|
197
|
+
// project collisions in shared accounts. See plan §5: this was
|
|
198
|
+
// historically hardcoded `RemovalPolicy.DESTROY` and now respects
|
|
199
|
+
// dataSafety.
|
|
200
|
+
this.rateLimitTable = this.createRateLimitTable(projectName, stage);
|
|
201
|
+
// ─── Storage buckets ─────────────────────────────────────────
|
|
202
|
+
if (infrastructure.storage) {
|
|
203
|
+
for (const intent of infrastructure.storage) {
|
|
204
|
+
this.createStorage(intent, projectName, stage);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// ─── Caches ──────────────────────────────────────────────────
|
|
208
|
+
if (infrastructure.caches) {
|
|
209
|
+
for (const intent of infrastructure.caches) {
|
|
210
|
+
if (!vpc) {
|
|
211
|
+
throw new Error(`VentureDataStack: cache '${intent.id}' requires a VPC. ` +
|
|
212
|
+
`Pass the VentureNetworkStack's vpc to VentureDataStackProps.vpc.`);
|
|
213
|
+
}
|
|
214
|
+
this.createCache(intent, projectName, stage, vpc);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// ─── RDS ───────────────────────────────────────────────────────
|
|
219
|
+
createDatabase(intent, projectName, stage, vpc) {
|
|
220
|
+
const isStrictRetain = this.isStrictRetain;
|
|
221
|
+
// Stable logical ID — never changes when preset changes.
|
|
222
|
+
const logicalId = `db-${intent.id}`;
|
|
223
|
+
const instanceType = sizeToInstanceType(intent.size || 'small');
|
|
224
|
+
const engine = intent.type === 'mysql'
|
|
225
|
+
? rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0 })
|
|
226
|
+
: rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_16 });
|
|
227
|
+
// Place DB in isolated subnets when the preset ships them
|
|
228
|
+
// (medium+); otherwise fall back to private-with-egress.
|
|
229
|
+
const subnetType = vpc.isolatedSubnets?.length
|
|
230
|
+
? ec2.SubnetType.PRIVATE_ISOLATED
|
|
231
|
+
: ec2.SubnetType.PRIVATE_WITH_EGRESS;
|
|
232
|
+
const commonDbProps = {
|
|
233
|
+
engine,
|
|
234
|
+
instanceType,
|
|
235
|
+
vpc,
|
|
236
|
+
vpcSubnets: { subnetType },
|
|
237
|
+
removalPolicy: isStrictRetain ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY,
|
|
238
|
+
// Native RDS deletion protection — CFN-level RETAIN doesn't
|
|
239
|
+
// block console / CLI deletes; this flag does.
|
|
240
|
+
deletionProtection: this.nativeDeletionProtection,
|
|
241
|
+
multiAz: intent.highAvailability ?? false,
|
|
242
|
+
storageEncrypted: intent.encrypted ?? true,
|
|
243
|
+
backupRetention: intent.backups !== false
|
|
244
|
+
? cdk.Duration.days(intent.backupRetentionDays ?? 7)
|
|
245
|
+
: cdk.Duration.days(0),
|
|
246
|
+
autoMinorVersionUpgrade: true,
|
|
247
|
+
enablePerformanceInsights: intent.performanceInsights ?? false,
|
|
248
|
+
};
|
|
249
|
+
const instance = intent.restoreFromSnapshot
|
|
250
|
+
? new rds.DatabaseInstanceFromSnapshot(this, logicalId, {
|
|
251
|
+
...commonDbProps,
|
|
252
|
+
snapshotIdentifier: intent.restoreFromSnapshot,
|
|
253
|
+
credentials: rds.SnapshotCredentials.fromGeneratedSecret('postgres'),
|
|
254
|
+
})
|
|
255
|
+
: new rds.DatabaseInstance(this, logicalId, {
|
|
256
|
+
...commonDbProps,
|
|
257
|
+
databaseName: intent.name || intent.id,
|
|
258
|
+
credentials: rds.Credentials.fromGeneratedSecret('postgres'),
|
|
259
|
+
});
|
|
260
|
+
// UpdateReplacePolicy=Retain on the DB instance so a property
|
|
261
|
+
// change that requires REPLACE doesn't drop the underlying
|
|
262
|
+
// storage. Mirrors the §5 retention model.
|
|
263
|
+
if (isStrictRetain) {
|
|
264
|
+
const cfnInstance = instance.node.defaultChild;
|
|
265
|
+
cfnInstance.cfnOptions.updateReplacePolicy = cdk.CfnDeletionPolicy.RETAIN;
|
|
266
|
+
}
|
|
267
|
+
// RETAIN the master credentials Secret. `instance.secret`
|
|
268
|
+
// returns a `SecretTargetAttachment` (CDK metadata, not the
|
|
269
|
+
// actual Secret); the underlying `AWS::SecretsManager::Secret`
|
|
270
|
+
// resource is a sibling child of the DB construct under the
|
|
271
|
+
// well-known id `'Secret'`. Walking to it directly is the only
|
|
272
|
+
// way to apply RemovalPolicy to the secret itself.
|
|
273
|
+
if (isStrictRetain) {
|
|
274
|
+
const dbSecret = instance.node.tryFindChild('Secret');
|
|
275
|
+
if (dbSecret) {
|
|
276
|
+
dbSecret.applyRemovalPolicy(cdk.RemovalPolicy.RETAIN);
|
|
277
|
+
const cfnSecret = dbSecret.node.defaultChild;
|
|
278
|
+
if (cfnSecret) {
|
|
279
|
+
cfnSecret.cfnOptions.updateReplacePolicy = cdk.CfnDeletionPolicy.RETAIN;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
this.databases.set(intent.id, { instance, intent });
|
|
284
|
+
// ─── Migration runner (CodeBuild in-VPC) ────────────────────
|
|
285
|
+
// Provisioned only for Postgres + when the project has a
|
|
286
|
+
// `db/migrations/` dir + when the VPC has outbound egress.
|
|
287
|
+
// Skipping silently is OK because the operator can run
|
|
288
|
+
// `vk migrate` manually via an SSM tunnel.
|
|
289
|
+
if (this.migrationsConfig &&
|
|
290
|
+
intent.type === 'postgres' &&
|
|
291
|
+
instance.secret) {
|
|
292
|
+
const dbDir = path.join(this.migrationsConfig.projectDir, 'db');
|
|
293
|
+
if (hasMigrations(dbDir)) {
|
|
294
|
+
if (this.natType === 'none') {
|
|
295
|
+
console.warn(`[venturekit] Skipping auto-migrations for db "${intent.id}": ` +
|
|
296
|
+
`natType is 'none', so the in-VPC migration runner can't ` +
|
|
297
|
+
`reach npmjs.com to install @venturekit/cli. Fix: set ` +
|
|
298
|
+
`'infrastructure.vpc.natType: "instance"' (or "gateway") in ` +
|
|
299
|
+
`vk.config.ts. Until then, run \`vk migrate -e ${stage}\` ` +
|
|
300
|
+
`manually once the DB is reachable (e.g. via SSM tunnel).`);
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
new MigrationRunner(this, `migration-runner-${intent.id}`, {
|
|
304
|
+
projectName,
|
|
305
|
+
stage,
|
|
306
|
+
dbId: intent.id,
|
|
307
|
+
vpc,
|
|
308
|
+
dbInstance: instance,
|
|
309
|
+
dbSecret: instance.secret,
|
|
310
|
+
dbName: intent.name || intent.id,
|
|
311
|
+
dbDir,
|
|
312
|
+
extraMigrationsDirs: this.migrationsConfig.packageMigrationsDirs ?? [],
|
|
313
|
+
vkCliVersion: this.migrationsConfig.vkCliVersion,
|
|
314
|
+
logRetentionDays: this.logRetentionDays,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
else if (this.migrationsConfig && intent.type === 'mysql') {
|
|
320
|
+
console.warn(`[venturekit] No auto-migration runner for MySQL db "${intent.id}". ` +
|
|
321
|
+
`Run migrations manually via tunnel.`);
|
|
322
|
+
}
|
|
323
|
+
// Publish to SSM for the app stack's connection wiring.
|
|
324
|
+
writeSsm(this, dataKeys.rdsEndpoint(projectName, stage, intent.id), instance.dbInstanceEndpointAddress, { description: `Postgres/MySQL endpoint for db '${intent.id}'` });
|
|
325
|
+
writeSsm(this, dataKeys.rdsPort(projectName, stage, intent.id), instance.dbInstanceEndpointPort, { description: `RDS port for db '${intent.id}'` });
|
|
326
|
+
writeSsm(this, dataKeys.rdsDatabaseName(projectName, stage, intent.id), intent.name || intent.id, { description: `Database name for db '${intent.id}'` });
|
|
327
|
+
if (instance.secret) {
|
|
328
|
+
writeSsm(this, dataKeys.rdsSecretArn(projectName, stage, intent.id), instance.secret.secretArn, { description: `Master credentials secret ARN for db '${intent.id}'` });
|
|
329
|
+
}
|
|
330
|
+
writeSsm(this, dataKeys.rdsSecurityGroupId(projectName, stage, intent.id), instance.connections.securityGroups[0].securityGroupId, { description: `Database SG ID — app stack imports + adds Lambda ingress` });
|
|
331
|
+
}
|
|
332
|
+
// ─── DynamoDB (free-tier `databases` substitute + user tables) ─
|
|
333
|
+
createDynamoDBTable(intent, projectName, stage) {
|
|
334
|
+
const isStrictRetain = this.isStrictRetain;
|
|
335
|
+
const logicalId = `dynamo-${intent.id}`;
|
|
336
|
+
const table = new dynamodb.Table(this, logicalId, {
|
|
337
|
+
tableName: `${projectName}-${intent.id}-${stage}`,
|
|
338
|
+
partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
|
|
339
|
+
sortKey: { name: 'sk', type: dynamodb.AttributeType.STRING },
|
|
340
|
+
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
|
|
341
|
+
removalPolicy: isStrictRetain ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY,
|
|
342
|
+
pointInTimeRecovery: isStrictRetain,
|
|
343
|
+
encryption: dynamodb.TableEncryption.AWS_MANAGED,
|
|
344
|
+
timeToLiveAttribute: 'ttl',
|
|
345
|
+
// Native DDB deletion protection — second layer beyond RETAIN.
|
|
346
|
+
deletionProtection: isStrictRetain,
|
|
347
|
+
});
|
|
348
|
+
if (isStrictRetain) {
|
|
349
|
+
const cfnTable = table.node.defaultChild;
|
|
350
|
+
cfnTable.cfnOptions.updateReplacePolicy = cdk.CfnDeletionPolicy.RETAIN;
|
|
351
|
+
}
|
|
352
|
+
table.addGlobalSecondaryIndex({
|
|
353
|
+
indexName: 'gsi1',
|
|
354
|
+
partitionKey: { name: 'gsi1pk', type: dynamodb.AttributeType.STRING },
|
|
355
|
+
sortKey: { name: 'gsi1sk', type: dynamodb.AttributeType.STRING },
|
|
356
|
+
projectionType: dynamodb.ProjectionType.ALL,
|
|
357
|
+
});
|
|
358
|
+
this.dynamoTables.set(intent.id, table);
|
|
359
|
+
writeSsm(this, dataKeys.dynamoTableName(projectName, stage, intent.id), table.tableName, { description: `DynamoDB table name '${intent.id}'` });
|
|
360
|
+
writeSsm(this, dataKeys.dynamoTableArn(projectName, stage, intent.id), table.tableArn, { description: `DynamoDB table ARN '${intent.id}'` });
|
|
361
|
+
}
|
|
362
|
+
createRateLimitTable(projectName, stage) {
|
|
363
|
+
const isStrictRetain = this.isStrictRetain;
|
|
364
|
+
// Per-stack name to avoid collisions across multiple projects in
|
|
365
|
+
// the same AWS account.
|
|
366
|
+
const tableName = `${projectName}-rate-limits-${stage}`;
|
|
367
|
+
const table = new dynamodb.Table(this, 'RateLimitTable', {
|
|
368
|
+
tableName,
|
|
369
|
+
partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
|
|
370
|
+
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
|
|
371
|
+
removalPolicy: isStrictRetain ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY,
|
|
372
|
+
timeToLiveAttribute: 'ttl',
|
|
373
|
+
encryption: dynamodb.TableEncryption.AWS_MANAGED,
|
|
374
|
+
deletionProtection: isStrictRetain,
|
|
375
|
+
});
|
|
376
|
+
if (isStrictRetain) {
|
|
377
|
+
const cfn = table.node.defaultChild;
|
|
378
|
+
cfn.cfnOptions.updateReplacePolicy = cdk.CfnDeletionPolicy.RETAIN;
|
|
379
|
+
}
|
|
380
|
+
// Publish under the stable id `'rate-limits'` so app-stack code
|
|
381
|
+
// reads through `dataKeys.dynamoTableName(project, stage, 'rate-limits')`.
|
|
382
|
+
writeSsm(this, dataKeys.dynamoTableName(projectName, stage, 'rate-limits'), table.tableName, { description: 'Rate-limits DDB table name (consumed by @venturekit/runtime)' });
|
|
383
|
+
writeSsm(this, dataKeys.dynamoTableArn(projectName, stage, 'rate-limits'), table.tableArn, { description: 'Rate-limits DDB table ARN — for IAM grants in app stack' });
|
|
384
|
+
return table;
|
|
385
|
+
}
|
|
386
|
+
// ─── S3 ────────────────────────────────────────────────────────
|
|
387
|
+
createStorage(intent, projectName, stage) {
|
|
388
|
+
const isStrictRetain = this.isStrictRetain;
|
|
389
|
+
const logicalId = `storage-${intent.id}`;
|
|
390
|
+
const bucket = new s3.Bucket(this, logicalId, {
|
|
391
|
+
bucketName: `${projectName}-${intent.id}-${stage}`,
|
|
392
|
+
versioned: intent.versioned ?? intent.purpose === 'backups',
|
|
393
|
+
removalPolicy: isStrictRetain ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY,
|
|
394
|
+
autoDeleteObjects: !isStrictRetain,
|
|
395
|
+
encryption: s3.BucketEncryption.S3_MANAGED,
|
|
396
|
+
// BLOCK_ALL even when CDN is in use; the edge-tier CloudFront
|
|
397
|
+
// distribution accesses the bucket through an Origin Access
|
|
398
|
+
// Control with `aws:SourceArn` scoping, not via direct
|
|
399
|
+
// anonymous reads.
|
|
400
|
+
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
|
|
401
|
+
enforceSSL: true,
|
|
402
|
+
cors: intent.corsOrigins
|
|
403
|
+
? [
|
|
404
|
+
{
|
|
405
|
+
allowedOrigins: intent.corsOrigins,
|
|
406
|
+
allowedMethods: [
|
|
407
|
+
s3.HttpMethods.GET,
|
|
408
|
+
s3.HttpMethods.PUT,
|
|
409
|
+
s3.HttpMethods.POST,
|
|
410
|
+
s3.HttpMethods.DELETE,
|
|
411
|
+
],
|
|
412
|
+
allowedHeaders: ['*'],
|
|
413
|
+
},
|
|
414
|
+
]
|
|
415
|
+
: undefined,
|
|
416
|
+
lifecycleRules: intent.purpose === 'logs'
|
|
417
|
+
? [{ expiration: cdk.Duration.days(intent.retentionDays ?? 90), id: 'log-expiry' }]
|
|
418
|
+
: intent.purpose === 'backups'
|
|
419
|
+
? [
|
|
420
|
+
{
|
|
421
|
+
transitions: [
|
|
422
|
+
{ storageClass: s3.StorageClass.INFREQUENT_ACCESS, transitionAfter: cdk.Duration.days(30) },
|
|
423
|
+
],
|
|
424
|
+
id: 'backup-ia',
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
transitions: [
|
|
428
|
+
{ storageClass: s3.StorageClass.GLACIER, transitionAfter: cdk.Duration.days(90) },
|
|
429
|
+
],
|
|
430
|
+
id: 'backup-glacier',
|
|
431
|
+
},
|
|
432
|
+
]
|
|
433
|
+
: [],
|
|
434
|
+
});
|
|
435
|
+
if (isStrictRetain) {
|
|
436
|
+
const cfnBucket = bucket.node.defaultChild;
|
|
437
|
+
cfnBucket.cfnOptions.updateReplacePolicy = cdk.CfnDeletionPolicy.RETAIN;
|
|
438
|
+
}
|
|
439
|
+
this.buckets.set(intent.id, { bucket, intent });
|
|
440
|
+
// ── OAC bucket policy (CDN-fronted buckets) ───────────────────
|
|
441
|
+
// The edge stack creates the CloudFront distribution with an
|
|
442
|
+
// OriginAccessControl pointing at this bucket. The bucket policy
|
|
443
|
+
// that authorizes CloudFront's signed S3 GET must live here (the
|
|
444
|
+
// bucket's home stack); creating it in the edge stack would force
|
|
445
|
+
// CDK to add a `data → edge` dependency for the distribution ARN
|
|
446
|
+
// *and* an `edge → data` dependency for the bucket's regional
|
|
447
|
+
// domain — a synth-time cycle.
|
|
448
|
+
//
|
|
449
|
+
// We scope the statement with `aws:SourceAccount` instead of
|
|
450
|
+
// `aws:SourceArn`. Pinning to a specific distribution ARN would
|
|
451
|
+
// require a cross-stack reference; the SourceAccount condition
|
|
452
|
+
// is the AWS-recommended fallback when the distribution lives in
|
|
453
|
+
// another stack and gives equivalent protection at the account
|
|
454
|
+
// boundary (the only CloudFront distribution with this OAC will
|
|
455
|
+
// be the one in the edge stack — anyone else attempting to use
|
|
456
|
+
// the same OAC must already be inside the same AWS account).
|
|
457
|
+
if (intent.cdn === true) {
|
|
458
|
+
bucket.addToResourcePolicy(new iam.PolicyStatement({
|
|
459
|
+
sid: 'AllowCloudFrontOacRead',
|
|
460
|
+
effect: iam.Effect.ALLOW,
|
|
461
|
+
principals: [new iam.ServicePrincipal('cloudfront.amazonaws.com')],
|
|
462
|
+
actions: ['s3:GetObject'],
|
|
463
|
+
resources: [bucket.arnForObjects('*')],
|
|
464
|
+
conditions: {
|
|
465
|
+
StringEquals: { 'aws:SourceAccount': cdk.Aws.ACCOUNT_ID },
|
|
466
|
+
},
|
|
467
|
+
}));
|
|
468
|
+
}
|
|
469
|
+
writeSsm(this, dataKeys.bucketName(projectName, stage, intent.id), bucket.bucketName, { description: `S3 bucket name for storage '${intent.id}'` });
|
|
470
|
+
writeSsm(this, dataKeys.bucketArn(projectName, stage, intent.id), bucket.bucketArn, { description: `S3 bucket ARN — IAM scoping target` });
|
|
471
|
+
}
|
|
472
|
+
// ─── ElastiCache ───────────────────────────────────────────────
|
|
473
|
+
createCache(intent, projectName, stage, vpc) {
|
|
474
|
+
const logicalId = `cache-${intent.id}`;
|
|
475
|
+
const securityGroup = new ec2.SecurityGroup(this, `${logicalId}-sg`, {
|
|
476
|
+
vpc,
|
|
477
|
+
description: `Security group for ${projectName}-${intent.id} Redis`,
|
|
478
|
+
allowAllOutbound: false,
|
|
479
|
+
});
|
|
480
|
+
// Allow Redis-port ingress from anywhere in the VPC. Tighter
|
|
481
|
+
// scoping (Lambda SG → cache SG) isn't possible from this
|
|
482
|
+
// stack — the Lambda SG lives in the app stack. The CIDR-scoped
|
|
483
|
+
// rule is still secure (no internet exposure) and sufficient.
|
|
484
|
+
securityGroup.addIngressRule(ec2.Peer.ipv4(vpc.vpcCidrBlock), ec2.Port.tcp(6379), 'Allow Redis access from VPC');
|
|
485
|
+
const subnetIds = vpc.isolatedSubnets?.length
|
|
486
|
+
? vpc.isolatedSubnets.map((s) => s.subnetId)
|
|
487
|
+
: vpc.privateSubnets.map((s) => s.subnetId);
|
|
488
|
+
const subnetGroup = new elasticache.CfnSubnetGroup(this, `${logicalId}-subnet-group`, {
|
|
489
|
+
description: `Subnet group for ${projectName}-${intent.id}`,
|
|
490
|
+
subnetIds,
|
|
491
|
+
});
|
|
492
|
+
const cacheNodeType = sizeToCacheNodeType(intent.size || 'small');
|
|
493
|
+
const redis = new elasticache.CfnCacheCluster(this, logicalId, {
|
|
494
|
+
engine: 'redis',
|
|
495
|
+
cacheNodeType,
|
|
496
|
+
numCacheNodes: 1,
|
|
497
|
+
vpcSecurityGroupIds: [securityGroup.securityGroupId],
|
|
498
|
+
cacheSubnetGroupName: subnetGroup.ref,
|
|
499
|
+
transitEncryptionEnabled: true,
|
|
500
|
+
// Snapshot retention for strict (preserves the cache contents
|
|
501
|
+
// for ad-hoc forensic restore — not as durable as RDS but
|
|
502
|
+
// better than nothing for stateful Redis use cases).
|
|
503
|
+
snapshotRetentionLimit: this.isStrictRetain ? 7 : 0,
|
|
504
|
+
autoMinorVersionUpgrade: true,
|
|
505
|
+
});
|
|
506
|
+
writeSsm(this, dataKeys.cacheEndpoint(projectName, stage, intent.id), redis.attrRedisEndpointAddress, { description: `Redis endpoint for cache '${intent.id}'` });
|
|
507
|
+
writeSsm(this, dataKeys.cachePort(projectName, stage, intent.id), redis.attrRedisEndpointPort, { description: `Redis port for cache '${intent.id}'` });
|
|
508
|
+
writeSsm(this, dataKeys.cacheSecurityGroupId(projectName, stage, intent.id), securityGroup.securityGroupId, { description: `Cache SG ID — app stack imports + adds Lambda ingress` });
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
//# sourceMappingURL=data-stack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-stack.js","sourceRoot":"","sources":["../../src/cdk/data-stack.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier 5 — Edge stack.
|
|
3
|
+
*
|
|
4
|
+
* Owns the public-facing entry surface:
|
|
5
|
+
*
|
|
6
|
+
* - The `AWS::ApiGatewayV2::Api` (HttpApi) shell with stable
|
|
7
|
+
* ID + access logging configured. Routes (which target Lambdas)
|
|
8
|
+
* are added in the app stack via `HttpApi.fromHttpApiAttributes`
|
|
9
|
+
* + `addRoutes()`.
|
|
10
|
+
* - Custom domain(s) per `domains[]` intent: ACM certificate
|
|
11
|
+
* (existing ARN, Route53-validated, or DNS-validation-pending),
|
|
12
|
+
* `AWS::ApiGatewayV2::DomainName`, `ApiMapping` to the API's
|
|
13
|
+
* default stage, optional Route53 alias.
|
|
14
|
+
* - CloudFront distribution(s) over imported S3 buckets per
|
|
15
|
+
* `storage[]` intent that has `cdn: true`. Bucket lives in the
|
|
16
|
+
* data stack; distribution + Origin Access Control + alias DNS
|
|
17
|
+
* live here. The OAC's `aws:SourceArn`-scoped bucket policy
|
|
18
|
+
* statement is added to the imported `IBucket` automatically by
|
|
19
|
+
* `S3BucketOrigin.withOriginAccessControl(...)`.
|
|
20
|
+
*
|
|
21
|
+
* # Lifecycle position
|
|
22
|
+
*
|
|
23
|
+
* Updated occasionally (~quarterly): a new domain, a new CDN, an
|
|
24
|
+
* ACM cert rotation. The HttpApi ID itself is the most fragile:
|
|
25
|
+
* the `*.execute-api.{region}.amazonaws.com` URL embeds it, and
|
|
26
|
+
* mobile apps shipped with that URL baked in cannot be patched
|
|
27
|
+
* without a full update cycle. RETAIN under strict is therefore
|
|
28
|
+
* not just durability — it's external-contract preservation.
|
|
29
|
+
*
|
|
30
|
+
* # Public surface (via SSM)
|
|
31
|
+
*
|
|
32
|
+
* - `edgeKeys.apiId / apiEndpoint / apiCustomDomain /
|
|
33
|
+
* apiDefaultStageName` — the app stack reads these to import
|
|
34
|
+
* the API and add its discovered routes.
|
|
35
|
+
* - `edgeKeys.storageCdnDomain(storageId) /
|
|
36
|
+
* storageCdnDistributionId(storageId)` per CDN-fronted bucket.
|
|
37
|
+
*
|
|
38
|
+
* # What this stack does NOT do
|
|
39
|
+
*
|
|
40
|
+
* - **No routes / integrations.** `HttpApi.addRoutes(...)` runs
|
|
41
|
+
* in the app stack. CDK's `HttpApi.fromHttpApiAttributes` returns
|
|
42
|
+
* an `IHttpApi` that supports `addRoutes`, so the cross-stack
|
|
43
|
+
* pattern is the same as the in-stack one.
|
|
44
|
+
*
|
|
45
|
+
* - **No Lambda permissions.** API Gateway invoking a Lambda
|
|
46
|
+
* requires a `lambda:InvokeFunction` resource policy on the
|
|
47
|
+
* Lambda granted to `apigateway.amazonaws.com` with `aws:SourceArn`
|
|
48
|
+
* scoping. CDK's `HttpLambdaIntegration` adds that grant
|
|
49
|
+
* automatically when the route is created — which happens in the
|
|
50
|
+
* app stack.
|
|
51
|
+
*
|
|
52
|
+
* - **No CloudFront-Lambda@Edge wiring.** Edge Lambdas (request /
|
|
53
|
+
* response viewer functions) are extreme-rare in the VentureKit
|
|
54
|
+
* surface. If they're added later, they'd live here because their
|
|
55
|
+
* lifecycle is tied to the distribution, not to a single regional
|
|
56
|
+
* handler.
|
|
57
|
+
*/
|
|
58
|
+
import * as cdk from 'aws-cdk-lib';
|
|
59
|
+
import * as apigatewayv2 from 'aws-cdk-lib/aws-apigatewayv2';
|
|
60
|
+
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
|
|
61
|
+
import * as s3 from 'aws-cdk-lib/aws-s3';
|
|
62
|
+
import { Construct } from 'constructs';
|
|
63
|
+
import { type EnvConfig, type VentureIntent } from '@venturekit/core';
|
|
64
|
+
export interface VentureEdgeStackProps extends cdk.StackProps {
|
|
65
|
+
projectName: string;
|
|
66
|
+
stage: string;
|
|
67
|
+
envConfig: EnvConfig;
|
|
68
|
+
/** Reads `domains[]` and `storage[]` (CDN flag only). */
|
|
69
|
+
infrastructure: VentureIntent;
|
|
70
|
+
/**
|
|
71
|
+
* Imported `IBucket` per storage intent that has `cdn: true`.
|
|
72
|
+
* The CLI generator wires these up: data stack creates the bucket
|
|
73
|
+
* → generator captures `dataStack.buckets.get(id).bucket` → passes
|
|
74
|
+
* the map here. Edge stack imports each one and creates the
|
|
75
|
+
* CloudFront distribution + OAC bucket policy.
|
|
76
|
+
*
|
|
77
|
+
* Cross-deploy consumers can reconstruct via `s3.Bucket.fromBucketAttributes`
|
|
78
|
+
* + the data stack's published `bucketArn` SSM key, but the
|
|
79
|
+
* common case is in-process refs.
|
|
80
|
+
*/
|
|
81
|
+
storageBuckets?: Map<string, s3.IBucket>;
|
|
82
|
+
}
|
|
83
|
+
export declare class VentureEdgeStack extends cdk.Stack {
|
|
84
|
+
readonly api: apigatewayv2.HttpApi;
|
|
85
|
+
readonly distributions: Map<string, cloudfront.Distribution>;
|
|
86
|
+
private readonly isStrictRetain;
|
|
87
|
+
constructor(scope: Construct, id: string, props: VentureEdgeStackProps);
|
|
88
|
+
private createDomain;
|
|
89
|
+
private createStorageCdn;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=edge-stack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edge-stack.d.ts","sourceRoot":"","sources":["../../src/cdk/edge-stack.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAEH,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AACnC,OAAO,KAAK,YAAY,MAAM,8BAA8B,CAAC;AAI7D,OAAO,KAAK,UAAU,MAAM,4BAA4B,CAAC;AAEzD,OAAO,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAEzC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAsB,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAI1F,MAAM,WAAW,qBAAsB,SAAQ,GAAG,CAAC,UAAU;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,SAAS,CAAC;IAErB,yDAAyD;IACzD,cAAc,EAAE,aAAa,CAAC;IAE9B;;;;;;;;;;OAUG;IACH,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC;CAC1C;AAaD,qBAAa,gBAAiB,SAAQ,GAAG,CAAC,KAAK;IAC7C,SAAgB,GAAG,EAAE,YAAY,CAAC,OAAO,CAAC;IAE1C,SAAgB,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,YAAY,CAAC,CAAa;IAEhF,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAU;gBAE7B,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,qBAAqB;IAqJtE,OAAO,CAAC,YAAY;IA8FpB,OAAO,CAAC,gBAAgB;CAoHzB"}
|