@deployfoundation/foundation-deploy 0.2.47 → 0.2.48
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 +2 -2
- package/dist/bin/foundation-deploy.js +20 -15
- package/dist/{chunk-k6mcv6dk.js → chunk-2tachxjf.js} +15 -4
- package/dist/{chunk-yn8x77x6.js → chunk-z4gsy5kh.js} +22 -2
- package/dist/src/index.js +2 -2
- package/package.json +1 -1
- package/src/deploy/assets/slack-app-manifest.yml +4 -0
- package/src/deploy/config-sync.ts +15 -1
- package/src/deploy/deploy.ts +5 -3
- package/src/stacks/agent-stack.ts +14 -0
- package/src/stacks/api-stack.ts +11 -3
package/dist/bin/app.js
CHANGED
|
@@ -8,13 +8,13 @@ import {
|
|
|
8
8
|
FoundationPages,
|
|
9
9
|
FoundationPipeline,
|
|
10
10
|
NewsletterStack
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-z4gsy5kh.js";
|
|
12
12
|
import {
|
|
13
13
|
adminsFor,
|
|
14
14
|
instanceNames,
|
|
15
15
|
loadInstanceFile,
|
|
16
16
|
provisionsIntegration
|
|
17
|
-
} from "../chunk-
|
|
17
|
+
} from "../chunk-2tachxjf.js";
|
|
18
18
|
import"../chunk-dz91avrb.js";
|
|
19
19
|
|
|
20
20
|
// bin/app.ts
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
slackCommandPrefix,
|
|
23
23
|
verifyManifest,
|
|
24
24
|
versionedConfigKey
|
|
25
|
-
} from "../chunk-
|
|
25
|
+
} from "../chunk-2tachxjf.js";
|
|
26
26
|
import {
|
|
27
27
|
DEFAULT_RELEASE_BUCKET,
|
|
28
28
|
expectedKeyArn
|
|
@@ -282,7 +282,7 @@ async function callerAccountId(ctx) {
|
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
// src/deploy/config-sync.ts
|
|
285
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, mkdtempSync as mkdtempSync2, readdirSync } from "node:fs";
|
|
285
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, mkdtempSync as mkdtempSync2, readFileSync as readFileSync3, readdirSync } from "node:fs";
|
|
286
286
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
287
287
|
import { join as join3, resolve as resolve3 } from "node:path";
|
|
288
288
|
|
|
@@ -494,6 +494,11 @@ async function uploadRuntimeConfig(ctx, opts) {
|
|
|
494
494
|
if (opts.runtimeConfigPath === undefined && ctx.instance.customization !== undefined)
|
|
495
495
|
throw new Error("customized instances require a freshly validated runtimeConfigPath; run through `deploy` or `config:sync`");
|
|
496
496
|
const source = opts.runtimeConfigPath ?? ctx.paths.configPath;
|
|
497
|
+
try {
|
|
498
|
+
parseInstanceConfig(readFileSync3(source, "utf8"));
|
|
499
|
+
} catch (error) {
|
|
500
|
+
throw new Error(`config for ${opts.version} does not parse with this release's schema, so nothing was uploaded: ${error instanceof Error ? error.message : String(error)}`);
|
|
501
|
+
}
|
|
497
502
|
const key = versionedConfigKey(ctx.names.configKey, opts.version);
|
|
498
503
|
await awsMutate(ctx, ["s3", "cp", source, `s3://${opts.bucket}/${key}`]);
|
|
499
504
|
return key;
|
|
@@ -908,7 +913,7 @@ var UPWORK_GRAPHQL_DOCUMENTS = Object.freeze({
|
|
|
908
913
|
var ETRADE_RESPONSE_MAX_BYTES = 1024 * 1024;
|
|
909
914
|
var MAX_SECRET_JSON_LENGTH = 16 * 1024;
|
|
910
915
|
// src/deploy/config.ts
|
|
911
|
-
import { readFileSync as
|
|
916
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
912
917
|
import { parse } from "yaml";
|
|
913
918
|
function adminsCsv(yamlText, source = "runtime config") {
|
|
914
919
|
const doc = parse(yamlText);
|
|
@@ -922,7 +927,7 @@ function adminsCsv(yamlText, source = "runtime config") {
|
|
|
922
927
|
}).join(",");
|
|
923
928
|
}
|
|
924
929
|
function adminsCsvFromFile(path) {
|
|
925
|
-
return adminsCsv(
|
|
930
|
+
return adminsCsv(readFileSync4(path, "utf8"), path);
|
|
926
931
|
}
|
|
927
932
|
|
|
928
933
|
// src/deploy/endpoint.ts
|
|
@@ -1416,7 +1421,7 @@ function composeRuntimeSecret(inputs) {
|
|
|
1416
1421
|
import {
|
|
1417
1422
|
lstatSync,
|
|
1418
1423
|
mkdirSync as mkdirSync3,
|
|
1419
|
-
readFileSync as
|
|
1424
|
+
readFileSync as readFileSync5,
|
|
1420
1425
|
realpathSync,
|
|
1421
1426
|
rmSync as rmSync2,
|
|
1422
1427
|
statSync,
|
|
@@ -1536,9 +1541,9 @@ function stageCustomization(options) {
|
|
|
1536
1541
|
const sourceFile = resolvedCustomizationFile(sourceRoot, customization.path);
|
|
1537
1542
|
if (sourceFile === undefined)
|
|
1538
1543
|
return migrationFallback(customization.required, log, platformConfigPath);
|
|
1539
|
-
const yaml =
|
|
1544
|
+
const yaml = readFileSync5(sourceFile);
|
|
1540
1545
|
const tenantConfig = parseInstanceConfig(yaml.toString("utf8"));
|
|
1541
|
-
const platformConfig = parseInstanceConfig(
|
|
1546
|
+
const platformConfig = parseInstanceConfig(readFileSync5(platformConfigPath, "utf8"));
|
|
1542
1547
|
if (tenantConfig.name !== instance.displayName)
|
|
1543
1548
|
throw new Error("Customization config name must match the selected instance.");
|
|
1544
1549
|
assertValidTenantJobs(tenantConfig);
|
|
@@ -1895,7 +1900,7 @@ async function deploy(ctx, opts) {
|
|
|
1895
1900
|
import { createServer } from "node:http";
|
|
1896
1901
|
|
|
1897
1902
|
// src/deploy/github-app-manifest.ts
|
|
1898
|
-
import { readFileSync as
|
|
1903
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
1899
1904
|
import { join as join6 } from "node:path";
|
|
1900
1905
|
import { parse as parseYaml } from "yaml";
|
|
1901
1906
|
var GITHUB_APP_MANIFEST_PATH = join6(PACKAGE_ASSETS, "github-app-manifest.yml");
|
|
@@ -1916,7 +1921,7 @@ function renderAppManifest(template, instance) {
|
|
|
1916
1921
|
});
|
|
1917
1922
|
}
|
|
1918
1923
|
function loadAppManifest(instance, template) {
|
|
1919
|
-
const text = template ??
|
|
1924
|
+
const text = template ?? readFileSync6(GITHUB_APP_MANIFEST_PATH, "utf8");
|
|
1920
1925
|
const manifest = parseYaml(renderAppManifest(text, instance));
|
|
1921
1926
|
if ("default_permissions" in manifest)
|
|
1922
1927
|
throw new Error("app-manifest.yml: default_permissions are generated from the capability registry; remove them from the template");
|
|
@@ -2034,7 +2039,7 @@ async function githubAppCreate(options) {
|
|
|
2034
2039
|
}
|
|
2035
2040
|
|
|
2036
2041
|
// src/deploy/job-key.ts
|
|
2037
|
-
import { readFileSync as
|
|
2042
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
2038
2043
|
function jobWebhookOutputKey(jobId) {
|
|
2039
2044
|
return `JobWebhookUrl${jobId.replace(/[^A-Za-z0-9]/g, "")}`;
|
|
2040
2045
|
}
|
|
@@ -2042,7 +2047,7 @@ function jobsWebhookSecretName(secretPrefix) {
|
|
|
2042
2047
|
return `${secretPrefix}/jobs/webhook`;
|
|
2043
2048
|
}
|
|
2044
2049
|
async function jobKey(ctx, jobId, options = {}) {
|
|
2045
|
-
const config = parseInstanceConfig(
|
|
2050
|
+
const config = parseInstanceConfig(readFileSync7(ctx.paths.configPath, "utf8"));
|
|
2046
2051
|
const job = config.jobs.find((entry) => entry.id === jobId);
|
|
2047
2052
|
if (job === undefined)
|
|
2048
2053
|
throw new Error(`no job "${jobId}" in ${ctx.paths.configPath}`);
|
|
@@ -2108,11 +2113,11 @@ async function postDeploy(ctx, opts = {}) {
|
|
|
2108
2113
|
}
|
|
2109
2114
|
|
|
2110
2115
|
// src/deploy/setup.ts
|
|
2111
|
-
import { existsSync as existsSync5, readFileSync as
|
|
2116
|
+
import { existsSync as existsSync5, readFileSync as readFileSync9 } from "node:fs";
|
|
2112
2117
|
import { resolve as resolve5 } from "node:path";
|
|
2113
2118
|
|
|
2114
2119
|
// src/deploy/slack-manifest.ts
|
|
2115
|
-
import { readFileSync as
|
|
2120
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
2116
2121
|
import { join as join7 } from "node:path";
|
|
2117
2122
|
import { parse as parse2 } from "yaml";
|
|
2118
2123
|
var SLACK_APP_MANIFEST_PATH = join7(PACKAGE_ASSETS, "slack-app-manifest.yml");
|
|
@@ -2151,7 +2156,7 @@ function buildSlackManifest(template, instance, env) {
|
|
|
2151
2156
|
return manifest;
|
|
2152
2157
|
}
|
|
2153
2158
|
function slackManifestFor(instance, env) {
|
|
2154
|
-
return buildSlackManifest(
|
|
2159
|
+
return buildSlackManifest(readFileSync8(SLACK_APP_MANIFEST_PATH, "utf8"), {
|
|
2155
2160
|
displayName: instance.displayName,
|
|
2156
2161
|
commandPrefix: slackCommandPrefix(instance),
|
|
2157
2162
|
pages: instance.integrations.pages
|
|
@@ -2377,7 +2382,7 @@ async function writeRuntimeSecret(ctx) {
|
|
|
2377
2382
|
async function seedCodex(ctx, codexFile) {
|
|
2378
2383
|
if (!existsSync5(codexFile))
|
|
2379
2384
|
throw new Error(`${codexFile} not found — sign in locally first, or pass --codex-file`);
|
|
2380
|
-
const document =
|
|
2385
|
+
const document = readFileSync9(codexFile, "utf8");
|
|
2381
2386
|
JSON.parse(document);
|
|
2382
2387
|
await putSecretString(ctx, ctx.names.secretCodex, document);
|
|
2383
2388
|
console.log(` ${ctx.names.secretCodex} seeded from ${codexFile}`);
|
|
@@ -369,7 +369,8 @@ var RawGoogleDriveCapability = z.object({
|
|
|
369
369
|
var RawGoogleDocsIdentity = z.object({
|
|
370
370
|
address: z.string().regex(/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i),
|
|
371
371
|
channel: z.string().regex(/^[CGD][A-Z0-9]{8,}$/),
|
|
372
|
-
owner: z.string().regex(/^U[A-Z0-9]{8,}$/, "owner must be a Slack U id")
|
|
372
|
+
owner: z.string().regex(/^U[A-Z0-9]{8,}$/, "owner must be a Slack U id"),
|
|
373
|
+
readPersonalDrive: z.boolean().optional()
|
|
373
374
|
}).strict();
|
|
374
375
|
var RawGoogleDocsCapability = z.object({
|
|
375
376
|
enabled: z.boolean().default(false),
|
|
@@ -384,6 +385,12 @@ var RawGoogleDocsCapability = z.object({
|
|
|
384
385
|
for (const field of ["owner", "channel"]) {
|
|
385
386
|
const seen = new Set;
|
|
386
387
|
for (const [index, identity] of value.identities.entries()) {
|
|
388
|
+
if (field === "owner" && identity.readPersonalDrive && !identity.channel.startsWith("D"))
|
|
389
|
+
ctx.addIssue({
|
|
390
|
+
code: z.ZodIssueCode.custom,
|
|
391
|
+
path: ["identities", index, "channel"],
|
|
392
|
+
message: "personal Drive reading requires an owner-bound DM"
|
|
393
|
+
});
|
|
387
394
|
if (seen.has(identity[field]))
|
|
388
395
|
ctx.addIssue({
|
|
389
396
|
code: z.ZodIssueCode.custom,
|
|
@@ -675,13 +682,17 @@ var CAPABILITIES = [
|
|
|
675
682
|
},
|
|
676
683
|
{
|
|
677
684
|
id: "googleDocs",
|
|
678
|
-
summary: "Draft Google Docs
|
|
685
|
+
summary: "Draft Google Docs by personal consent; optionally connect a separate read-only personal Drive grant in the owner's DM.",
|
|
679
686
|
config: RawGoogleDocsCapability,
|
|
680
687
|
...NONE,
|
|
681
688
|
secrets: ["google/docs", GOOGLE_OAUTH_SECRET],
|
|
682
689
|
resources: ["googleDocsProxyFunctionName"],
|
|
683
|
-
slackCommands: ["docs-connect"],
|
|
684
|
-
googleScopes: [
|
|
690
|
+
slackCommands: ["docs-connect", "personal-drive"],
|
|
691
|
+
googleScopes: [
|
|
692
|
+
"https://www.googleapis.com/auth/drive.file",
|
|
693
|
+
"https://www.googleapis.com/auth/drive.readonly",
|
|
694
|
+
...GOOGLE_IDENTITY_SCOPES
|
|
695
|
+
]
|
|
685
696
|
},
|
|
686
697
|
{
|
|
687
698
|
id: "googleCalendar",
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
scheduledJobBindings,
|
|
25
25
|
slackCommandPrefixes,
|
|
26
26
|
webhookJobBindings
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-2tachxjf.js";
|
|
28
28
|
|
|
29
29
|
// src/stacks/agent-stack.ts
|
|
30
30
|
import * as cdk from "aws-cdk-lib";
|
|
@@ -259,6 +259,12 @@ class FoundationAgent extends cdk.Stack {
|
|
|
259
259
|
resource: "secret",
|
|
260
260
|
resourceName: `${docsSecretPrefix}/personal/*-??????`,
|
|
261
261
|
arnFormat: cdk.ArnFormat.COLON_RESOURCE_NAME
|
|
262
|
+
}),
|
|
263
|
+
cdk.Stack.of(this).formatArn({
|
|
264
|
+
service: "secretsmanager",
|
|
265
|
+
resource: "secret",
|
|
266
|
+
resourceName: `${docsSecretPrefix}/personal-drive/*-??????`,
|
|
267
|
+
arnFormat: cdk.ArnFormat.COLON_RESOURCE_NAME
|
|
262
268
|
})
|
|
263
269
|
]
|
|
264
270
|
}));
|
|
@@ -382,6 +388,12 @@ class FoundationAgent extends cdk.Stack {
|
|
|
382
388
|
this.denyDirectKnockSecretAccess(role, props);
|
|
383
389
|
this.denyDirectEmailSecretAccess(role, props.secrets.googleEmail, instanceNames(props.instance).secretGoogleEmail);
|
|
384
390
|
this.denyDirectDocsSecretAccess(role, instanceNames(props.instance).secretGoogleDocs);
|
|
391
|
+
role.addToPolicy(new iam.PolicyStatement({
|
|
392
|
+
sid: "DenySlackSigningSecret",
|
|
393
|
+
effect: iam.Effect.DENY,
|
|
394
|
+
actions: ["secretsmanager:*"],
|
|
395
|
+
resources: [secretArnPattern(props.secrets.signing)]
|
|
396
|
+
}));
|
|
385
397
|
this.denyDirectUpworkApprovalAccess(role, props.upworkApprovalTable);
|
|
386
398
|
this.denyDirectEtradeAccess(role, props);
|
|
387
399
|
this.pagesPolicy("AgentReadOnlyPagesPolicy", role, props.instance, { invoke: false });
|
|
@@ -906,6 +918,14 @@ class FoundationApi extends cdk2.Stack {
|
|
|
906
918
|
generateStringKey: "placeholder"
|
|
907
919
|
}
|
|
908
920
|
}));
|
|
921
|
+
for (const identity of docsIdentities.filter((entry) => entry.readPersonalDrive === true)) {
|
|
922
|
+
docsSecrets.push(new secretsmanager.Secret(this, `PersonalDriveSecret${identity.owner}`, {
|
|
923
|
+
secretName: `${instanceNames(props.instance).secretGoogleDocs}/personal-drive/${identity.owner}`,
|
|
924
|
+
description: `${display} separate read-only personal Drive grant for Slack ${identity.owner}`,
|
|
925
|
+
removalPolicy: cdk2.RemovalPolicy.DESTROY,
|
|
926
|
+
generateSecretString: { secretStringTemplate: "{}", generateStringKey: "placeholder" }
|
|
927
|
+
}));
|
|
928
|
+
}
|
|
909
929
|
const gatewayRole = new iam2.Role(this, "GatewayRole", {
|
|
910
930
|
assumedBy: new iam2.ServicePrincipal("lambda.amazonaws.com"),
|
|
911
931
|
description: `${display} gateway Lambda role (least privilege)`,
|
|
@@ -1807,7 +1827,7 @@ function configTimezoneFor(configPath) {
|
|
|
1807
1827
|
}
|
|
1808
1828
|
function googleDocsIdentitiesFor(configPath) {
|
|
1809
1829
|
const docs = parseInstanceConfig(readFileSync(configPath, "utf8")).capabilities.googleDocs;
|
|
1810
|
-
return docs.enabled ? docs.identities.map((
|
|
1830
|
+
return docs.enabled ? docs.identities.map((identity) => ({ ...identity })) : [];
|
|
1811
1831
|
}
|
|
1812
1832
|
function emailIdentitiesFor(configPath) {
|
|
1813
1833
|
const email = parseInstanceConfig(readFileSync(configPath, "utf8")).capabilities.email;
|
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-z4gsy5kh.js";
|
|
14
14
|
import {
|
|
15
15
|
BUN_VERSION,
|
|
16
16
|
LAMBDA_ENTRY_POINTS,
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
skillsKey,
|
|
45
45
|
skipBundle,
|
|
46
46
|
verifyManifest
|
|
47
|
-
} from "../chunk-
|
|
47
|
+
} from "../chunk-2tachxjf.js";
|
|
48
48
|
import"../chunk-dz91avrb.js";
|
|
49
49
|
export {
|
|
50
50
|
BUILD_TIMEOUT,
|
package/package.json
CHANGED
|
@@ -73,6 +73,10 @@ features:
|
|
|
73
73
|
url: ${COMMANDS_REQUEST_URL}
|
|
74
74
|
description: Let ${DISPLAY_NAME} draft Google Docs in your own Drive
|
|
75
75
|
should_escape: false
|
|
76
|
+
- command: /${COMMAND_PREFIX}-personal-drive
|
|
77
|
+
url: ${COMMANDS_REQUEST_URL}
|
|
78
|
+
description: Connect your personal Drive read-only in your configured DM
|
|
79
|
+
should_escape: false
|
|
76
80
|
- command: /${COMMAND_PREFIX}-knock-connect
|
|
77
81
|
url: ${COMMANDS_REQUEST_URL}
|
|
78
82
|
description: Connect ${DISPLAY_NAME} to Knock read-only tools (admins)
|
|
@@ -17,9 +17,10 @@
|
|
|
17
17
|
* checkout's `skills/` otherwise. A company's own skills are a source in its
|
|
18
18
|
* runtime config (a GitHub repo), never a directory in this package.
|
|
19
19
|
*/
|
|
20
|
-
import { existsSync, mkdirSync, mkdtempSync, readdirSync } from "node:fs";
|
|
20
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync } from "node:fs";
|
|
21
21
|
import { tmpdir } from "node:os";
|
|
22
22
|
import { join, resolve } from "node:path";
|
|
23
|
+
import { parseInstanceConfig } from "@deployfoundation/foundation-core";
|
|
23
24
|
import { versionedConfigKey } from "@deployfoundation/foundation-core/instance";
|
|
24
25
|
import { skillsKey } from "../release/manifest.ts";
|
|
25
26
|
import { type AwsContext, awsMutate, stackOutput } from "./aws.ts";
|
|
@@ -130,6 +131,19 @@ export async function uploadRuntimeConfig(
|
|
|
130
131
|
"customized instances require a freshly validated runtimeConfigPath; run through `deploy` or `config:sync`",
|
|
131
132
|
);
|
|
132
133
|
const source = opts.runtimeConfigPath ?? ctx.paths.configPath;
|
|
134
|
+
// Parse with THIS release's schema before anything is written. Redeploying
|
|
135
|
+
// the live version writes the very object the live runtime reads, so an
|
|
136
|
+
// unparseable upload puts every session on the fallback config even though
|
|
137
|
+
// the deploy then fails at synth (Sky, 2026-10-19: no Upwork for a day).
|
|
138
|
+
try {
|
|
139
|
+
parseInstanceConfig(readFileSync(source, "utf8"));
|
|
140
|
+
} catch (error) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`config for ${opts.version} does not parse with this release's schema, so nothing was uploaded: ${
|
|
143
|
+
error instanceof Error ? error.message : String(error)
|
|
144
|
+
}`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
133
147
|
const key = versionedConfigKey(ctx.names.configKey, opts.version);
|
|
134
148
|
await awsMutate(ctx, ["s3", "cp", source, `s3://${opts.bucket}/${key}`]);
|
|
135
149
|
return key;
|
package/src/deploy/deploy.ts
CHANGED
|
@@ -672,9 +672,11 @@ export async function deploy(ctx: AwsContext, opts: DeployOptions): Promise<void
|
|
|
672
672
|
|
|
673
673
|
// The config of the version going in, written BEFORE `cdk deploy` creates
|
|
674
674
|
// that version, so its pre-warmed containers boot with it rather than with
|
|
675
|
-
// the fallback.
|
|
676
|
-
//
|
|
677
|
-
//
|
|
675
|
+
// the fallback. When the version going in is the one serving now (a config
|
|
676
|
+
// change on the same release), the live runtime reads this very object, so
|
|
677
|
+
// uploadRuntimeConfig parses it with this release's schema first and refuses
|
|
678
|
+
// rather than write a config the runtime would reject. A brand-new instance
|
|
679
|
+
// has no bucket yet; the config sync after `cdk deploy` covers it.
|
|
678
680
|
const version = foundationVersion(release);
|
|
679
681
|
const bucket = phase1
|
|
680
682
|
? undefined
|
|
@@ -433,6 +433,12 @@ export class FoundationAgent extends cdk.Stack {
|
|
|
433
433
|
resourceName: `${docsSecretPrefix}/personal/*-??????`,
|
|
434
434
|
arnFormat: cdk.ArnFormat.COLON_RESOURCE_NAME,
|
|
435
435
|
}),
|
|
436
|
+
cdk.Stack.of(this).formatArn({
|
|
437
|
+
service: "secretsmanager",
|
|
438
|
+
resource: "secret",
|
|
439
|
+
resourceName: `${docsSecretPrefix}/personal-drive/*-??????`,
|
|
440
|
+
arnFormat: cdk.ArnFormat.COLON_RESOURCE_NAME,
|
|
441
|
+
}),
|
|
436
442
|
],
|
|
437
443
|
}),
|
|
438
444
|
);
|
|
@@ -629,6 +635,14 @@ export class FoundationAgent extends cdk.Stack {
|
|
|
629
635
|
namesFor(props.instance).secretGoogleEmail,
|
|
630
636
|
);
|
|
631
637
|
this.denyDirectDocsSecretAccess(role, namesFor(props.instance).secretGoogleDocs);
|
|
638
|
+
role.addToPolicy(
|
|
639
|
+
new iam.PolicyStatement({
|
|
640
|
+
sid: "DenySlackSigningSecret",
|
|
641
|
+
effect: iam.Effect.DENY,
|
|
642
|
+
actions: ["secretsmanager:*"],
|
|
643
|
+
resources: [secretArnPattern(props.secrets.signing)],
|
|
644
|
+
}),
|
|
645
|
+
);
|
|
632
646
|
this.denyDirectUpworkApprovalAccess(role, props.upworkApprovalTable);
|
|
633
647
|
this.denyDirectEtradeAccess(role, props);
|
|
634
648
|
this.pagesPolicy("AgentReadOnlyPagesPolicy", role, props.instance, { invoke: false });
|
package/src/stacks/api-stack.ts
CHANGED
|
@@ -233,6 +233,16 @@ export class FoundationApi extends cdk.Stack {
|
|
|
233
233
|
},
|
|
234
234
|
}),
|
|
235
235
|
);
|
|
236
|
+
for (const identity of docsIdentities.filter((entry) => entry.readPersonalDrive === true)) {
|
|
237
|
+
docsSecrets.push(
|
|
238
|
+
new secretsmanager.Secret(this, `PersonalDriveSecret${identity.owner}`, {
|
|
239
|
+
secretName: `${namesFor(props.instance).secretGoogleDocs}/personal-drive/${identity.owner}`,
|
|
240
|
+
description: `${display} separate read-only personal Drive grant for Slack ${identity.owner}`,
|
|
241
|
+
removalPolicy: cdk.RemovalPolicy.DESTROY,
|
|
242
|
+
generateSecretString: { secretStringTemplate: "{}", generateStringKey: "placeholder" },
|
|
243
|
+
}),
|
|
244
|
+
);
|
|
245
|
+
}
|
|
236
246
|
|
|
237
247
|
const gatewayRole = new iam.Role(this, "GatewayRole", {
|
|
238
248
|
assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
|
|
@@ -1495,9 +1505,7 @@ function configTimezoneFor(configPath: string): string {
|
|
|
1495
1505
|
/** The Google Docs drafting identities to provision, read from the instance config at synth. */
|
|
1496
1506
|
export function googleDocsIdentitiesFor(configPath: string): GoogleDocsIdentity[] {
|
|
1497
1507
|
const docs = parseInstanceConfig(readFileSync(configPath, "utf8")).capabilities.googleDocs;
|
|
1498
|
-
return docs.enabled
|
|
1499
|
-
? docs.identities.map(({ address, channel, owner }) => ({ address, channel, owner }))
|
|
1500
|
-
: [];
|
|
1508
|
+
return docs.enabled ? docs.identities.map((identity) => ({ ...identity })) : [];
|
|
1501
1509
|
}
|
|
1502
1510
|
|
|
1503
1511
|
export function emailIdentitiesFor(configPath: string): EmailIdentity[] {
|