@go-to-k/cdkd 0.284.13 → 0.284.15
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-H-Dl8qng.js → asg-provider-BZwQzb8e.js} +49 -14
- package/dist/asg-provider-BZwQzb8e.js.map +1 -0
- package/dist/cli.js +541 -145
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-4Eh5Qqlm.js → deploy-engine-BNujuzW5.js} +316 -19
- package/dist/deploy-engine-BNujuzW5.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/asg-provider-H-Dl8qng.js.map +0 -1
- package/dist/deploy-engine-4Eh5Qqlm.js.map +0 -1
|
@@ -12850,6 +12850,25 @@ function buildUnknownIntrinsicError(key) {
|
|
|
12850
12850
|
const issueUrl = `https://github.com/go-to-k/cdkd/issues/new?title=${encodeURIComponent(title)}&labels=intrinsic-support`;
|
|
12851
12851
|
return /* @__PURE__ */ new Error(`Unsupported CloudFormation intrinsic function "${key}": cdkd does not support resolving it yet. Deploying this template would produce a broken value. Please request support by opening an issue: ${issueUrl}`);
|
|
12852
12852
|
}
|
|
12853
|
+
/**
|
|
12854
|
+
* Does `value` carry a CloudFormation dynamic reference anywhere inside it?
|
|
12855
|
+
*
|
|
12856
|
+
* Used as the identity fast path of {@link
|
|
12857
|
+
* IntrinsicFunctionResolver.reresolveCrossStackValue}: a cross-stack value that
|
|
12858
|
+
* carries none is returned untouched, so every ordinary import keeps its
|
|
12859
|
+
* pre-#1934 behaviour with no walk, no AWS call and no allocation.
|
|
12860
|
+
*
|
|
12861
|
+
* The walk descends arrays and objects because `state.outputs` is typed
|
|
12862
|
+
* `Record<string, unknown>` and deliberately NOT coerced to string — a
|
|
12863
|
+
* list-valued `Fn::GetAtt` persists a JSON array — so a secret-bearing output
|
|
12864
|
+
* is not always a bare string.
|
|
12865
|
+
*/
|
|
12866
|
+
function carriesDynamicReference(value) {
|
|
12867
|
+
if (typeof value === "string") return value.includes("{{resolve:");
|
|
12868
|
+
if (Array.isArray(value)) return value.some(carriesDynamicReference);
|
|
12869
|
+
if (value !== null && typeof value === "object") return Object.values(value).some(carriesDynamicReference);
|
|
12870
|
+
return false;
|
|
12871
|
+
}
|
|
12853
12872
|
let cachedAccountIdentity = null;
|
|
12854
12873
|
/**
|
|
12855
12874
|
* Cache for availability zones per region
|
|
@@ -13148,7 +13167,7 @@ function stringifyParameterForLog(paramDef, value) {
|
|
|
13148
13167
|
if (paramDef?.NoEcho === true) return "<redacted>";
|
|
13149
13168
|
return stringifyValue(value);
|
|
13150
13169
|
}
|
|
13151
|
-
var IntrinsicFunctionResolver = class {
|
|
13170
|
+
var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
13152
13171
|
logger = getLogger().child("IntrinsicFunctionResolver");
|
|
13153
13172
|
resolverRegion;
|
|
13154
13173
|
/**
|
|
@@ -13208,6 +13227,55 @@ var IntrinsicFunctionResolver = class {
|
|
|
13208
13227
|
* another's identity, which is a worse bug than the one this cache serves.
|
|
13209
13228
|
*/
|
|
13210
13229
|
regionScopedClients = /* @__PURE__ */ new Map();
|
|
13230
|
+
/**
|
|
13231
|
+
* ServiceDiscovery clients keyed by the region {@link clientsForRegion}
|
|
13232
|
+
* selected (`''` when it selected none). See {@link serviceDiscoveryClient}
|
|
13233
|
+
* for why this one service is not read off an `AwsClients` bag, and why the
|
|
13234
|
+
* PROMISE rather than the client is what is stored.
|
|
13235
|
+
*/
|
|
13236
|
+
serviceDiscoveryClients = /* @__PURE__ */ new Map();
|
|
13237
|
+
/**
|
|
13238
|
+
* Resolvers pinned to a PRODUCER stack's region, for re-resolving a
|
|
13239
|
+
* cross-stack imported value that was persisted REDACTED (issue
|
|
13240
|
+
* [#1934](https://github.com/go-to-k/cdkd/issues/1934)); see
|
|
13241
|
+
* {@link reresolveCrossStackValue}.
|
|
13242
|
+
*
|
|
13243
|
+
* A whole resolver rather than a client bag, because what has to be
|
|
13244
|
+
* region-scoped is not only the lookup but the VALUE CACHE behind it:
|
|
13245
|
+
* {@link cachedDynamicReferences} is keyed by the expression alone and is
|
|
13246
|
+
* sound only because one resolver stands for one stack in one region (issue
|
|
13247
|
+
* #1933). Resolving a producer's expression inside THIS resolver would put a
|
|
13248
|
+
* foreign region's answer under a key the consumer's own lookups read — the
|
|
13249
|
+
* exact cross-region leak that field's instance scope closed. A separate
|
|
13250
|
+
* resolver per producer region keeps that invariant by construction.
|
|
13251
|
+
*
|
|
13252
|
+
* WHAT THAT DOES *NOT* BUY, stated because an earlier revision of this note
|
|
13253
|
+
* implied it did: a separate instance isolates only the state this class
|
|
13254
|
+
* OWNS. The `{{resolve:...}}` SECRET VERDICT store lives in
|
|
13255
|
+
* `secret-redaction.ts`, is process-global and is keyed by the expression
|
|
13256
|
+
* string alone, so a guest's resolution would still pin a foreign region's
|
|
13257
|
+
* verdict for every later reader. That half is closed at the WRITE instead —
|
|
13258
|
+
* see {@link pinSecretVerdict} and {@link producerRegionGuest}, which also
|
|
13259
|
+
* record why re-keying that store is not available from this lane.
|
|
13260
|
+
*
|
|
13261
|
+
* Bounded like {@link regionScopedClients}: an entry exists only for a
|
|
13262
|
+
* producer region that DIFFERS from this resolver's own, and at most one per
|
|
13263
|
+
* such region. Same lifetime too — the resolver's own, with no teardown.
|
|
13264
|
+
*/
|
|
13265
|
+
producerRegionResolvers = /* @__PURE__ */ new Map();
|
|
13266
|
+
/**
|
|
13267
|
+
* True on a resolver built by {@link resolverForProducerRegion} to answer for
|
|
13268
|
+
* ANOTHER stack's region — a read-only guest of this deploy.
|
|
13269
|
+
*
|
|
13270
|
+
* Its one consequence is {@link pinSecretVerdict}: a guest never writes the
|
|
13271
|
+
* PROCESS-GLOBAL secret-verdict store, because a verdict keyed by the
|
|
13272
|
+
* expression string alone would carry a foreign region's answer into the
|
|
13273
|
+
* consumer's own next pass. Deliberately NOT on
|
|
13274
|
+
* {@link IntrinsicFunctionResolverOptions} — no caller outside this class may
|
|
13275
|
+
* declare itself a guest, and the flag is set by the one line that builds
|
|
13276
|
+
* one.
|
|
13277
|
+
*/
|
|
13278
|
+
producerRegionGuest = false;
|
|
13211
13279
|
strictGetAtt;
|
|
13212
13280
|
cfnFallback;
|
|
13213
13281
|
/**
|
|
@@ -13474,6 +13542,56 @@ var IntrinsicFunctionResolver = class {
|
|
|
13474
13542
|
return scoped;
|
|
13475
13543
|
}
|
|
13476
13544
|
/**
|
|
13545
|
+
* The ServiceDiscovery client for the `HostedZoneId` namespace lookup, in the
|
|
13546
|
+
* region {@link clientsForRegion} selects (issue
|
|
13547
|
+
* [#1994](https://github.com/go-to-k/cdkd/issues/1994)).
|
|
13548
|
+
*
|
|
13549
|
+
* It is BUILT here rather than read off the bag for one reason: `AwsClients`
|
|
13550
|
+
* carries no `serviceDiscovery` member, and adding one would put a static
|
|
13551
|
+
* `@aws-sdk/client-servicediscovery` import into a module every command
|
|
13552
|
+
* loads. So the REGION DECISION is still `clientsForRegion`'s — including its
|
|
13553
|
+
* ambient-reuse rule and its refusal of a region that is not client-safe —
|
|
13554
|
+
* and only the construction is local: the chosen bag's
|
|
13555
|
+
* {@link AwsClients.credentialConfig} carries `--profile` / explicit
|
|
13556
|
+
* credentials across, and its {@link AwsClients.configuredRegion} is the
|
|
13557
|
+
* region to pin. An UNCONFIGURED bag (no region was ever named, i.e. the
|
|
13558
|
+
* no-argument constructor) pins nothing and lets the SDK's own chain
|
|
13559
|
+
* resolve — the same arm-1 answer `clientsForRegion` gives, and strictly
|
|
13560
|
+
* better than the `resolverRegion` this site used to read, which substitutes
|
|
13561
|
+
* `AWS_REGION` and then a hard-coded `us-east-1`.
|
|
13562
|
+
*
|
|
13563
|
+
* The PROMISE is memoized, not the client: the dynamic import makes this
|
|
13564
|
+
* async, so two callers arriving from different await depths can both be
|
|
13565
|
+
* inside the seam and would each construct (and leak) their own client. That
|
|
13566
|
+
* is defensive rather than measured — the unit case dispatching ten lookups
|
|
13567
|
+
* together produces ONE client either way, because they serialize on
|
|
13568
|
+
* `getAccountInfo`'s in-flight promise and reach here one at a time, which
|
|
13569
|
+
* the case says out loud. A REJECTED import is evicted
|
|
13570
|
+
* so a transient failure does not poison the rest of the deploy, mirroring
|
|
13571
|
+
* `cfnExportsPromise`. Lifetime is the resolver's own, like
|
|
13572
|
+
* {@link regionScopedClients} and {@link cfnClients}: at most one per region
|
|
13573
|
+
* per resolver, versus the one-per-CALL this replaces.
|
|
13574
|
+
*/
|
|
13575
|
+
async serviceDiscoveryClient() {
|
|
13576
|
+
const scoped = this.clientsForRegion(this.explicitRegion);
|
|
13577
|
+
const region = scoped.configuredRegion;
|
|
13578
|
+
const key = region ?? "";
|
|
13579
|
+
const cached = this.serviceDiscoveryClients.get(key);
|
|
13580
|
+
if (cached) return cached;
|
|
13581
|
+
const building = (async () => {
|
|
13582
|
+
const { ServiceDiscoveryClient } = await import("@aws-sdk/client-servicediscovery");
|
|
13583
|
+
return new ServiceDiscoveryClient({
|
|
13584
|
+
...scoped.credentialConfig ?? {},
|
|
13585
|
+
...region ? { region } : {}
|
|
13586
|
+
});
|
|
13587
|
+
})();
|
|
13588
|
+
this.serviceDiscoveryClients.set(key, building);
|
|
13589
|
+
building.catch(() => {
|
|
13590
|
+
if (this.serviceDiscoveryClients.get(key) === building) this.serviceDiscoveryClients.delete(key);
|
|
13591
|
+
});
|
|
13592
|
+
return building;
|
|
13593
|
+
}
|
|
13594
|
+
/**
|
|
13477
13595
|
* Resolve parameter values from template Parameters section
|
|
13478
13596
|
*
|
|
13479
13597
|
* Merges default values from template with user-provided parameter values.
|
|
@@ -13884,8 +14002,7 @@ var IntrinsicFunctionResolver = class {
|
|
|
13884
14002
|
case "VpcId": return physicalId;
|
|
13885
14003
|
case "CidrBlock": return resource.attributes?.["CidrBlock"] || resource.properties?.["CidrBlock"];
|
|
13886
14004
|
case "Ipv6CidrBlocks": try {
|
|
13887
|
-
const
|
|
13888
|
-
const ec2 = new EC2Client({ region: this.resolverRegion });
|
|
14005
|
+
const ec2 = this.clientsForRegion(this.explicitRegion).ec2;
|
|
13889
14006
|
const maxAttempts = 15;
|
|
13890
14007
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
13891
14008
|
const associations = (await ec2.send(new DescribeVpcsCommand({ VpcIds: [physicalId] }))).Vpcs?.[0]?.Ipv6CidrBlockAssociationSet || [];
|
|
@@ -13984,8 +14101,8 @@ var IntrinsicFunctionResolver = class {
|
|
|
13984
14101
|
case "Arn": return `arn:${partition}:servicediscovery:${region}:${accountId}:namespace/${physicalId}`;
|
|
13985
14102
|
case "Id": return physicalId;
|
|
13986
14103
|
case "HostedZoneId": try {
|
|
13987
|
-
const {
|
|
13988
|
-
return (await
|
|
14104
|
+
const { GetNamespaceCommand } = await import("@aws-sdk/client-servicediscovery");
|
|
14105
|
+
return (await (await this.serviceDiscoveryClient()).send(new GetNamespaceCommand({ Id: physicalId }))).Namespace?.Properties?.DnsProperties?.HostedZoneId;
|
|
13989
14106
|
} catch (error) {
|
|
13990
14107
|
this.logger.warn(`Failed to fetch HostedZoneId for namespace ${physicalId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
13991
14108
|
return;
|
|
@@ -14542,6 +14659,174 @@ var IntrinsicFunctionResolver = class {
|
|
|
14542
14659
|
return result;
|
|
14543
14660
|
}
|
|
14544
14661
|
/**
|
|
14662
|
+
* Re-resolve the dynamic references in a value read out of a PRODUCER
|
|
14663
|
+
* stack's persisted outputs, before it is handed to the consumer (issue
|
|
14664
|
+
* [#1934](https://github.com/go-to-k/cdkd/issues/1934)).
|
|
14665
|
+
*
|
|
14666
|
+
* Since PR #1899 a secret-bearing output is stored REDACTED: `state.outputs`
|
|
14667
|
+
* and the exports index hold `{{resolve:secretsmanager:X:SecretString:pw}}`
|
|
14668
|
+
* rather than the resolved value. A consumer resolving `Fn::ImportValue` /
|
|
14669
|
+
* `Fn::GetStackOutput` got that string back VERBATIM and shipped the literal
|
|
14670
|
+
* token to AWS as the property value — the GHSA-p5qg-v9gv-hc7w class one
|
|
14671
|
+
* more time, and the same shape `resolveReplayProps` fixed for the rollback
|
|
14672
|
+
* replay and #1914 fixed for `cdkd drift --revert`. Every place that reads a
|
|
14673
|
+
* REDACTED bag and hands it onward has to re-resolve first; the cross-stack
|
|
14674
|
+
* import edge was the one that was missed, because the redaction and the
|
|
14675
|
+
* consumption live in different stacks and often different runs.
|
|
14676
|
+
*
|
|
14677
|
+
* `{{resolve:` re-resolution in {@link resolveValue} could not cover it: that
|
|
14678
|
+
* arm fires for a leaf INPUT string, and an intrinsic's RETURN value is never
|
|
14679
|
+
* fed back through it.
|
|
14680
|
+
*
|
|
14681
|
+
* WHICH REGION RESOLVES IT — the producer's, via {@link
|
|
14682
|
+
* resolverForProducerRegion}. The expression was resolved BY the producer IN
|
|
14683
|
+
* the producer's region, so that is the only region whose answer reproduces
|
|
14684
|
+
* the value the producer exported; a Secrets Manager secret or an SSM
|
|
14685
|
+
* parameter of the same NAME in two regions is two independent values (issue
|
|
14686
|
+
* #1933). The index hit carries `entry.producerRegion` (a required field) and
|
|
14687
|
+
* `Fn::GetStackOutput` carries the reference's own resolved `Region`, so both
|
|
14688
|
+
* of those are recorded rather than inferred.
|
|
14689
|
+
*
|
|
14690
|
+
* THE SCAN ARM IS THE EXCEPTION, and it is one this method inherits rather
|
|
14691
|
+
* than introduces: a pre-v2 state record has no `region` field, so the scan
|
|
14692
|
+
* reads it as `refRegion ?? this.resolverRegion` — the consumer's own region,
|
|
14693
|
+
* itself defaulted through `AWS_REGION` and then `us-east-1`. Where that
|
|
14694
|
+
* guess is wrong the re-resolution asks the wrong region and either misses
|
|
14695
|
+
* the secret (a hard failure naming the lookup, since issue #1934's
|
|
14696
|
+
* restructure surfaces it rather than degrading to `export not found`) or
|
|
14697
|
+
* resolves a same-named secret in the consumer's region. What bounds it is
|
|
14698
|
+
* that the guess is the SAME one the state READ just used, so this method
|
|
14699
|
+
* cannot disagree with the record it was handed — a region-less record was
|
|
14700
|
+
* already being read from that region or not found at all. The honest fix is
|
|
14701
|
+
* a region on the record, which is what schema v2 did for every record
|
|
14702
|
+
* written since.
|
|
14703
|
+
*
|
|
14704
|
+
* WHICH CREDENTIALS — the consumer's, which are the producer's too for every
|
|
14705
|
+
* path that reaches here. `Fn::ImportValue` reads the account-scoped state
|
|
14706
|
+
* bucket / exports index, so a producer it can see is in this account by
|
|
14707
|
+
* construction, and the cross-ACCOUNT `Fn::GetStackOutput` path deliberately
|
|
14708
|
+
* never calls this (see the refusal at its call site) rather than resolving a
|
|
14709
|
+
* producer's expression under the consumer's identity — which would answer
|
|
14710
|
+
* from a same-named secret in the WRONG account, the #1957 disclosure shape.
|
|
14711
|
+
*
|
|
14712
|
+
* The CONSUMER's `context` is passed through, which is what keeps the
|
|
14713
|
+
* consumer's own state redacted: each resolved plaintext is recorded into its
|
|
14714
|
+
* `recordedSecretValues`, so the deploy engine's save choke point rewrites it
|
|
14715
|
+
* back to the expression on the way into `state.json`. It also means
|
|
14716
|
+
* `skipDynamicReferences` is honoured, so the diff / no-op path keeps
|
|
14717
|
+
* comparing expression-vs-expression instead of fetching a secret to print.
|
|
14718
|
+
*
|
|
14719
|
+
* Identity-returns a value carrying no `{{resolve:` at all, so an ordinary
|
|
14720
|
+
* import is untouched.
|
|
14721
|
+
*
|
|
14722
|
+
* THE WALK BELOW IS A THIRD COPY, and that is recorded rather than fixed.
|
|
14723
|
+
* `rollback-executor.ts`'s `resolveReplayProps` carries the same descent, and
|
|
14724
|
+
* `drift.ts`'s `resolveStateSecretExpressions` the same idea. Extracting one
|
|
14725
|
+
* helper is the right end state and is NOT this change's to make: the natural
|
|
14726
|
+
* home is beside those callers, in files a parallel lane owns, and a
|
|
14727
|
+
* cross-module extraction done from here would edit them. Two things a future
|
|
14728
|
+
* extractor needs that a mechanical merge would drop: this copy rebuilds
|
|
14729
|
+
* objects with `Object.create(null)` (the `__proto__` hazard below), and it
|
|
14730
|
+
* takes the RESOLVER as a parameter because the region it must answer for is
|
|
14731
|
+
* the producer's rather than the caller's — which is exactly what issue #2057
|
|
14732
|
+
* says the other two copies get wrong.
|
|
14733
|
+
*/
|
|
14734
|
+
async reresolveCrossStackValue(value, producerRegion, context, origin) {
|
|
14735
|
+
if (!carriesDynamicReference(value)) return value;
|
|
14736
|
+
const resolver = this.resolverForProducerRegion(producerRegion);
|
|
14737
|
+
const walk = async (v) => {
|
|
14738
|
+
if (typeof v === "string") return v.includes("{{resolve:") ? await resolver.resolveDynamicReferences(v, context) : v;
|
|
14739
|
+
if (Array.isArray(v)) {
|
|
14740
|
+
const out = new Array(v.length);
|
|
14741
|
+
for (let i = 0; i < v.length; i++) out[i] = await walk(v[i]);
|
|
14742
|
+
return out;
|
|
14743
|
+
}
|
|
14744
|
+
if (v !== null && typeof v === "object") {
|
|
14745
|
+
const out = Object.create(null);
|
|
14746
|
+
for (const [k, val] of Object.entries(v)) out[k] = await walk(val);
|
|
14747
|
+
return out;
|
|
14748
|
+
}
|
|
14749
|
+
return v;
|
|
14750
|
+
};
|
|
14751
|
+
this.logger.debug(`Re-resolving dynamic reference(s) in ${origin}`);
|
|
14752
|
+
return await walk(value);
|
|
14753
|
+
}
|
|
14754
|
+
/**
|
|
14755
|
+
* The resolver that must answer for a PRODUCER region — `this` when the
|
|
14756
|
+
* producer shares this resolver's own region, otherwise the pinned sibling
|
|
14757
|
+
* from {@link producerRegionResolvers}.
|
|
14758
|
+
*
|
|
14759
|
+
* The comparison is against {@link explicitRegion}, NOT {@link
|
|
14760
|
+
* resolverRegion}: the latter substitutes `AWS_REGION` and then a hard-coded
|
|
14761
|
+
* `us-east-1`, so comparing against it would answer "same region" on the
|
|
14762
|
+
* strength of a guess and resolve the producer's expression against whatever
|
|
14763
|
+
* the ambient clients happen to point at. When no region was named, a producer
|
|
14764
|
+
* region that IS named still binds — "unknown means SCOPE, not skip", the same
|
|
14765
|
+
* rule {@link clientsForRegion} applies, and the same one `Fn::GetAZs` already
|
|
14766
|
+
* follows for its template-named region.
|
|
14767
|
+
*/
|
|
14768
|
+
/**
|
|
14769
|
+
* Pin (or retract) a `{{resolve:...}}` secret verdict in the PROCESS-GLOBAL
|
|
14770
|
+
* store — unless this resolver is a producer-region GUEST, which writes
|
|
14771
|
+
* nothing there.
|
|
14772
|
+
*
|
|
14773
|
+
* The guest suppression is the correction the review of issue #1934 forced,
|
|
14774
|
+
* and the isolation note on {@link producerRegionResolvers} used to overstate
|
|
14775
|
+
* what a per-region resolver bought. The value cache is per-instance, but the
|
|
14776
|
+
* VERDICT store is not this class's — it lives in `secret-redaction.ts` and is
|
|
14777
|
+
* keyed by the expression STRING alone, so a producer-region resolution would
|
|
14778
|
+
* pin a FOREIGN region's answer for the whole process. The consequence is
|
|
14779
|
+
* concrete, and it lands on the consumer's very next pass: `isKnownSecret`
|
|
14780
|
+
* consults that store, and on the `skipDynamicReferences` (diff / no-op) path
|
|
14781
|
+
* a `true` verdict SKIPS the lookup and leaves the expression unresolved — so
|
|
14782
|
+
* a consumer-region parameter that is a plain `String`, and which state
|
|
14783
|
+
* therefore holds RESOLVED, would be compared as an expression and report a
|
|
14784
|
+
* spurious change on every run. That is issue #1901's perpetual-UPDATE class,
|
|
14785
|
+
* arriving through a region boundary the store cannot see.
|
|
14786
|
+
*
|
|
14787
|
+
* KEYING THE STORE BY REGION IS THE BETTER FIX AND IS NOT AVAILABLE FROM
|
|
14788
|
+
* HERE. `secret-redaction.ts` reads its own store internally with the BARE
|
|
14789
|
+
* expression (`isKnownSecretExpression`, and the mixed-leaf public-reference
|
|
14790
|
+
* test), so a region-qualified key would silently stop matching for the
|
|
14791
|
+
* redaction path — losing the #1910 losing-member arm and changing the #1926
|
|
14792
|
+
* empty-map verdict — and that file is owned by another lane in this run.
|
|
14793
|
+
* Suppressing the WRITE is the half that is correct on its own: it removes
|
|
14794
|
+
* the new cross-region reachability without changing the key, and the
|
|
14795
|
+
* consumer's own resolver keeps pinning its own region's verdicts exactly as
|
|
14796
|
+
* before.
|
|
14797
|
+
*
|
|
14798
|
+
* READS are deliberately NOT suppressed. A guest reading the consumer's
|
|
14799
|
+
* verdict can only seed `isKnownSecret`, which for `ssm` is OVERWRITTEN by
|
|
14800
|
+
* the fresh `GetParameter` response, and on the skip path it produces the
|
|
14801
|
+
* unresolved expression the diff wants anyway. Only the write direction
|
|
14802
|
+
* carried the defect.
|
|
14803
|
+
*
|
|
14804
|
+
* The cost of suppressing is one `GetParameter` per foreign expression per
|
|
14805
|
+
* later pass, since the guest's OWN instance cache still carries the verdict
|
|
14806
|
+
* alongside the value (issue #1933's design) and answers every repeat within
|
|
14807
|
+
* the deploy.
|
|
14808
|
+
*/
|
|
14809
|
+
pinSecretVerdict(expression, secret) {
|
|
14810
|
+
if (this.producerRegionGuest) return;
|
|
14811
|
+
if (secret) recordedSecretExpressions.add(expression);
|
|
14812
|
+
else recordedSecretExpressions.delete(expression);
|
|
14813
|
+
}
|
|
14814
|
+
resolverForProducerRegion(producerRegion) {
|
|
14815
|
+
if (!producerRegion) return this;
|
|
14816
|
+
const target = canonicalizeRegion(producerRegion);
|
|
14817
|
+
if (target === canonicalizeRegion(this.explicitRegion)) return this;
|
|
14818
|
+
const cached = this.producerRegionResolvers.get(target);
|
|
14819
|
+
if (cached) return cached;
|
|
14820
|
+
const scoped = new IntrinsicFunctionResolver(target, {
|
|
14821
|
+
strictGetAtt: this.strictGetAtt,
|
|
14822
|
+
cfnFallback: this.cfnFallback
|
|
14823
|
+
});
|
|
14824
|
+
scoped.producerRegionGuest = true;
|
|
14825
|
+
this.producerRegionResolvers.set(target, scoped);
|
|
14826
|
+
this.logger.debug(`Using a producer-region resolver for ${target}`);
|
|
14827
|
+
return scoped;
|
|
14828
|
+
}
|
|
14829
|
+
/**
|
|
14545
14830
|
* Resolve Fn::ImportValue (cross-stack references)
|
|
14546
14831
|
*
|
|
14547
14832
|
* Searches all other stacks for an exported output with the given name.
|
|
@@ -14551,18 +14836,23 @@ var IntrinsicFunctionResolver = class {
|
|
|
14551
14836
|
if (typeof exportName !== "string") throw new Error(`Fn::ImportValue: export name must resolve to a string, got ${typeof exportName}`);
|
|
14552
14837
|
if (!context.stateBackend) throw new Error("Fn::ImportValue: state backend is required for cross-stack references");
|
|
14553
14838
|
this.logger.debug(`Resolving Fn::ImportValue: ${exportName}`);
|
|
14554
|
-
if (context.exportIndex)
|
|
14555
|
-
|
|
14839
|
+
if (context.exportIndex) {
|
|
14840
|
+
let entry;
|
|
14841
|
+
try {
|
|
14842
|
+
entry = await context.exportIndex.lookup(exportName);
|
|
14843
|
+
} catch (err) {
|
|
14844
|
+
this.logger.warn(`Exports index lookup failed for '${exportName}': ${err instanceof Error ? err.message : String(err)}; falling back to state.json scan`);
|
|
14845
|
+
entry = void 0;
|
|
14846
|
+
}
|
|
14556
14847
|
if (entry && (!context.stackName || entry.producerStack !== context.stackName)) {
|
|
14557
14848
|
this.recordImport(context, exportName, entry.producerStack, entry.producerRegion);
|
|
14558
14849
|
this.logger.info(`Resolved Fn::ImportValue: ${exportName} = ${JSON.stringify(entry.value)} (from index: ${entry.producerStack} / ${entry.producerRegion})`);
|
|
14559
|
-
return entry.value;
|
|
14850
|
+
return await this.reresolveCrossStackValue(entry.value, entry.producerRegion, context, `Fn::ImportValue '${exportName}' (producer ${entry.producerStack} / ${entry.producerRegion})`);
|
|
14560
14851
|
}
|
|
14561
|
-
} catch (err) {
|
|
14562
|
-
this.logger.warn(`Exports index lookup failed for '${exportName}': ${err instanceof Error ? err.message : String(err)}; falling back to state.json scan`);
|
|
14563
14852
|
}
|
|
14564
14853
|
const allStacks = await context.stateBackend.listStacks();
|
|
14565
14854
|
this.logger.debug(`Found ${allStacks.length} state record(s) to search for export: ${exportName}`);
|
|
14855
|
+
let found;
|
|
14566
14856
|
for (const ref of allStacks) {
|
|
14567
14857
|
const { stackName: refStack, region: refRegion } = ref;
|
|
14568
14858
|
if (context.stackName && refStack === context.stackName) {
|
|
@@ -14592,13 +14882,19 @@ var IntrinsicFunctionResolver = class {
|
|
|
14592
14882
|
this.logger.debug(`Failed to patch exports index for '${exportName}': ${err instanceof Error ? err.message : String(err)}`);
|
|
14593
14883
|
});
|
|
14594
14884
|
this.recordImport(context, exportName, refStack, lookupRegion);
|
|
14595
|
-
|
|
14885
|
+
found = {
|
|
14886
|
+
value,
|
|
14887
|
+
refStack,
|
|
14888
|
+
lookupRegion
|
|
14889
|
+
};
|
|
14890
|
+
break;
|
|
14596
14891
|
}
|
|
14597
14892
|
} catch (error) {
|
|
14598
14893
|
this.logger.warn(`Failed to read state for stack ${refStack}: ${error instanceof Error ? error.message : String(error)}`);
|
|
14599
14894
|
continue;
|
|
14600
14895
|
}
|
|
14601
14896
|
}
|
|
14897
|
+
if (found) return await this.reresolveCrossStackValue(found.value, found.lookupRegion, context, `Fn::ImportValue '${exportName}' (producer ${found.refStack} / ${found.lookupRegion})`);
|
|
14602
14898
|
if (this.cfnFallback) {
|
|
14603
14899
|
const cfnExport = await this.lookupCfnExport(exportName);
|
|
14604
14900
|
if (cfnExport) {
|
|
@@ -14817,7 +15113,8 @@ var IntrinsicFunctionResolver = class {
|
|
|
14817
15113
|
const value = outputs[outputName];
|
|
14818
15114
|
this.logger.info(`Resolved Fn::GetStackOutput: StackName=${stackName}, Region=${region}, OutputName=${outputName}${roleArn ? `, RoleArn=${roleArn}` : ""} -> ${JSON.stringify(value)}`);
|
|
14819
15115
|
if (!roleArn) this.recordOutputRead(context, stackName, region, outputName);
|
|
14820
|
-
|
|
15116
|
+
if (roleArn && !context.skipDynamicReferences && carriesDynamicReference(value)) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Fn::GetStackOutput: output '${outputName}' of stack '${stackName}' (${region}) is a redacted dynamic reference, and this is a CROSS-ACCOUNT reference (RoleArn ${roleArn}). cdkd will not resolve a producer account's secret with the consumer's credentials — a same-named secret in the consumer account would answer instead. Export a non-secret value (e.g. the secret's ARN) and resolve it in the consumer stack, or reference the producer stack from within its own account.`));
|
|
15117
|
+
return await this.reresolveCrossStackValue(value, region, context, `Fn::GetStackOutput '${outputName}' (producer ${stackName} / ${region})`);
|
|
14821
15118
|
}
|
|
14822
15119
|
/**
|
|
14823
15120
|
* Push a resolved `Fn::GetStackOutput` into the consumer's
|
|
@@ -15091,8 +15388,8 @@ var IntrinsicFunctionResolver = class {
|
|
|
15091
15388
|
else if (service === "ssm") {
|
|
15092
15389
|
const decrypt = context?.skipDynamicReferences !== true;
|
|
15093
15390
|
const param = await this.resolveSSMReference(parts, decrypt);
|
|
15094
|
-
if (param.type === "SecureString")
|
|
15095
|
-
else if (!param.secure)
|
|
15391
|
+
if (param.type === "SecureString") this.pinSecretVerdict(fullMatch, true);
|
|
15392
|
+
else if (!param.secure) this.pinSecretVerdict(fullMatch, false);
|
|
15096
15393
|
else cacheable = false;
|
|
15097
15394
|
isSecret = param.secure;
|
|
15098
15395
|
if (param.secure) {
|
|
@@ -15109,7 +15406,7 @@ var IntrinsicFunctionResolver = class {
|
|
|
15109
15406
|
});
|
|
15110
15407
|
if (isSecret && resolved) {
|
|
15111
15408
|
context?.recordedSecretValues?.set(resolved, fullMatch);
|
|
15112
|
-
if (service === "secretsmanager")
|
|
15409
|
+
if (service === "secretsmanager") this.pinSecretVerdict(fullMatch, true);
|
|
15113
15410
|
}
|
|
15114
15411
|
result = result.replace(fullMatch, () => resolved);
|
|
15115
15412
|
}
|
|
@@ -16206,7 +16503,7 @@ var CloudControlProvider = class {
|
|
|
16206
16503
|
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);
|
|
16207
16504
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
16208
16505
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
16209
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
16506
|
+
const { ASGProvider } = await import("./asg-provider-BZwQzb8e.js").then((n) => n.n);
|
|
16210
16507
|
return await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
|
|
16211
16508
|
}
|
|
16212
16509
|
const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
|
|
@@ -18884,7 +19181,7 @@ const PROPERTY_COVERAGE_BY_TYPE = /* @__PURE__ */ new Map([
|
|
|
18884
19181
|
silentDrop: /* @__PURE__ */ new Map([
|
|
18885
19182
|
["IssuerConfiguration", "not yet implemented by cdkd"],
|
|
18886
19183
|
["KeyConfiguration", "not yet implemented by cdkd"],
|
|
18887
|
-
["WebAuthnFactorConfiguration", "No
|
|
19184
|
+
["WebAuthnFactorConfiguration", "No wire path in the PINNED SDK (@aws-sdk/client-cognito-identity-provider 3.1018.0): WebAuthnConfigurationType is {RelyingPartyId?, UserVerification?} and no CreateUserPool/UpdateUserPool field accepts SINGLE_FACTOR | MULTI_FACTOR_WITH_USER_VERIFICATION. This is an SDK-VERSION limit, NOT an API one -- the live API does honour FactorConfiguration (measured us-east-1 2026-08-20: SetUserPoolMfaConfig(ON) on a pool allowing WEB_AUTHN as a first auth factor is rejected with \"Cannot set WebAuthn factor configuration to SINGLE_FACTOR if MFA is required and WebAuthn is an allowed first auth factor\", i.e. the service reads a field the SDK cannot send). RE-EVALUATE THIS ENTRY ON AN SDK BUMP: if WebAuthnConfigurationType gains FactorConfiguration, the property becomes handleable and this entry must go"]
|
|
18888
19185
|
])
|
|
18889
19186
|
}],
|
|
18890
19187
|
["AWS::DLM::LifecyclePolicy", {
|
|
@@ -24304,7 +24601,7 @@ const FLUSH_INTERVAL_MS = 2e3;
|
|
|
24304
24601
|
const FLUSH_EVENT_THRESHOLD = 50;
|
|
24305
24602
|
/** Build-time cdkd version, with a dev fallback for non-built contexts. */
|
|
24306
24603
|
function getCdkdVersion() {
|
|
24307
|
-
return "0.284.
|
|
24604
|
+
return "0.284.15";
|
|
24308
24605
|
}
|
|
24309
24606
|
/**
|
|
24310
24607
|
* Generate a time-sortable unique run id, e.g.
|
|
@@ -26844,4 +27141,4 @@ var DeployEngine = class {
|
|
|
26844
27141
|
|
|
26845
27142
|
//#endregion
|
|
26846
27143
|
export { isTerminationProtectionPropagationError as $, ResourceUpdateNotSupportedError as $n, validateAssetBucketName as $t, renderStatefulReason as A, derivePartitionAndUrlSuffix as An, INTRINSIC_KEYS as At, exportAliasCollisionScrubWarning as B, CdkdError as Bn, stringifyValue as Bt, isFinalSnapshotError as C, CFN_TEMPLATE_URL_LIMIT as Cn, s3BucketArn as Ct, extractDeploymentEventError as D, expectedOwnerParam as Dn, s3BucketWebsiteUrl as Dt, makeCanonicalizePropertiesFn as E, uploadCfnTemplate as En, s3BucketRegionalDomainName as Et, green as F, AwsClients as Fn, LockManager as Ft, collectInlinePolicyNamesManagedBySiblings as G, LocalMigrateError as Gn, rewriteTemplateAssetReferences as Gt, secretBearingStateKeyWarning as H, DependencyError as Hn, buildAssetRedirectMap as Ht, red as I, getAwsClients as In, S3StateBackend as It, findActionableSilentDrops as J, MissingCdkCliError as Jn, AssetModeResolver as Jt, clearOnUpdateRemoval as K, LocalStartServiceError as Kn, escapeRegExp$1 as Kt, yellow as L, resetAwsClients as Ln, rebuildClientForBucketRegion as Lt, bold as M, processStackMessages as Mn, withRetry as Mt, cyan as N, clearBucketRegionCache as Nn, DagBuilder as Nt, MULTI_REGION_RECREATE_BLOCKED_TYPES as O, PARTITION_TABLE as On, applyRoleArnIfSet as Ot, gray as P, resolveBucketRegion as Pn, TemplateParser as Pt, disableInstanceApiTermination as Q, ResourceTimeoutError as Qn, parseBootstrapMarker as Qt, collectDeclaredOutputNames as R, setAwsClients as Rn, shouldRetainResource as Rt, createPreDeleteFinalSnapshot as S, CFN_TEMPLATE_BODY_LIMIT as Sn, scrubResourceRecord as St, unsupportedFinalSnapshotError as T, findLargeInlineResources as Tn, s3BucketDualStackDomainName as Tt, stateKeySecretExposure as U, DeployCancelledError as Un, createAssetRedirectResolver as Ut, isExportAliasCollision as V, ConfigError as Vn, WorkGraph as Vt, IAMRoleProvider as W, LocalInvokeBuildError as Wn, loadPublishableAssetManifest as Wt, CloudControlProvider as X, PartialFailureError as Xn, ensureAssetStorage as Xt, findSilentDropProperties as Y, NestedStackChildDirectDestroyError as Yn, BOOTSTRAP_MARKER_PREFIX as Yt, slowCcOperationTimeoutMs as Z, ProvisioningError as Zn, getBootstrapMarkerKey as Zt, computeImplicitDeleteEdges as _, resolveStateBucketWithDefault as _n, STATE_SOURCED_READBACK_RULES as _t, DeploymentEventsStore as a, runDockerForeground as an, isCdkdError as ar, normalizeAwsTagsToCfn as at, buildFinalSnapshotIdentifier as b, stateBucketExistenceConfirmed as bn, maskSecretsInText as bt, replayFailedOperations as c, getDockerImageBySourceHash as cn, isMarkedNonRetryable as cr, coerceCfnBoolean as ct, updatePartialReason as d, getDefaultStateBucketName as dn, markNonRetryable as dr, readConfigString as dt, validateContainerRepoName as en, StackHasActiveImportsError as er, IntrinsicFunctionResolver as et, UNSPECIFIED_SKIP_REASON as f, getLegacyStateBucketName as fn, __exportAll as fr, replayWarn as ft, IMPLICIT_DELETE_DEPENDENCIES as g, resolveSkipPrefix as gn, STATE_SOURCED_CROSS_GENERATION_RULES as gt, maskingRetryLogger as h, resolveCaptureObservedState as hn, requireConfigString as ht, DeploymentEventsReader as i, getDockerCmd as in, formatError as ir, WAFv2WebACLProvider as it, formatResourceLine as j, AssemblyReader as jn, describeTypeWithThrottleRetry as jt, isStatefulRecreateTargetSync as k, canonicalizeRegion as kn, DiffCalculator as kt, replayRollback as l, Synthesizer as ln, isRetryableTransientError as lr, configBooleanRefusal as lt, withResourceDeadline as m, resolveAutoAssetStorage as mn, requireConfigObject as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, buildDockerImage as nn, StateError as nr, getAccountInfo as nt, planFailedOps as o, runDockerStreaming as on, normalizeAwsError as or, resolveExplicitPhysicalId as ot, deleteSkipReason as p, resolveApp as pn, requireConfigArray as pt, ProviderRegistry as q, LockError as qn, stripControlChars as qt, DeployEngine as r, formatDockerLoginError as rn, SynthesisError as rr, refStateLookupFromResource as rt, planRollback as s, AssetManifestLoader as sn, withErrorHandling as sr, assertRegionMatch as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, buildDenyExternalAccessPolicy as tn, StackTerminationProtectionError as tr, cfnRefValueFromPhysicalId as tt, updatePartialMessage as u, synthesisStatusMessage as un, isThrottlingError as ur, configStringRefusal as ut, ATOMIC_FINAL_SNAPSHOT_TYPES as v, resolveStateBucketWithDefaultAndSource as vn, TEMPLATE_SOURCED_RULES as vt, refusesFinalSnapshot as w, MIGRATE_TMP_PREFIX as wn, s3BucketDomainName as wt, ccRoutedFinalSnapshotError as x, warnDeprecatedNoPrefixCliFlag as xn, redactSecretsForState as xt, PRE_DELETE_SNAPSHOT_TYPES as y, resolveUseCdkBootstrapAssets as yn, createSecretMasker as yt, collectPublishedOutputNames as z, AssetError as zn, AssetPublisher as zt };
|
|
26847
|
-
//# sourceMappingURL=deploy-engine-
|
|
27144
|
+
//# sourceMappingURL=deploy-engine-BNujuzW5.js.map
|