@go-to-k/cdkd 0.283.18 → 0.283.20
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/README.md +5 -0
- package/dist/{asg-provider-D8Ln3-ij.js → asg-provider-k-TEX_Ej.js} +2 -2
- package/dist/{asg-provider-D8Ln3-ij.js.map → asg-provider-k-TEX_Ej.js.map} +1 -1
- package/dist/cli.js +358 -16
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-B-0iA5XV.js → deploy-engine-ISOKkuun.js} +331 -39
- package/dist/deploy-engine-ISOKkuun.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-B-0iA5XV.js.map +0 -1
|
@@ -5557,7 +5557,7 @@ var AssetModeResolver = class {
|
|
|
5557
5557
|
* non-`u` pattern. `-` is deliberately absent: it is only special INSIDE a
|
|
5558
5558
|
* character class, and no caller embeds into one.
|
|
5559
5559
|
*/
|
|
5560
|
-
function escapeRegExp(value) {
|
|
5560
|
+
function escapeRegExp$1(value) {
|
|
5561
5561
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5562
5562
|
}
|
|
5563
5563
|
|
|
@@ -5582,11 +5582,11 @@ function flattenAssetPlaceholders(value, accountId, region, partition = "aws") {
|
|
|
5582
5582
|
* scope and left verbatim.
|
|
5583
5583
|
*/
|
|
5584
5584
|
function isDefaultBootstrapBucketName(name, accountId, region) {
|
|
5585
|
-
return new RegExp(`^cdk-[a-z0-9]+-assets-${accountId}-${escapeRegExp(region)}$`).test(name);
|
|
5585
|
+
return new RegExp(`^cdk-[a-z0-9]+-assets-${accountId}-${escapeRegExp$1(region)}$`).test(name);
|
|
5586
5586
|
}
|
|
5587
5587
|
/** §8 scope rule, container-image leg (`cdk-<qualifier>-container-assets-…`). */
|
|
5588
5588
|
function isDefaultBootstrapRepoName(name, accountId, region) {
|
|
5589
|
-
return new RegExp(`^cdk-[a-z0-9]+-container-assets-${accountId}-${escapeRegExp(region)}$`).test(name);
|
|
5589
|
+
return new RegExp(`^cdk-[a-z0-9]+-container-assets-${accountId}-${escapeRegExp$1(region)}$`).test(name);
|
|
5590
5590
|
}
|
|
5591
5591
|
/**
|
|
5592
5592
|
* Build the §6 asset-location mapping table from a stack's asset manifest.
|
|
@@ -5613,7 +5613,7 @@ function buildAssetRedirectMap(manifest, marker, accountId, region, partition =
|
|
|
5613
5613
|
const addForms = (rawName, flattened, target) => {
|
|
5614
5614
|
sources.set(flattened, target);
|
|
5615
5615
|
if (rawName !== flattened) sources.set(rawName, target);
|
|
5616
|
-
const suffixRe = new RegExp(`-${accountId}-${escapeRegExp(region)}$`);
|
|
5616
|
+
const suffixRe = new RegExp(`-${accountId}-${escapeRegExp$1(region)}$`);
|
|
5617
5617
|
for (const suffix of [
|
|
5618
5618
|
"-${AWS::AccountId}-${AWS::Region}",
|
|
5619
5619
|
`-\${AWS::AccountId}-${region}`,
|
|
@@ -5669,7 +5669,7 @@ function buildAssetRedirectMap(manifest, marker, accountId, region, partition =
|
|
|
5669
5669
|
* with the deploy account+region bootstrap shape — accepted as pathological.
|
|
5670
5670
|
*/
|
|
5671
5671
|
function buildBoundaryRegex(source) {
|
|
5672
|
-
return new RegExp(`(?<![A-Za-z0-9_.-])${escapeRegExp(source)}(?![A-Za-z0-9_-])`, "g");
|
|
5672
|
+
return new RegExp(`(?<![A-Za-z0-9_.-])${escapeRegExp$1(source)}(?![A-Za-z0-9_-])`, "g");
|
|
5673
5673
|
}
|
|
5674
5674
|
function rewriteString(value, map, counter) {
|
|
5675
5675
|
let result = value;
|
|
@@ -9816,6 +9816,95 @@ function s3BucketWebsiteUrl(bucketName, region) {
|
|
|
9816
9816
|
return `http://${bucketName}.s3-website${S3_WEBSITE_ENDPOINT_LEGACY_DASH_REGIONS.has(folded) ? "-" : "."}${folded}.${urlSuffix}`;
|
|
9817
9817
|
}
|
|
9818
9818
|
|
|
9819
|
+
//#endregion
|
|
9820
|
+
//#region src/deployment/secret-redaction.ts
|
|
9821
|
+
/** Fixed marker substituted for a secret value in log / error output. */
|
|
9822
|
+
const SECRET_MASK = "***";
|
|
9823
|
+
/**
|
|
9824
|
+
* A resolved secret value shorter than this is NOT used as a redaction needle:
|
|
9825
|
+
* a 1-2 character plaintext (e.g. a secret whose JSON key holds `"0"`) would
|
|
9826
|
+
* match incidental characters everywhere and mangle unrelated state. Such a
|
|
9827
|
+
* value is still masked at the exact leaf where it was the WHOLE value (handled
|
|
9828
|
+
* by the caller), but is not scanned for as a substring. Real secrets are far
|
|
9829
|
+
* longer than this, so the bound only excludes degenerate cases.
|
|
9830
|
+
*/
|
|
9831
|
+
const MIN_NEEDLE_LENGTH = 4;
|
|
9832
|
+
function escapeRegExp(value) {
|
|
9833
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9834
|
+
}
|
|
9835
|
+
/**
|
|
9836
|
+
* Build a single alternation regex matching any recorded secret value, longest
|
|
9837
|
+
* first so an overlapping shorter secret cannot pre-empt a longer match. Returns
|
|
9838
|
+
* `undefined` when there is nothing worth scanning for.
|
|
9839
|
+
*/
|
|
9840
|
+
function buildNeedleRegex(values) {
|
|
9841
|
+
const needles = Array.from(new Set(values)).filter((v) => v.length >= MIN_NEEDLE_LENGTH).sort((a, b) => b.length - a.length);
|
|
9842
|
+
if (needles.length === 0) return void 0;
|
|
9843
|
+
return new RegExp(needles.map(escapeRegExp).join("|"), "g");
|
|
9844
|
+
}
|
|
9845
|
+
/**
|
|
9846
|
+
* Deep-clone `bag`, replacing every occurrence of a recorded secret value with
|
|
9847
|
+
* the unresolved `{{resolve:...}}` expression it came from. A string whose WHOLE
|
|
9848
|
+
* value equals a secret is replaced by that secret's expression exactly; a
|
|
9849
|
+
* string that merely CONTAINS one (an `Fn::Join` / `Fn::Sub` result) has the
|
|
9850
|
+
* secret substring replaced in place. Returns the input by identity when there
|
|
9851
|
+
* is nothing to redact, so callers can persist the original object unchanged in
|
|
9852
|
+
* the common no-secret case.
|
|
9853
|
+
*/
|
|
9854
|
+
function redactSecretsForState(bag, secrets) {
|
|
9855
|
+
if (secrets.size === 0) return bag;
|
|
9856
|
+
const regex = buildNeedleRegex(secrets.keys());
|
|
9857
|
+
const wholeValueExpr = (s) => s === "" ? void 0 : secrets.get(s);
|
|
9858
|
+
const walk = (value) => {
|
|
9859
|
+
if (typeof value === "string") {
|
|
9860
|
+
const whole = wholeValueExpr(value);
|
|
9861
|
+
if (whole !== void 0) return whole;
|
|
9862
|
+
if (!regex) return value;
|
|
9863
|
+
regex.lastIndex = 0;
|
|
9864
|
+
if (!regex.test(value)) return value;
|
|
9865
|
+
regex.lastIndex = 0;
|
|
9866
|
+
return value.replace(regex, (m) => secrets.get(m) ?? "***");
|
|
9867
|
+
}
|
|
9868
|
+
if (Array.isArray(value)) return value.map(walk);
|
|
9869
|
+
if (value !== null && typeof value === "object") {
|
|
9870
|
+
const out = {};
|
|
9871
|
+
for (const [k, v] of Object.entries(value)) out[k] = walk(v);
|
|
9872
|
+
return out;
|
|
9873
|
+
}
|
|
9874
|
+
return value;
|
|
9875
|
+
};
|
|
9876
|
+
return walk(bag);
|
|
9877
|
+
}
|
|
9878
|
+
/**
|
|
9879
|
+
* Redact resolved secret plaintext out of one resource state record's
|
|
9880
|
+
* `properties` / `attributes` / `observedProperties`, replacing each secret
|
|
9881
|
+
* value with its unresolved expression. Returns a NEW record when any field
|
|
9882
|
+
* changed, or the input by identity when there are no secrets — so callers can
|
|
9883
|
+
* detect a no-op cheaply. Shared by the deploy engine's save choke point and
|
|
9884
|
+
* the `cdkd scrub` command so both scrub the same three fields identically.
|
|
9885
|
+
*/
|
|
9886
|
+
function scrubResourceRecord(record, secrets) {
|
|
9887
|
+
if (secrets.size === 0) return record;
|
|
9888
|
+
const next = { ...record };
|
|
9889
|
+
next.properties = redactSecretsForState(record.properties, secrets);
|
|
9890
|
+
if (record.attributes) next.attributes = redactSecretsForState(record.attributes, secrets);
|
|
9891
|
+
if (record.observedProperties) next.observedProperties = redactSecretsForState(record.observedProperties, secrets);
|
|
9892
|
+
return next;
|
|
9893
|
+
}
|
|
9894
|
+
/**
|
|
9895
|
+
* Replace every recorded secret value inside `text` with {@link SECRET_MASK}.
|
|
9896
|
+
* Used on log lines and error messages where a resolved secret could otherwise
|
|
9897
|
+
* be echoed. Whole-value and embedded matches are both masked. Returns `text`
|
|
9898
|
+
* unchanged when there is nothing to mask.
|
|
9899
|
+
*/
|
|
9900
|
+
function maskSecretsInText(text, secrets) {
|
|
9901
|
+
if (secrets.size === 0) return text;
|
|
9902
|
+
if (text !== "" && secrets.has(text)) return "***";
|
|
9903
|
+
const regex = buildNeedleRegex(secrets.keys());
|
|
9904
|
+
if (!regex) return text;
|
|
9905
|
+
return text.replace(regex, "***");
|
|
9906
|
+
}
|
|
9907
|
+
|
|
9819
9908
|
//#endregion
|
|
9820
9909
|
//#region src/provisioning/config-shape.ts
|
|
9821
9910
|
/**
|
|
@@ -11770,7 +11859,7 @@ var IntrinsicFunctionResolver = class {
|
|
|
11770
11859
|
*/
|
|
11771
11860
|
async resolveValue(value, context) {
|
|
11772
11861
|
if (typeof value !== "object" || value === null) {
|
|
11773
|
-
if (typeof value === "string" && value.includes("{{resolve:")) return await this.resolveDynamicReferences(value);
|
|
11862
|
+
if (typeof value === "string" && value.includes("{{resolve:")) return await this.resolveDynamicReferences(value, context);
|
|
11774
11863
|
return value;
|
|
11775
11864
|
}
|
|
11776
11865
|
if (Array.isArray(value)) return (await Promise.all(value.map((v) => this.resolveValue(v, context)))).filter((v) => v !== AWS_NO_VALUE);
|
|
@@ -12373,8 +12462,8 @@ var IntrinsicFunctionResolver = class {
|
|
|
12373
12462
|
const resolved = await this.resolveValue(v, context);
|
|
12374
12463
|
return String(resolved);
|
|
12375
12464
|
}))).join(delimiter);
|
|
12376
|
-
if (result.includes("{{resolve:")) result = await this.resolveDynamicReferences(result);
|
|
12377
|
-
this.logger.debug(`Resolved Fn::Join: ${result}`);
|
|
12465
|
+
if (result.includes("{{resolve:")) result = await this.resolveDynamicReferences(result, context);
|
|
12466
|
+
this.logger.debug(`Resolved Fn::Join: ${this.maskSecretsForLog(result, context)}`);
|
|
12378
12467
|
return result;
|
|
12379
12468
|
}
|
|
12380
12469
|
/**
|
|
@@ -12459,8 +12548,8 @@ var IntrinsicFunctionResolver = class {
|
|
|
12459
12548
|
const entry = replacements[cursor++];
|
|
12460
12549
|
return entry ? entry.replacement : whole;
|
|
12461
12550
|
});
|
|
12462
|
-
if (result.includes("{{resolve:")) result = await this.resolveDynamicReferences(result);
|
|
12463
|
-
this.logger.debug(`Resolved Fn::Sub: ${result}`);
|
|
12551
|
+
if (result.includes("{{resolve:")) result = await this.resolveDynamicReferences(result, context);
|
|
12552
|
+
this.logger.debug(`Resolved Fn::Sub: ${this.maskSecretsForLog(result, context)}`);
|
|
12464
12553
|
return result;
|
|
12465
12554
|
}
|
|
12466
12555
|
/**
|
|
@@ -12478,7 +12567,7 @@ var IntrinsicFunctionResolver = class {
|
|
|
12478
12567
|
return `{{Fn::Select:${index}:OutOfBounds}}`;
|
|
12479
12568
|
}
|
|
12480
12569
|
const result = resolvedList[index];
|
|
12481
|
-
this.logger.debug(`Resolved Fn::Select: index ${index} -> ${JSON.stringify(result)}`);
|
|
12570
|
+
this.logger.debug(`Resolved Fn::Select: index ${index} -> ${this.maskSecretsForLog(JSON.stringify(result), context)}`);
|
|
12482
12571
|
return result;
|
|
12483
12572
|
}
|
|
12484
12573
|
/**
|
|
@@ -12620,7 +12709,7 @@ var IntrinsicFunctionResolver = class {
|
|
|
12620
12709
|
throw markNonRetryable(new IntrinsicResolutionRefusalError(`Fn::Split: the value to split${sourceClause} must be a string, got ${resolvedValue === null ? "null" : typeof resolvedValue}. Fn::Split accepts only a string; check the value or the intrinsic that produced it.`));
|
|
12621
12710
|
}
|
|
12622
12711
|
const result = resolvedValue.split(delimiter);
|
|
12623
|
-
this.logger.debug(`Resolved Fn::Split: split by "${delimiter}" -> ${JSON.stringify(result)}`);
|
|
12712
|
+
this.logger.debug(`Resolved Fn::Split: split by "${delimiter}" -> ${this.maskSecretsForLog(JSON.stringify(result), context)}`);
|
|
12624
12713
|
return result;
|
|
12625
12714
|
}
|
|
12626
12715
|
/**
|
|
@@ -13128,7 +13217,7 @@ var IntrinsicFunctionResolver = class {
|
|
|
13128
13217
|
const resolvedValue = await this.resolveValue(value, context);
|
|
13129
13218
|
if (typeof resolvedValue !== "string") throw new Error(`Fn::Base64: value must resolve to a string, got ${typeof resolvedValue}`);
|
|
13130
13219
|
const result = Buffer.from(resolvedValue).toString("base64");
|
|
13131
|
-
this.logger.debug(`Resolved Fn::Base64: ${resolvedValue} -> ${result}`);
|
|
13220
|
+
this.logger.debug(`Resolved Fn::Base64: ${this.maskSecretsForLog(resolvedValue, context)} -> ${this.maskSecretsForLog(result, context)}`);
|
|
13132
13221
|
return result;
|
|
13133
13222
|
}
|
|
13134
13223
|
/**
|
|
@@ -13195,7 +13284,16 @@ var IntrinsicFunctionResolver = class {
|
|
|
13195
13284
|
*
|
|
13196
13285
|
* Results are cached to avoid repeated API calls.
|
|
13197
13286
|
*/
|
|
13198
|
-
|
|
13287
|
+
/**
|
|
13288
|
+
* Mask any resolved secret value out of a string bound for a log line, using
|
|
13289
|
+
* the secrets recorded on the resolution pass (GHSA fix). No-op when the pass
|
|
13290
|
+
* recorded no secrets.
|
|
13291
|
+
*/
|
|
13292
|
+
maskSecretsForLog(text, context) {
|
|
13293
|
+
const secrets = context?.recordedSecretValues;
|
|
13294
|
+
return secrets ? maskSecretsInText(text, secrets) : text;
|
|
13295
|
+
}
|
|
13296
|
+
async resolveDynamicReferences(value, context) {
|
|
13199
13297
|
const pattern = /\{\{resolve:([^}]+)\}\}/g;
|
|
13200
13298
|
let result = value;
|
|
13201
13299
|
let match;
|
|
@@ -13205,12 +13303,16 @@ var IntrinsicFunctionResolver = class {
|
|
|
13205
13303
|
inner: match[1]
|
|
13206
13304
|
});
|
|
13207
13305
|
for (const { fullMatch, inner } of matches) {
|
|
13306
|
+
const service = inner.split(":")[0];
|
|
13307
|
+
const isSecret = service === "secretsmanager";
|
|
13308
|
+
if (isSecret && context?.skipDynamicReferences) continue;
|
|
13208
13309
|
if (fullMatch in cachedDynamicReferences) {
|
|
13209
|
-
|
|
13310
|
+
const cached = cachedDynamicReferences[fullMatch];
|
|
13311
|
+
if (isSecret && cached) context?.recordedSecretValues?.set(cached, fullMatch);
|
|
13312
|
+
result = result.replace(fullMatch, cached);
|
|
13210
13313
|
continue;
|
|
13211
13314
|
}
|
|
13212
13315
|
const parts = inner.split(":");
|
|
13213
|
-
const service = parts[0];
|
|
13214
13316
|
let resolved;
|
|
13215
13317
|
if (service === "secretsmanager") resolved = await this.resolveSecretsManagerReference(inner);
|
|
13216
13318
|
else if (service === "ssm") resolved = await this.resolveSSMReference(parts);
|
|
@@ -13219,6 +13321,7 @@ var IntrinsicFunctionResolver = class {
|
|
|
13219
13321
|
continue;
|
|
13220
13322
|
}
|
|
13221
13323
|
cachedDynamicReferences[fullMatch] = resolved;
|
|
13324
|
+
if (isSecret && resolved) context?.recordedSecretValues?.set(resolved, fullMatch);
|
|
13222
13325
|
result = result.replace(fullMatch, resolved);
|
|
13223
13326
|
}
|
|
13224
13327
|
return result;
|
|
@@ -14182,7 +14285,13 @@ var CloudControlProvider = class {
|
|
|
14182
14285
|
};
|
|
14183
14286
|
}
|
|
14184
14287
|
}
|
|
14185
|
-
this.logger.debug(`Generated ${patch.length} patch operations for ${logicalId}: ${JSON.stringify(patch)
|
|
14288
|
+
this.logger.debug(`Generated ${patch.length} patch operations for ${logicalId}: ${JSON.stringify(patch.map((op) => {
|
|
14289
|
+
const anyOp = op;
|
|
14290
|
+
return {
|
|
14291
|
+
op: anyOp.op,
|
|
14292
|
+
path: anyOp.path
|
|
14293
|
+
};
|
|
14294
|
+
}))}`);
|
|
14186
14295
|
const updateResponse = await this.cloudControlClient.send(new UpdateResourceCommand({
|
|
14187
14296
|
TypeName: resourceType,
|
|
14188
14297
|
Identifier: physicalId,
|
|
@@ -14212,7 +14321,7 @@ var CloudControlProvider = class {
|
|
|
14212
14321
|
if (context?.finalSnapshotIdentifier !== void 0) throw new ProvisioningError(`${logicalId} (${resourceType}) requires a final snapshot (DeletionPolicy: Snapshot), but the Cloud Control API delete route has no final-snapshot parameter. Re-run with --skip-final-snapshot after snapshotting manually, or retain the resource.`, resourceType, logicalId, physicalId);
|
|
14213
14322
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
14214
14323
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
14215
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
14324
|
+
const { ASGProvider } = await import("./asg-provider-k-TEX_Ej.js").then((n) => n.n);
|
|
14216
14325
|
return await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
|
|
14217
14326
|
}
|
|
14218
14327
|
const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
|
|
@@ -20765,6 +20874,61 @@ async function replayRollback(operations, stateResources, stackName, ctx, option
|
|
|
20765
20874
|
* just announced and leaving the record describing something AWS does not
|
|
20766
20875
|
* hold.
|
|
20767
20876
|
*/
|
|
20877
|
+
/**
|
|
20878
|
+
* Re-resolve dynamic-reference SECRET expressions
|
|
20879
|
+
* (`{{resolve:secretsmanager:...}}`) in a property bag being REPLAYED to a
|
|
20880
|
+
* provider during rollback (GHSA fix, issue #1899 review).
|
|
20881
|
+
*
|
|
20882
|
+
* The rollback journal — and the state record the replay writes — store the
|
|
20883
|
+
* redacted EXPRESSION, never the plaintext. But a `provider.update()` /
|
|
20884
|
+
* `create()` / `delete()` call must receive the concrete secret value the
|
|
20885
|
+
* reference points at, exactly as the forward deploy did; replaying the literal
|
|
20886
|
+
* `{{resolve:...}}` string would corrupt the resource (e.g. a Lambda env var or
|
|
20887
|
+
* Cognito `client_secret`). Rollback is synth-free, so re-resolve straight from
|
|
20888
|
+
* the expression string here.
|
|
20889
|
+
*
|
|
20890
|
+
* Records each `plaintext -> expression` into `secrets` so the caller can redact
|
|
20891
|
+
* the persisted state record back to the expression — the same
|
|
20892
|
+
* resolve-for-provider + redact-for-state split the deploy engine applies at its
|
|
20893
|
+
* save choke point. A bag with no `{{resolve:...}}` string resolves to a
|
|
20894
|
+
* structural copy of itself (secrets stays empty), so the non-secret rollback
|
|
20895
|
+
* path is behaviourally unchanged. Only secretsmanager references are recorded
|
|
20896
|
+
* (plain `ssm` is public config, stored resolved — it never appears as an
|
|
20897
|
+
* expression in the journal), matching the resolver's own `isSecret` gate.
|
|
20898
|
+
*/
|
|
20899
|
+
async function resolveReplayProps(props, resolver, secrets) {
|
|
20900
|
+
if (props === void 0) return void 0;
|
|
20901
|
+
const ctx = {
|
|
20902
|
+
template: { Resources: {} },
|
|
20903
|
+
resources: {},
|
|
20904
|
+
recordedSecretValues: secrets
|
|
20905
|
+
};
|
|
20906
|
+
const walk = async (v) => {
|
|
20907
|
+
if (typeof v === "string") return v.includes("{{resolve:") ? await resolver.resolveDynamicReferences(v, ctx) : v;
|
|
20908
|
+
if (Array.isArray(v)) {
|
|
20909
|
+
const out = new Array(v.length);
|
|
20910
|
+
for (let i = 0; i < v.length; i++) out[i] = await walk(v[i]);
|
|
20911
|
+
return out;
|
|
20912
|
+
}
|
|
20913
|
+
if (v !== null && typeof v === "object") {
|
|
20914
|
+
const out = {};
|
|
20915
|
+
for (const [k, val] of Object.entries(v)) out[k] = await walk(val);
|
|
20916
|
+
return out;
|
|
20917
|
+
}
|
|
20918
|
+
return v;
|
|
20919
|
+
};
|
|
20920
|
+
return await walk(props);
|
|
20921
|
+
}
|
|
20922
|
+
/**
|
|
20923
|
+
* Redact resolved secret plaintext back out of a post-rollback state record
|
|
20924
|
+
* (GHSA fix). The record's `properties` may be the provider's
|
|
20925
|
+
* `effectiveProperties`, which can echo the value we just resolved for the
|
|
20926
|
+
* provider call — so scrub it with the same per-op secrets map before it is
|
|
20927
|
+
* persisted. No-op when the op resolved no secret.
|
|
20928
|
+
*/
|
|
20929
|
+
function redactRollbackRecord(record, secrets) {
|
|
20930
|
+
return secrets.size > 0 ? scrubResourceRecord(record, secrets) : record;
|
|
20931
|
+
}
|
|
20768
20932
|
async function updateWithRollbackRetry(provider, args, logicalId, logger, isInterrupted) {
|
|
20769
20933
|
if (provider.disableOuterRetry) return await provider.update(...args);
|
|
20770
20934
|
return await withRetry(() => provider.update(...args), logicalId, {
|
|
@@ -20822,6 +20986,7 @@ function recordedPropertiesAfterReplayCreate(restored, result) {
|
|
|
20822
20986
|
async function replaySingle(op, stateResources, stackName, ctx, orphanLogicalIds, result, afterOp, isInterrupted) {
|
|
20823
20987
|
const action = classifyRollbackOp(op, stateResources, orphanLogicalIds);
|
|
20824
20988
|
const { logger } = ctx;
|
|
20989
|
+
const resolver = new IntrinsicFunctionResolver(ctx.region);
|
|
20825
20990
|
/**
|
|
20826
20991
|
* The route a CREATE-rollback arm resolved for this op (issue #1366) —
|
|
20827
20992
|
* hoisted so the shared catch's ROLLBACK_RESOURCE_FAILED reports the route
|
|
@@ -20946,6 +21111,8 @@ async function replaySingle(op, stateResources, stackName, ctx, orphanLogicalIds
|
|
|
20946
21111
|
case "reverse-replacement": {
|
|
20947
21112
|
const current = stateResources[op.logicalId];
|
|
20948
21113
|
const prev = op.previousState;
|
|
21114
|
+
const secrets = /* @__PURE__ */ new Map();
|
|
21115
|
+
const resolvedPrevProps = await resolveReplayProps(prev.properties, resolver, secrets) ?? {};
|
|
20949
21116
|
logger.info(` Rollback: Reversing replacement of ${op.logicalId} (${op.resourceType}) — re-creating the old resource and deleting the new one`);
|
|
20950
21117
|
if (STATEFUL_TYPES.has(op.resourceType)) logger.warn(` ⚠ ${op.logicalId} (${op.resourceType}) is a stateful type — the old physical resource's data was destroyed by the replacement and CANNOT be recovered; the re-created resource starts empty.`);
|
|
20951
21118
|
const { provider: createProvider } = ctx.providerRegistry.getProviderFor({
|
|
@@ -20959,7 +21126,7 @@ async function replaySingle(op, stateResources, stackName, ctx, orphanLogicalIds
|
|
|
20959
21126
|
let deletedNewFirst = false;
|
|
20960
21127
|
let createResult;
|
|
20961
21128
|
try {
|
|
20962
|
-
createResult = await withRetry(() => createProvider.create(op.logicalId, op.resourceType, { ...
|
|
21129
|
+
createResult = await withRetry(() => createProvider.create(op.logicalId, op.resourceType, { ...resolvedPrevProps }, REPLAYING_STATE_CREATE_CONTEXT), op.logicalId, {
|
|
20963
21130
|
...RECREATE_RETRY_SCHEDULE,
|
|
20964
21131
|
logger,
|
|
20965
21132
|
...isInterrupted && {
|
|
@@ -20982,7 +21149,7 @@ async function replaySingle(op, stateResources, stackName, ctx, orphanLogicalIds
|
|
|
20982
21149
|
delete stateResources[op.logicalId];
|
|
20983
21150
|
await afterOp?.(op.logicalId);
|
|
20984
21151
|
try {
|
|
20985
|
-
createResult = await withRetry(() => createProvider.create(op.logicalId, op.resourceType, { ...
|
|
21152
|
+
createResult = await withRetry(() => createProvider.create(op.logicalId, op.resourceType, { ...resolvedPrevProps }, REPLAYING_STATE_CREATE_CONTEXT), op.logicalId, {
|
|
20986
21153
|
...RECREATE_RETRY_SCHEDULE,
|
|
20987
21154
|
logger,
|
|
20988
21155
|
...isInterrupted && {
|
|
@@ -21001,12 +21168,12 @@ async function replaySingle(op, stateResources, stackName, ctx, orphanLogicalIds
|
|
|
21001
21168
|
result.warnings++;
|
|
21002
21169
|
}
|
|
21003
21170
|
const { observedProperties: _staleObserved, ...prevRecord } = prev;
|
|
21004
|
-
stateResources[op.logicalId] = {
|
|
21171
|
+
stateResources[op.logicalId] = redactRollbackRecord({
|
|
21005
21172
|
...prevRecord,
|
|
21006
21173
|
physicalId: createResult.physicalId,
|
|
21007
21174
|
attributes: createResult.attributes ?? {},
|
|
21008
21175
|
properties: recordedPropertiesAfterReplayCreate(prevRecord, createResult)
|
|
21009
|
-
};
|
|
21176
|
+
}, secrets);
|
|
21010
21177
|
await afterOp?.(op.logicalId);
|
|
21011
21178
|
if (!deletedNewFirst && !adoptedLiveNewResource) try {
|
|
21012
21179
|
const finalSnapshotIdentifier = rollbackFinalSnapshotId(op.resourceType, current, op.provisionedBy);
|
|
@@ -21047,14 +21214,17 @@ async function replaySingle(op, stateResources, stackName, ctx, orphanLogicalIds
|
|
|
21047
21214
|
resourceType: op.resourceType,
|
|
21048
21215
|
provisionedBy: op.provisionedBy
|
|
21049
21216
|
});
|
|
21217
|
+
const secrets = /* @__PURE__ */ new Map();
|
|
21218
|
+
const desiredProps = await resolveReplayProps(previousState.properties, resolver, secrets);
|
|
21219
|
+
const currentProps = await resolveReplayProps(current.properties, resolver, secrets);
|
|
21050
21220
|
const revertResult = await updateWithRollbackRetry(provider, [
|
|
21051
21221
|
op.logicalId,
|
|
21052
21222
|
current.physicalId,
|
|
21053
21223
|
op.resourceType,
|
|
21054
|
-
|
|
21055
|
-
|
|
21224
|
+
desiredProps ?? {},
|
|
21225
|
+
currentProps ?? {}
|
|
21056
21226
|
], op.logicalId, logger, isInterrupted);
|
|
21057
|
-
stateResources[op.logicalId] = recordAfterRollbackUpdate(previousState, revertResult);
|
|
21227
|
+
stateResources[op.logicalId] = redactRollbackRecord(recordAfterRollbackUpdate(previousState, revertResult), secrets);
|
|
21058
21228
|
logger.info(` Rollback: ${op.logicalId} restored successfully`);
|
|
21059
21229
|
await afterOp?.(op.logicalId);
|
|
21060
21230
|
ctx.recordEvent?.({
|
|
@@ -21102,6 +21272,7 @@ async function replayFailedOperations(failedOps, stateResources, stackName, ctx,
|
|
|
21102
21272
|
remainingFailedOps: []
|
|
21103
21273
|
};
|
|
21104
21274
|
const { logger } = ctx;
|
|
21275
|
+
const resolver = new IntrinsicFunctionResolver(ctx.region);
|
|
21105
21276
|
const emitEnvelope = options.emitEnvelope === true && failedOps.length > 0;
|
|
21106
21277
|
if (emitEnvelope) ctx.recordEvent?.({
|
|
21107
21278
|
eventType: "ROLLBACK_STARTED",
|
|
@@ -21183,14 +21354,17 @@ async function replayFailedOperations(failedOps, stateResources, stackName, ctx,
|
|
|
21183
21354
|
resourceType: op.resourceType,
|
|
21184
21355
|
provisionedBy: op.provisionedBy ?? current.provisionedBy
|
|
21185
21356
|
});
|
|
21357
|
+
const secrets = /* @__PURE__ */ new Map();
|
|
21358
|
+
const desiredProps = await resolveReplayProps(prev.properties, resolver, secrets);
|
|
21359
|
+
const attemptedProps = await resolveReplayProps(op.attemptedProperties ?? current.properties, resolver, secrets);
|
|
21186
21360
|
const revertFailedResult = await updateWithRollbackRetry(provider, [
|
|
21187
21361
|
op.logicalId,
|
|
21188
21362
|
current.physicalId,
|
|
21189
21363
|
op.resourceType,
|
|
21190
|
-
|
|
21191
|
-
|
|
21364
|
+
desiredProps ?? {},
|
|
21365
|
+
attemptedProps ?? {}
|
|
21192
21366
|
], op.logicalId, logger, options.isInterrupted);
|
|
21193
|
-
stateResources[op.logicalId] = recordAfterRollbackUpdate(prev, revertFailedResult);
|
|
21367
|
+
stateResources[op.logicalId] = redactRollbackRecord(recordAfterRollbackUpdate(prev, revertFailedResult), secrets);
|
|
21194
21368
|
logger.info(` Rollback: ${op.logicalId} reverted successfully`);
|
|
21195
21369
|
await options.afterOp?.(op.logicalId);
|
|
21196
21370
|
ctx.recordEvent?.({
|
|
@@ -21316,7 +21490,7 @@ const FLUSH_INTERVAL_MS = 2e3;
|
|
|
21316
21490
|
const FLUSH_EVENT_THRESHOLD = 50;
|
|
21317
21491
|
/** Build-time cdkd version, with a dev fallback for non-built contexts. */
|
|
21318
21492
|
function getCdkdVersion() {
|
|
21319
|
-
return "0.283.
|
|
21493
|
+
return "0.283.20";
|
|
21320
21494
|
}
|
|
21321
21495
|
/**
|
|
21322
21496
|
* Generate a time-sortable unique run id, e.g.
|
|
@@ -21913,6 +22087,30 @@ var DeployEngine = class {
|
|
|
21913
22087
|
*/
|
|
21914
22088
|
recordedOutputReads = [];
|
|
21915
22089
|
/**
|
|
22090
|
+
* PER-RESOURCE map of resolved SECRET dynamic-reference values
|
|
22091
|
+
* (plaintext -> `{{resolve:...}}` expression) the resolver records for each
|
|
22092
|
+
* resource's own resolution (GHSA fix). Keyed by logicalId. Per-resource, NOT
|
|
22093
|
+
* session-wide, because a session-wide map cross-contaminates: if resource A
|
|
22094
|
+
* resolves a `{{resolve:...:SecretString}}` whole-secret reference to value V,
|
|
22095
|
+
* and resource B (e.g. the `AWS::SecretsManager::Secret` that OWNS the secret)
|
|
22096
|
+
* carries V as its own LITERAL property, a session-wide value scan would
|
|
22097
|
+
* wrongly rewrite B's literal to A's expression — a false positive that shows
|
|
22098
|
+
* up as a permanent spurious diff. Redacting each resource only with the
|
|
22099
|
+
* secrets substituted during ITS OWN resolution scopes the value match
|
|
22100
|
+
* correctly. Kept on the engine (not just the resolver context) so the async
|
|
22101
|
+
* observed-property capture — which drains after the context is gone — can
|
|
22102
|
+
* still redact an AWS-readback secret (Cognito `client_secret`). Reset per
|
|
22103
|
+
* `deploy()`. See `secret-redaction.ts`.
|
|
22104
|
+
*/
|
|
22105
|
+
perResourceSecrets = /* @__PURE__ */ new Map();
|
|
22106
|
+
/**
|
|
22107
|
+
* Resolved secrets recorded while resolving the stack OUTPUTS (a `CfnOutput`
|
|
22108
|
+
* whose Value resolves a `{{resolve:...}}` reference). Separate from the
|
|
22109
|
+
* per-resource maps for the same anti-cross-contamination reason. Reset per
|
|
22110
|
+
* `deploy()`.
|
|
22111
|
+
*/
|
|
22112
|
+
outputSecrets = /* @__PURE__ */ new Map();
|
|
22113
|
+
/**
|
|
21916
22114
|
* Per-logical-id snapshot of the intrinsic-RESOLVED desired properties
|
|
21917
22115
|
* each CREATE / UPDATE attempted (issue #1198). Written just before the
|
|
21918
22116
|
* provider call; read only when the op FAILS, to journal the failed op's
|
|
@@ -21953,6 +22151,8 @@ var DeployEngine = class {
|
|
|
21953
22151
|
async deploy(stackName, template) {
|
|
21954
22152
|
this.recordedImports = [];
|
|
21955
22153
|
this.recordedOutputReads = [];
|
|
22154
|
+
this.perResourceSecrets = /* @__PURE__ */ new Map();
|
|
22155
|
+
this.outputSecrets = /* @__PURE__ */ new Map();
|
|
21956
22156
|
this.resolver.resetPhysicalIdFallbackCount();
|
|
21957
22157
|
return withStackName(stackName, () => this.doDeploy(stackName, template));
|
|
21958
22158
|
}
|
|
@@ -21972,7 +22172,63 @@ var DeployEngine = class {
|
|
|
21972
22172
|
stackName,
|
|
21973
22173
|
...this.exportIndexStore && { exportIndex: this.exportIndexStore },
|
|
21974
22174
|
recordedImports: this.recordedImports,
|
|
21975
|
-
recordedOutputReads: this.recordedOutputReads
|
|
22175
|
+
recordedOutputReads: this.recordedOutputReads,
|
|
22176
|
+
recordedSecretValues: /* @__PURE__ */ new Map()
|
|
22177
|
+
};
|
|
22178
|
+
}
|
|
22179
|
+
/**
|
|
22180
|
+
* Redact resolved secret plaintext out of a bag about to be PERSISTED to
|
|
22181
|
+
* state, replacing each secret value with the unresolved `{{resolve:...}}`
|
|
22182
|
+
* expression it came from (GHSA fix; see `secret-redaction.ts`). No-op when
|
|
22183
|
+
* the deploy recorded no secrets. The bag sent to the AWS API is the
|
|
22184
|
+
* un-redacted resolved bag; only the persisted copy is rewritten.
|
|
22185
|
+
*/
|
|
22186
|
+
/**
|
|
22187
|
+
* Bag-level redaction of a single properties bag against a specific secrets
|
|
22188
|
+
* map (GHSA fix). Used by the UPDATE no-op re-check so a resolved-plaintext
|
|
22189
|
+
* bag is compared against the expression-bearing stored bag on equal footing.
|
|
22190
|
+
*/
|
|
22191
|
+
redactPropsWith(props, secrets) {
|
|
22192
|
+
if (secrets.size === 0) return props;
|
|
22193
|
+
return redactSecretsForState(props, secrets);
|
|
22194
|
+
}
|
|
22195
|
+
/**
|
|
22196
|
+
* Redact resolved secret plaintext out of rollback-journal operations (GHSA
|
|
22197
|
+
* fix). Each op may carry resolved `properties` / `attemptedProperties` and a
|
|
22198
|
+
* `previousState` snapshot (whose `properties` / `attributes` /
|
|
22199
|
+
* `observedProperties` also hold resolved values). Each op is redacted with
|
|
22200
|
+
* the secrets recorded for ITS OWN resource (`perResourceSecrets`), so a
|
|
22201
|
+
* whole-secret value from one resource cannot rewrite another's literal.
|
|
22202
|
+
* Preserves ops that carry none of those fields, or whose resource recorded
|
|
22203
|
+
* no secret.
|
|
22204
|
+
*/
|
|
22205
|
+
redactOperationsForJournal(operations) {
|
|
22206
|
+
return operations.map((op) => {
|
|
22207
|
+
const secrets = this.perResourceSecrets.get(op.logicalId);
|
|
22208
|
+
if (!secrets || secrets.size === 0) return op;
|
|
22209
|
+
const next = { ...op };
|
|
22210
|
+
if (next.properties) next.properties = redactSecretsForState(next.properties, secrets);
|
|
22211
|
+
if (next.attemptedProperties) next.attemptedProperties = redactSecretsForState(next.attemptedProperties, secrets);
|
|
22212
|
+
if (next.previousState) {
|
|
22213
|
+
const prev = { ...next.previousState };
|
|
22214
|
+
prev.properties = redactSecretsForState(prev.properties, secrets);
|
|
22215
|
+
if (prev.attributes) prev.attributes = redactSecretsForState(prev.attributes, secrets);
|
|
22216
|
+
if (prev.observedProperties) prev.observedProperties = redactSecretsForState(prev.observedProperties, secrets);
|
|
22217
|
+
next.previousState = prev;
|
|
22218
|
+
}
|
|
22219
|
+
return next;
|
|
22220
|
+
});
|
|
22221
|
+
}
|
|
22222
|
+
redactStateForPersist(state) {
|
|
22223
|
+
const resources = {};
|
|
22224
|
+
for (const [logicalId, record] of Object.entries(state.resources)) {
|
|
22225
|
+
const secrets = this.perResourceSecrets.get(logicalId);
|
|
22226
|
+
resources[logicalId] = secrets ? scrubResourceRecord(record, secrets) : record;
|
|
22227
|
+
}
|
|
22228
|
+
return {
|
|
22229
|
+
...state,
|
|
22230
|
+
resources,
|
|
22231
|
+
outputs: this.outputSecrets.size > 0 ? redactSecretsForState(state.outputs, this.outputSecrets) : state.outputs
|
|
21976
22232
|
};
|
|
21977
22233
|
}
|
|
21978
22234
|
/**
|
|
@@ -21981,12 +22237,21 @@ var DeployEngine = class {
|
|
|
21981
22237
|
* constructed with `options.parentStackInfo` (= it's deploying a
|
|
21982
22238
|
* nested-stack child). Returns the state unchanged for top-level
|
|
21983
22239
|
* deploys so the three v6 fields stay absent from non-child state files.
|
|
22240
|
+
*
|
|
22241
|
+
* ALSO the single choke point where resolved SECRET plaintext is redacted out
|
|
22242
|
+
* of the persisted state (GHSA fix): every `stateBackend.saveState` call in
|
|
22243
|
+
* this engine wraps its state through here, so redacting `resources` once here
|
|
22244
|
+
* covers `properties` / `attributes` / `observedProperties` across every
|
|
22245
|
+
* create / update / replacement / rollback / observed-capture path uniformly —
|
|
22246
|
+
* including the async observed-capture drain that runs after the resolver
|
|
22247
|
+
* context is gone (which is why the secret map is session-wide).
|
|
21984
22248
|
*/
|
|
21985
22249
|
withParentInfo(state) {
|
|
21986
|
-
|
|
22250
|
+
const redacted = this.redactStateForPersist(state);
|
|
22251
|
+
if (!this.options.parentStackInfo) return redacted;
|
|
21987
22252
|
const { parentStack, parentLogicalId, parentRegion } = this.options.parentStackInfo;
|
|
21988
22253
|
return {
|
|
21989
|
-
...
|
|
22254
|
+
...redacted,
|
|
21990
22255
|
parentStack,
|
|
21991
22256
|
parentLogicalId,
|
|
21992
22257
|
parentRegion
|
|
@@ -22241,13 +22506,14 @@ var DeployEngine = class {
|
|
|
22241
22506
|
conditions
|
|
22242
22507
|
}, stackName);
|
|
22243
22508
|
diffResolverContext.bestEffort = true;
|
|
22509
|
+
diffResolverContext.skipDynamicReferences = true;
|
|
22244
22510
|
const diffResolveFn = (value) => this.resolver.resolve(value, diffResolverContext);
|
|
22245
22511
|
const changes = await this.diffCalculator.calculateDiff(currentState, effectiveTemplate, diffResolveFn, makeCanonicalizePropertiesFn(this.providerRegistry));
|
|
22246
22512
|
if (!this.diffCalculator.hasChanges(changes)) {
|
|
22247
22513
|
this.logger.info("No changes detected. Stack is up to date.");
|
|
22248
22514
|
let persistedOutputs = currentState.outputs ?? {};
|
|
22249
22515
|
if (!this.options.dryRun) {
|
|
22250
|
-
const resolvedOutputs = await this.resolveOutputs(effectiveTemplate, currentState.resources, stackName, parameterValues, conditions);
|
|
22516
|
+
const resolvedOutputs = this.redactPropsWith(await this.resolveOutputs(effectiveTemplate, currentState.resources, stackName, parameterValues, conditions), this.outputSecrets);
|
|
22251
22517
|
const resolutionFailed = Object.values(resolvedOutputs).some((v) => v === void 0);
|
|
22252
22518
|
const outputsChanged = !resolutionFailed && !outputMapsEqual(persistedOutputs, resolvedOutputs);
|
|
22253
22519
|
if (resolutionFailed && !outputMapsEqual(persistedOutputs, resolvedOutputs)) this.logger.warn("Outputs changed but one or more could not be resolved; keeping the previously persisted outputs. A downstream Fn::ImportValue may fail until the next deploy.");
|
|
@@ -22575,6 +22841,7 @@ var DeployEngine = class {
|
|
|
22575
22841
|
let outputs;
|
|
22576
22842
|
try {
|
|
22577
22843
|
outputs = await this.resolveOutputs(template, newResources, stackName, parameterValues, conditions);
|
|
22844
|
+
outputs = this.redactPropsWith(outputs, this.outputSecrets);
|
|
22578
22845
|
} catch (outputError) {
|
|
22579
22846
|
await this.persistStateAfterOutputFailure(stackName, currentState, newResources, currentEtag, pendingMigration);
|
|
22580
22847
|
await this.writeRollbackJournalSegment(stackName, completedOperations, failedOperations, "no-rollback-failure", currentEtag === void 0);
|
|
@@ -22730,6 +22997,8 @@ var DeployEngine = class {
|
|
|
22730
22997
|
*/
|
|
22731
22998
|
async writeRollbackJournalSegment(stackName, completedOperations, failedOperations, reason, initialDeploy) {
|
|
22732
22999
|
if (completedOperations.length === 0 && failedOperations.length === 0) return;
|
|
23000
|
+
const redactedCompleted = this.redactOperationsForJournal(completedOperations);
|
|
23001
|
+
const redactedFailed = this.redactOperationsForJournal(failedOperations);
|
|
22733
23002
|
try {
|
|
22734
23003
|
const segment = {
|
|
22735
23004
|
...this.options.eventRecorder?.runId !== void 0 && { runId: this.options.eventRecorder.runId },
|
|
@@ -22738,8 +23007,8 @@ var DeployEngine = class {
|
|
|
22738
23007
|
initialDeploy,
|
|
22739
23008
|
...this.options.roleArn && { roleArn: this.options.roleArn },
|
|
22740
23009
|
cdkdVersion: getCdkdVersion(),
|
|
22741
|
-
operations:
|
|
22742
|
-
...
|
|
23010
|
+
operations: redactedCompleted,
|
|
23011
|
+
...redactedFailed.length > 0 && { failedOperations: redactedFailed }
|
|
22743
23012
|
};
|
|
22744
23013
|
await this.stateBackend.appendRollbackJournalSegment(stackName, this.stackRegion, segment);
|
|
22745
23014
|
this.logger.debug(`Rollback journal segment written (${reason})`);
|
|
@@ -22846,10 +23115,29 @@ var DeployEngine = class {
|
|
|
22846
23115
|
recordEvent(event) {
|
|
22847
23116
|
if (!this.options.eventRecorder) return;
|
|
22848
23117
|
try {
|
|
22849
|
-
this.options.eventRecorder.record(event);
|
|
23118
|
+
this.options.eventRecorder.record(this.maskSecretsInEvent(event));
|
|
22850
23119
|
} catch {}
|
|
22851
23120
|
}
|
|
22852
23121
|
/**
|
|
23122
|
+
* Mask any resolved secret value out of an event's human-authored text before
|
|
23123
|
+
* it is persisted to `deployments/*.jsonl` (which outlives `cdkd destroy`)
|
|
23124
|
+
* — GHSA fix. An AWS validation error can quote the offending property value
|
|
23125
|
+
* (`Value '<secret>' at 'X' failed to satisfy ...`), and a provider `reason`
|
|
23126
|
+
* is provider-authored prose; both reach the event store as `error.message` /
|
|
23127
|
+
* `reason`. No-op when the deploy recorded no secrets.
|
|
23128
|
+
*/
|
|
23129
|
+
maskSecretsInEvent(event) {
|
|
23130
|
+
const secrets = event.logicalId ? this.perResourceSecrets.get(event.logicalId) : void 0;
|
|
23131
|
+
if (!secrets || secrets.size === 0) return event;
|
|
23132
|
+
const next = { ...event };
|
|
23133
|
+
if (next.error?.message) next.error = {
|
|
23134
|
+
...next.error,
|
|
23135
|
+
message: maskSecretsInText(next.error.message, secrets)
|
|
23136
|
+
};
|
|
23137
|
+
if (next.reason) next.reason = maskSecretsInText(next.reason, secrets);
|
|
23138
|
+
return next;
|
|
23139
|
+
}
|
|
23140
|
+
/**
|
|
22853
23141
|
* Issue #1002 PR 2 — §7 step 3 post-resolution audit (defense in depth).
|
|
22854
23142
|
* No-op in legacy mode (`options.assetRedirect` unset). In cdkd-assets
|
|
22855
23143
|
* mode, a resolved property still naming a mapped SOURCE (CDK bootstrap)
|
|
@@ -22949,6 +23237,7 @@ var DeployEngine = class {
|
|
|
22949
23237
|
...conditions && { conditions }
|
|
22950
23238
|
}, stackName);
|
|
22951
23239
|
const resolvedProps = await this.resolver.resolve(desiredProps, context);
|
|
23240
|
+
if (context.recordedSecretValues) this.perResourceSecrets.set(logicalId, context.recordedSecretValues);
|
|
22952
23241
|
this.auditResolvedAssetReferences(logicalId, resourceType, resolvedProps);
|
|
22953
23242
|
this.attemptedResolvedProps.set(logicalId, resolvedProps);
|
|
22954
23243
|
const createDecision = this.providerRegistry.getProviderFor({
|
|
@@ -22990,9 +23279,11 @@ var DeployEngine = class {
|
|
|
22990
23279
|
...conditions && { conditions }
|
|
22991
23280
|
}, stackName);
|
|
22992
23281
|
const resolvedProps = await this.resolver.resolve(desiredProps, context);
|
|
23282
|
+
const updateSecrets = context.recordedSecretValues ?? /* @__PURE__ */ new Map();
|
|
23283
|
+
this.perResourceSecrets.set(logicalId, updateSecrets);
|
|
22993
23284
|
this.auditResolvedAssetReferences(logicalId, resourceType, resolvedProps);
|
|
22994
23285
|
this.attemptedResolvedProps.set(logicalId, resolvedProps);
|
|
22995
|
-
if (JSON.stringify(resolvedProps) === JSON.stringify(currentProps)) {
|
|
23286
|
+
if (JSON.stringify(this.redactPropsWith(resolvedProps, updateSecrets)) === JSON.stringify(currentProps)) {
|
|
22996
23287
|
if (change.attributeChanges && change.attributeChanges.length > 0) {
|
|
22997
23288
|
const attrSummary = change.attributeChanges.map((a) => `${a.attribute}: ${a.oldValue ?? "(unset)"} → ${a.newValue ?? "(unset)"}`).join(", ");
|
|
22998
23289
|
this.logger.info(` ↻ ${logicalId} (${resourceType}) attribute update: ${attrSummary}`);
|
|
@@ -23518,6 +23809,7 @@ var DeployEngine = class {
|
|
|
23518
23809
|
outputs[outputKey] = void 0;
|
|
23519
23810
|
}
|
|
23520
23811
|
}
|
|
23812
|
+
if (context.recordedSecretValues) for (const [value, expr] of context.recordedSecretValues) this.outputSecrets.set(value, expr);
|
|
23521
23813
|
return outputs;
|
|
23522
23814
|
}
|
|
23523
23815
|
buildDisplayOutputs(template, resolvedOutputs) {
|
|
@@ -23532,5 +23824,5 @@ var DeployEngine = class {
|
|
|
23532
23824
|
};
|
|
23533
23825
|
|
|
23534
23826
|
//#endregion
|
|
23535
|
-
export { coerceCfnBoolean as $,
|
|
23536
|
-
//# sourceMappingURL=deploy-engine-
|
|
23827
|
+
export { coerceCfnBoolean as $, resolveCaptureObservedState as $t, cyan as A, LocalStartServiceError as An, rewriteTemplateAssetReferences as At, findSilentDropProperties as B, StateError as Bn, buildDockerImage as Bt, makeCanonicalizePropertiesFn as C, AssetError as Cn, shouldRetainResource as Ct, renderStatefulReason as D, DeployCancelledError as Dn, buildAssetRedirectMap as Dt, isStatefulRecreateTargetSync as E, DependencyError as En, WorkGraph as Et, IAMRoleProvider as F, ProvisioningError as Fn, getBootstrapMarkerKey as Ft, IntrinsicFunctionResolver as G, withErrorHandling as Gn, AssetManifestLoader as Gt, slowCcOperationTimeoutMs as H, formatError as Hn, getDockerCmd as Ht, collectInlinePolicyNamesManagedBySiblings as I, ResourceTimeoutError as In, parseBootstrapMarker as It, refStateLookupFromResource as J, isThrottlingError as Jn, synthesisStatusMessage as Jt, cfnRefValueFromPhysicalId as K, isMarkedNonRetryable as Kn, getDockerImageBySourceHash as Kt, clearOnUpdateRemoval as L, ResourceUpdateNotSupportedError as Ln, validateAssetBucketName as Lt, green as M, MissingCdkCliError as Mn, AssetModeResolver as Mt, red as N, NestedStackChildDirectDestroyError as Nn, BOOTSTRAP_MARKER_PREFIX as Nt, formatResourceLine as O, LocalInvokeBuildError as On, createAssetRedirectResolver as Ot, yellow as P, PartialFailureError as Pn, ensureAssetStorage as Pt, assertRegionMatch as Q, resolveAutoAssetStorage as Qt, ProviderRegistry as R, StackHasActiveImportsError as Rn, validateContainerRepoName as Rt, unsupportedFinalSnapshotError as S, setAwsClients as Sn, rebuildClientForBucketRegion as St, MULTI_REGION_RECREATE_BLOCKED_TYPES as T, ConfigError as Tn, stringifyValue as Tt, disableInstanceApiTermination as U, isCdkdError as Un, runDockerForeground as Ut, CloudControlProvider as V, SynthesisError as Vn, formatDockerLoginError as Vt, isTerminationProtectionPropagationError as W, normalizeAwsError as Wn, runDockerStreaming as Wt, normalizeAwsTagsToCfn as X, __exportAll as Xn, getLegacyStateBucketName as Xt, WAFv2WebACLProvider as Y, markNonRetryable as Yn, getDefaultStateBucketName as Yt, resolveExplicitPhysicalId as Z, resolveApp as Zt, buildFinalSnapshotIdentifier as _, clearBucketRegionCache as _n, withRetry as _t, DeploymentEventsStore as a, warnDeprecatedNoPrefixCliFlag as an, requireConfigObject as at, isFinalSnapshotError as b, getAwsClients as bn, LockManager as bt, replayFailedOperations as c, MIGRATE_TMP_PREFIX as cn, scrubResourceRecord as ct, deleteSkipReason as d, expectedOwnerParam as dn, s3BucketDualStackDomainName as dt, resolveSkipPrefix as en, configBooleanRefusal as et, withResourceDeadline as f, PARTITION_TABLE as fn, s3BucketRegionalDomainName as ft, PRE_DELETE_SNAPSHOT_TYPES as g, processStackMessages as gn, describeTypeWithThrottleRetry as gt, ATOMIC_FINAL_SNAPSHOT_TYPES as h, AssemblyReader as hn, DiffCalculator as ht, DeploymentEventsReader as i, stateBucketExistenceConfirmed as in, requireConfigArray as it, gray as j, LockError as jn, escapeRegExp$1 as jt, bold as k, LocalMigrateError as kn, loadPublishableAssetManifest as kt, replayRollback as l, findLargeInlineResources as ln, s3BucketArn as lt, computeImplicitDeleteEdges as m, derivePartitionAndUrlSuffix as mn, applyRoleArnIfSet as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, resolveStateBucketWithDefaultAndSource as nn, readConfigString as nt, planFailedOps as o, CFN_TEMPLATE_BODY_LIMIT as on, requireConfigString as ot, IMPLICIT_DELETE_DEPENDENCIES as p, canonicalizeRegion as pn, s3BucketWebsiteUrl as pt, getAccountInfo as q, isRetryableTransientError as qn, Synthesizer as qt, DeployEngine as r, resolveUseCdkBootstrapAssets as rn, replayWarn as rt, planRollback as s, CFN_TEMPLATE_URL_LIMIT as sn, redactSecretsForState as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, resolveStateBucketWithDefault as tn, configStringRefusal as tt, UNSPECIFIED_SKIP_REASON as u, uploadCfnTemplate as un, s3BucketDomainName as ut, ccRoutedFinalSnapshotError as v, resolveBucketRegion as vn, DagBuilder as vt, extractDeploymentEventError as w, CdkdError as wn, AssetPublisher as wt, refusesFinalSnapshot as x, resetAwsClients as xn, S3StateBackend as xt, createPreDeleteFinalSnapshot as y, AwsClients as yn, TemplateParser as yt, findActionableSilentDrops as z, StackTerminationProtectionError as zn, buildDenyExternalAccessPolicy as zt };
|
|
23828
|
+
//# sourceMappingURL=deploy-engine-ISOKkuun.js.map
|