@go-to-k/cdkd 0.284.61 → 0.284.63
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/{asg-provider-DTolzTHQ.js → asg-provider-B9V61Fbq.js} +2 -2
- package/dist/{asg-provider-DTolzTHQ.js.map → asg-provider-B9V61Fbq.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-0d-g0TT4.js → deploy-engine-B-RuozdO.js} +410 -36
- package/dist/deploy-engine-B-RuozdO.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-CrJMVg4e.js → program-alOnKpLV.js} +260 -39
- package/dist/{program-CrJMVg4e.js.map → program-alOnKpLV.js.map} +1 -1
- package/dist/{version-iXYxz3AH.js → version-Cmw63bLc.js} +2 -2
- package/dist/{version-iXYxz3AH.js.map → version-Cmw63bLc.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-0d-g0TT4.js.map +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { a as getLiveRenderer, d as generateResourceNameWithFallback, f as getCurrentStackName, h as withStackName, l as applyDefaultNameForFallback, n as getLogger, p as looksLikeCdkdGeneratedName, u as generateResourceName } from "./logger-zRrlbaQt.js";
|
|
2
|
-
import { t as getCdkdVersion } from "./version-
|
|
2
|
+
import { t as getCdkdVersion } from "./version-Cmw63bLc.js";
|
|
3
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
4
|
import { randomUUID } from "node:crypto";
|
|
4
5
|
import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
5
6
|
import { CloudControlClient, CreateResourceCommand, DeleteResourceCommand, GetResourceCommand, GetResourceRequestStatusCommand, ListResourcesCommand, UpdateResourceCommand } from "@aws-sdk/client-cloudcontrol";
|
|
@@ -11709,6 +11710,10 @@ function literalStringOrUndefined(value) {
|
|
|
11709
11710
|
* (`(producer X / Y)`), so it is neither derivable from the source leaf nor
|
|
11710
11711
|
* stable.
|
|
11711
11712
|
*
|
|
11713
|
+
* THREE arms answer: `Fn::ImportValue`, `Fn::GetStackOutput`, and the
|
|
11714
|
+
* `Fn::GetAtt` on a nested-stack OUTPUT that issue #2055's read site
|
|
11715
|
+
* re-resolves. Every other leaf refuses.
|
|
11716
|
+
*
|
|
11712
11717
|
* `Region` and `RoleArn` are OPTIONAL slots, and an ABSENT one keys as empty
|
|
11713
11718
|
* while a PRESENT-but-non-literal one refuses. Absent has to be its own key
|
|
11714
11719
|
* rather than being filled in with the resolver's own region: the persist path
|
|
@@ -11767,6 +11772,26 @@ function crossStackSourceKey(source) {
|
|
|
11767
11772
|
}
|
|
11768
11773
|
return slots.join(CROSS_STACK_KEY_SEPARATOR);
|
|
11769
11774
|
}
|
|
11775
|
+
if (key === "Fn::GetAtt") {
|
|
11776
|
+
const raw = source[key];
|
|
11777
|
+
let logicalId;
|
|
11778
|
+
let attributeName;
|
|
11779
|
+
if (typeof raw === "string") {
|
|
11780
|
+
const parts = raw.split(".");
|
|
11781
|
+
if (parts.length !== 2) return void 0;
|
|
11782
|
+
logicalId = literalStringOrUndefined(parts[0]);
|
|
11783
|
+
attributeName = literalStringOrUndefined(parts[1]);
|
|
11784
|
+
} else if (Array.isArray(raw) && raw.length === 2) {
|
|
11785
|
+
logicalId = literalStringOrUndefined(raw[0]);
|
|
11786
|
+
attributeName = literalStringOrUndefined(raw[1]);
|
|
11787
|
+
}
|
|
11788
|
+
if (logicalId === void 0 || attributeName === void 0) return void 0;
|
|
11789
|
+
return [
|
|
11790
|
+
"Fn::GetAtt",
|
|
11791
|
+
logicalId,
|
|
11792
|
+
attributeName
|
|
11793
|
+
].join(CROSS_STACK_KEY_SEPARATOR);
|
|
11794
|
+
}
|
|
11770
11795
|
}
|
|
11771
11796
|
/**
|
|
11772
11797
|
* Remember, FOR THE PASS THAT OWNS `secrets`, that the cross-stack source leaf
|
|
@@ -12335,7 +12360,7 @@ function plaintextIndexOf(secrets) {
|
|
|
12335
12360
|
* bag could not be vouched for, because it rewrote a `{Name: '', Value:
|
|
12336
12361
|
* 'an-unrelated-literal'}` pair. Nothing here can do that: the answer is never
|
|
12337
12362
|
* the source subtree, it is an expression a WRITER recorded against this exact
|
|
12338
|
-
* leaf identity; the arm fires for exactly
|
|
12363
|
+
* leaf identity; the arm fires for exactly three intrinsic spellings; and
|
|
12339
12364
|
* condition 1 still demands that the bag leaf be a plaintext this pass
|
|
12340
12365
|
* resolved. Every rejection degrades to {@link positionByIntrinsicSkeleton} and
|
|
12341
12366
|
* then to the value scan, i.e. to today's behavior.
|
|
@@ -14868,6 +14893,36 @@ function carriesDynamicReference(value) {
|
|
|
14868
14893
|
if (value !== null && typeof value === "object") return Object.values(value).some(carriesDynamicReference);
|
|
14869
14894
|
return false;
|
|
14870
14895
|
}
|
|
14896
|
+
/** The nested-stack resource type, whose `Outputs.<Name>` attributes are re-resolved (issue #2055). */
|
|
14897
|
+
const NESTED_STACK_RESOURCE_TYPE = "AWS::CloudFormation::Stack";
|
|
14898
|
+
/** Prefix `NestedStackProvider` records a child stack output under. */
|
|
14899
|
+
const NESTED_STACK_OUTPUT_ATTRIBUTE_PREFIX = "Outputs.";
|
|
14900
|
+
/**
|
|
14901
|
+
* `arn:cdkd-local:<childRegion>:<accountId>:nested-stack/<parent>/<logicalId>` —
|
|
14902
|
+
* the synthesized physicalId `NestedStackProvider.synthesizeArn` records on the
|
|
14903
|
+
* parent's `AWS::CloudFormation::Stack` row.
|
|
14904
|
+
*/
|
|
14905
|
+
const NESTED_STACK_LOCAL_ARN = /^arn:cdkd-local:([a-z0-9-]+):[^:]*:nested-stack\//i;
|
|
14906
|
+
/**
|
|
14907
|
+
* The CHILD stack's region, read off the parent row's synthesized physicalId
|
|
14908
|
+
* (issue [#2055](https://github.com/go-to-k/cdkd/issues/2055)).
|
|
14909
|
+
*
|
|
14910
|
+
* WHY THE ARN AND NOT A STATE READ. The child's own record carries `region`,
|
|
14911
|
+
* but its state KEY is `cdkd/<parent>~<logicalId>/<region>/state.json` — the
|
|
14912
|
+
* region is part of the key, so reading the record to learn the region is
|
|
14913
|
+
* circular. The synthesized physicalId is the SAME provider's durable record of
|
|
14914
|
+
* the region it deployed the child into, it sits on the resource row the
|
|
14915
|
+
* resolver already holds, and reading it costs no I/O on a path that is
|
|
14916
|
+
* otherwise hot.
|
|
14917
|
+
*
|
|
14918
|
+
* Returns `undefined` for anything that is not that shape (a hand-edited state
|
|
14919
|
+
* file, a record written before this provider existed), which the caller reads
|
|
14920
|
+
* as "use this resolver's own region".
|
|
14921
|
+
*/
|
|
14922
|
+
function nestedStackChildRegionFromLocalArn(physicalId) {
|
|
14923
|
+
if (typeof physicalId !== "string") return void 0;
|
|
14924
|
+
return NESTED_STACK_LOCAL_ARN.exec(physicalId)?.[1];
|
|
14925
|
+
}
|
|
14871
14926
|
let cachedAccountIdentity = null;
|
|
14872
14927
|
/**
|
|
14873
14928
|
* Cache for availability zones per region
|
|
@@ -15224,6 +15279,79 @@ function collectReferencedParameterNames(template) {
|
|
|
15224
15279
|
return referenced;
|
|
15225
15280
|
}
|
|
15226
15281
|
/**
|
|
15282
|
+
* Does coercing to `type` risk destroying the plaintext cdkd redacts against?
|
|
15283
|
+
*
|
|
15284
|
+
* DERIVED from {@link coerceParameterTypedValue}, never enumerated beside it.
|
|
15285
|
+
* The previous shape was a hand-kept set naming `Number` / `List<Number>`,
|
|
15286
|
+
* whose doc cleared `CommaDelimitedList` as safe because it "produces an array
|
|
15287
|
+
* of strings (both of which the recording scan and the redactor handle)". That
|
|
15288
|
+
* holds only for a comma-FREE secret -- and the dominant Secrets Manager shape
|
|
15289
|
+
* is a JSON blob, which is nothing but commas, so `,`-splitting shreds the
|
|
15290
|
+
* plaintext into fragments matching neither arm of
|
|
15291
|
+
* {@link inheritedSecretsCarriedBy}. An audited allow-list was wrong about one
|
|
15292
|
+
* of its own three entries, which is why this is now measured, not listed.
|
|
15293
|
+
*
|
|
15294
|
+
* Probe the REAL coercion with a canary carrying the separators the arms use --
|
|
15295
|
+
* a comma and surrounding whitespace -- and call the type risky when the canary
|
|
15296
|
+
* does not survive as one string. A `Type` added to the switch is covered the
|
|
15297
|
+
* day it is added, with nothing to keep in sync.
|
|
15298
|
+
*
|
|
15299
|
+
* The DEPLOY path does better: `refuseCoercedInheritedSecret` measures the loss
|
|
15300
|
+
* on the ACTUAL value, so a comma-free secret in a `CommaDelimitedList` still
|
|
15301
|
+
* works. This coarser predicate is for `cdkd diff`, which holds no secrets bag
|
|
15302
|
+
* and therefore cannot measure.
|
|
15303
|
+
*/
|
|
15304
|
+
const SECRET_IDENTITY_CANARY = "a, b";
|
|
15305
|
+
function parameterTypeMayLoseSecretIdentity(type) {
|
|
15306
|
+
return coerceParameterTypedValue(SECRET_IDENTITY_CANARY, type) !== SECRET_IDENTITY_CANARY;
|
|
15307
|
+
}
|
|
15308
|
+
/**
|
|
15309
|
+
* ONE definition of parameter-type coercion, at module scope so
|
|
15310
|
+
* {@link parameterTypeMayLoseSecretIdentity} probes the same code the resolver
|
|
15311
|
+
* runs rather than a copy of it.
|
|
15312
|
+
*/
|
|
15313
|
+
function coerceParameterTypedValue(value, type) {
|
|
15314
|
+
switch (type) {
|
|
15315
|
+
case "Number": return Number(value);
|
|
15316
|
+
case "List<Number>": return value.split(",").map((v) => Number(v.trim()));
|
|
15317
|
+
case "CommaDelimitedList": return value.split(",").map((v) => v.trim());
|
|
15318
|
+
default: return value;
|
|
15319
|
+
}
|
|
15320
|
+
}
|
|
15321
|
+
/**
|
|
15322
|
+
* The inherited `plaintext -> expression` pairs that `value` CARRIES.
|
|
15323
|
+
*
|
|
15324
|
+
* ONE definition, shared by the RECORDING side
|
|
15325
|
+
* (`recordInheritedParameterSecrets`) and the REFUSAL side
|
|
15326
|
+
* (`refuseCoercedInheritedSecret`), because a refusal narrower than the
|
|
15327
|
+
* recording would let exactly the values it exists to catch through — and the
|
|
15328
|
+
* two drifting apart is how this class of bug reappears.
|
|
15329
|
+
*
|
|
15330
|
+
* TWO ARMS, mirroring the two `redactSecretsForState` performs, so the
|
|
15331
|
+
* recording side cannot be narrower than the redaction side:
|
|
15332
|
+
*
|
|
15333
|
+
* - WHOLE VALUE at any length — `{Ref: Param}` returning exactly the secret.
|
|
15334
|
+
* - SUBSTRING at or above {@link MIN_NEEDLE_LENGTH} — the parent built the
|
|
15335
|
+
* parameter with an `Fn::Sub`, so the value is `postgres://u:<secret>@host`
|
|
15336
|
+
* and only part of it is the secret. Short needles are excluded on this arm
|
|
15337
|
+
* for the same reason the redactor excludes them: a 3-character secret
|
|
15338
|
+
* matches half the alphabet's worth of ordinary identifiers.
|
|
15339
|
+
*
|
|
15340
|
+
* A `CommaDelimitedList` parameter arrives as an array, so the scan walks
|
|
15341
|
+
* string elements too.
|
|
15342
|
+
*/
|
|
15343
|
+
function inheritedSecretsCarriedBy(value, inherited) {
|
|
15344
|
+
const candidates = [];
|
|
15345
|
+
if (typeof value === "string") candidates.push(value);
|
|
15346
|
+
else if (Array.isArray(value)) {
|
|
15347
|
+
for (const element of value) if (typeof element === "string") candidates.push(element);
|
|
15348
|
+
}
|
|
15349
|
+
if (candidates.length === 0) return [];
|
|
15350
|
+
const carried = [];
|
|
15351
|
+
for (const [plaintext, expression] of inherited) if (candidates.some((candidate) => candidate === plaintext || plaintext.length >= 4 && candidate.includes(plaintext))) carried.push([plaintext, expression]);
|
|
15352
|
+
return carried;
|
|
15353
|
+
}
|
|
15354
|
+
/**
|
|
15227
15355
|
* Render a parameter VALUE for a debug log line, honoring the definition's
|
|
15228
15356
|
* `NoEcho` flag (issue #1329). `NoEcho: true` is the template author's
|
|
15229
15357
|
* explicit "this value is sensitive" declaration — CloudFormation masks such
|
|
@@ -15669,7 +15797,9 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
15669
15797
|
* @param userParameters User-provided parameter values (e.g., from CLI)
|
|
15670
15798
|
* @returns Record of parameter names to resolved values
|
|
15671
15799
|
*/
|
|
15672
|
-
async resolveParameters(template, userParameters) {
|
|
15800
|
+
async resolveParameters(template, userParameters, options) {
|
|
15801
|
+
const inheritedSecrets = options?.inheritedSecrets;
|
|
15802
|
+
const maskInherited = (text) => inheritedSecrets && inheritedSecrets.size > 0 ? maskSecretsInText(text, inheritedSecrets) : text;
|
|
15673
15803
|
const parameters = {};
|
|
15674
15804
|
const templateParameters = template.Parameters;
|
|
15675
15805
|
if (!templateParameters || typeof templateParameters !== "object") return parameters;
|
|
@@ -15679,8 +15809,9 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
15679
15809
|
if (userParameters && name in userParameters) {
|
|
15680
15810
|
const userValue = userParameters[name];
|
|
15681
15811
|
if (userValue !== void 0) {
|
|
15812
|
+
this.refuseCoercedInheritedSecret(name, paramDef, userValue, inheritedSecrets);
|
|
15682
15813
|
parameters[name] = this.coerceParameterValue(userValue, paramDef.Type);
|
|
15683
|
-
this.logger.debug(`Parameter ${name}: using user-provided value ${stringifyParameterForLog(paramDef, userValue)}`);
|
|
15814
|
+
this.logger.debug(`Parameter ${name}: using user-provided value ${maskInherited(stringifyParameterForLog(paramDef, userValue))}`);
|
|
15684
15815
|
continue;
|
|
15685
15816
|
}
|
|
15686
15817
|
}
|
|
@@ -15695,11 +15826,11 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
15695
15826
|
this.logger.debug(`Parameter ${name}: resolving SSM parameter path ${ssmPath}`);
|
|
15696
15827
|
const resolved = await this.resolveSSMParameter(ssmPath);
|
|
15697
15828
|
parameters[name] = resolved;
|
|
15698
|
-
this.logger.debug(`Parameter ${name}: resolved SSM value ${stringifyParameterForLog(paramDef, resolved)}`);
|
|
15829
|
+
this.logger.debug(`Parameter ${name}: resolved SSM value ${maskInherited(stringifyParameterForLog(paramDef, resolved))}`);
|
|
15699
15830
|
continue;
|
|
15700
15831
|
}
|
|
15701
15832
|
parameters[name] = paramDef.Default;
|
|
15702
|
-
this.logger.debug(`Parameter ${name}: using default value ${stringifyParameterForLog(paramDef, paramDef.Default)}`);
|
|
15833
|
+
this.logger.debug(`Parameter ${name}: using default value ${maskInherited(stringifyParameterForLog(paramDef, paramDef.Default))}`);
|
|
15703
15834
|
continue;
|
|
15704
15835
|
}
|
|
15705
15836
|
throw new Error(`Parameter ${name} is required but no value was provided and no default exists`);
|
|
@@ -15717,12 +15848,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
15717
15848
|
* Coerce parameter value to the correct type based on parameter definition
|
|
15718
15849
|
*/
|
|
15719
15850
|
coerceParameterValue(value, type) {
|
|
15720
|
-
|
|
15721
|
-
case "Number": return Number(value);
|
|
15722
|
-
case "List<Number>": return value.split(",").map((v) => Number(v.trim()));
|
|
15723
|
-
case "CommaDelimitedList": return value.split(",").map((v) => v.trim());
|
|
15724
|
-
default: return value;
|
|
15725
|
-
}
|
|
15851
|
+
return coerceParameterTypedValue(value, type);
|
|
15726
15852
|
}
|
|
15727
15853
|
/**
|
|
15728
15854
|
* Resolve all intrinsic functions in a value
|
|
@@ -15812,6 +15938,101 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
15812
15938
|
return resolved;
|
|
15813
15939
|
}
|
|
15814
15940
|
/**
|
|
15941
|
+
* Copy any {@link ResolverContext.inheritedSecrets} pair whose PLAINTEXT is
|
|
15942
|
+
* present in a just-resolved parameter value into this context's
|
|
15943
|
+
* `recordedSecretValues` (issues #1903 / #2087).
|
|
15944
|
+
*
|
|
15945
|
+
* WHY AT RESOLUTION TIME. This is the whole of the #2087 fix. The parent
|
|
15946
|
+
* hands a nested child already-resolved plaintext, so the child's own
|
|
15947
|
+
* resolution never sees a `{{resolve:` and cannot record the pair itself; the
|
|
15948
|
+
* first cut pre-SEEDED every child resource's map with the parent's bag,
|
|
15949
|
+
* which restored the redaction but destroyed the per-resource scoping
|
|
15950
|
+
* `perResourceSecrets` exists for. `redactSecretsForState` substring-matches
|
|
15951
|
+
* at or above {@link MIN_NEEDLE_LENGTH}, so a child resource that never
|
|
15952
|
+
* referenced the parameter but happens to spell `my-production-bucket` while
|
|
15953
|
+
* the secret is `production` had its state persisted as
|
|
15954
|
+
* `my-{{resolve:...}}-bucket` — which `redactParametersForDiff` does NOT
|
|
15955
|
+
* mirror on the desired side (it rewrites only the PARAMETERS), so every
|
|
15956
|
+
* later deploy saw a change: a perpetual UPDATE, or a perpetual REPLACEMENT
|
|
15957
|
+
* on a create-only property.
|
|
15958
|
+
*
|
|
15959
|
+
* Recording here binds the pair to exactly the resources whose resolution
|
|
15960
|
+
* consumed the parameter — which are exactly the ones that can carry the
|
|
15961
|
+
* plaintext into their persisted state — so the child gets the SAME scoping
|
|
15962
|
+
* RULE the PARENT already has, where `perResourceSecrets` is keyed by logical
|
|
15963
|
+
* id.
|
|
15964
|
+
*
|
|
15965
|
+
* That is PARITY with the parent, not a claim of exactness. Once a pair is in
|
|
15966
|
+
* a resource's bag, `redactSecretsForState` substring-matches every leaf of
|
|
15967
|
+
* THAT resource, so a resource which both `Ref`s the parameter and carries an
|
|
15968
|
+
* unrelated literal spelling the plaintext has the literal rewritten too. The
|
|
15969
|
+
* parent has precisely this residual for any resource that resolves a
|
|
15970
|
+
* `{{resolve:...}}`; what #2087 removed was the much wider version, where
|
|
15971
|
+
* every resource in the child got the bag whether it consumed the parameter
|
|
15972
|
+
* or not.
|
|
15973
|
+
*
|
|
15974
|
+
* The TWO ARMS of the match live in {@link inheritedSecretsCarriedBy}, shared
|
|
15975
|
+
* with the refusal below so the two can never drift apart.
|
|
15976
|
+
*
|
|
15977
|
+
* Covers every consumption shape, because `Fn::Sub` / `Fn::Join` /
|
|
15978
|
+
* `Fn::Select` / `Fn::FindInMap` all re-enter `resolveValue` and reach the
|
|
15979
|
+
* parameter through this same `Ref` branch.
|
|
15980
|
+
*
|
|
15981
|
+
* Substituting is deliberately NOT done here — the resolved value is what
|
|
15982
|
+
* reaches AWS, and an `Fn::Equals` over a parameter must compare the real
|
|
15983
|
+
* value or the condition flips.
|
|
15984
|
+
*/
|
|
15985
|
+
recordInheritedParameterSecrets(value, context) {
|
|
15986
|
+
const inherited = context.inheritedSecrets;
|
|
15987
|
+
const recorded = context.recordedSecretValues;
|
|
15988
|
+
if (!inherited || inherited.size === 0 || !recorded) return;
|
|
15989
|
+
for (const [plaintext, expression] of inheritedSecretsCarriedBy(value, inherited)) recorded.set(plaintext, expression);
|
|
15990
|
+
}
|
|
15991
|
+
/**
|
|
15992
|
+
* Refuse a child parameter whose declared `Type` would COERCE an inherited
|
|
15993
|
+
* secret out of cdkd's string-keyed secret model (issue #1903, review round
|
|
15994
|
+
* 2).
|
|
15995
|
+
*
|
|
15996
|
+
* THE MODEL IS STRING-KEYED END TO END. `RecordedSecretValues` is keyed by
|
|
15997
|
+
* plaintext STRING, {@link recordInheritedParameterSecrets} scans strings and
|
|
15998
|
+
* string array elements, and `redactSecretsForState` rewrites string LEAVES.
|
|
15999
|
+
* `coerceParameterValue` turns a `Number` / `List<Number>` parameter into a JS
|
|
16000
|
+
* number before any of that runs, so the pair was never recorded, the leaf was
|
|
16001
|
+
* never rewritten, and the child's `state.json` persisted the DECRYPTED value
|
|
16002
|
+
* verbatim — the exact disclosure this issue closes for `String` parameters —
|
|
16003
|
+
* with `cdkd diff --recursive` then reporting a change on every run.
|
|
16004
|
+
*
|
|
16005
|
+
* WHY A REFUSAL RATHER THAN RECORDING ON THE PRE-COERCION STRING. Recording
|
|
16006
|
+
* the pair is not enough on its own: the persisted leaf is a NUMBER, so the
|
|
16007
|
+
* redactor would additionally have to rewrite a number leaf into an
|
|
16008
|
+
* expression STRING, matched by `String(n) === plaintext`. That comparison
|
|
16009
|
+
* both UNDER-covers (`"007"` coerces to `7` and stringifies back to `"7"`, so
|
|
16010
|
+
* a zero-padded secret silently stays plaintext) and OVER-covers (a numeric
|
|
16011
|
+
* secret like `8080` whole-value-matches every unrelated port in the bag —
|
|
16012
|
+
* issue #2087's class, on a path where `MIN_NEEDLE_LENGTH` does not apply).
|
|
16013
|
+
* A remedy that can silently under-cover is the wrong one for a disclosure
|
|
16014
|
+
* path, so this refuses and NAMES the parameter instead.
|
|
16015
|
+
*
|
|
16016
|
+
* The blast radius is nil for CDK-authored apps: CDK synthesizes every
|
|
16017
|
+
* nested-stack cross-reference parameter as `Type: String`. A hand-authored
|
|
16018
|
+
* template that really wants a numeric secret can declare the parameter
|
|
16019
|
+
* `String` and keep the value a string, which is what CloudFormation's own
|
|
16020
|
+
* `NoEcho` / dynamic-reference handling assumes anyway.
|
|
16021
|
+
*
|
|
16022
|
+
* SCOPED TO THE INHERITED BAG, which is non-empty only on a nested-stack
|
|
16023
|
+
* child engine, and only for a value that actually carries a pair the parent
|
|
16024
|
+
* PROVED secret. An ordinary `Type: Number` parameter is untouched.
|
|
16025
|
+
*
|
|
16026
|
+
* The message never quotes the value.
|
|
16027
|
+
*/
|
|
16028
|
+
refuseCoercedInheritedSecret(name, paramDef, userValue, inherited) {
|
|
16029
|
+
if (!inherited || inherited.size === 0) return;
|
|
16030
|
+
const carriedBefore = inheritedSecretsCarriedBy(userValue, inherited).length;
|
|
16031
|
+
if (carriedBefore === 0) return;
|
|
16032
|
+
if (inheritedSecretsCarriedBy(this.coerceParameterValue(userValue, paramDef.Type), inherited).length >= carriedBefore) return;
|
|
16033
|
+
throw markNonRetryable(new IntrinsicResolutionRefusalError(`Nested-stack parameter '${name}' is declared 'Type: ${paramDef.Type}', but the parent stack resolved a SECRET dynamic reference into it. cdkd keeps a resolved secret out of persisted state by rewriting STRING leaves back to their {{resolve:...}} expression; coercing this value to '${paramDef.Type}' destroys the plaintext cdkd would have matched on, so the DECRYPTED secret would be left in the child stack's state.json with nothing to redact it back to. Declare '${name}' as 'Type: String' in the nested stack's template (CDK does this by default for cross-stack references), or stop passing a secret reference into it.`, void 0, "NESTED_STACK_SECRET_PARAMETER_TYPE"));
|
|
16034
|
+
}
|
|
16035
|
+
/**
|
|
15815
16036
|
* Resolve Ref intrinsic function
|
|
15816
16037
|
*
|
|
15817
16038
|
* Ref can reference:
|
|
@@ -15829,7 +16050,8 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
15829
16050
|
if (context.parameters && logicalId in context.parameters) {
|
|
15830
16051
|
const value = context.parameters[logicalId];
|
|
15831
16052
|
const paramDef = context.template.Parameters?.[logicalId];
|
|
15832
|
-
this.logger.debug(`Resolved Ref to parameter: ${logicalId} -> ${stringifyParameterForLog(paramDef, value)}`);
|
|
16053
|
+
this.logger.debug(`Resolved Ref to parameter: ${logicalId} -> ${this.maskSecretsForLog(stringifyParameterForLog(paramDef, value), context)}`);
|
|
16054
|
+
this.recordInheritedParameterSecrets(value, context);
|
|
15833
16055
|
return value;
|
|
15834
16056
|
}
|
|
15835
16057
|
const pseudoValue = await this.resolvePseudoParameter(logicalId, context);
|
|
@@ -15934,6 +16156,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
15934
16156
|
return nameServers;
|
|
15935
16157
|
}
|
|
15936
16158
|
this.logger.debug(`Resolved Fn::GetAtt from attributes: ${logicalId}.${attributeName} -> ${stringifyAttributeForLog(attributeName, flatValue)}`);
|
|
16159
|
+
if (resource.resourceType === NESTED_STACK_RESOURCE_TYPE && attributeName.startsWith(NESTED_STACK_OUTPUT_ATTRIBUTE_PREFIX) && carriesDynamicReference(flatValue)) return await this.reresolveCrossStackValue(flatValue, nestedStackChildRegionFromLocalArn(resource.physicalId), context, `nested stack ${logicalId} ${attributeName}`, crossStackSourceKey({ "Fn::GetAtt": getAtt }));
|
|
15937
16160
|
return flatValue;
|
|
15938
16161
|
}
|
|
15939
16162
|
if (attributeName.includes(".")) {
|
|
@@ -17453,8 +17676,12 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
17453
17676
|
* recorded no secrets.
|
|
17454
17677
|
*/
|
|
17455
17678
|
maskSecretsForLog(text, context) {
|
|
17679
|
+
let masked = text;
|
|
17680
|
+
const inherited = context?.inheritedSecrets;
|
|
17681
|
+
if (inherited && inherited.size > 0) masked = maskSecretsInText(masked, inherited);
|
|
17456
17682
|
const secrets = context?.recordedSecretValues;
|
|
17457
|
-
|
|
17683
|
+
if (secrets && secrets.size > 0) masked = maskSecretsInText(masked, secrets);
|
|
17684
|
+
return masked;
|
|
17458
17685
|
}
|
|
17459
17686
|
async resolveDynamicReferences(value, context) {
|
|
17460
17687
|
const pattern = /\{\{resolve:([^}]+)\}\}/g;
|
|
@@ -18617,7 +18844,7 @@ var CloudControlProvider = class {
|
|
|
18617
18844
|
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);
|
|
18618
18845
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
18619
18846
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
18620
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
18847
|
+
const { ASGProvider } = await import("./asg-provider-B9V61Fbq.js").then((n) => n.n);
|
|
18621
18848
|
return await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
|
|
18622
18849
|
}
|
|
18623
18850
|
const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
|
|
@@ -19345,8 +19572,10 @@ var CloudControlProvider = class {
|
|
|
19345
19572
|
* at command start. It is deliberately NOT
|
|
19346
19573
|
* `process.listenerCount('SIGINT') > 0`, which was the first cut and is
|
|
19347
19574
|
* defeated by the very case it was meant to catch: `cdkd drift` runs
|
|
19348
|
-
* `provider.update` at concurrency 4, and a concurrent
|
|
19349
|
-
*
|
|
19575
|
+
* `provider.update` at concurrency 4, and a concurrent CustomResource /
|
|
19576
|
+
* CloudFront / ACM wait (`grep -rn "process.on('SIGINT'"
|
|
19577
|
+
* src/provisioning/providers/` for the closed set — Route53's provider
|
|
19578
|
+
* registers none) installs a TRANSIENT SIGINT listener of its own — so an ELBv2
|
|
19350
19579
|
* update starting inside that window saw a non-zero count, armed, and then
|
|
19351
19580
|
* kept the listener for the rest of the command after the transient one was
|
|
19352
19581
|
* removed. A count answers "is anyone listening right now"; the question is
|
|
@@ -19379,9 +19608,29 @@ var CloudControlProvider = class {
|
|
|
19379
19608
|
* graceful owner, that watch must NOT treat its presence as "someone else
|
|
19380
19609
|
* will handle this" (it subtracts `interruptWatchListenerCount()` for
|
|
19381
19610
|
* exactly that reason), or the swallow above returns one window inward.
|
|
19382
|
-
*
|
|
19383
|
-
*
|
|
19384
|
-
*
|
|
19611
|
+
*
|
|
19612
|
+
* **That leaves this force-quit with NO population among today's commands,
|
|
19613
|
+
* and it is worth being exact about why**, because the obvious guess — the
|
|
19614
|
+
* commands that register no handler at all, `import` / `export` / `scrub` /
|
|
19615
|
+
* `orphan` / `drift` / `state refresh-observed` — is wrong in a way an
|
|
19616
|
+
* earlier version of this note shipped. Property 3 gates ARMING on the
|
|
19617
|
+
* command scope, and those commands never open one (only
|
|
19618
|
+
* `forwardSigtermToSigint()` does, and only `deploy` / `destroy` /
|
|
19619
|
+
* `rollback` / `state destroy` call it), so this handler is never installed
|
|
19620
|
+
* during them and cannot force-quit there. See the scope note below, which
|
|
19621
|
+
* says the same thing from the lock's side and is what this contradicted.
|
|
19622
|
+
* Among the four commands that DO open a scope, each holds a graceful
|
|
19623
|
+
* SIGINT handler across the whole of it — `deploy.ts`'s top-level handler,
|
|
19624
|
+
* `rollback.ts`'s (removed adjacent to, and synchronously with, its
|
|
19625
|
+
* `unforwardSigterm()`), and now `watchCommandInterrupt` in both destroy
|
|
19626
|
+
* commands — so `others.length` is never 0 while armed.
|
|
19627
|
+
*
|
|
19628
|
+
* The branch is therefore a STRUCTURAL guarantee rather than a live code
|
|
19629
|
+
* path: it becomes reachable again the moment a command opens the interrupt
|
|
19630
|
+
* scope without holding a SIGINT handler across it, or an existing one tears
|
|
19631
|
+
* its handler down before closing the scope. That is a one-line mistake in a
|
|
19632
|
+
* command file, and the failure it produces — a swallowed Ctrl-C during a
|
|
19633
|
+
* provider wait — is silent, which is exactly why the branch stays.
|
|
19385
19634
|
*
|
|
19386
19635
|
* So when no other listener remains, the handler restores exactly what Node
|
|
19387
19636
|
* would have done with no listener at all. That is deliberately not a second
|
|
@@ -19458,8 +19707,12 @@ let sigintLatched = false;
|
|
|
19458
19707
|
* default terminate, and a swallowed Ctrl-C in a multi-stack destroy really is
|
|
19459
19708
|
* a blocker. `cdkd drift --revert` is the live instance of the first. The second
|
|
19460
19709
|
* was `cdkd destroy`, until issue #2117 gave both destroy commands a
|
|
19461
|
-
* command-scoped handler of their own
|
|
19462
|
-
*
|
|
19710
|
+
* command-scoped handler of their own — which leaves the force-quit with no
|
|
19711
|
+
* live population among today's commands at all. Property 4 works through why,
|
|
19712
|
+
* including why the commands that register no handler are NOT it: they never
|
|
19713
|
+
* open the interrupt scope, so this handler never arms for them. The seam is
|
|
19714
|
+
* what keeps the branch testable now that only a future command shape reaches
|
|
19715
|
+
* it.
|
|
19463
19716
|
*
|
|
19464
19717
|
* `commandOwnsInterrupts` exists because a provider suite never runs a COMMAND,
|
|
19465
19718
|
* so every interrupt test would otherwise exercise the UNARMED path while
|
|
@@ -24583,6 +24836,67 @@ function collectInlinePolicyNamesManagedBySiblings(targetPhysicalId, context, at
|
|
|
24583
24836
|
return result;
|
|
24584
24837
|
}
|
|
24585
24838
|
|
|
24839
|
+
//#endregion
|
|
24840
|
+
//#region src/deployment/resource-secrets-scope.ts
|
|
24841
|
+
/**
|
|
24842
|
+
* The secrets THIS process substituted into the property bag of the resource
|
|
24843
|
+
* currently being provisioned, scoped to that provider call's async chain
|
|
24844
|
+
* (issue [#1903](https://github.com/go-to-k/cdkd/issues/1903)).
|
|
24845
|
+
*
|
|
24846
|
+
* WHY AN ASYNC-LOCAL STORE RATHER THAN A FIELD ON `CreateContext`. Exactly ONE
|
|
24847
|
+
* provider needs the pairs — `NestedStackProvider`, which must SEED them into
|
|
24848
|
+
* the child `DeployEngine` it builds (see
|
|
24849
|
+
* `DeployEngineOptions.inheritedSecrets`) — and a `RecordedSecretValues` is
|
|
24850
|
+
* keyed by PLAINTEXT. `.claude/rules/providers.md` already records the rule
|
|
24851
|
+
* this follows: the reason `SecretMaskingContext` carries a masking FUNCTION
|
|
24852
|
+
* and not the bag is that putting the bag on the shared context makes every one
|
|
24853
|
+
* of the ~130 registered providers a place a `[...secrets.keys()]` can leak
|
|
24854
|
+
* from. A function cannot substitute here — seeding needs the pairs, not the
|
|
24855
|
+
* ability to mask — so the bag is handed through a channel only this one
|
|
24856
|
+
* provider reads, instead of widening the type every provider sees.
|
|
24857
|
+
*
|
|
24858
|
+
* It is also the idiom this particular provider already lives in:
|
|
24859
|
+
* `NestedStackProvider` reads its whole world out of
|
|
24860
|
+
* `getCurrentNestedStackContext()`, another `AsyncLocalStorage`.
|
|
24861
|
+
*
|
|
24862
|
+
* WHY ITS OWN LEAF MODULE rather than living in `deploy-engine.ts`, where it
|
|
24863
|
+
* started. Both BINDERS need it — the deploy engine and
|
|
24864
|
+
* `rollback-executor.ts`, whose replay arms re-resolve the journal's
|
|
24865
|
+
* `{{resolve:...}}` back to plaintext and drive the very same providers (issue
|
|
24866
|
+
* [#2086](https://github.com/go-to-k/cdkd/issues/2086)) — and
|
|
24867
|
+
* `deploy-engine.ts` already imports `rollback-executor.ts`, so keeping the
|
|
24868
|
+
* store there would have made the two modules a cycle. A leaf that imports one
|
|
24869
|
+
* TYPE cannot participate in one.
|
|
24870
|
+
*
|
|
24871
|
+
* SCOPE. {@link withCurrentResourceSecrets} wraps the provider CREATE / UPDATE
|
|
24872
|
+
* call itself, so the store is bound per resource and per retry attempt, and
|
|
24873
|
+
* two resources provisioned concurrently under `--concurrency` cannot see each
|
|
24874
|
+
* other's bag. Absent (every caller that binds nothing — `cdkd drift --revert`,
|
|
24875
|
+
* the import path, tests) reads as `undefined`, which the provider treats as
|
|
24876
|
+
* "no secrets to inherit" — the pre-#1903 behaviour.
|
|
24877
|
+
*/
|
|
24878
|
+
const currentResourceSecretsStore = new AsyncLocalStorage();
|
|
24879
|
+
/**
|
|
24880
|
+
* Run `fn` with `secrets` visible to {@link getCurrentResourceSecrets}. Used by
|
|
24881
|
+
* the deploy engine and the rollback executor around a provider CREATE / UPDATE
|
|
24882
|
+
* call; see the store's own doc for why the bag travels this way rather than on
|
|
24883
|
+
* `CreateContext`.
|
|
24884
|
+
*/
|
|
24885
|
+
function withCurrentResourceSecrets(secrets, fn) {
|
|
24886
|
+
return currentResourceSecretsStore.run(secrets, fn);
|
|
24887
|
+
}
|
|
24888
|
+
/**
|
|
24889
|
+
* The bag {@link withCurrentResourceSecrets} bound for the provider call
|
|
24890
|
+
* currently in flight, or `undefined` when no binder is on the stack.
|
|
24891
|
+
*
|
|
24892
|
+
* Read by `NestedStackProvider` alone. A provider reading this MUST NOT
|
|
24893
|
+
* enumerate or log its KEYS — they are secret plaintext; the only sanctioned
|
|
24894
|
+
* use is handing the map on as a redaction seed.
|
|
24895
|
+
*/
|
|
24896
|
+
function getCurrentResourceSecrets() {
|
|
24897
|
+
return currentResourceSecretsStore.getStore();
|
|
24898
|
+
}
|
|
24899
|
+
|
|
24586
24900
|
//#endregion
|
|
24587
24901
|
//#region src/deployment/outputs-export-alias.ts
|
|
24588
24902
|
/**
|
|
@@ -26482,6 +26796,29 @@ async function replayRollback(operations, stateResources, stackName, ctx, option
|
|
|
26482
26796
|
* backoff schedule per op — ~47s on the generic grid, or ~64s if the op
|
|
26483
26797
|
* hits a name cooldown, which rides its own longer grid since issue #2116.
|
|
26484
26798
|
*
|
|
26799
|
+
* A FOURTH thing since issue
|
|
26800
|
+
* [#2086](https://github.com/go-to-k/cdkd/issues/2086): the call is bound in
|
|
26801
|
+
* {@link withCurrentResourceSecrets}, the async-local channel
|
|
26802
|
+
* `NestedStackProvider` reads to seed a nested CHILD engine with the pairs the
|
|
26803
|
+
* parent already resolved (issue #1903). `resolveReplayProps` has just
|
|
26804
|
+
* re-resolved the journal's `{{resolve:...}}` expressions back to PLAINTEXT
|
|
26805
|
+
* into `secrets`, so the bag in hand here is exactly the one the deploy engine
|
|
26806
|
+
* would have bound — and without the binding a rollback that reverts a
|
|
26807
|
+
* nested-stack row calls `NestedStackProvider.update`, the child engine seeds
|
|
26808
|
+
* nothing, and the child's `state.json` is rewritten with the DECRYPTED secret.
|
|
26809
|
+
* A recovery path that restores the pre-fix behaviour re-opens the very
|
|
26810
|
+
* disclosure the fix closes, so "absent reads as undefined, the pre-#1903
|
|
26811
|
+
* baseline" is not an acceptable answer HERE, however it reads elsewhere.
|
|
26812
|
+
*
|
|
26813
|
+
* `NestedStackProvider` is reachable on this path by construction, not in
|
|
26814
|
+
* theory: it is one of the two `disableOuterRetry` providers named above that
|
|
26815
|
+
* also implement `update()`, and `cdkd deploy`'s in-process auto-rollback runs
|
|
26816
|
+
* inside a DEPLOY-mode `withNestedStackContext` (`deploy.ts` passes
|
|
26817
|
+
* `nestedTemplates` / `dagBuilder` / `diffCalculator`). Standalone `cdkd
|
|
26818
|
+
* rollback` is NOT affected — `rollback.ts` builds a destroy-mode context with
|
|
26819
|
+
* none of those three fields, so `requireDeployContext` throws loudly before
|
|
26820
|
+
* any child engine is built.
|
|
26821
|
+
*
|
|
26485
26822
|
* Returns the provider's result so the caller can honour
|
|
26486
26823
|
* `effectiveProperties` (issue #1644) — both revert arms used to write the
|
|
26487
26824
|
* previous state record back verbatim, dropping a narrowing the provider had
|
|
@@ -26714,8 +27051,8 @@ function redactRollbackRecord(record, secrets, journaledProps) {
|
|
|
26714
27051
|
}, secrets);
|
|
26715
27052
|
}
|
|
26716
27053
|
async function updateWithRollbackRetry(provider, args, logicalId, logger, isInterrupted, secrets) {
|
|
26717
|
-
if (provider.disableOuterRetry) return await provider.update(...args);
|
|
26718
|
-
return await withRetry(() => provider.update(...args), logicalId, {
|
|
27054
|
+
if (provider.disableOuterRetry) return await withCurrentResourceSecrets(secrets, () => provider.update(...args));
|
|
27055
|
+
return await withRetry(() => withCurrentResourceSecrets(secrets, () => provider.update(...args)), logicalId, {
|
|
26719
27056
|
logger: maskingRetryLogger(logger, secrets),
|
|
26720
27057
|
...isInterrupted && {
|
|
26721
27058
|
isInterrupted,
|
|
@@ -26793,6 +27130,16 @@ async function updateWithRollbackRetry(provider, args, logicalId, logger, isInte
|
|
|
26793
27130
|
* now earns its place by ALSO covering the late name release that the inner
|
|
26794
27131
|
* default classifier rejects. The two compound — measured at 640s of total
|
|
26795
27132
|
* sleep on a cooldown, inside the 30-minute per-resource deadline.
|
|
27133
|
+
*
|
|
27134
|
+
* ## The secrets scope, on both call sites (issue #2086)
|
|
27135
|
+
*
|
|
27136
|
+
* Each caller's `create` thunk binds {@link withCurrentResourceSecrets} around
|
|
27137
|
+
* `createProvider.create(...)`, for the same reason
|
|
27138
|
+
* {@link updateWithRollbackRetry} does around `update(...)`: a
|
|
27139
|
+
* reverse-replacement replay of an `AWS::CloudFormation::Stack` row re-CREATES
|
|
27140
|
+
* the child, and an unbound store makes the child engine persist the parent's
|
|
27141
|
+
* plaintext. It sits INSIDE the thunk, so it is re-established on every
|
|
27142
|
+
* attempt of both loops rather than once around them.
|
|
26796
27143
|
*/
|
|
26797
27144
|
async function createWithRollbackRetry(provider, create, logicalId, logger, isInterrupted, secrets, outer) {
|
|
26798
27145
|
if (provider.disableOuterRetry) return await create();
|
|
@@ -27013,7 +27360,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
27013
27360
|
let deletedNewFirst = false;
|
|
27014
27361
|
let createResult;
|
|
27015
27362
|
try {
|
|
27016
|
-
createResult = await createWithRollbackRetry(createProvider, () => createProvider.create(op.logicalId, op.resourceType, { ...resolvedPrevProps }, replayingStateCreateContext(secrets)), op.logicalId, logger, isInterrupted, secrets, {
|
|
27363
|
+
createResult = await createWithRollbackRetry(createProvider, () => withCurrentResourceSecrets(secrets, () => createProvider.create(op.logicalId, op.resourceType, { ...resolvedPrevProps }, replayingStateCreateContext(secrets))), op.logicalId, logger, isInterrupted, secrets, {
|
|
27017
27364
|
isRetryable: isNameCooldownError,
|
|
27018
27365
|
interruptedMessage: "Rollback interrupted while waiting out the name cooldown"
|
|
27019
27366
|
});
|
|
@@ -27031,7 +27378,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
27031
27378
|
delete stateResources[op.logicalId];
|
|
27032
27379
|
await afterOp?.(op.logicalId);
|
|
27033
27380
|
try {
|
|
27034
|
-
createResult = await createWithRollbackRetry(createProvider, () => createProvider.create(op.logicalId, op.resourceType, { ...resolvedPrevProps }, replayingStateCreateContext(secrets)), op.logicalId, logger, isInterrupted, secrets, {
|
|
27381
|
+
createResult = await createWithRollbackRetry(createProvider, () => withCurrentResourceSecrets(secrets, () => createProvider.create(op.logicalId, op.resourceType, { ...resolvedPrevProps }, replayingStateCreateContext(secrets))), op.logicalId, logger, isInterrupted, secrets, {
|
|
27035
27382
|
isRetryable: isRecreateRetryableError,
|
|
27036
27383
|
interruptedMessage: "Rollback interrupted while waiting for the old name to release"
|
|
27037
27384
|
});
|
|
@@ -28197,10 +28544,37 @@ var DeployEngine = class {
|
|
|
28197
28544
|
...this.exportIndexStore && { exportIndex: this.exportIndexStore },
|
|
28198
28545
|
recordedImports: this.recordedImports,
|
|
28199
28546
|
recordedOutputReads: this.recordedOutputReads,
|
|
28547
|
+
...this.options.inheritedSecrets && this.options.inheritedSecrets.size > 0 && { inheritedSecrets: this.options.inheritedSecrets },
|
|
28200
28548
|
recordedSecretValues: /* @__PURE__ */ new Map()
|
|
28201
28549
|
};
|
|
28202
28550
|
}
|
|
28203
28551
|
/**
|
|
28552
|
+
* The parameter bag the DIFF resolver context binds, with any inherited
|
|
28553
|
+
* secret plaintext rewritten back to its `{{resolve:...}}` expression (issue
|
|
28554
|
+
* #1903).
|
|
28555
|
+
*
|
|
28556
|
+
* The provisioning pass must keep the REAL values — that is what actually
|
|
28557
|
+
* reaches AWS — but the child's persisted state holds the expression, so the
|
|
28558
|
+
* comparison side has to hold it too or every deploy of a secret-bearing
|
|
28559
|
+
* nested stack reports a spurious UPDATE and re-issues an AWS call that
|
|
28560
|
+
* changes nothing. This is the child-stack twin of the
|
|
28561
|
+
* `skipDynamicReferences` flag the parent's own diff sets: same goal
|
|
28562
|
+
* (expression-vs-expression), reached differently because the child's
|
|
28563
|
+
* template carries `{Ref: Param}` rather than a `{{resolve:` string, so
|
|
28564
|
+
* there is no reference for that flag to decline to resolve.
|
|
28565
|
+
*
|
|
28566
|
+
* `redactSecretsForState` rather than a `Map.get` lookup so an EMBEDDED
|
|
28567
|
+
* secret — a parameter whose value is `postgres://u:<secret>@host` because
|
|
28568
|
+
* the parent built it with `Fn::Sub` — is rewritten the same way the
|
|
28569
|
+
* state-save choke point rewrites it, keeping the two sides byte-identical.
|
|
28570
|
+
* Identity-returns when nothing was inherited.
|
|
28571
|
+
*/
|
|
28572
|
+
redactParametersForDiff(parameterValues) {
|
|
28573
|
+
const inherited = this.options.inheritedSecrets;
|
|
28574
|
+
if (!inherited || inherited.size === 0) return parameterValues;
|
|
28575
|
+
return redactSecretsForState(parameterValues, inherited);
|
|
28576
|
+
}
|
|
28577
|
+
/**
|
|
28204
28578
|
* Redact resolved secret plaintext out of a bag about to be PERSISTED to
|
|
28205
28579
|
* state, replacing each secret value with the unresolved `{{resolve:...}}`
|
|
28206
28580
|
* expression it came from (GHSA fix; see `secret-redaction.ts`). No-op when
|
|
@@ -28520,7 +28894,7 @@ var DeployEngine = class {
|
|
|
28520
28894
|
} catch {}
|
|
28521
28895
|
this.kickOffAutoRefreshObservedProperties(currentState.resources);
|
|
28522
28896
|
this.logger.debug(`Template has ${Object.keys(template.Resources || {}).length} resources`);
|
|
28523
|
-
const parameterValues = await this.resolver.resolveParameters(template, this.options.parameters);
|
|
28897
|
+
const parameterValues = await this.resolver.resolveParameters(template, this.options.parameters, { ...this.options.inheritedSecrets && this.options.inheritedSecrets.size > 0 && { inheritedSecrets: this.options.inheritedSecrets } });
|
|
28524
28898
|
this.logger.debug(`Resolved ${Object.keys(parameterValues).length} parameters: ${Object.keys(parameterValues).join(", ")}`);
|
|
28525
28899
|
const context = this.buildResolverContext({
|
|
28526
28900
|
template,
|
|
@@ -28547,7 +28921,7 @@ var DeployEngine = class {
|
|
|
28547
28921
|
const diffResolverContext = this.buildResolverContext({
|
|
28548
28922
|
template: effectiveTemplate,
|
|
28549
28923
|
resources: currentState.resources,
|
|
28550
|
-
parameters: parameterValues,
|
|
28924
|
+
parameters: this.redactParametersForDiff(parameterValues),
|
|
28551
28925
|
conditions
|
|
28552
28926
|
}, stackName);
|
|
28553
28927
|
diffResolverContext.bestEffort = true;
|
|
@@ -29278,7 +29652,7 @@ var DeployEngine = class {
|
|
|
29278
29652
|
this.logger.info(` ${green("✓")} Old resource deleted`);
|
|
29279
29653
|
this.logger.info(` Re-creating ${logicalId}...`);
|
|
29280
29654
|
try {
|
|
29281
|
-
return await withRetry(() => this.withRetry(() => replaceProvider.create(logicalId, resourceType, replaceProps, createContext), logicalId, void 0, void 0, replaceProvider), logicalId, {
|
|
29655
|
+
return await withRetry(() => this.withRetry(() => withCurrentResourceSecrets(secrets, () => replaceProvider.create(logicalId, resourceType, replaceProps, createContext)), logicalId, void 0, void 0, replaceProvider), logicalId, {
|
|
29282
29656
|
maxRetries: 8,
|
|
29283
29657
|
initialDelayMs: 2e3,
|
|
29284
29658
|
maxDelayMs: 1e4,
|
|
@@ -29322,7 +29696,7 @@ var DeployEngine = class {
|
|
|
29322
29696
|
});
|
|
29323
29697
|
const createProvider = createDecision.provider;
|
|
29324
29698
|
const createProps = createDecision.provisionedBy === "cc-api" ? this.preparePropertiesForCcApi(resourceType, resolvedProps, logicalId) : resolvedProps;
|
|
29325
|
-
const result = await this.withRetry(() => createProvider.create(logicalId, resourceType, createProps, { maskSecrets: createSecretMasker(createSecrets) }), logicalId, void 0, void 0, createProvider);
|
|
29699
|
+
const result = await this.withRetry(() => withCurrentResourceSecrets(createSecrets, () => createProvider.create(logicalId, resourceType, createProps, { maskSecrets: createSecretMasker(createSecrets) })), logicalId, void 0, void 0, createProvider);
|
|
29326
29700
|
const dependencies = this.extractAllDependencies(template, logicalId);
|
|
29327
29701
|
const templateAttrs = this.extractTemplateAttributes(template, logicalId);
|
|
29328
29702
|
stateResources[logicalId] = {
|
|
@@ -29433,7 +29807,7 @@ var DeployEngine = class {
|
|
|
29433
29807
|
this.logger.info(` ${green("✓")} Old resource deleted`);
|
|
29434
29808
|
}
|
|
29435
29809
|
this.logger.info(` Creating new ${logicalId}...`);
|
|
29436
|
-
createResult = await withRetry(() => this.withRetry(() => replaceProvider.create(logicalId, resourceType, replaceProps, { maskSecrets: createSecretMasker(updateSecrets) }), logicalId, void 0, void 0, replaceProvider), logicalId, {
|
|
29810
|
+
createResult = await withRetry(() => this.withRetry(() => withCurrentResourceSecrets(updateSecrets, () => replaceProvider.create(logicalId, resourceType, replaceProps, { maskSecrets: createSecretMasker(updateSecrets) })), logicalId, void 0, void 0, replaceProvider), logicalId, {
|
|
29437
29811
|
maxRetries: 8,
|
|
29438
29812
|
initialDelayMs: 2e3,
|
|
29439
29813
|
maxDelayMs: 1e4,
|
|
@@ -29447,7 +29821,7 @@ var DeployEngine = class {
|
|
|
29447
29821
|
this.logger.info(` Creating new ${logicalId}...`);
|
|
29448
29822
|
let deletedOldFirst = false;
|
|
29449
29823
|
try {
|
|
29450
|
-
createResult = await this.withRetry(() => replaceProvider.create(logicalId, resourceType, replaceProps, { maskSecrets: createSecretMasker(updateSecrets) }), logicalId, void 0, void 0, replaceProvider);
|
|
29824
|
+
createResult = await this.withRetry(() => withCurrentResourceSecrets(updateSecrets, () => replaceProvider.create(logicalId, resourceType, replaceProps, { maskSecrets: createSecretMasker(updateSecrets) })), logicalId, void 0, void 0, replaceProvider);
|
|
29451
29825
|
} catch (createError) {
|
|
29452
29826
|
const createMsg = createError instanceof Error ? createError.message : String(createError);
|
|
29453
29827
|
if (!isNameCollisionError(createMsg)) throw createError;
|
|
@@ -29524,7 +29898,7 @@ var DeployEngine = class {
|
|
|
29524
29898
|
let result;
|
|
29525
29899
|
let resultProvisionedBy = updateDecision.provisionedBy;
|
|
29526
29900
|
try {
|
|
29527
|
-
result = await this.withRetry(() => updateProvider.update(logicalId, currentResource.physicalId, resourceType, updateProps, currentProps, { maskSecrets: createSecretMasker(updateSecrets) }), logicalId, void 0, void 0, updateProvider);
|
|
29901
|
+
result = await this.withRetry(() => withCurrentResourceSecrets(updateSecrets, () => updateProvider.update(logicalId, currentResource.physicalId, resourceType, updateProps, currentProps, { maskSecrets: createSecretMasker(updateSecrets) })), logicalId, void 0, void 0, updateProvider);
|
|
29528
29902
|
} catch (updateError) {
|
|
29529
29903
|
const msg = updateError instanceof Error ? updateError.message : String(updateError);
|
|
29530
29904
|
const ccUnsupported = msg.includes("UnsupportedActionException") || msg.includes("does not support UPDATE");
|
|
@@ -29556,7 +29930,7 @@ var DeployEngine = class {
|
|
|
29556
29930
|
});
|
|
29557
29931
|
const replProvider = replDecision.provider;
|
|
29558
29932
|
const replProps = replDecision.provisionedBy === "cc-api" ? this.preparePropertiesForCcApi(resourceType, resolvedProps, logicalId) : resolvedProps;
|
|
29559
|
-
const createResult = await this.withRetry(() => replProvider.create(logicalId, resourceType, replProps, { maskSecrets: createSecretMasker(updateSecrets) }), logicalId, void 0, void 0, replProvider);
|
|
29933
|
+
const createResult = await this.withRetry(() => withCurrentResourceSecrets(updateSecrets, () => replProvider.create(logicalId, resourceType, replProps, { maskSecrets: createSecretMasker(updateSecrets) })), logicalId, void 0, void 0, replProvider);
|
|
29560
29934
|
const replacementResult = {
|
|
29561
29935
|
physicalId: createResult.physicalId,
|
|
29562
29936
|
wasReplaced: true,
|
|
@@ -30025,5 +30399,5 @@ var DeployEngine = class {
|
|
|
30025
30399
|
};
|
|
30026
30400
|
|
|
30027
30401
|
//#endregion
|
|
30028
|
-
export {
|
|
30029
|
-
//# sourceMappingURL=deploy-engine-
|
|
30402
|
+
export { beginCommandInterruptScope as $, PARTITION_TABLE as $n, S3StateBackend as $t, renderStatefulReason as A, runDockerForeground as An, SynthesisError as Ar, dynamicReferenceTokens as At, exportAliasCollisionScrubWarning as B, resolveCaptureObservedState as Bn, s3BucketDomainName as Bt, isFinalSnapshotError as C, validateContainerRepoName as Cn, PartialFailureError as Cr, requireConfigArray as Ct, extractDeploymentEventError as D, formatDockerLoginError as Dn, StackHasActiveImportsError as Dr, STATE_SOURCED_READBACK_RULES as Dt, makeCanonicalizePropertiesFn as E, dockerSpawnEnvWithSensitive as En, ResourceUpdateNotSupportedError as Er, STATE_SOURCED_CROSS_GENERATION_RULES as Et, green as F, synthesisStatusMessage as Fn, isMarkedNonRetryable as Fr, redactSecretsForState as Ft, IAMRoleProvider as G, stateBucketExistenceConfirmed as Gn, DiffCalculator as Gt, secretBearingStateKeyWarning as H, resolveStateBucketWithDefault as Hn, s3BucketRegionalDomainName as Ht, red as I, getDefaultStateBucketName as In, isRetryableTransientError as Ir, scrubResourceRecord as It, ProviderRegistry as J, CFN_TEMPLATE_URL_LIMIT as Jn, withRetry as Jt, collectInlinePolicyNamesManagedBySiblings as K, warnDeprecatedNoPrefixCliFlag as Kn, INTRINSIC_KEYS as Kt, yellow as L, getLegacyStateBucketName as Ln, isThrottlingError as Lr, classifyReplaySecretRegion as Lt, bold as M, AssetManifestLoader as Mn, isCdkdError as Mr, isSingleDynamicReferenceToken as Mt, cyan as N, getDockerImageBySourceHash as Nn, normalizeAwsError as Nr, maskSecretsInError as Nt, MULTI_REGION_RECREATE_BLOCKED_TYPES as O, getDockerCmd as On, StackTerminationProtectionError as Or, TEMPLATE_SOURCED_RULES as Ot, gray as P, Synthesizer as Pn, withErrorHandling as Pr, maskSecretsInText as Pt, DEFAULT_STATE_PREFIX as Q, expectedOwnerParam as Qn, displaySafe as Qt, collectDeclaredOutputNames as R, resolveApp as Rn, markNonRetryable as Rr, producerRegionsFromState as Rt, createPreDeleteFinalSnapshot as S, validateAssetBucketName as Sn, NestedStackChildDirectDestroyError as Sr, replayWarn as St, unsupportedFinalSnapshotError as T, buildDockerImage as Tn, ResourceTimeoutError as Tr, requireConfigString as Tt, stateKeySecretExposure as U, resolveStateBucketWithDefaultAndSource as Un, s3BucketWebsiteUrl as Ut, isExportAliasCollision as V, resolveSkipPrefix as Vn, s3BucketDualStackDomainName as Vt, getCurrentResourceSecrets as W, resolveUseCdkBootstrapAssets as Wn, applyRoleArnIfSet as Wt, findSilentDropProperties as X, findLargeInlineResources as Xn, TemplateParser as Xt, findActionableSilentDrops as Y, MIGRATE_TMP_PREFIX as Yn, DagBuilder as Yt, CUSTOM_RESOURCE_RESPONSE_PREFIX as Z, uploadCfnTemplate as Zn, LockManager as Zt, computeImplicitDeleteEdges as _, ensureAssetStorage as _n, LocalInvokeBuildError as _r, assertRegionMatch as _t, DeploymentEventsStore as a, AssetPublisher as an, resolveBucketRegion as ar, slowCcOperationTimeoutMs as at, buildFinalSnapshotIdentifier as b, parseBootstrapMarker as bn, LockError as br, configStringRefusal as bt, replayFailedOperations as c, buildAssetRedirectMap as cn, resetAwsClients as cr, IntrinsicFunctionResolver as ct, updatePartialReason as d, rewriteTemplateAssetReferences as dn, CdkdError as dr, getAccountInfo as dt, rebuildClientForBucketRegion as en, canonicalizeRegion as er, endCommandInterruptScope as et, UNSPECIFIED_SKIP_REASON as f, escapeRegExp$1 as fn, ConfigError as fr, parameterTypeMayLoseSecretIdentity as ft, IMPLICIT_DELETE_DEPENDENCIES as g, assertAssetBucketRegion as gn, DynamicReferenceRegionAmbiguousError as gr, resolveExplicitPhysicalId as gt, maskingRetryLogger as h, BOOTSTRAP_MARKER_PREFIX as hn, DeployCancelledError as hr, normalizeAwsTagsToCfn as ht, DeploymentEventsReader as i, shouldRetainResource as in, clearBucketRegionCache as ir, CloudControlProvider as it, formatResourceLine as j, runDockerStreaming as jn, formatError as jr, errorCauseChain as jt, isStatefulRecreateTargetSync as k, partitionSensitiveEnv as kn, StateError as kr, createSecretMasker as kt, replayRollback as l, createAssetRedirectResolver as ln, setAwsClients as lr, carriesDynamicReference as lt, withResourceDeadline as m, AssetModeResolver as mn, DependencyError as mr, WAFv2WebACLProvider as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, importableOutputKeys as nn, AssemblyReader as nr, isInterruptedWaitError as nt, planFailedOps as o, stringifyValue as on, AwsClients as or, disableInstanceApiTermination as ot, deleteSkipReason as p, stripControlChars as pn, CrossAccountSecretRefusalError as pr, refStateLookupFromResource as pt, clearOnUpdateRemoval as q, CFN_TEMPLATE_BODY_LIMIT as qn, describeTypeWithThrottleRetry as qt, DeployEngine as r, importableOutputs as rn, processStackMessages as rr, startInterruptWatch as rt, planRollback as s, WorkGraph as sn, getAwsClients as sr, isTerminationProtectionPropagationError as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, exportNamesCarriedFrom as tn, derivePartitionAndUrlSuffix as tr, interruptWatchListenerCount as tt, updatePartialMessage as u, loadPublishableAssetManifest as un, AssetError as ur, cfnRefValueFromPhysicalId as ut, ATOMIC_FINAL_SNAPSHOT_TYPES as v, getBootstrapMarkerKey as vn, LocalMigrateError as vr, coerceCfnBoolean as vt, refusesFinalSnapshot as w, buildDenyExternalAccessPolicy as wn, ProvisioningError as wr, requireConfigObject as wt, ccRoutedFinalSnapshotError as x, readBootstrapMarkerBody as xn, MissingCdkCliError as xr, readConfigString as xt, PRE_DELETE_SNAPSHOT_TYPES as y, isCrossRegionRedirect as yn, LocalStartServiceError as yr, configBooleanRefusal as yt, collectPublishedOutputNames as z, resolveAutoAssetStorage as zn, __exportAll as zr, s3BucketArn as zt };
|
|
30403
|
+
//# sourceMappingURL=deploy-engine-B-RuozdO.js.map
|