@deployfoundation/foundation-deploy 0.2.38 → 0.2.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/bin/app.js +3 -2
- package/dist/bin/foundation-deploy.js +503 -100
- package/dist/bin/release-account.js +1 -2
- package/dist/chunk-dz91avrb.js +16 -0
- package/dist/{chunk-j52g7bsc.js → chunk-fwqmac5n.js} +18 -4
- package/dist/{chunk-2ra18dqx.js → chunk-g56mqyfx.js} +105 -75
- package/dist/src/index.js +5 -2
- package/package.json +1 -1
- package/src/artifacts.ts +33 -0
- package/src/deploy/release.ts +1 -35
- package/src/deploy-permissions.ts +37 -2
- package/src/index.ts +2 -0
- package/src/release/trust.ts +52 -0
- package/src/stacks/ci-stack.ts +2 -1
- package/src/stacks/pipeline-stack.ts +2 -1
- package/dist/chunk-8e2z4rhf.js +0 -442
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// src/release/trust.ts
|
|
2
|
+
var DEFAULT_RELEASE_BUCKET = "foundry41-foundation-releases";
|
|
3
|
+
var RELEASE_KMS_KEY_ARN_ENV = "FOUNDATION_RELEASE_KMS_KEY_ARN";
|
|
4
|
+
var RELEASE_KMS_KEY_ARN = "arn:aws:kms:us-east-1:793593623536:key/fba7d9f3-e9b6-49ae-b4a5-c8c5f1429231";
|
|
5
|
+
function configuredKeyArn(env = process.env) {
|
|
6
|
+
const configured = env[RELEASE_KMS_KEY_ARN_ENV] ?? RELEASE_KMS_KEY_ARN;
|
|
7
|
+
return configured === undefined || configured === "" ? undefined : configured;
|
|
8
|
+
}
|
|
9
|
+
function expectedKeyArn(env = process.env) {
|
|
10
|
+
const configured = configuredKeyArn(env);
|
|
11
|
+
if (configured === undefined)
|
|
12
|
+
throw new Error(`no Foundation release signing key is configured; set ${RELEASE_KMS_KEY_ARN_ENV} to the key ARN published with the release`);
|
|
13
|
+
return configured;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { DEFAULT_RELEASE_BUCKET, configuredKeyArn, expectedKeyArn };
|
|
@@ -17,13 +17,14 @@ import {
|
|
|
17
17
|
parseInstanceConfig,
|
|
18
18
|
parseSchedule,
|
|
19
19
|
provisionsIntegration,
|
|
20
|
+
releaseAccess,
|
|
20
21
|
releaseImageRepositoryArn,
|
|
21
22
|
releaseSource,
|
|
22
23
|
requiresAuthenticatedQueue,
|
|
23
24
|
scheduledJobBindings,
|
|
24
25
|
slackCommandPrefixes,
|
|
25
26
|
webhookJobBindings
|
|
26
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-g56mqyfx.js";
|
|
27
28
|
|
|
28
29
|
// src/stacks/agent-stack.ts
|
|
29
30
|
import * as cdk from "aws-cdk-lib";
|
|
@@ -1846,6 +1847,18 @@ function deployStatements(stack, names, targets, instance) {
|
|
|
1846
1847
|
const runtimeSecret = secretArn(names.secretRuntime);
|
|
1847
1848
|
const knockOauthClient = secretArn(names.secretKnockOauthClient);
|
|
1848
1849
|
return [
|
|
1850
|
+
new iam3.PolicyStatement({
|
|
1851
|
+
sid: "ReadFoundationRelease",
|
|
1852
|
+
actions: ["s3:GetObject"],
|
|
1853
|
+
resources: [`arn:${stack.partition}:s3:::${targets.release.bucket}/releases/*`]
|
|
1854
|
+
}),
|
|
1855
|
+
...targets.release.signingKeyArn === undefined ? [] : [
|
|
1856
|
+
new iam3.PolicyStatement({
|
|
1857
|
+
sid: "VerifyReleaseSignature",
|
|
1858
|
+
actions: ["kms:Verify"],
|
|
1859
|
+
resources: [targets.release.signingKeyArn]
|
|
1860
|
+
})
|
|
1861
|
+
],
|
|
1849
1862
|
new iam3.PolicyStatement({
|
|
1850
1863
|
sid: "AssumeCdkBootstrapRoles",
|
|
1851
1864
|
actions: ["sts:AssumeRole"],
|
|
@@ -1927,7 +1940,8 @@ function deployStatements(stack, names, targets, instance) {
|
|
|
1927
1940
|
"bedrock-agentcore:GetAgentRuntime",
|
|
1928
1941
|
"bedrock-agentcore:ListAgentRuntimeVersions",
|
|
1929
1942
|
"bedrock-agentcore:GetAgentRuntimeEndpoint",
|
|
1930
|
-
"bedrock-agentcore:UpdateAgentRuntimeEndpoint"
|
|
1943
|
+
"bedrock-agentcore:UpdateAgentRuntimeEndpoint",
|
|
1944
|
+
"bedrock-agentcore:CreateAgentRuntimeEndpoint"
|
|
1931
1945
|
],
|
|
1932
1946
|
resources: [
|
|
1933
1947
|
`arn:${stack.partition}:bedrock-agentcore:${stack.region}:${stack.account}:runtime/${names.runtimeName}*`,
|
|
@@ -1970,7 +1984,7 @@ class FoundationCi extends cdk3.Stack {
|
|
|
1970
1984
|
}),
|
|
1971
1985
|
inlinePolicies: {
|
|
1972
1986
|
FoundationDeploy: new iam4.PolicyDocument({
|
|
1973
|
-
statements: deployStatements(this, names, { bucket: props.bucket, dataKey: props.dataKey }, props.instance)
|
|
1987
|
+
statements: deployStatements(this, names, { bucket: props.bucket, dataKey: props.dataKey, release: releaseAccess(this) }, props.instance)
|
|
1974
1988
|
})
|
|
1975
1989
|
}
|
|
1976
1990
|
});
|
|
@@ -3453,7 +3467,7 @@ class FoundationPipeline extends cdk8.Stack {
|
|
|
3453
3467
|
assumedBy: new iam9.ServicePrincipal("codebuild.amazonaws.com"),
|
|
3454
3468
|
inlinePolicies: {
|
|
3455
3469
|
FoundationDeploy: new iam9.PolicyDocument({
|
|
3456
|
-
statements: deployStatements(this, names, { bucket: props.bucket, dataKey: props.dataKey }, props.instance)
|
|
3470
|
+
statements: deployStatements(this, names, { bucket: props.bucket, dataKey: props.dataKey, release: releaseAccess(this) }, props.instance)
|
|
3457
3471
|
})
|
|
3458
3472
|
}
|
|
3459
3473
|
});
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_RELEASE_BUCKET,
|
|
3
|
+
configuredKeyArn
|
|
4
|
+
} from "./chunk-dz91avrb.js";
|
|
5
|
+
|
|
1
6
|
// ../core/src/instance.ts
|
|
2
7
|
import { parse as parseYaml } from "yaml";
|
|
3
8
|
import { z as z2 } from "zod";
|
|
@@ -1256,31 +1261,45 @@ var contextStorage = new AsyncLocalStorage;
|
|
|
1256
1261
|
// ../core/src/config.ts
|
|
1257
1262
|
import { createHash as createHash2 } from "node:crypto";
|
|
1258
1263
|
import { parse as parseYaml2 } from "yaml";
|
|
1259
|
-
import { z as
|
|
1264
|
+
import { z as z5 } from "zod";
|
|
1260
1265
|
|
|
1261
1266
|
// ../core/src/jobs.ts
|
|
1262
1267
|
import { createHash, createHmac, timingSafeEqual } from "node:crypto";
|
|
1268
|
+
import { z as z4 } from "zod";
|
|
1269
|
+
|
|
1270
|
+
// ../core/src/thinking-level.ts
|
|
1263
1271
|
import { z as z3 } from "zod";
|
|
1272
|
+
var ThinkingLevel = z3.enum(["none", "low", "medium", "high", "xhigh", "max"]);
|
|
1273
|
+
|
|
1274
|
+
// ../core/src/jobs.ts
|
|
1264
1275
|
var JOB_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,40}$/;
|
|
1265
|
-
var RawScheduleTrigger =
|
|
1266
|
-
schedule:
|
|
1276
|
+
var RawScheduleTrigger = z4.object({
|
|
1277
|
+
schedule: z4.string().min(1)
|
|
1267
1278
|
}).strict();
|
|
1268
|
-
var RawWebhookTrigger =
|
|
1269
|
-
webhook:
|
|
1279
|
+
var RawWebhookTrigger = z4.object({
|
|
1280
|
+
webhook: z4.object({}).strict()
|
|
1270
1281
|
}).strict();
|
|
1271
|
-
var RawJob =
|
|
1272
|
-
id:
|
|
1273
|
-
name:
|
|
1274
|
-
channel:
|
|
1275
|
-
on:
|
|
1276
|
-
prompt:
|
|
1277
|
-
|
|
1278
|
-
|
|
1282
|
+
var RawJob = z4.object({
|
|
1283
|
+
id: z4.string().regex(JOB_ID_PATTERN),
|
|
1284
|
+
name: z4.string().min(1).max(80),
|
|
1285
|
+
channel: z4.string().regex(/^[CG][A-Z0-9]{8,}$/, "a job runs in a Slack channel id"),
|
|
1286
|
+
on: z4.union([RawScheduleTrigger, RawWebhookTrigger]),
|
|
1287
|
+
prompt: z4.string().min(1).max(4000),
|
|
1288
|
+
thinkingLevel: ThinkingLevel.optional()
|
|
1289
|
+
}).strict().superRefine((job, ctx) => {
|
|
1290
|
+
if (job.thinkingLevel !== undefined && !("schedule" in job.on))
|
|
1291
|
+
ctx.addIssue({
|
|
1292
|
+
code: z4.ZodIssueCode.custom,
|
|
1293
|
+
path: ["thinkingLevel"],
|
|
1294
|
+
message: "thinkingLevel applies to scheduled jobs only"
|
|
1295
|
+
});
|
|
1296
|
+
});
|
|
1297
|
+
var RawJobs = z4.array(RawJob).default([]).superRefine((jobs, ctx) => {
|
|
1279
1298
|
const seen = new Set;
|
|
1280
1299
|
for (const [index, job] of jobs.entries()) {
|
|
1281
1300
|
if (seen.has(job.id)) {
|
|
1282
1301
|
ctx.addIssue({
|
|
1283
|
-
code:
|
|
1302
|
+
code: z4.ZodIssueCode.custom,
|
|
1284
1303
|
path: [index, "id"],
|
|
1285
1304
|
message: `duplicate job id: ${job.id}`
|
|
1286
1305
|
});
|
|
@@ -1301,9 +1320,13 @@ function scheduledJobFingerprint(input) {
|
|
|
1301
1320
|
return createHash("sha256").update(JSON.stringify([input.routineId, input.name, input.channel, input.prompt])).digest("hex");
|
|
1302
1321
|
}
|
|
1303
1322
|
function scheduledJobBindings(jobs) {
|
|
1304
|
-
return jobs.filter(isScheduledJob).map(({ id, name, channel, prompt }) => {
|
|
1323
|
+
return jobs.filter(isScheduledJob).map(({ id, name, channel, prompt, thinkingLevel }) => {
|
|
1305
1324
|
const input = { routineId: `job-${id}`, name, channel, prompt };
|
|
1306
|
-
return {
|
|
1325
|
+
return {
|
|
1326
|
+
routineId: input.routineId,
|
|
1327
|
+
fingerprint: scheduledJobFingerprint(input),
|
|
1328
|
+
...thinkingLevel === undefined ? {} : { thinkingLevel }
|
|
1329
|
+
};
|
|
1307
1330
|
});
|
|
1308
1331
|
}
|
|
1309
1332
|
function jobWebhookKey(instanceSecret, jobId) {
|
|
@@ -1369,11 +1392,10 @@ function capabilityScheduledJobs(config) {
|
|
|
1369
1392
|
|
|
1370
1393
|
// ../core/src/config.ts
|
|
1371
1394
|
var MODEL_RE = /^([a-z0-9-]+)\/(.+)$/;
|
|
1372
|
-
var
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
dailyCap: z4.number().int().min(0).default(40)
|
|
1395
|
+
var RawImages = z5.object({
|
|
1396
|
+
backend: z5.enum(["gemini"]).default("gemini"),
|
|
1397
|
+
model: z5.string().min(1).default("gemini-2.5-flash-image"),
|
|
1398
|
+
dailyCap: z5.number().int().min(0).default(40)
|
|
1377
1399
|
}).strict().default({});
|
|
1378
1400
|
var OMP_MODEL_ROLES = [
|
|
1379
1401
|
"default",
|
|
@@ -1386,40 +1408,40 @@ var OMP_MODEL_ROLES = [
|
|
|
1386
1408
|
"task",
|
|
1387
1409
|
"advisor"
|
|
1388
1410
|
];
|
|
1389
|
-
var RawModelRoles =
|
|
1390
|
-
var RawChannelModels =
|
|
1391
|
-
var RawSubagents =
|
|
1392
|
-
enabled:
|
|
1393
|
-
maxConcurrency:
|
|
1394
|
-
maxRecursionDepth:
|
|
1395
|
-
effort:
|
|
1411
|
+
var RawModelRoles = z5.record(z5.enum(OMP_MODEL_ROLES), z5.string().regex(MODEL_RE)).default({});
|
|
1412
|
+
var RawChannelModels = z5.record(z5.string().regex(/^[CDG][A-Z0-9]+$/, "channelModels keys must be Slack conversation ids"), z5.string().regex(MODEL_RE, "channel model must be provider/modelId")).default({});
|
|
1413
|
+
var RawSubagents = z5.object({
|
|
1414
|
+
enabled: z5.boolean().default(true),
|
|
1415
|
+
maxConcurrency: z5.number().int().min(1).max(8).default(3),
|
|
1416
|
+
maxRecursionDepth: z5.number().int().min(1).max(3).default(2),
|
|
1417
|
+
effort: z5.boolean().default(true)
|
|
1396
1418
|
}).strict().default({});
|
|
1397
|
-
var RawMemory =
|
|
1419
|
+
var RawMemory = z5.object({ ompBackend: z5.enum(["off", "local"]).default("off") }).strict().default({});
|
|
1398
1420
|
var SKILL_SOURCE_SCOPES = ["shared", "instance"];
|
|
1399
1421
|
var RawSkillSourceMetadata = {
|
|
1400
|
-
owner:
|
|
1401
|
-
scope:
|
|
1402
|
-
requires:
|
|
1422
|
+
owner: z5.string().min(1).optional(),
|
|
1423
|
+
scope: z5.enum(SKILL_SOURCE_SCOPES).optional(),
|
|
1424
|
+
requires: z5.array(z5.enum(CAPABILITY_IDS)).optional()
|
|
1403
1425
|
};
|
|
1404
|
-
var RawSkillSource =
|
|
1405
|
-
|
|
1406
|
-
github:
|
|
1407
|
-
ref:
|
|
1408
|
-
path:
|
|
1426
|
+
var RawSkillSource = z5.union([
|
|
1427
|
+
z5.object({
|
|
1428
|
+
github: z5.string().min(1),
|
|
1429
|
+
ref: z5.string().min(1).optional(),
|
|
1430
|
+
path: z5.string().optional(),
|
|
1409
1431
|
...RawSkillSourceMetadata
|
|
1410
1432
|
}).strict(),
|
|
1411
|
-
|
|
1433
|
+
z5.object({ s3: z5.string().min(1), ...RawSkillSourceMetadata }).strict()
|
|
1412
1434
|
]);
|
|
1413
|
-
var RawSkills =
|
|
1414
|
-
sources:
|
|
1415
|
-
promoteTo:
|
|
1435
|
+
var RawSkills = z5.object({
|
|
1436
|
+
sources: z5.array(RawSkillSource).optional(),
|
|
1437
|
+
promoteTo: z5.string().min(1).optional()
|
|
1416
1438
|
}).strict().default({});
|
|
1417
|
-
var RawInstanceConfig =
|
|
1418
|
-
name:
|
|
1419
|
-
model:
|
|
1420
|
-
admins:
|
|
1421
|
-
instructions:
|
|
1422
|
-
timezone:
|
|
1439
|
+
var RawInstanceConfig = z5.object({
|
|
1440
|
+
name: z5.string().min(1),
|
|
1441
|
+
model: z5.string().regex(MODEL_RE, "model must be provider/modelId"),
|
|
1442
|
+
admins: z5.array(z5.string().min(1)).min(1, "admins must list at least one Slack user id"),
|
|
1443
|
+
instructions: z5.string().default(""),
|
|
1444
|
+
timezone: z5.string().default("UTC").refine(isValidTimezone, (tz) => ({ message: `unknown timezone: ${tz}` })),
|
|
1423
1445
|
thinkingLevel: ThinkingLevel.optional(),
|
|
1424
1446
|
skills: RawSkills,
|
|
1425
1447
|
images: RawImages,
|
|
@@ -1496,24 +1518,24 @@ function parseInstanceConfig(yamlText) {
|
|
|
1496
1518
|
}
|
|
1497
1519
|
// ../core/src/company-capabilities.ts
|
|
1498
1520
|
import { parse as parseYaml3 } from "yaml";
|
|
1499
|
-
import { z as
|
|
1521
|
+
import { z as z6 } from "zod";
|
|
1500
1522
|
var COMPANY_CAPABILITY_DECLARATION_VERSION = 1;
|
|
1501
1523
|
var COMPANY_CAPABILITY_DECLARATION_MAX_COUNT = CAPABILITY_IDS.length;
|
|
1502
1524
|
var COMPANY_CAPABILITY_DESCRIPTION_MAX_CHARS = 280;
|
|
1503
1525
|
var COMPANY_CAPABILITY_DECLARATION_MAX_BYTES = 16 * 1024;
|
|
1504
|
-
var RawCompanyCapabilityDeclaration =
|
|
1505
|
-
id:
|
|
1506
|
-
description:
|
|
1526
|
+
var RawCompanyCapabilityDeclaration = z6.object({
|
|
1527
|
+
id: z6.enum(CAPABILITY_IDS),
|
|
1528
|
+
description: z6.string().min(1).max(COMPANY_CAPABILITY_DESCRIPTION_MAX_CHARS).refine((value) => value.trim() === value && !Array.from(value).some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127), "description must not have surrounding whitespace or control characters")
|
|
1507
1529
|
}).strict();
|
|
1508
|
-
var RawCompanyCapabilityDeclarations =
|
|
1509
|
-
version:
|
|
1510
|
-
capabilities:
|
|
1530
|
+
var RawCompanyCapabilityDeclarations = z6.object({
|
|
1531
|
+
version: z6.literal(COMPANY_CAPABILITY_DECLARATION_VERSION),
|
|
1532
|
+
capabilities: z6.array(RawCompanyCapabilityDeclaration).max(COMPANY_CAPABILITY_DECLARATION_MAX_COUNT)
|
|
1511
1533
|
}).strict().superRefine((document, ctx) => {
|
|
1512
1534
|
const seen = new Set;
|
|
1513
1535
|
for (const [index, declaration] of document.capabilities.entries()) {
|
|
1514
1536
|
if (seen.has(declaration.id)) {
|
|
1515
1537
|
ctx.addIssue({
|
|
1516
|
-
code:
|
|
1538
|
+
code: z6.ZodIssueCode.custom,
|
|
1517
1539
|
path: ["capabilities", index, "id"],
|
|
1518
1540
|
message: `duplicate capability declaration: ${declaration.id}`
|
|
1519
1541
|
});
|
|
@@ -1580,13 +1602,13 @@ var LIVE_ENDPOINT_NAME = "live";
|
|
|
1580
1602
|
// src/release/manifest.ts
|
|
1581
1603
|
import { createHash as createHash3 } from "node:crypto";
|
|
1582
1604
|
import { readFileSync as readFileSync2, statSync } from "node:fs";
|
|
1583
|
-
import { z as
|
|
1605
|
+
import { z as z7 } from "zod";
|
|
1584
1606
|
var RELEASE_VERSION_RE = /^v\d+\.\d+\.\d+$/;
|
|
1585
|
-
var Sha256 =
|
|
1586
|
-
var ReleaseArtifactSchema =
|
|
1587
|
-
key:
|
|
1607
|
+
var Sha256 = z7.string().regex(/^[0-9a-f]{64}$/, "must be a lowercase hex sha256");
|
|
1608
|
+
var ReleaseArtifactSchema = z7.object({
|
|
1609
|
+
key: z7.string().min(1),
|
|
1588
1610
|
sha256: Sha256,
|
|
1589
|
-
bytes:
|
|
1611
|
+
bytes: z7.number().int().nonnegative()
|
|
1590
1612
|
}).strict();
|
|
1591
1613
|
var SIGNING_ALGORITHMS = [
|
|
1592
1614
|
"RSASSA_PSS_SHA_256",
|
|
@@ -1599,21 +1621,21 @@ var SIGNING_ALGORITHMS = [
|
|
|
1599
1621
|
"ECDSA_SHA_384",
|
|
1600
1622
|
"ECDSA_SHA_512"
|
|
1601
1623
|
];
|
|
1602
|
-
var ReleaseManifestSchema =
|
|
1603
|
-
version:
|
|
1604
|
-
gitCommit:
|
|
1605
|
-
createdAt:
|
|
1606
|
-
agentImage:
|
|
1607
|
-
repository:
|
|
1608
|
-
tag:
|
|
1609
|
-
digest:
|
|
1624
|
+
var ReleaseManifestSchema = z7.object({
|
|
1625
|
+
version: z7.string().regex(RELEASE_VERSION_RE, "must be vMAJOR.MINOR.PATCH"),
|
|
1626
|
+
gitCommit: z7.string().regex(/^[0-9a-f]{40}$/, "must be a full commit sha"),
|
|
1627
|
+
createdAt: z7.string().datetime(),
|
|
1628
|
+
agentImage: z7.object({
|
|
1629
|
+
repository: z7.string().min(1),
|
|
1630
|
+
tag: z7.string().min(1),
|
|
1631
|
+
digest: z7.string().regex(/^sha256:[0-9a-f]{64}$/)
|
|
1610
1632
|
}).strict(),
|
|
1611
|
-
lambda:
|
|
1612
|
-
skills:
|
|
1613
|
-
signature:
|
|
1614
|
-
kmsKeyArn:
|
|
1615
|
-
algorithm:
|
|
1616
|
-
value:
|
|
1633
|
+
lambda: z7.record(ReleaseArtifactSchema),
|
|
1634
|
+
skills: z7.object({ key: z7.string().min(1), sha256: Sha256 }).strict(),
|
|
1635
|
+
signature: z7.object({
|
|
1636
|
+
kmsKeyArn: z7.string().min(1),
|
|
1637
|
+
algorithm: z7.enum(SIGNING_ALGORITHMS),
|
|
1638
|
+
value: z7.string().min(1)
|
|
1617
1639
|
}).strict().optional()
|
|
1618
1640
|
}).strict();
|
|
1619
1641
|
function parseManifest(json) {
|
|
@@ -1816,6 +1838,14 @@ function releaseSource(scope) {
|
|
|
1816
1838
|
throw new Error(`-c releaseBucket=<name> is required when deploying a release (or set ${RELEASE_BUCKET_ENV}); it is the bucket holding ${manifest.lambda.gateway?.key ?? "the release artifacts"}`);
|
|
1817
1839
|
return { version: manifest.version, bucket, manifest };
|
|
1818
1840
|
}
|
|
1841
|
+
function releaseAccess(scope, env = process.env) {
|
|
1842
|
+
const context = scope.node.tryGetContext("releaseBucket");
|
|
1843
|
+
const fromContext = typeof context === "string" && context !== "" ? context : undefined;
|
|
1844
|
+
const fromEnv = env[RELEASE_BUCKET_ENV];
|
|
1845
|
+
const bucket = fromContext ?? (fromEnv !== undefined && fromEnv !== "" ? fromEnv : DEFAULT_RELEASE_BUCKET);
|
|
1846
|
+
const signingKeyArn = configuredKeyArn(env);
|
|
1847
|
+
return { bucket, ...signingKeyArn === undefined ? {} : { signingKeyArn } };
|
|
1848
|
+
}
|
|
1819
1849
|
function releaseBucket(scope, source) {
|
|
1820
1850
|
const stack = cdk.Stack.of(scope);
|
|
1821
1851
|
const existing = stack.node.tryFindChild("FoundationReleaseBucket");
|
|
@@ -1899,4 +1929,4 @@ function ecrRepositoryArn(repositoryUri) {
|
|
|
1899
1929
|
return `arn:aws:ecr:${region}:${account}:repository/${name}`;
|
|
1900
1930
|
}
|
|
1901
1931
|
|
|
1902
|
-
export { parseSchedule, BASE_SLACK_EVENTS, CAPABILITY_IDS, requiredGithubPermissions, requiredSlackScopes, requiredSlackCommands, encodeProactiveChannels, CUSTOMIZATION_ARTIFACT_NAME, newsletterManifestFor, FOUNDATION_VERSION_RE, versionedConfigKey, instanceNames, slackCommandPrefix, slackCommandPrefixes, isScheduledJob, isWebhookJob, webhookJobBindings, scheduledJobBindings, jobWebhookKey, capabilityScheduledJobs, crmOutreachConfigured, crmPolicyFingerprint, parseInstanceConfig, loadInstanceFile, configPathFor, capabilityEnabled, requiresAuthenticatedQueue, enabledIntegrations, provisionsIntegration, adminsFor, WEB_SEARCH_TARGET, WEB_SEARCH_CONNECTOR_VERSION, LIVE_ENDPOINT_NAME, RELEASE_VERSION_RE, parseManifest, manifestKey, lambdaKey, skillsKey, buildManifest, manifestSigningPayload, verifyManifest, lambdaBundleContext, BUN_VERSION, LAMBDA_ENTRY_POINTS, skipBundle, RELEASE_BUCKET_ENV, RELEASE_DIR_ENV, releaseCacheDir, releaseSource, lambdaCode, agentImage, agentImageTagRequired, releaseImageRepositoryArn, ecrRepositoryArn };
|
|
1932
|
+
export { parseSchedule, BASE_SLACK_EVENTS, CAPABILITY_IDS, requiredGithubPermissions, requiredSlackScopes, requiredSlackCommands, encodeProactiveChannels, CUSTOMIZATION_ARTIFACT_NAME, newsletterManifestFor, FOUNDATION_VERSION_RE, versionedConfigKey, instanceNames, slackCommandPrefix, slackCommandPrefixes, isScheduledJob, isWebhookJob, webhookJobBindings, scheduledJobBindings, jobWebhookKey, capabilityScheduledJobs, crmOutreachConfigured, crmPolicyFingerprint, parseInstanceConfig, loadInstanceFile, configPathFor, capabilityEnabled, requiresAuthenticatedQueue, enabledIntegrations, provisionsIntegration, adminsFor, WEB_SEARCH_TARGET, WEB_SEARCH_CONNECTOR_VERSION, LIVE_ENDPOINT_NAME, RELEASE_VERSION_RE, parseManifest, manifestKey, lambdaKey, skillsKey, buildManifest, manifestSigningPayload, verifyManifest, lambdaBundleContext, BUN_VERSION, LAMBDA_ENTRY_POINTS, skipBundle, RELEASE_BUCKET_ENV, RELEASE_DIR_ENV, releaseCacheDir, releaseSource, releaseAccess, lambdaCode, agentImage, agentImageTagRequired, releaseImageRepositoryArn, ecrRepositoryArn };
|
package/dist/src/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
FoundationPipeline,
|
|
11
11
|
NewsletterStack,
|
|
12
12
|
deployStatements
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-fwqmac5n.js";
|
|
14
14
|
import {
|
|
15
15
|
BUN_VERSION,
|
|
16
16
|
LAMBDA_ENTRY_POINTS,
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
manifestSigningPayload,
|
|
37
37
|
parseManifest,
|
|
38
38
|
provisionsIntegration,
|
|
39
|
+
releaseAccess,
|
|
39
40
|
releaseCacheDir,
|
|
40
41
|
releaseImageRepositoryArn,
|
|
41
42
|
releaseSource,
|
|
@@ -43,7 +44,8 @@ import {
|
|
|
43
44
|
skillsKey,
|
|
44
45
|
skipBundle,
|
|
45
46
|
verifyManifest
|
|
46
|
-
} from "../chunk-
|
|
47
|
+
} from "../chunk-g56mqyfx.js";
|
|
48
|
+
import"../chunk-dz91avrb.js";
|
|
47
49
|
export {
|
|
48
50
|
BUILD_TIMEOUT,
|
|
49
51
|
BUN_VERSION,
|
|
@@ -80,6 +82,7 @@ export {
|
|
|
80
82
|
instanceNames as namesFor,
|
|
81
83
|
parseManifest,
|
|
82
84
|
provisionsIntegration,
|
|
85
|
+
releaseAccess,
|
|
83
86
|
releaseCacheDir,
|
|
84
87
|
releaseImageRepositoryArn,
|
|
85
88
|
releaseSource,
|
package/package.json
CHANGED
package/src/artifacts.ts
CHANGED
|
@@ -41,6 +41,7 @@ import * as s3 from "aws-cdk-lib/aws-s3";
|
|
|
41
41
|
import type { Construct } from "constructs";
|
|
42
42
|
import { lambdaBundleContext } from "./lambda-bundle-context.ts";
|
|
43
43
|
import { type ReleaseManifest, parseManifest } from "./release/manifest.ts";
|
|
44
|
+
import { DEFAULT_RELEASE_BUCKET, configuredKeyArn } from "./release/trust.ts";
|
|
44
45
|
|
|
45
46
|
/** Pinned Bun used to bundle every Lambda (and the docker fallback image tag). */
|
|
46
47
|
export const BUN_VERSION = "1.4.0";
|
|
@@ -174,6 +175,38 @@ export function releaseSource(scope: Construct): ReleaseSource | undefined {
|
|
|
174
175
|
return { version: manifest.version, bucket, manifest };
|
|
175
176
|
}
|
|
176
177
|
|
|
178
|
+
/**
|
|
179
|
+
* What a deployer of this app reads from Foundation's release account: the
|
|
180
|
+
* bucket the tool fetches `releases/*` from and the key whose signature it
|
|
181
|
+
* checks. The deployer roles (`deploy-permissions.ts`) are granted exactly
|
|
182
|
+
* these, cross-account, so a CodePipeline or GitHub Actions deploy can verify
|
|
183
|
+
* a release the way an admin's laptop can.
|
|
184
|
+
*
|
|
185
|
+
* Resolved the way `foundation-deploy` resolves them — `-c releaseBucket`
|
|
186
|
+
* (which the tool passes on a release deploy) or `FOUNDATION_RELEASE_BUCKET`,
|
|
187
|
+
* else the default bucket; `FOUNDATION_RELEASE_KMS_KEY_ARN`, else the pinned
|
|
188
|
+
* key — and resolved on EVERY synth, release or local build, so the grant does
|
|
189
|
+
* not come and go with how the last deploy was run.
|
|
190
|
+
*/
|
|
191
|
+
export interface ReleaseAccess {
|
|
192
|
+
bucket: string;
|
|
193
|
+
/** Absent only when no signing key is configured at all; nothing is verified then. */
|
|
194
|
+
signingKeyArn?: string;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function releaseAccess(
|
|
198
|
+
scope: Construct,
|
|
199
|
+
env: Record<string, string | undefined> = process.env,
|
|
200
|
+
): ReleaseAccess {
|
|
201
|
+
const context: unknown = scope.node.tryGetContext("releaseBucket");
|
|
202
|
+
const fromContext = typeof context === "string" && context !== "" ? context : undefined;
|
|
203
|
+
const fromEnv = env[RELEASE_BUCKET_ENV];
|
|
204
|
+
const bucket =
|
|
205
|
+
fromContext ?? (fromEnv !== undefined && fromEnv !== "" ? fromEnv : DEFAULT_RELEASE_BUCKET);
|
|
206
|
+
const signingKeyArn = configuredKeyArn(env);
|
|
207
|
+
return { bucket, ...(signingKeyArn === undefined ? {} : { signingKeyArn }) };
|
|
208
|
+
}
|
|
209
|
+
|
|
177
210
|
/**
|
|
178
211
|
* The release bucket as one imported construct per stack. Every Lambda in a
|
|
179
212
|
* stack references the same bucket, and importing it once keeps the template's
|
package/src/deploy/release.ts
CHANGED
|
@@ -29,34 +29,10 @@ import {
|
|
|
29
29
|
manifestKey,
|
|
30
30
|
verifyManifest,
|
|
31
31
|
} from "../release/manifest.ts";
|
|
32
|
+
import { DEFAULT_RELEASE_BUCKET, expectedKeyArn } from "../release/trust.ts";
|
|
32
33
|
import { type AwsCallContext, aws } from "./aws.ts";
|
|
33
34
|
import { FOUNDATION_ROOT, INFRA_ROOT } from "./paths.ts";
|
|
34
35
|
|
|
35
|
-
/**
|
|
36
|
-
* The bucket a release is fetched from when nothing says otherwise.
|
|
37
|
-
*
|
|
38
|
-
* One name, owned by Foundry 41, created by `release-account-stack.ts`. A
|
|
39
|
-
* mirror is `--release-bucket` or `FOUNDATION_RELEASE_BUCKET`, and the
|
|
40
|
-
* signature is what makes a mirror safe to use.
|
|
41
|
-
*/
|
|
42
|
-
export const DEFAULT_RELEASE_BUCKET = "foundry41-foundation-releases";
|
|
43
|
-
|
|
44
|
-
/** The env var that names the key a deployer will accept a manifest from. */
|
|
45
|
-
export const RELEASE_KMS_KEY_ARN_ENV = "FOUNDATION_RELEASE_KMS_KEY_ARN";
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* The Foundation release signing key, in the Foundry 41 release account
|
|
49
|
-
* (793593623536), created by `ReleaseAccountStack` on 2026-09-20.
|
|
50
|
-
*
|
|
51
|
-
* This constant is what makes signature verification mean anything: a
|
|
52
|
-
* deployer refuses a manifest signed by any other key. Changing it rotates
|
|
53
|
-
* the root of trust for every customer, so it changes only with a key
|
|
54
|
-
* rotation and a release that says so. {@link RELEASE_KMS_KEY_ARN_ENV}
|
|
55
|
-
* overrides it, for a private mirror or a test key.
|
|
56
|
-
*/
|
|
57
|
-
export const RELEASE_KMS_KEY_ARN: string | undefined =
|
|
58
|
-
"arn:aws:kms:us-east-1:793593623536:key/fba7d9f3-e9b6-49ae-b4a5-c8c5f1429231";
|
|
59
|
-
|
|
60
36
|
/** What the caller asked for, before anything has been fetched. */
|
|
61
37
|
export interface ReleaseRequest {
|
|
62
38
|
/** `vX.Y.Z`, when the caller named a version or one was implied. */
|
|
@@ -149,16 +125,6 @@ export function releaseRequest(
|
|
|
149
125
|
return { version, bucket };
|
|
150
126
|
}
|
|
151
127
|
|
|
152
|
-
/** The key ARN a deployer will accept a manifest from. */
|
|
153
|
-
export function expectedKeyArn(env: Record<string, string | undefined> = process.env): string {
|
|
154
|
-
const configured = env[RELEASE_KMS_KEY_ARN_ENV] ?? RELEASE_KMS_KEY_ARN;
|
|
155
|
-
if (configured === undefined || configured === "")
|
|
156
|
-
throw new Error(
|
|
157
|
-
`no Foundation release signing key is configured; set ${RELEASE_KMS_KEY_ARN_ENV} to the key ARN published with the release`,
|
|
158
|
-
);
|
|
159
|
-
return configured;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
128
|
export interface ResolveReleaseDeps {
|
|
163
129
|
/** Defaults to the SDK-backed verifier for the expected key. */
|
|
164
130
|
kms?: KmsVerifier;
|
|
@@ -17,6 +17,7 @@ import type * as cdk from "aws-cdk-lib";
|
|
|
17
17
|
import * as iam from "aws-cdk-lib/aws-iam";
|
|
18
18
|
import type * as kms from "aws-cdk-lib/aws-kms";
|
|
19
19
|
import type * as s3 from "aws-cdk-lib/aws-s3";
|
|
20
|
+
import type { ReleaseAccess } from "./artifacts.ts";
|
|
20
21
|
import { type Instance, type InstanceNames, provisionsIntegration } from "./names.ts";
|
|
21
22
|
|
|
22
23
|
export interface DeployTargets {
|
|
@@ -24,6 +25,13 @@ export interface DeployTargets {
|
|
|
24
25
|
bucket: s3.IBucket;
|
|
25
26
|
/** The CMK that bucket is encrypted with. */
|
|
26
27
|
dataKey: kms.IKey;
|
|
28
|
+
/**
|
|
29
|
+
* Foundation's release bucket and signing key, in the release account
|
|
30
|
+
* (`releaseAccess()` in `artifacts.ts`). Cross-account access needs BOTH
|
|
31
|
+
* sides to allow it: the release account's bucket and key policies name the
|
|
32
|
+
* customer account, and the statements below are this account's half.
|
|
33
|
+
*/
|
|
34
|
+
release: ReleaseAccess;
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
/**
|
|
@@ -49,6 +57,30 @@ export function deployStatements(
|
|
|
49
57
|
const knockOauthClient = secretArn(names.secretKnockOauthClient);
|
|
50
58
|
|
|
51
59
|
return [
|
|
60
|
+
// A release deploy, before anything else: `aws s3 cp` of
|
|
61
|
+
// `releases/<v>/manifest.json`, then of every Lambda zip and
|
|
62
|
+
// `skills.tar.gz` the manifest names (the digest check in `verifyRelease`),
|
|
63
|
+
// and of `skills.tar.gz` again for the config sync (`unpackReleaseSkills`).
|
|
64
|
+
// HeadObject is authorized by s3:GetObject. The bucket is SSE-S3, so there
|
|
65
|
+
// is no key to grant for it.
|
|
66
|
+
new iam.PolicyStatement({
|
|
67
|
+
sid: "ReadFoundationRelease",
|
|
68
|
+
actions: ["s3:GetObject"],
|
|
69
|
+
resources: [`arn:${stack.partition}:s3:::${targets.release.bucket}/releases/*`],
|
|
70
|
+
}),
|
|
71
|
+
// ...and the manifest's signature, checked with one KMS `Verify` against
|
|
72
|
+
// the pinned key (`src/release/kms.ts`); nothing calls GetPublicKey or
|
|
73
|
+
// DescribeKey. By key ARN, not alias: an identity policy cannot name
|
|
74
|
+
// another account's key by its alias.
|
|
75
|
+
...(targets.release.signingKeyArn === undefined
|
|
76
|
+
? []
|
|
77
|
+
: [
|
|
78
|
+
new iam.PolicyStatement({
|
|
79
|
+
sid: "VerifyReleaseSignature",
|
|
80
|
+
actions: ["kms:Verify"],
|
|
81
|
+
resources: [targets.release.signingKeyArn],
|
|
82
|
+
}),
|
|
83
|
+
]),
|
|
52
84
|
// How `cdk deploy` does everything else: it assumes the bootstrap roles for
|
|
53
85
|
// lookup, asset publishing and the CloudFormation call.
|
|
54
86
|
new iam.PolicyStatement({
|
|
@@ -140,8 +172,10 @@ export function deployStatements(
|
|
|
140
172
|
actions: ["kms:Encrypt", "kms:Decrypt", "kms:GenerateDataKey*"],
|
|
141
173
|
resources: [targets.dataKey.keyArn],
|
|
142
174
|
}),
|
|
143
|
-
// `foundation-deploy post-deploy
|
|
144
|
-
// `_fs_probe`) against DEFAULT, then promotion
|
|
175
|
+
// `foundation-deploy post-deploy` (and `deploy`'s inline promotion): the
|
|
176
|
+
// smoke test (`_smoke_test`, `_fs_probe`) against DEFAULT, then promotion
|
|
177
|
+
// of the new version onto `live`, which CREATES `live` when an instance
|
|
178
|
+
// brought up by `deploy` alone has none yet (`promoteEndpoint`).
|
|
145
179
|
new iam.PolicyStatement({
|
|
146
180
|
sid: "SmokeTestRuntime",
|
|
147
181
|
actions: [
|
|
@@ -150,6 +184,7 @@ export function deployStatements(
|
|
|
150
184
|
"bedrock-agentcore:ListAgentRuntimeVersions",
|
|
151
185
|
"bedrock-agentcore:GetAgentRuntimeEndpoint",
|
|
152
186
|
"bedrock-agentcore:UpdateAgentRuntimeEndpoint",
|
|
187
|
+
"bedrock-agentcore:CreateAgentRuntimeEndpoint",
|
|
153
188
|
],
|
|
154
189
|
resources: [
|
|
155
190
|
`arn:${stack.partition}:bedrock-agentcore:${stack.region}:${stack.account}:runtime/${names.runtimeName}*`,
|
package/src/index.ts
CHANGED
|
@@ -4,11 +4,13 @@ export {
|
|
|
4
4
|
RELEASE_BUCKET_ENV,
|
|
5
5
|
RELEASE_DIR_ENV,
|
|
6
6
|
type LambdaArtifactId,
|
|
7
|
+
type ReleaseAccess,
|
|
7
8
|
type ReleaseSource,
|
|
8
9
|
agentImage,
|
|
9
10
|
agentImageTagRequired,
|
|
10
11
|
ecrRepositoryArn,
|
|
11
12
|
lambdaCode,
|
|
13
|
+
releaseAccess,
|
|
12
14
|
releaseCacheDir,
|
|
13
15
|
releaseImageRepositoryArn,
|
|
14
16
|
releaseSource,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a deployer gets a release from, and whose signature it accepts.
|
|
3
|
+
*
|
|
4
|
+
* Pure, like `manifest.ts`: the deploy tool reads these to fetch and verify a
|
|
5
|
+
* release, and the CDK app reads them to give the deployer roles
|
|
6
|
+
* (`deploy-permissions.ts`) the release-account permissions that fetch needs.
|
|
7
|
+
* Both must resolve to the same bucket and the same key, so the resolution
|
|
8
|
+
* lives here once rather than in each.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The bucket a release is fetched from when nothing says otherwise.
|
|
13
|
+
*
|
|
14
|
+
* One name, owned by Foundry 41, created by `release-account-stack.ts`. A
|
|
15
|
+
* mirror is `--release-bucket` or `FOUNDATION_RELEASE_BUCKET`, and the
|
|
16
|
+
* signature is what makes a mirror safe to use.
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_RELEASE_BUCKET = "foundry41-foundation-releases";
|
|
19
|
+
|
|
20
|
+
/** The env var that names the key a deployer will accept a manifest from. */
|
|
21
|
+
export const RELEASE_KMS_KEY_ARN_ENV = "FOUNDATION_RELEASE_KMS_KEY_ARN";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The Foundation release signing key, in the Foundry 41 release account
|
|
25
|
+
* (793593623536), created by `ReleaseAccountStack` on 2026-09-20.
|
|
26
|
+
*
|
|
27
|
+
* This constant is what makes signature verification mean anything: a
|
|
28
|
+
* deployer refuses a manifest signed by any other key. Changing it rotates
|
|
29
|
+
* the root of trust for every customer, so it changes only with a key
|
|
30
|
+
* rotation and a release that says so. {@link RELEASE_KMS_KEY_ARN_ENV}
|
|
31
|
+
* overrides it, for a private mirror or a test key.
|
|
32
|
+
*/
|
|
33
|
+
export const RELEASE_KMS_KEY_ARN: string | undefined =
|
|
34
|
+
"arn:aws:kms:us-east-1:793593623536:key/fba7d9f3-e9b6-49ae-b4a5-c8c5f1429231";
|
|
35
|
+
|
|
36
|
+
/** The key ARN a deployer will accept a manifest from, or `undefined` when none is configured. */
|
|
37
|
+
export function configuredKeyArn(
|
|
38
|
+
env: Record<string, string | undefined> = process.env,
|
|
39
|
+
): string | undefined {
|
|
40
|
+
const configured = env[RELEASE_KMS_KEY_ARN_ENV] ?? RELEASE_KMS_KEY_ARN;
|
|
41
|
+
return configured === undefined || configured === "" ? undefined : configured;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The key ARN a deployer will accept a manifest from. Throws when none is configured. */
|
|
45
|
+
export function expectedKeyArn(env: Record<string, string | undefined> = process.env): string {
|
|
46
|
+
const configured = configuredKeyArn(env);
|
|
47
|
+
if (configured === undefined)
|
|
48
|
+
throw new Error(
|
|
49
|
+
`no Foundation release signing key is configured; set ${RELEASE_KMS_KEY_ARN_ENV} to the key ARN published with the release`,
|
|
50
|
+
);
|
|
51
|
+
return configured;
|
|
52
|
+
}
|