@deployfoundation/foundation-deploy 0.2.51 → 0.2.53

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.
@@ -16,7 +16,8 @@ export interface FoundationDataProps extends cdk.StackProps {
16
16
  * FoundationData — the durable layer: one CMK, two S3 buckets (config +
17
17
  * skills, and channel documents), two always-present DynamoDB tables, an
18
18
  * optional CRM table, an optional proxy-only Upwork approval table, an optional
19
- * retained Upwork OAuth secret, and the Secrets Manager entries.
19
+ * retained Upwork OAuth secret, the optional key for a company's own CRM, and
20
+ * the Secrets Manager entries.
20
21
  * The two always-present DynamoDB tables use
21
22
  * different access patterns: `FoundationTable` is `pk` (S) only:
22
23
  * dedupe rows, the Codex credential lease and the thread→session index, every
@@ -53,6 +54,12 @@ export class FoundationData extends cdk.Stack {
53
54
  public readonly upworkApprovalTable?: dynamodb.Table;
54
55
  /** Short-lived OOB request-token state; gateway-only and destroyed with the opt-in. */
55
56
  public readonly etradePendingTable?: dynamodb.Table;
57
+ /**
58
+ * The `crm/1` key, present only with `crm.provider` kind `http`. Other stacks
59
+ * reach it by its fixed name, never through this object, so switching back to
60
+ * `dynamodb` can delete it without a cross-stack export holding it in place.
61
+ */
62
+ public readonly crmHttpSecret?: secretsmanager.Secret;
56
63
  public readonly secrets: {
57
64
  signing: secretsmanager.ISecret;
58
65
  slackApp: secretsmanager.ISecret;
@@ -230,6 +237,25 @@ export class FoundationData extends cdk.Stack {
230
237
  })
231
238
  : undefined;
232
239
 
240
+ // The key for a company's own CRM (`crm.provider` kind `http`): empty
241
+ // until `foundation-deploy crm-key` writes `{ api_key }`, and until then
242
+ // the CRM proxy answers `unconfigured`. Like the Otter key it is destroyed
243
+ // with the opt-in: a rollback to `dynamodb` must not strand a fixed-name
244
+ // secret that a later switch back to `http` could not create again, and
245
+ // the CRM issues a fresh key in seconds.
246
+ this.crmHttpSecret =
247
+ provisionsIntegration(props.instance, "crm") && props.instance.crm.provider.kind === "http"
248
+ ? new secretsmanager.Secret(this, "CrmHttpSecret", {
249
+ secretName: names.secretCrmHttp,
250
+ description: `crm/1 key for ${display}'s CRM ({ api_key }); written by foundation-deploy crm-key, read only by the CRM proxy`,
251
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
252
+ generateSecretString: {
253
+ secretStringTemplate: "{}",
254
+ generateStringKey: "placeholder",
255
+ },
256
+ })
257
+ : undefined;
258
+
233
259
  // Unlike the disposable Otter API key, the Upwork OAuth secret carries
234
260
  // client credentials plus refresh state. Retaining it makes a deploy safe
235
261
  // and leaves manually entered client credentials intact.
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Reads that AWS's `ReadOnlyAccess` grants whose RESPONSES carry secrets in
3
+ * plaintext: environment variables, user data, connection properties, request
4
+ * headers, issued tokens. Both shell-reachable agent roles deny every one of
5
+ * them (`FoundationAgent.secretBearingReadsPolicy`), so "read-only" never means
6
+ * "can read the credentials that write".
7
+ *
8
+ * Found 2026-10-26 in a company account: three Lambdas held a read-write
9
+ * MongoDB connection string in their environment, and sampled WAF requests
10
+ * held the web app's admin bearer tokens. Either one turned the read-only role
11
+ * into a write path.
12
+ *
13
+ * Judged against `ReadOnlyAccess` v190 (2026-09-24). The rule for adding an
14
+ * action: its response can contain a value someone typed in as configuration
15
+ * (env, user data, connection or job arguments, hosted config), a credential
16
+ * AWS mints for the caller, or another caller's request. Data-plane reads
17
+ * (S3 objects, DynamoDB items, queue messages, log events) are a different
18
+ * question, decided separately: see docs/capabilities/aws-readonly.md.
19
+ *
20
+ * What stays readable for diagnosis: CloudWatch Logs (Insights included),
21
+ * metrics, alarms, X-Ray, CloudTrail, every `Describe*`/`List*` not named here,
22
+ * stack events and outputs, SSM parameter NAMES, and Lambda's event source
23
+ * mappings, aliases, concurrency and policies.
24
+ *
25
+ * Every entry is an exact action name, never a wildcard, so a reviewer can
26
+ * check each one against the service authorization reference.
27
+ */
28
+ export const SECRET_BEARING_READ_ACTIONS: readonly string[] = [
29
+ // WAF sampled requests return full request headers, Authorization included.
30
+ "wafv2:GetSampledRequests",
31
+ "waf:GetSampledRequests",
32
+ "waf-regional:GetSampledRequests",
33
+
34
+ // Lambda: function configuration includes `Environment.Variables`, and
35
+ // GetFunction / GetLayerVersion add a presigned code download URL. List
36
+ // calls return the same configuration for every function or version.
37
+ "lambda:GetFunction",
38
+ "lambda:GetFunctionConfiguration",
39
+ "lambda:ListFunctions",
40
+ "lambda:ListVersionsByFunction",
41
+ "lambda:GetLayerVersion",
42
+ "lambda:GetLayerVersionByArn",
43
+
44
+ // Container and build configuration: plaintext environment variables.
45
+ "ecs:DescribeTaskDefinition",
46
+ "apprunner:DescribeService",
47
+ "batch:DescribeJobDefinitions",
48
+ "batch:DescribeJobs",
49
+ "codebuild:BatchGetProjects",
50
+ "codebuild:BatchGetBuilds",
51
+ "codebuild:BatchGetBuildBatches",
52
+
53
+ // Amplify apps and branches carry environment variables and basic-auth
54
+ // credentials; backend environments carry deployment settings.
55
+ "amplify:GetApp",
56
+ "amplify:ListApps",
57
+ "amplify:GetBranch",
58
+ "amplify:ListBranches",
59
+ "amplify:GetBackendEnvironment",
60
+
61
+ // Elastic Beanstalk environment properties are its environment variables.
62
+ "elasticbeanstalk:DescribeConfigurationSettings",
63
+
64
+ // EC2 user data (instance attribute and launch template versions).
65
+ "ec2:DescribeLaunchTemplateVersions",
66
+ "ec2:DescribeInstanceAttribute",
67
+
68
+ // SSM parameter VALUES. Denied for plain String parameters too: teams
69
+ // store secrets as String by mistake, and with the account's default
70
+ // `aws/ssm` key a SecureString can decrypt through the same call.
71
+ // DescribeParameters (names, types, versions) stays readable.
72
+ "ssm:GetParameter",
73
+ "ssm:GetParameters",
74
+ "ssm:GetParametersByPath",
75
+ "ssm:GetParameterHistory",
76
+
77
+ // CloudFormation template bodies can hold literal secrets and parameter
78
+ // defaults. Denied: DescribeStacks, DescribeStackEvents and
79
+ // DescribeStackResources answer the diagnosis questions without them.
80
+ "cloudformation:GetTemplate",
81
+
82
+ // AppConfig hosted configuration content is application configuration.
83
+ "appconfig:GetConfiguration",
84
+ "appconfig:GetHostedConfigurationVersion",
85
+
86
+ // Glue job default arguments and connection properties (JDBC user and
87
+ // password when not in Secrets Manager); dev endpoints carry job arguments.
88
+ // v190 does not grant GetConnection(s); they are named anyway because AWS
89
+ // revises the policy without notice and these return the password.
90
+ "glue:GetJob",
91
+ "glue:GetJobs",
92
+ "glue:BatchGetJobs",
93
+ "glue:GetConnection",
94
+ "glue:GetConnections",
95
+ "glue:GetDevEndpoint",
96
+ "glue:GetDevEndpoints",
97
+ "glue:BatchGetDevEndpoints",
98
+
99
+ // SageMaker container `Environment` maps on models and jobs.
100
+ "sagemaker:DescribeModel",
101
+ "sagemaker:DescribeTrainingJob",
102
+ "sagemaker:DescribeProcessingJob",
103
+ "sagemaker:DescribeTransformJob",
104
+
105
+ // Data Pipeline definitions hold activity fields, database passwords included.
106
+ "datapipeline:GetPipelineDefinition",
107
+
108
+ // AppSync: API key values, and HTTP data source authorization config.
109
+ "appsync:ListApiKeys",
110
+ "appsync:GetDataSource",
111
+ "appsync:ListDataSources",
112
+
113
+ // Kinesis Data Analytics / Managed Flink environment properties.
114
+ "kinesisanalytics:DescribeApplication",
115
+
116
+ // Lightsail bucket access keys and container service environment.
117
+ "lightsail:GetBucketAccessKeys",
118
+ "lightsail:GetContainerServices",
119
+ "lightsail:GetContainerServiceDeployments",
120
+
121
+ // OpsWorks app environment variables and stack custom JSON.
122
+ "opsworks:DescribeApps",
123
+ "opsworks:DescribeStacks",
124
+
125
+ // Calls that MINT a credential rather than describe one.
126
+ "cognito-identity:GetCredentialsForIdentity",
127
+ "cognito-identity:GetOpenIdToken",
128
+ "cognito-identity:GetOpenIdTokenForDeveloperIdentity",
129
+ "connect:GetFederationToken",
130
+ "codeartifact:GetAuthorizationToken",
131
+ ];