@go-to-k/cdkd 0.284.77 → 0.284.78
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-CDfupCgW.js → asg-provider-R60Irey3.js} +2 -2
- package/dist/{asg-provider-CDfupCgW.js.map → asg-provider-R60Irey3.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-Ce3payPI.js → deploy-engine-Cvkqa30x.js} +333 -11
- package/dist/deploy-engine-Cvkqa30x.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-SpnBYAoA.js → program-DyZp744n.js} +4 -4
- package/dist/{program-SpnBYAoA.js.map → program-DyZp744n.js.map} +1 -1
- package/dist/{version-rsHq2qI3.js → version-GEIr41xq.js} +2 -2
- package/dist/{version-rsHq2qI3.js.map → version-GEIr41xq.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-Ce3payPI.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { d as generateResourceName, f as generateResourceNameWithFallback, g as withStackName, m as looksLikeCdkdGeneratedName, n as getLogger, o as getLiveRenderer, p as getCurrentStackName, u as applyDefaultNameForFallback } from "./logger-C9-E73FI.js";
|
|
2
|
-
import { t as getCdkdVersion } from "./version-
|
|
2
|
+
import { t as getCdkdVersion } from "./version-GEIr41xq.js";
|
|
3
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
@@ -9158,9 +9158,13 @@ function splitGetAttStringForm(getAtt) {
|
|
|
9158
9158
|
* (`(producer X / Y)`), so it is neither derivable from the source leaf nor
|
|
9159
9159
|
* stable.
|
|
9160
9160
|
*
|
|
9161
|
-
*
|
|
9162
|
-
* `Fn::
|
|
9163
|
-
*
|
|
9161
|
+
* FIVE arms answer, and the enumeration is kept current because a stale one
|
|
9162
|
+
* reads as exhaustive: `Fn::ImportValue`, `Fn::GetStackOutput`, the `Fn::GetAtt`
|
|
9163
|
+
* on a nested-stack OUTPUT that issue #2055's read site re-resolves, the
|
|
9164
|
+
* single-placeholder `Fn::Sub` that normalizes ONTO that `Fn::GetAtt` key
|
|
9165
|
+
* (issue #2270 round 3 -- this list said THREE until issue #2291 noticed it had
|
|
9166
|
+
* been four since then), and the `Ref` to a nested-stack CHILD's own PARAMETER
|
|
9167
|
+
* (issue #2291). Every other leaf refuses.
|
|
9164
9168
|
*
|
|
9165
9169
|
* `Region` and `RoleArn` are OPTIONAL slots, and an ABSENT one keys as empty
|
|
9166
9170
|
* while a PRESENT-but-non-literal one refuses. Absent has to be its own key
|
|
@@ -9263,6 +9267,11 @@ function crossStackSourceKey(source) {
|
|
|
9263
9267
|
split.attributeName
|
|
9264
9268
|
].join(CROSS_STACK_KEY_SEPARATOR);
|
|
9265
9269
|
}
|
|
9270
|
+
if (key === "Ref") {
|
|
9271
|
+
const parameterName = literalStringOrUndefined(source[key]);
|
|
9272
|
+
if (parameterName === void 0) return void 0;
|
|
9273
|
+
return ["Ref", parameterName].join(CROSS_STACK_KEY_SEPARATOR);
|
|
9274
|
+
}
|
|
9266
9275
|
}
|
|
9267
9276
|
/**
|
|
9268
9277
|
* Remember, FOR THE PASS THAT OWNS `secrets`, that the cross-stack source leaf
|
|
@@ -9282,6 +9291,34 @@ function recordCrossStackExpression(secrets, key, expression, plaintext) {
|
|
|
9282
9291
|
associations = /* @__PURE__ */ new Map();
|
|
9283
9292
|
crossStackAssociations.set(secrets, associations);
|
|
9284
9293
|
}
|
|
9294
|
+
storeAssociation(associations, key, expression, plaintext);
|
|
9295
|
+
}
|
|
9296
|
+
/**
|
|
9297
|
+
* The store's WRITE rule, shared by {@link recordCrossStackExpression} and
|
|
9298
|
+
* {@link recordNestedStackParameterExpressions} so the two tables cannot drift
|
|
9299
|
+
* apart on what a second sighting means.
|
|
9300
|
+
*
|
|
9301
|
+
* A key recorded against a DIFFERENT (expression, plaintext) pair is POISONED
|
|
9302
|
+
* to {@link CONFLICTING_CROSS_STACK} rather than overwritten; an already
|
|
9303
|
+
* poisoned key stays poisoned, because a third sighting cannot un-contradict
|
|
9304
|
+
* the first two. Every reader refuses a poisoned key, which degrades to the
|
|
9305
|
+
* value scan — today's behaviour — rather than guessing between the two.
|
|
9306
|
+
*
|
|
9307
|
+
* WHAT IS AND IS NOT FENCED, stated because a reader would otherwise assume the
|
|
9308
|
+
* whole thing is (review of issue #2291). The POISON-vs-OVERWRITE decision IS
|
|
9309
|
+
* fenced: a second sighting under a THIRD expression makes the two outcomes
|
|
9310
|
+
* differ (poison refuses and falls back; overwriting would certify the third),
|
|
9311
|
+
* and a case asserts it. Nothing fences the branch from
|
|
9312
|
+
* {@link recordNestedStackParameterExpressions}'s side in PRODUCTION, because
|
|
9313
|
+
* that writer runs once per resource bag and `Object.entries` cannot yield one
|
|
9314
|
+
* name twice — the poison is reachable only through this module's API. It is
|
|
9315
|
+
* kept as an INVARIANT, not claimed as covered behaviour.
|
|
9316
|
+
*
|
|
9317
|
+
* The `isSingleDynamicReferenceToken` shape invariant is deliberately NOT here:
|
|
9318
|
+
* it belongs to each writer, which is where the parameters are named and where
|
|
9319
|
+
* a swap could be introduced.
|
|
9320
|
+
*/
|
|
9321
|
+
function storeAssociation(associations, key, expression, plaintext) {
|
|
9285
9322
|
const seen = associations.get(key);
|
|
9286
9323
|
if (seen === void 0) {
|
|
9287
9324
|
associations.set(key, {
|
|
@@ -9294,6 +9331,218 @@ function recordCrossStackExpression(secrets, key, expression, plaintext) {
|
|
|
9294
9331
|
if (seen.expression !== expression || seen.plaintext !== plaintext) associations.set(key, CONFLICTING_CROSS_STACK);
|
|
9295
9332
|
}
|
|
9296
9333
|
/**
|
|
9334
|
+
* The `AWS::CloudFormation::Stack` type string, named once because the recorder
|
|
9335
|
+
* below gates on it and the tests assert against the same population.
|
|
9336
|
+
*
|
|
9337
|
+
* DUPLICATED, deliberately: `intrinsic-function-resolver.ts` declares the same
|
|
9338
|
+
* literal under the same name (for the `Outputs.<Name>` re-resolution of issue
|
|
9339
|
+
* #2055). This module is a LEAF by design -- see the file header, it imports
|
|
9340
|
+
* nothing, because both the resolver and the deploy engine consume it -- so
|
|
9341
|
+
* importing that spelling would close a cycle, and exporting this one for the
|
|
9342
|
+
* resolver to import would make the leaf a source of values rather than of
|
|
9343
|
+
* pure functions. The two cannot drift into DISAGREEMENT in any way that
|
|
9344
|
+
* matters: an AWS resource type string is fixed by AWS, and a typo in either
|
|
9345
|
+
* copy makes that copy's gate simply never fire (no-op), never fire wrongly.
|
|
9346
|
+
*/
|
|
9347
|
+
const NESTED_STACK_RESOURCE_TYPE$1 = "AWS::CloudFormation::Stack";
|
|
9348
|
+
/**
|
|
9349
|
+
* What a PARENT stack's resolution proved about each `Parameters` entry of an
|
|
9350
|
+
* `AWS::CloudFormation::Stack` row it is about to provision, keyed by the
|
|
9351
|
+
* child's PARAMETER NAME (issue
|
|
9352
|
+
* [#2291](https://github.com/go-to-k/cdkd/issues/2291)).
|
|
9353
|
+
*
|
|
9354
|
+
* WHY A THIRD STORE, when {@link crossStackAssociations} already holds
|
|
9355
|
+
* per-leaf associations. This one is OUTBOUND: it is written against the
|
|
9356
|
+
* PARENT's bag and describes leaves of the CHILD's template, so it has to be
|
|
9357
|
+
* transported across the engine boundary before any reader can use it. The
|
|
9358
|
+
* child's per-resource bag then receives those entries as ORDINARY
|
|
9359
|
+
* {@link crossStackAssociations} rows (see
|
|
9360
|
+
* {@link inheritNestedStackParameterAssociations}), which is what lets the
|
|
9361
|
+
* existing three-condition reader answer for them with no new arm.
|
|
9362
|
+
*
|
|
9363
|
+
* KEEPING THE TWO TABLES SEPARATE IS LOAD-BEARING, not tidiness. A child engine
|
|
9364
|
+
* that itself owns a grandchild `AWS::CloudFormation::Stack` row records the
|
|
9365
|
+
* GRANDCHILD's parameter names against the CHILD's bag — the same bag that
|
|
9366
|
+
* already carries the child's own INBOUND `Ref` associations. Writing both into
|
|
9367
|
+
* one table means a grandchild parameter sharing a NAME with a child parameter
|
|
9368
|
+
* poisons the child's entry (two expressions under one key), so a leaf that was
|
|
9369
|
+
* being certified correctly falls back to the value scan the moment a
|
|
9370
|
+
* same-named parameter appears one level down. Two tables make the collision
|
|
9371
|
+
* impossible rather than unlikely.
|
|
9372
|
+
*
|
|
9373
|
+
* A `WeakMap` keyed by the parent pass's own bag, for the reason
|
|
9374
|
+
* {@link crossStackAssociations} gives: the entries hold PLAINTEXT, and they
|
|
9375
|
+
* must not outlive the pass that fetched them.
|
|
9376
|
+
*/
|
|
9377
|
+
const nestedStackParameterExpressions = /* @__PURE__ */ new WeakMap();
|
|
9378
|
+
/**
|
|
9379
|
+
* Record, for the pass that owns `secrets`, which `{{resolve:...}}` expression
|
|
9380
|
+
* each `Parameters` entry of a nested-stack row was resolved FROM (issue
|
|
9381
|
+
* #2291). No-op for every other resource type.
|
|
9382
|
+
*
|
|
9383
|
+
* THE EXPRESSIONS COME FROM THE POSITION PASS, not from `secrets`, and that is
|
|
9384
|
+
* the whole reason this works at all. `RecordedSecretValues` is keyed by
|
|
9385
|
+
* PLAINTEXT, so two parameters resolving to one value have already collapsed to
|
|
9386
|
+
* a single entry there by the time this runs — asking the map which expression
|
|
9387
|
+
* a given parameter came from returns the SURVIVOR for both. What has not
|
|
9388
|
+
* collapsed is the parent's own template: `Properties.Parameters.<Name>` still
|
|
9389
|
+
* holds each entry's own source leaf. So this walks the parent's resolved
|
|
9390
|
+
* parameter bag against that source with {@link redactSecretsForState}, which
|
|
9391
|
+
* is exactly the machinery the parent's persist path already uses and which
|
|
9392
|
+
* certifies PER LEAF (measured on this issue: the parent's own two properties
|
|
9393
|
+
* come out correct while the child's collapse onto one). Deriving the answer
|
|
9394
|
+
* from the positioner rather than restating its rules is what keeps this from
|
|
9395
|
+
* drifting away from the persist path.
|
|
9396
|
+
*
|
|
9397
|
+
* THE `rules` PARAMETER IS THE CALLER'S GENERATION CLAIM, not a knob. The
|
|
9398
|
+
* DEPLOY path passes {@link TEMPLATE_DERIVED_RULES} (the default): its source is
|
|
9399
|
+
* the parent's TEMPLATE, which can carry a PUBLIC `ssm:` reference that must
|
|
9400
|
+
* stay resolved (issue #1901) and which cannot certify the generation of
|
|
9401
|
+
* anything. The rollback REPLAY passes {@link STATE_DERIVED_RULES}, because its
|
|
9402
|
+
* source is the JOURNAL record — a persisted bag holding no public expressions,
|
|
9403
|
+
* and the same generation the bag was resolved from one statement earlier. That
|
|
9404
|
+
* is the identical pairing `redactRollbackRecord` already makes for the record
|
|
9405
|
+
* it positions, so the two replay walks now agree about what their source is.
|
|
9406
|
+
*
|
|
9407
|
+
* "No public expressions" carries the carve-out {@link PathSourceRules} states
|
|
9408
|
+
* and this note must not restate without it: `cdkd import` warns and persists
|
|
9409
|
+
* the RAW template intrinsic, so a public `ssm:` token CAN sit in a record. The
|
|
9410
|
+
* replay then CERTIFIES one the deploy default would refuse, at a cost bounded
|
|
9411
|
+
* to the issue #1901 class — a spurious UPDATE over a value state should hold
|
|
9412
|
+
* resolved, never a disclosure, since an expression is what gets persisted
|
|
9413
|
+
* either way. See the replay call sites for why gating on
|
|
9414
|
+
* {@link isKnownSecretExpression} is the wrong way to close it.
|
|
9415
|
+
*
|
|
9416
|
+
* FOUR REFUSALS, each degrading to today's behaviour (the child leaf falls to
|
|
9417
|
+
* the plaintext-keyed value scan):
|
|
9418
|
+
*
|
|
9419
|
+
* 1. REFUSAL — the resolved parameter value is not a WHOLE recorded plaintext.
|
|
9420
|
+
* A parameter the parent built with an `Fn::Sub` merely EMBEDS the secret,
|
|
9421
|
+
* so there is no single expression the child's leaf could be persisted as.
|
|
9422
|
+
* It also bounds what this store HOLDS: an entry carries a plaintext, and
|
|
9423
|
+
* remembering one this pass never resolved has no purpose.
|
|
9424
|
+
* 2. REFUSAL — the expression is not a single complete `{{resolve:...}}` token.
|
|
9425
|
+
* The same test {@link recordCrossStackExpression} applies at its own
|
|
9426
|
+
* boundary, spelled here because this writer populates a DIFFERENT table.
|
|
9427
|
+
* This is what rejects an embedded case that survived refusal 1 (a value
|
|
9428
|
+
* scan produced `postgres://u:{{resolve:...}}@host`, which is not a token).
|
|
9429
|
+
* 3. REFUSAL — the expression may never EQUAL the plaintext it is recorded
|
|
9430
|
+
* against. The reader returns the expression to be PERSISTED, so an entry
|
|
9431
|
+
* whose two halves coincide hands a SECRET back as though it were a
|
|
9432
|
+
* reference.
|
|
9433
|
+
*
|
|
9434
|
+
* THIS IS REACHABLE, and an earlier revision of this note called it an
|
|
9435
|
+
* unreachable invariant and told the next reader not to try to fence it.
|
|
9436
|
+
* That was wrong twice over: wrong on the fact, and wrong to assert it,
|
|
9437
|
+
* because {@link plaintextIndexOf}'s own note records the rule that
|
|
9438
|
+
* "asserting something cannot be fenced suppresses the attempt, so it needs
|
|
9439
|
+
* the same evidence a fence does" -- and no such evidence existed. The
|
|
9440
|
+
* reaching shape is the issue
|
|
9441
|
+
* [#1917](https://github.com/go-to-k/cdkd/issues/1917) family: a
|
|
9442
|
+
* SELF-REFERENTIAL secret, whose stored VALUE is byte-identical to its own
|
|
9443
|
+
* `{{resolve:...}}` text, so the pass records `SELF -> SELF`. The route is
|
|
9444
|
+
* NOT the value scan the old note named. It is {@link redactByPath}'s
|
|
9445
|
+
* `!sourceIsSameGeneration && isSingleDynamicReferenceToken(bag)` arm, which
|
|
9446
|
+
* returns `secrets.get(bag) ?? bag` -- and for a self-referential secret
|
|
9447
|
+
* that IS the bag. Under {@link STATE_DERIVED_RULES} the same input arrives
|
|
9448
|
+
* by the other door (`sourceIsSameGeneration` is true, so the arm takes
|
|
9449
|
+
* `return source`, which is the same string again), so both callers reach it.
|
|
9450
|
+
* Fenced by the self-referential case in
|
|
9451
|
+
* `secret-redaction-nested-parameter-source.test.ts`.
|
|
9452
|
+
*/
|
|
9453
|
+
function recordNestedStackParameterExpressions(secrets, resourceType, resolvedProperties, sourceProperties, rules = TEMPLATE_DERIVED_RULES) {
|
|
9454
|
+
if (resourceType !== NESTED_STACK_RESOURCE_TYPE$1) return;
|
|
9455
|
+
if (secrets.size === 0) return;
|
|
9456
|
+
if (!isPlainObject$2(resolvedProperties) || !isPlainObject$2(sourceProperties)) return;
|
|
9457
|
+
if (!Object.hasOwn(resolvedProperties, "Parameters")) return;
|
|
9458
|
+
if (!Object.hasOwn(sourceProperties, "Parameters")) return;
|
|
9459
|
+
const resolvedParameters = resolvedProperties["Parameters"];
|
|
9460
|
+
const sourceParameters = sourceProperties["Parameters"];
|
|
9461
|
+
if (!isPlainObject$2(resolvedParameters) || !isPlainObject$2(sourceParameters)) return;
|
|
9462
|
+
const positioned = redactSecretsForState(resolvedParameters, secrets, sourceParameters, rules);
|
|
9463
|
+
let table = nestedStackParameterExpressions.get(secrets);
|
|
9464
|
+
for (const [name, resolvedValue] of Object.entries(resolvedParameters)) {
|
|
9465
|
+
if (typeof resolvedValue !== "string" || !secrets.has(resolvedValue)) continue;
|
|
9466
|
+
const expression = positioned[name];
|
|
9467
|
+
if (typeof expression !== "string") continue;
|
|
9468
|
+
if (!isSingleDynamicReferenceToken(expression)) continue;
|
|
9469
|
+
const sourceLeaf = sourceParameters[name];
|
|
9470
|
+
if (typeof sourceLeaf === "string" && expression !== sourceLeaf) continue;
|
|
9471
|
+
if (expression === resolvedValue) continue;
|
|
9472
|
+
if (table === void 0) {
|
|
9473
|
+
table = /* @__PURE__ */ new Map();
|
|
9474
|
+
nestedStackParameterExpressions.set(secrets, table);
|
|
9475
|
+
}
|
|
9476
|
+
storeAssociation(table, name, expression, resolvedValue);
|
|
9477
|
+
}
|
|
9478
|
+
}
|
|
9479
|
+
/**
|
|
9480
|
+
* Copy a parent pass's per-PARAMETER associations onto a nested-stack CHILD
|
|
9481
|
+
* resource's own bag, as ordinary {@link crossStackAssociations} rows keyed by
|
|
9482
|
+
* `{Ref: <ParamName>}` (issue #2291).
|
|
9483
|
+
*
|
|
9484
|
+
* Called by the child {@link DeployEngine} once per resolver context, so each
|
|
9485
|
+
* child resource's fresh bag carries them. Pre-seeding every resource this way
|
|
9486
|
+
* does NOT reintroduce issue #2087's over-redaction: an association can only
|
|
9487
|
+
* change an answer through {@link positionByCrossStackSource}'s condition 1,
|
|
9488
|
+
* which requires the bag leaf to be a plaintext THIS resource's bag holds — and
|
|
9489
|
+
* since #2087 that is true only of resources whose own resolution consumed the
|
|
9490
|
+
* parameter. The scoping still comes from the plaintext bag; this table only
|
|
9491
|
+
* decides WHICH expression such a leaf takes.
|
|
9492
|
+
*
|
|
9493
|
+
* Nothing is copied when the parent recorded nothing, which is every non-nested
|
|
9494
|
+
* caller and every nested one whose parameters carry no secret.
|
|
9495
|
+
*/
|
|
9496
|
+
function inheritNestedStackParameterAssociations(childSecrets, parentSecrets) {
|
|
9497
|
+
const table = nestedStackParameterExpressions.get(parentSecrets);
|
|
9498
|
+
if (table === void 0 || table.size === 0) return;
|
|
9499
|
+
let associations = crossStackAssociations.get(childSecrets);
|
|
9500
|
+
for (const [name, association] of table) {
|
|
9501
|
+
const key = crossStackSourceKey({ Ref: name });
|
|
9502
|
+
if (key === void 0) continue;
|
|
9503
|
+
if (associations === void 0) {
|
|
9504
|
+
associations = /* @__PURE__ */ new Map();
|
|
9505
|
+
crossStackAssociations.set(childSecrets, associations);
|
|
9506
|
+
}
|
|
9507
|
+
if (typeof association === "symbol") {
|
|
9508
|
+
associations.set(key, association);
|
|
9509
|
+
continue;
|
|
9510
|
+
}
|
|
9511
|
+
storeAssociation(associations, key, association.expression, association.plaintext);
|
|
9512
|
+
}
|
|
9513
|
+
}
|
|
9514
|
+
/**
|
|
9515
|
+
* The expression a nested-stack child's PARAMETER was resolved from, or
|
|
9516
|
+
* `undefined` when this pass cannot certify one (issue #2291).
|
|
9517
|
+
*
|
|
9518
|
+
* The DIFF-side twin of {@link positionByCrossStackSource}, and it must exist
|
|
9519
|
+
* or the fix trades one bug for another. The child's persisted state now holds
|
|
9520
|
+
* each parameter-fed leaf's OWN expression, so the desired side of the next
|
|
9521
|
+
* diff has to hold it too; the engine's `redactParametersForDiff` rewrites the
|
|
9522
|
+
* parameter bag through the plaintext-keyed map alone, which hands BOTH members
|
|
9523
|
+
* of a coinciding pair the survivor's expression. The two sides would then
|
|
9524
|
+
* never match for the losing parameter: a perpetual UPDATE on every deploy of
|
|
9525
|
+
* such a child, which is issue #2087's user-visible symptom arriving through a
|
|
9526
|
+
* different door.
|
|
9527
|
+
*
|
|
9528
|
+
* `parentSecrets` is the INHERITED bag — the parent's own per-resource map, the
|
|
9529
|
+
* object this table is keyed by — not the child resource's bag.
|
|
9530
|
+
*
|
|
9531
|
+
* The same THREE conditions {@link positionByCrossStackSource} applies, for the
|
|
9532
|
+
* same reasons; see that function's own notes for why condition 3 is not
|
|
9533
|
+
* subsumed by condition 2.
|
|
9534
|
+
*/
|
|
9535
|
+
function inheritedParameterExpression(parentSecrets, parameterName, resolvedValue) {
|
|
9536
|
+
if (typeof resolvedValue !== "string" || resolvedValue === "") return void 0;
|
|
9537
|
+
if (!parentSecrets.has(resolvedValue)) return void 0;
|
|
9538
|
+
const association = nestedStackParameterExpressions.get(parentSecrets)?.get(parameterName);
|
|
9539
|
+
if (association === void 0 || typeof association === "symbol") return void 0;
|
|
9540
|
+
if (association.plaintext !== resolvedValue) return void 0;
|
|
9541
|
+
const recordedPlaintext = plaintextIndexOf(parentSecrets).get(association.expression);
|
|
9542
|
+
if (recordedPlaintext !== void 0 && recordedPlaintext !== resolvedValue) return void 0;
|
|
9543
|
+
return association.expression;
|
|
9544
|
+
}
|
|
9545
|
+
/**
|
|
9297
9546
|
* A resolved secret value shorter than this is NOT used as a redaction needle:
|
|
9298
9547
|
* a 1-2 character plaintext (e.g. a secret whose JSON key holds `"0"`) would
|
|
9299
9548
|
* match incidental characters everywhere and mangle unrelated state. Such a
|
|
@@ -16602,15 +16851,79 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
16602
16851
|
* `Fn::Select` / `Fn::FindInMap` all re-enter `resolveValue` and reach the
|
|
16603
16852
|
* parameter through this same `Ref` branch.
|
|
16604
16853
|
*
|
|
16854
|
+
* WHICH EXPRESSION the pair is recorded against is the issue
|
|
16855
|
+
* [#2291](https://github.com/go-to-k/cdkd/issues/2291) round-2 fix, and
|
|
16856
|
+
* skipping it made the persist and diff halves DISAGREE.
|
|
16857
|
+
*
|
|
16858
|
+
* `inherited` is keyed by PLAINTEXT, so two parent parameters resolving to
|
|
16859
|
+
* ONE value collapse to a single entry there and this method used to copy
|
|
16860
|
+
* whichever expression SURVIVED. That is invisible for a leaf spelled exactly
|
|
16861
|
+
* `{Ref: P}` — the persist path positions such a leaf through the parent's
|
|
16862
|
+
* per-parameter association and never consults this bag's value — but every
|
|
16863
|
+
* EMBEDDING shape (`Fn::Sub`, `Fn::Join`, and `{'Fn::Sub': '${P}'}`, which
|
|
16864
|
+
* `crossStackSourceKey` refuses because its `Fn::Sub` arm requires a dotted
|
|
16865
|
+
* attribute) falls to the plaintext-keyed VALUE SCAN, which reads exactly this
|
|
16866
|
+
* bag. Meanwhile `DeployEngine.redactParametersForDiff` answers PER PARAMETER.
|
|
16867
|
+
* So `Fn::Sub "postgres://u:${LoserParam}@host"` — the dominant CDK
|
|
16868
|
+
* connection-string shape — persisted the SURVIVOR's expression while the
|
|
16869
|
+
* desired side computed the LOSER's, and the two never matched again: a
|
|
16870
|
+
* perpetual UPDATE, or a perpetual REPLACEMENT on a create-only property.
|
|
16871
|
+
* That is issue [#2087](https://github.com/go-to-k/cdkd/issues/2087)'s symptom
|
|
16872
|
+
* arriving through a different door, prevented for the bare-`Ref` leaf and
|
|
16873
|
+
* created for the embedded one.
|
|
16874
|
+
*
|
|
16875
|
+
* Recording THIS parameter's own expression makes the value scan agree with
|
|
16876
|
+
* the diff side, so both halves move together.
|
|
16877
|
+
*
|
|
16878
|
+
* THE RESIDUAL, and the earlier version of this note UNDERSTATED IT. It said
|
|
16879
|
+
* the leftover case was "no worse than the behaviour before this fix". That
|
|
16880
|
+
* is true only of the shape it was measured on. MEASURED 2026-08-27 against
|
|
16881
|
+
* `main` (f56c2cf9) and against this branch, one child resource, parameters
|
|
16882
|
+
* `A` and `B` resolving to one plaintext:
|
|
16883
|
+
*
|
|
16884
|
+
* | shape | main | here |
|
|
16885
|
+
* | ---------------------------------------- | ------- | ------- |
|
|
16886
|
+
* | `{Ref: A}` and `{Ref: B}` | agree | agree |
|
|
16887
|
+
* | `Fn::Sub '${A}'` only | agree | agree |
|
|
16888
|
+
* | `Fn::Sub 'x${A}'` **and** `{Ref: B}` | agree | DISAGREE|
|
|
16889
|
+
*
|
|
16890
|
+
* The third row is a NEW disagreement this PR introduces, not a pre-existing
|
|
16891
|
+
* one it fails to fix: `main` had both halves take the collapsed survivor, so
|
|
16892
|
+
* they matched (on the WRONG expression, which is issue #2291, but they
|
|
16893
|
+
* matched). Here the DIFF side is per-parameter while an EMBEDDED leaf can
|
|
16894
|
+
* only be redacted by the plaintext-keyed value scan, and this bag holds ONE
|
|
16895
|
+
* entry — whichever `Ref` resolved LAST. So the embedded leaf takes `B`'s
|
|
16896
|
+
* expression while the desired side computes `A`'s, and the resource reports
|
|
16897
|
+
* an UPDATE on every deploy (a REPLACEMENT, on a create-only property).
|
|
16898
|
+
*
|
|
16899
|
+
* It is ORDER-DEPENDENT, which is why it is narrow: reversing the two
|
|
16900
|
+
* properties makes the embedded parameter the last one resolved and the two
|
|
16901
|
+
* halves agree again (measured). It also needs BOTH leaves in ONE resource —
|
|
16902
|
+
* `perResourceSecrets` is keyed by logical id, so two resources get two bags
|
|
16903
|
+
* and each is right. A resource that consumes and embeds BOTH parameters is
|
|
16904
|
+
* unfixable here for the same reason and is genuinely inherent.
|
|
16905
|
+
*
|
|
16906
|
+
* CLOSING IT NEEDS A PLACEHOLDER-SPAN POSITION ARM — aligning an `Fn::Sub` /
|
|
16907
|
+
* `Fn::Join` source against the resolved string to locate each placeholder's
|
|
16908
|
+
* span and rewrite it from the association. That is a new positioning
|
|
16909
|
+
* CONCEPT rather than an arm beside the existing ones: the persist path would
|
|
16910
|
+
* have to reproduce the resolver's substitution semantics from a module that
|
|
16911
|
+
* holds neither a resolver nor a parameter bag, and any divergence between
|
|
16912
|
+
* the two reproductions is this same perpetual-UPDATE bug. Deferred to issue
|
|
16913
|
+
* [#2320](https://github.com/go-to-k/cdkd/issues/2320) with the measurement.
|
|
16914
|
+
* It shares only the word "span" with issue #2102, which registers live
|
|
16915
|
+
* values for `{{resolve:...}}` TOKEN spans on the drift paths.
|
|
16916
|
+
*
|
|
16605
16917
|
* Substituting is deliberately NOT done here — the resolved value is what
|
|
16606
16918
|
* reaches AWS, and an `Fn::Equals` over a parameter must compare the real
|
|
16607
16919
|
* value or the condition flips.
|
|
16608
16920
|
*/
|
|
16609
|
-
recordInheritedParameterSecrets(value, context) {
|
|
16921
|
+
recordInheritedParameterSecrets(parameterName, value, context) {
|
|
16610
16922
|
const inherited = context.inheritedSecrets;
|
|
16611
16923
|
const recorded = context.recordedSecretValues;
|
|
16612
16924
|
if (!inherited || inherited.size === 0 || !recorded) return;
|
|
16613
|
-
|
|
16925
|
+
const own = inheritedParameterExpression(inherited, parameterName, value);
|
|
16926
|
+
for (const [plaintext, expression] of inheritedSecretsCarriedBy(value, inherited)) recorded.set(plaintext, own !== void 0 && plaintext === value ? own : expression);
|
|
16614
16927
|
}
|
|
16615
16928
|
/**
|
|
16616
16929
|
* Refuse a child parameter whose declared `Type` would COERCE an inherited
|
|
@@ -16675,7 +16988,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
16675
16988
|
const value = context.parameters[logicalId];
|
|
16676
16989
|
const paramDef = context.template.Parameters?.[logicalId];
|
|
16677
16990
|
this.logger.debug(`Resolved Ref to parameter: ${logicalId} -> ${this.maskSecretsForLog(stringifyParameterForLog(paramDef, value), context)}`);
|
|
16678
|
-
this.recordInheritedParameterSecrets(value, context);
|
|
16991
|
+
this.recordInheritedParameterSecrets(logicalId, value, context);
|
|
16679
16992
|
return value;
|
|
16680
16993
|
}
|
|
16681
16994
|
const pseudoValue = await this.resolvePseudoParameter(logicalId, context);
|
|
@@ -19696,7 +20009,7 @@ var CloudControlProvider = class {
|
|
|
19696
20009
|
await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
|
|
19697
20010
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
19698
20011
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
19699
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
20012
|
+
const { ASGProvider } = await import("./asg-provider-R60Irey3.js").then((n) => n.n);
|
|
19700
20013
|
return await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
|
|
19701
20014
|
}
|
|
19702
20015
|
const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
|
|
@@ -28528,6 +28841,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
28528
28841
|
const current = stateResources[op.logicalId];
|
|
28529
28842
|
const prev = op.previousState;
|
|
28530
28843
|
const resolvedPrevProps = await resolveReplayProps(prev.properties, resolver, secrets, ctx, op.logicalId) ?? {};
|
|
28844
|
+
recordNestedStackParameterExpressions(secrets, op.resourceType, resolvedPrevProps, prev.properties, STATE_DERIVED_RULES);
|
|
28531
28845
|
logger.info(` Rollback: Reversing replacement of ${op.logicalId} (${op.resourceType}) — re-creating the old resource and deleting the new one`);
|
|
28532
28846
|
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.`);
|
|
28533
28847
|
const { provider: createProvider } = ctx.providerRegistry.getProviderFor({
|
|
@@ -28621,6 +28935,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
|
|
|
28621
28935
|
});
|
|
28622
28936
|
const desiredProps = await resolveReplayProps(previousState.properties, resolver, secrets, ctx, op.logicalId);
|
|
28623
28937
|
const currentProps = await resolveReplayProps(current.properties, resolver, secrets, ctx, op.logicalId);
|
|
28938
|
+
recordNestedStackParameterExpressions(secrets, op.resourceType, desiredProps, previousState.properties, STATE_DERIVED_RULES);
|
|
28624
28939
|
const revertResult = await updateWithRollbackRetry(provider, [
|
|
28625
28940
|
op.logicalId,
|
|
28626
28941
|
current.physicalId,
|
|
@@ -28773,6 +29088,7 @@ async function replayFailedOperations(failedOps, stateResources, stackName, ctx,
|
|
|
28773
29088
|
});
|
|
28774
29089
|
const desiredProps = await resolveReplayProps(prev.properties, resolver, secrets, ctx, op.logicalId);
|
|
28775
29090
|
const attemptedProps = await resolveReplayProps(op.attemptedProperties ?? current.properties, resolver, secrets, ctx, op.logicalId);
|
|
29091
|
+
recordNestedStackParameterExpressions(secrets, op.resourceType, desiredProps, prev.properties, STATE_DERIVED_RULES);
|
|
28776
29092
|
const revertFailedResult = await updateWithRollbackRetry(provider, [
|
|
28777
29093
|
op.logicalId,
|
|
28778
29094
|
current.physicalId,
|
|
@@ -29715,6 +30031,8 @@ var DeployEngine = class {
|
|
|
29715
30031
|
* `this.buildResolverContext({...}, stackName)`.
|
|
29716
30032
|
*/
|
|
29717
30033
|
buildResolverContext(base, stackName) {
|
|
30034
|
+
const recordedSecretValues = /* @__PURE__ */ new Map();
|
|
30035
|
+
if (this.options.inheritedSecrets && this.options.inheritedSecrets.size > 0) inheritNestedStackParameterAssociations(recordedSecretValues, this.options.inheritedSecrets);
|
|
29718
30036
|
return {
|
|
29719
30037
|
template: base.template,
|
|
29720
30038
|
resources: base.resources,
|
|
@@ -29726,7 +30044,7 @@ var DeployEngine = class {
|
|
|
29726
30044
|
recordedImports: this.recordedImports,
|
|
29727
30045
|
recordedOutputReads: this.recordedOutputReads,
|
|
29728
30046
|
...this.options.inheritedSecrets && this.options.inheritedSecrets.size > 0 && { inheritedSecrets: this.options.inheritedSecrets },
|
|
29729
|
-
recordedSecretValues
|
|
30047
|
+
recordedSecretValues
|
|
29730
30048
|
};
|
|
29731
30049
|
}
|
|
29732
30050
|
/**
|
|
@@ -29753,7 +30071,9 @@ var DeployEngine = class {
|
|
|
29753
30071
|
redactParametersForDiff(parameterValues) {
|
|
29754
30072
|
const inherited = this.options.inheritedSecrets;
|
|
29755
30073
|
if (!inherited || inherited.size === 0) return parameterValues;
|
|
29756
|
-
|
|
30074
|
+
const out = {};
|
|
30075
|
+
for (const [name, value] of Object.entries(parameterValues)) out[name] = inheritedParameterExpression(inherited, name, value) ?? redactSecretsForState(value, inherited);
|
|
30076
|
+
return out;
|
|
29757
30077
|
}
|
|
29758
30078
|
/**
|
|
29759
30079
|
* Redact resolved secret plaintext out of a bag about to be PERSISTED to
|
|
@@ -30869,6 +31189,7 @@ var DeployEngine = class {
|
|
|
30869
31189
|
const resolvedProps = await this.resolver.resolve(desiredProps, context);
|
|
30870
31190
|
this.perResourceTemplateProps.set(logicalId, desiredProps);
|
|
30871
31191
|
const createSecrets = context.recordedSecretValues ?? /* @__PURE__ */ new Map();
|
|
31192
|
+
recordNestedStackParameterExpressions(createSecrets, resourceType, resolvedProps, desiredProps);
|
|
30872
31193
|
this.auditResolvedAssetReferences(logicalId, resourceType, resolvedProps);
|
|
30873
31194
|
this.attemptedResolvedProps.set(logicalId, resolvedProps);
|
|
30874
31195
|
const createDecision = this.providerRegistry.getProviderFor({
|
|
@@ -30913,6 +31234,7 @@ var DeployEngine = class {
|
|
|
30913
31234
|
this.perResourceSecrets.set(logicalId, updateSecrets);
|
|
30914
31235
|
const resolvedProps = await this.resolver.resolve(desiredProps, context);
|
|
30915
31236
|
this.perResourceTemplateProps.set(logicalId, desiredProps);
|
|
31237
|
+
recordNestedStackParameterExpressions(updateSecrets, resourceType, resolvedProps, desiredProps);
|
|
30916
31238
|
this.auditResolvedAssetReferences(logicalId, resourceType, resolvedProps);
|
|
30917
31239
|
this.attemptedResolvedProps.set(logicalId, resolvedProps);
|
|
30918
31240
|
if (JSON.stringify(redactSecretsForState(resolvedProps, updateSecrets, desiredProps)) === JSON.stringify(currentProps)) {
|
|
@@ -31581,4 +31903,4 @@ var DeployEngine = class {
|
|
|
31581
31903
|
|
|
31582
31904
|
//#endregion
|
|
31583
31905
|
export { maskerOrIdentity as $, CFN_TEMPLATE_URL_LIMIT as $n, redactSecretsForState as $t, renderStatefulReason as A, buildDockerImage as An, ResourceTimeoutError as Ar, classifyReplaySecretRegion as At, exportAliasCollisionScrubWarning as B, synthesisStatusMessage as Bn, isMarkedNonRetryable as Br, describeTypeWithThrottleRetry as Bt, isFinalSnapshotError as C, isCrossRegionRedirect as Cn, LocalMigrateError as Cr, configBooleanRefusal as Ct, extractDeploymentEventError as D, validateContainerRepoName as Dn, NestedStackChildDirectDestroyError as Dr, requireConfigArray as Dt, makeCanonicalizePropertiesFn as E, validateAssetBucketName as En, MissingCdkCliError as Er, replayWarn as Et, green as F, runDockerForeground as Fn, SynthesisError as Fr, s3BucketRegionalDomainName as Ft, IAMRoleProvider as G, resolveCaptureObservedState as Gn, retryClassificationText as Gr, STATE_SOURCED_READBACK_RULES as Gt, secretBearingStateKeyWarning as H, getLegacyStateBucketName as Hn, isThrottlingError as Hr, DagBuilder as Ht, red as I, runDockerStreaming as In, formatError as Ir, s3BucketWebsiteUrl as It, ProviderRegistry as J, resolveStateBucketWithDefaultAndSource as Jn, dynamicReferenceTokens as Jt, collectInlinePolicyNamesManagedBySiblings as K, resolveSkipPrefix as Kn, __exportAll as Kr, TEMPLATE_SOURCED_RULES as Kt, yellow as L, AssetManifestLoader as Ln, isCdkdError as Lr, applyRoleArnIfSet as Lt, bold as M, formatDockerLoginError as Mn, StackHasActiveImportsError as Mr, s3BucketArn as Mt, cyan as N, getDockerCmd as Nn, StackTerminationProtectionError as Nr, s3BucketDomainName as Nt, MULTI_REGION_RECREATE_BLOCKED_TYPES as O, buildDenyExternalAccessPolicy as On, PartialFailureError as Or, requireConfigObject as Ot, gray as P, partitionSensitiveEnv as Pn, StateError as Pr, s3BucketDualStackDomainName as Pt, maskDeep as Q, CFN_TEMPLATE_BODY_LIMIT as Qn, maskSecretsInText as Qt, collectDeclaredOutputNames as R, getDockerImageBySourceHash as Rn, normalizeAwsError as Rr, DiffCalculator as Rt, createPreDeleteFinalSnapshot as S, getBootstrapMarkerKey as Sn, LocalInvokeBuildError as Sr, coerceCfnBoolean as St, unsupportedFinalSnapshotError as T, readBootstrapMarkerBody as Tn, LockError as Tr, readConfigString as Tt, stateKeySecretExposure as U, resolveApp as Un, markNonRetryable as Ur, TemplateParser as Ut, isExportAliasCollision as V, getDefaultStateBucketName as Vn, isRetryableTransientError as Vr, withRetry as Vt, getCurrentResourceSecrets as W, resolveAutoAssetStorage as Wn, markRedactedCause as Wr, STATE_SOURCED_CROSS_GENERATION_RULES as Wt, findSilentDropProperties as X, stateBucketExistenceConfirmed as Xn, isSingleDynamicReferenceToken as Xt, findActionableSilentDrops as Y, resolveUseCdkBootstrapAssets as Yn, errorCauseChain as Yt, createMaskedRetryLogger as Z, warnDeprecatedNoPrefixCliFlag as Zn, maskSecretsInError as Zt, computeImplicitDeleteEdges as _, stripControlChars as _n, ConfigError as _r, refStateLookupFromResource as _t, DeploymentEventsStore as a, exportNamesCarriedFrom as an, canonicalizeRegion as ar, isInterruptedWaitError as at, buildFinalSnapshotIdentifier as b, assertAssetBucketRegion as bn, DeployCancelledError as br, resolveExplicitPhysicalId as bt, replayFailedOperations as c, shouldRetainResource as cn, processStackMessages as cr, slowCcOperationTimeoutMs as ct, updatePartialReason as d, WorkGraph as dn, AwsClients as dr, IntrinsicFunctionResolver as dt, scrubResourceRecord as en, MIGRATE_TMP_PREFIX as er, CUSTOM_RESOURCE_RESPONSE_PREFIX as et, UNSPECIFIED_SKIP_REASON as f, buildAssetRedirectMap as fn, getAwsClients as fr, carriesDynamicReference as ft, IMPLICIT_DELETE_DEPENDENCIES as g, escapeRegExp$1 as gn, CdkdError as gr, parameterTypeMayLoseSecretIdentity as gt, maskingRetryLogger as h, rewriteTemplateAssetReferences as hn, AssetError as hr, isUnboundTemplateParameter as ht, DeploymentEventsReader as i, rebuildClientForBucketRegion as in, PARTITION_TABLE as ir, interruptWatchListenerCount as it, formatResourceLine as j, dockerSpawnEnvWithSensitive as jn, ResourceUpdateNotSupportedError as jr, producerRegionsFromState as jt, isStatefulRecreateTargetSync as k, describeAwsFailure as kn, ProvisioningError as kr, requireConfigString as kt, replayRollback as l, AssetPublisher as ln, clearBucketRegionCache as lr, disableInstanceApiTermination as lt, withResourceDeadline as m, loadPublishableAssetManifest as mn, setAwsClients as mr, getAccountInfo as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, displaySafe as nn, uploadCfnTemplate as nr, beginCommandInterruptScope as nt, planFailedOps as o, importableOutputKeys as on, derivePartitionAndUrlSuffix as or, startInterruptWatch as ot, deleteSkipReason as p, createAssetRedirectResolver as pn, resetAwsClients as pr, cfnRefValueFromPhysicalId as pt, clearOnUpdateRemoval as q, resolveStateBucketWithDefault as qn, createSecretMasker as qt, DeployEngine as r, S3StateBackend as rn, expectedOwnerParam as rr, endCommandInterruptScope as rt, planRollback as s, importableOutputs as sn, AssemblyReader as sr, CloudControlProvider as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, LockManager as tn, findLargeInlineResources as tr, DEFAULT_STATE_PREFIX as tt, updatePartialMessage as u, stringifyValue as un, resolveBucketRegion as ur, isTerminationProtectionPropagationError as ut, ATOMIC_FINAL_SNAPSHOT_TYPES as v, AssetModeResolver as vn, CrossAccountSecretRefusalError as vr, WAFv2WebACLProvider as vt, refusesFinalSnapshot as w, parseBootstrapMarker as wn, LocalStartServiceError as wr, configStringRefusal as wt, ccRoutedFinalSnapshotError as x, ensureAssetStorage as xn, DynamicReferenceRegionAmbiguousError as xr, assertRegionMatch as xt, PRE_DELETE_SNAPSHOT_TYPES as y, BOOTSTRAP_MARKER_PREFIX as yn, DependencyError as yr, normalizeAwsTagsToCfn as yt, collectPublishedOutputNames as z, Synthesizer as zn, withErrorHandling as zr, INTRINSIC_KEYS as zt };
|
|
31584
|
-
//# sourceMappingURL=deploy-engine-
|
|
31906
|
+
//# sourceMappingURL=deploy-engine-Cvkqa30x.js.map
|