@go-to-k/cdkd 0.284.35 → 0.284.37
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-bJ8nim2C.js → asg-provider-BbWubpjP.js} +2 -2
- package/dist/{asg-provider-bJ8nim2C.js.map → asg-provider-BbWubpjP.js.map} +1 -1
- package/dist/cli.js +182 -106
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-Wei5Yw-_.js → deploy-engine-C62UGXid.js} +296 -222
- package/dist/deploy-engine-C62UGXid.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-Wei5Yw-_.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as
|
|
2
|
+
import { $ as startInterruptWatch, $t as buildAssetRedirectMap, A as renderStatefulReason, An as resolveStateBucketWithDefaultAndSource, At as redactSecretsForState, B as exportAliasCollisionScrubWarning, Bn as PARTITION_TABLE, Bt as DiffCalculator, C as isFinalSnapshotError, Cn as getDefaultStateBucketName, Cr as isRetryableTransientError, Ct as TEMPLATE_SOURCED_RULES, D as extractDeploymentEventError, Dn as resolveCaptureObservedState, Dt as isSingleDynamicReferenceToken, E as makeCanonicalizePropertiesFn, En as resolveAutoAssetStorage, Et as errorCauseChain, F as green, Fn as CFN_TEMPLATE_URL_LIMIT, Ft as s3BucketDomainName, G as collectInlinePolicyNamesManagedBySiblings, Gt as TemplateParser, H as secretBearingStateKeyWarning, Hn as derivePartitionAndUrlSuffix, Ht as describeTypeWithThrottleRetry, I as red, In as MIGRATE_TMP_PREFIX, It as s3BucketDualStackDomainName, J as findActionableSilentDrops, Jn as getAwsClients, Jt as rebuildClientForBucketRegion, K as clearOnUpdateRemoval, Kn as resolveBucketRegion, Kt as LockManager, L as yellow, Ln as findLargeInlineResources, Lt as s3BucketRegionalDomainName, M as bold, Mn as stateBucketExistenceConfirmed, Mt as classifyReplaySecretRegion, N as cyan, Nn as warnDeprecatedNoPrefixCliFlag, Nt as producerRegionsFromState, O as MULTI_REGION_RECREATE_BLOCKED_TYPES, On as resolveSkipPrefix, Ot as maskSecretsInError, P as gray, Pn as CFN_TEMPLATE_BODY_LIMIT, Pt as s3BucketArn, Q as isInterruptedWaitError, Qn as CdkdError, Qt as WorkGraph, R as collectDeclaredOutputNames, Rn as uploadCfnTemplate, Rt as s3BucketWebsiteUrl, S as createPreDeleteFinalSnapshot, Sn as synthesisStatusMessage, Sr as isMarkedNonRetryable, St as STATE_SOURCED_READBACK_RULES, T as unsupportedFinalSnapshotError, Tn as resolveApp, Tr as markNonRetryable, Tt as dynamicReferenceTokens, U as stateKeySecretExposure, Un as AssemblyReader, Ut as withRetry, V as isExportAliasCollision, Vn as canonicalizeRegion, Vt as INTRINSIC_KEYS, W as IAMRoleProvider, Wn as processStackMessages, Wt as DagBuilder, X as beginCommandInterruptScope, Xn as setAwsClients, Xt as AssetPublisher, Y as findSilentDropProperties, Yn as resetAwsClients, Yt as shouldRetainResource, Z as endCommandInterruptScope, Zt as stringifyValue, _ as computeImplicitDeleteEdges, _n as runDockerForeground, _t as replayWarn, a as DeploymentEventsStore, an as AssetModeResolver, ar as LocalMigrateError, at as carriesDynamicReference, b as buildFinalSnapshotIdentifier, bn as getDockerImageBySourceHash, br as normalizeAwsError, bt as requireConfigString, c as replayFailedOperations, cn as getBootstrapMarkerKey, cr as MissingCdkCliError, ct as refStateLookupFromResource, d as updatePartialReason, dn as validateAssetBucketName, dr as ProvisioningError, dt as resolveExplicitPhysicalId, en as createAssetRedirectResolver, er as CrossAccountSecretRefusalError, et as CloudControlProvider, f as UNSPECIFIED_SKIP_REASON, fn as validateContainerRepoName, fr as ResourceTimeoutError, ft as assertRegionMatch, g as IMPLICIT_DELETE_DEPENDENCIES, gn as getDockerCmd, gt as readConfigString, h as maskingRetryLogger, hn as formatDockerLoginError, hr as StackTerminationProtectionError, ht as configStringRefusal, i as DeploymentEventsReader, in as stripControlChars, ir as LocalInvokeBuildError$1, it as IntrinsicFunctionResolver, j as formatResourceLine, jn as resolveUseCdkBootstrapAssets, jt as scrubResourceRecord, k as isStatefulRecreateTargetSync, kn as resolveStateBucketWithDefault, kt as maskSecretsInText, l as replayRollback, ln as parseBootstrapMarker, lr as NestedStackChildDirectDestroyError, lt as WAFv2WebACLProvider, m as withResourceDeadline, mn as buildDockerImage, mr as StackHasActiveImportsError, mt as configBooleanRefusal, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as rewriteTemplateAssetReferences, nr as DeployCancelledError, nt as disableInstanceApiTermination, o as planFailedOps, on as BOOTSTRAP_MARKER_PREFIX, or as LocalStartServiceError, ot as cfnRefValueFromPhysicalId, p as deleteSkipReason, pn as buildDenyExternalAccessPolicy, pr as ResourceUpdateNotSupportedError, pt as coerceCfnBoolean, q as ProviderRegistry, qn as AwsClients, qt as S3StateBackend, r as DeployEngine, rn as escapeRegExp, rr as DynamicReferenceRegionAmbiguousError, rt as isTerminationProtectionPropagationError, s as planRollback, sn as ensureAssetStorage, st as getAccountInfo, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as loadPublishableAssetManifest, tt as slowCcOperationTimeoutMs, u as updatePartialMessage, un as readBootstrapMarkerBody, ur as PartialFailureError, ut as normalizeAwsTagsToCfn, v as ATOMIC_FINAL_SNAPSHOT_TYPES, vn as runDockerStreaming, vt as requireConfigArray, w as refusesFinalSnapshot, wn as getLegacyStateBucketName, wr as isThrottlingError, wt as createSecretMasker, x as ccRoutedFinalSnapshotError, xn as Synthesizer, xr as withErrorHandling, xt as STATE_SOURCED_CROSS_GENERATION_RULES, y as PRE_DELETE_SNAPSHOT_TYPES, yn as AssetManifestLoader, yt as requireConfigObject, z as collectPublishedOutputNames, zn as expectedOwnerParam, zt as applyRoleArnIfSet } from "./deploy-engine-C62UGXid.js";
|
|
3
3
|
import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, f as getCurrentStackName, h as withStackName, i as runStackBuffered, m as withSkipPrefix, n as getLogger, o as PATTERN_B_NAME_OPTIONS, s as PATTERN_B_NAME_PROPERTIES, u as generateResourceName } from "./logger-zRrlbaQt.js";
|
|
4
|
-
import { t as ASGProvider } from "./asg-provider-
|
|
4
|
+
import { t as ASGProvider } from "./asg-provider-BbWubpjP.js";
|
|
5
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
6
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
7
7
|
import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketEncryptionCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketOwnershipControlsCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketOwnershipControlsCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchBucket, PutBucketAccelerateConfigurationCommand, PutBucketAnalyticsConfigurationCommand, PutBucketCorsCommand, PutBucketEncryptionCommand, PutBucketIntelligentTieringConfigurationCommand, PutBucketInventoryConfigurationCommand, PutBucketLifecycleConfigurationCommand, PutBucketLoggingCommand, PutBucketMetricsConfigurationCommand, PutBucketNotificationConfigurationCommand, PutBucketOwnershipControlsCommand, PutBucketPolicyCommand, PutBucketReplicationCommand, PutBucketTaggingCommand, PutBucketVersioningCommand, PutBucketWebsiteCommand, PutObjectCommand, PutObjectLockConfigurationCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
@@ -62709,28 +62709,6 @@ function redactUnaccountedOutputs(positioned, stored, accountedKeys, secrets) {
|
|
|
62709
62709
|
return repaired ?? positioned;
|
|
62710
62710
|
}
|
|
62711
62711
|
/**
|
|
62712
|
-
* The producer regions that are genuinely FOREIGN to this stack, canonicalized
|
|
62713
|
-
* and de-duplicated (issue #2109 review).
|
|
62714
|
-
*
|
|
62715
|
-
* The same filter {@link classifyReplaySecretRegion} applies to its own
|
|
62716
|
-
* `importedProducerRegions` argument, spelled here because the
|
|
62717
|
-
* unclassifiable-reference guard needs the answer WITHOUT an expression to hand
|
|
62718
|
-
* to the classifier. A same-region cross-stack read is the common case and
|
|
62719
|
-
* raises no cross-region question, so it must not arm a refusal.
|
|
62720
|
-
*/
|
|
62721
|
-
function foreignRegionsOf(producerRegions, stackRegion) {
|
|
62722
|
-
const seen = /* @__PURE__ */ new Set();
|
|
62723
|
-
const foreign = [];
|
|
62724
|
-
for (const candidate of producerRegions) {
|
|
62725
|
-
const canonical = canonicalizeRegion(candidate);
|
|
62726
|
-
if (!canonical || canonical === canonicalizeRegion(stackRegion)) continue;
|
|
62727
|
-
if (seen.has(canonical)) continue;
|
|
62728
|
-
seen.add(canonical);
|
|
62729
|
-
foreign.push(candidate);
|
|
62730
|
-
}
|
|
62731
|
-
return foreign;
|
|
62732
|
-
}
|
|
62733
|
-
/**
|
|
62734
62712
|
* The `cdkd scrub` resolvers: the stack's own, plus one pinned sibling per
|
|
62735
62713
|
* FOREIGN region an ARN-named secret reference asks for (issue
|
|
62736
62714
|
* [#2109](https://github.com/go-to-k/cdkd/issues/2109)).
|
|
@@ -62822,7 +62800,7 @@ const DYNAMIC_REFERENCE_OPENING = "{{resolve:";
|
|
|
62822
62800
|
* followed by a service whose VALUE CloudFormation defines as a secret. Counted
|
|
62823
62801
|
* against the number of WHOLE tokens of the same class the scan matched, which
|
|
62824
62802
|
* is how an ASSEMBLED reference is detected without parsing intrinsics — see
|
|
62825
|
-
* {@link
|
|
62803
|
+
* {@link isAssembledSecretReference}.
|
|
62826
62804
|
*
|
|
62827
62805
|
* WHY THE SERVICE IS PART OF THE OPENING (issue #2109 review). Counting the bare
|
|
62828
62806
|
* `{{resolve:` made the guard fire on any leaf that merely MENTIONS the syntax
|
|
@@ -62859,7 +62837,7 @@ const SECRET_REFERENCE_OPENINGS = [
|
|
|
62859
62837
|
];
|
|
62860
62838
|
/**
|
|
62861
62839
|
* What an `Fn::Sub` placeholder opens with. A whole token that CONTAINS one is
|
|
62862
|
-
* the third assembled shape — see {@link
|
|
62840
|
+
* the third assembled shape — see {@link isAssembledSecretReference}.
|
|
62863
62841
|
*/
|
|
62864
62842
|
const SUB_PLACEHOLDER_OPENING = "${";
|
|
62865
62843
|
/** How many SECRET references {@link SECRET_REFERENCE_OPENINGS} `leaf` opens. */
|
|
@@ -62873,16 +62851,17 @@ function isSecretReferenceToken(token) {
|
|
|
62873
62851
|
return SECRET_REFERENCE_OPENINGS.some((opening) => token.startsWith(opening));
|
|
62874
62852
|
}
|
|
62875
62853
|
/**
|
|
62876
|
-
*
|
|
62877
|
-
*
|
|
62878
|
-
* class in it, or a whole token that still
|
|
62854
|
+
* Does this leaf ASSEMBLE a secret reference out of parts (issue #2109 review)
|
|
62855
|
+
* — i.e. does it OPEN more SECRET references than the scan found COMPLETE
|
|
62856
|
+
* tokens of that class in it, or carry a whole token that still holds an
|
|
62857
|
+
* `Fn::Sub` placeholder?
|
|
62879
62858
|
*
|
|
62880
62859
|
* WHY THIS EXISTS, and it is the load-bearing half of this pre-pass rather than
|
|
62881
62860
|
* an edge case. The region split runs on the RAW template leaf, BEFORE intrinsic
|
|
62882
62861
|
* resolution, and the shared token scan is `\{\{resolve:[^}]+\}\}` — a class
|
|
62883
62862
|
* that cannot cross a `}`. So none of the three shapes that assemble a
|
|
62884
62863
|
* reference out of parts yields the ONE WHOLE token a literal reference does,
|
|
62885
|
-
* and the third one is why this
|
|
62864
|
+
* and the third one is why this predicate needs two tests rather than one
|
|
62886
62865
|
* (all four rows MEASURED):
|
|
62887
62866
|
*
|
|
62888
62867
|
* ```text
|
|
@@ -62900,40 +62879,42 @@ function isSecretReferenceToken(token) {
|
|
|
62900
62879
|
* test: a WHOLE token that still contains `${` has not been assembled yet.
|
|
62901
62880
|
* Neither a Secrets Manager secret name nor an SSM parameter name may contain
|
|
62902
62881
|
* `$` or `{`, so the only false positive that test can produce is a JSON key
|
|
62903
|
-
* literally spelled `${...}` in a leaf that is NOT under an `Fn::Sub` — and
|
|
62904
|
-
*
|
|
62905
|
-
*
|
|
62906
|
-
*
|
|
62907
|
-
*
|
|
62908
|
-
*
|
|
62909
|
-
*
|
|
62910
|
-
*
|
|
62911
|
-
*
|
|
62912
|
-
*
|
|
62913
|
-
*
|
|
62914
|
-
*
|
|
62915
|
-
*
|
|
62916
|
-
*
|
|
62917
|
-
*
|
|
62918
|
-
*
|
|
62919
|
-
*
|
|
62920
|
-
*
|
|
62921
|
-
*
|
|
62922
|
-
*
|
|
62923
|
-
*
|
|
62924
|
-
*
|
|
62925
|
-
*
|
|
62926
|
-
*
|
|
62927
|
-
*
|
|
62928
|
-
*
|
|
62929
|
-
*
|
|
62930
|
-
*
|
|
62931
|
-
*
|
|
62932
|
-
*
|
|
62933
|
-
*
|
|
62934
|
-
|
|
62935
|
-
|
|
62936
|
-
|
|
62882
|
+
* literally spelled `${...}` in a leaf that is NOT under an `Fn::Sub` — and a
|
|
62883
|
+
* false positive here now costs a DEFERRAL rather than a refusal, so it is
|
|
62884
|
+
* cheaper than it was when this predicate raised
|
|
62885
|
+
* `SCRUB_SECRET_REFERENCE_UNCLASSIFIABLE`.
|
|
62886
|
+
*
|
|
62887
|
+
* WHAT A `true` MEANS FOR THE CALLER, and it changed with issue
|
|
62888
|
+
* [#2157](https://github.com/go-to-k/cdkd/issues/2157): the leaf is handed on
|
|
62889
|
+
* BY IDENTITY, so the PRIMARY resolver classifies the reference once
|
|
62890
|
+
* `resolveSub` / `resolveJoin` have assembled it. Before issue
|
|
62891
|
+
* [#2134](https://github.com/go-to-k/cdkd/issues/2134) that was issue #2109
|
|
62892
|
+
* verbatim — the resolver had no region question at all, so an identity return
|
|
62893
|
+
* meant a foreign secret fetched against the stack's own endpoint, and this
|
|
62894
|
+
* predicate therefore had to REFUSE (gated on a foreign producer region being
|
|
62895
|
+
* on record, to keep it from refusing the world). #2134 put the classification
|
|
62896
|
+
* in `resolveDynamicReferences`, which sees the COMPLETE expression, so the
|
|
62897
|
+
* safety property the refusal bought — a reference whose region cannot be
|
|
62898
|
+
* established is never resolved in the stack's own region — is now bought
|
|
62899
|
+
* downstream over strictly more information.
|
|
62900
|
+
*
|
|
62901
|
+
* The trailing-placeholder row reached the resolver a different way before that
|
|
62902
|
+
* second test existed: the truncated token WAS classified here, and only
|
|
62903
|
+
* downstream luck kept it safe (the name form classifies `ambiguous`, the ARN
|
|
62904
|
+
* form fails into `SCRUB_CROSS_REGION_SECRET_UNRESOLVED` because the id it
|
|
62905
|
+
* looks up still has a literal `${Field}` in it). Post-#2157 it is deferred
|
|
62906
|
+
* like the other three, and the resolver answers it on the assembled
|
|
62907
|
+
* expression instead of on a token one brace short. Every row is pinned by the
|
|
62908
|
+
* suite rather than assumed.
|
|
62909
|
+
*
|
|
62910
|
+
* Residual, unchanged and stated rather than hidden: an `Fn::Join` that splits
|
|
62911
|
+
* BEFORE the service name opens no counted reference at all (see
|
|
62912
|
+
* {@link SECRET_REFERENCE_OPENINGS}), so this predicate returns `false` for it
|
|
62913
|
+
* and the leaf is returned by identity anyway — the same destination, reached
|
|
62914
|
+
* without being noticed.
|
|
62915
|
+
*/
|
|
62916
|
+
function isAssembledSecretReference(leaf, secretTokens) {
|
|
62917
|
+
return countSecretReferenceOpenings(leaf) !== secretTokens.length || secretTokens.some((token) => token.includes(SUB_PLACEHOLDER_OPENING));
|
|
62937
62918
|
}
|
|
62938
62919
|
/**
|
|
62939
62920
|
* Resolve every FOREIGN-region `{{resolve:...}}` reference in one leaf through
|
|
@@ -62955,10 +62936,9 @@ function unclassifiableScrubSecretError(origin, leafPath, stackName, foreignProd
|
|
|
62955
62936
|
* closed: a stopped scrub is recoverable, a lookup for a secret id spliced
|
|
62956
62937
|
* together out of a plaintext is not.
|
|
62957
62938
|
*/
|
|
62958
|
-
async function resolveForeignRegionTokens(leaf, stackName, ctx
|
|
62939
|
+
async function resolveForeignRegionTokens(leaf, stackName, ctx) {
|
|
62959
62940
|
const tokens = dynamicReferenceTokens(leaf);
|
|
62960
|
-
|
|
62961
|
-
if ((countSecretReferenceOpenings(leaf) !== secretTokens.length || secretTokens.some((token) => token.includes(SUB_PLACEHOLDER_OPENING))) && ctx.foreignProducerRegions.length > 0) throw unclassifiableScrubSecretError(ctx.origin, leafPath, stackName, ctx.foreignProducerRegions, ctx.stackRegion);
|
|
62941
|
+
if (isAssembledSecretReference(leaf, tokens.filter(isSecretReferenceToken))) return leaf;
|
|
62962
62942
|
const verdicts = tokens.map((token) => [token, classifyReplaySecretRegion(token, ctx.stackRegion, ctx.producerRegions)]);
|
|
62963
62943
|
for (const [, verdict] of verdicts) if (verdict.kind === "ambiguous") throw regionAmbiguousScrubSecretError(ctx.origin, stackName, verdict.secretName, verdict.foreignProducerRegions, ctx.stackRegion);
|
|
62964
62944
|
if (!verdicts.some(([, verdict]) => verdict.kind === "named-region")) return leaf;
|
|
@@ -63026,16 +63006,16 @@ async function resolveForeignRegionTokens(leaf, stackName, ctx, leafPath) {
|
|
|
63026
63006
|
* off it.
|
|
63027
63007
|
*/
|
|
63028
63008
|
async function pinCrossRegionSecrets(bag, stackName, ctx) {
|
|
63029
|
-
const walk = async (v
|
|
63009
|
+
const walk = async (v) => {
|
|
63030
63010
|
if (typeof v === "string") {
|
|
63031
63011
|
if (!v.includes(DYNAMIC_REFERENCE_OPENING)) return v;
|
|
63032
|
-
return await resolveForeignRegionTokens(v, stackName, ctx
|
|
63012
|
+
return await resolveForeignRegionTokens(v, stackName, ctx);
|
|
63033
63013
|
}
|
|
63034
63014
|
if (Array.isArray(v)) {
|
|
63035
63015
|
const out = new Array(v.length);
|
|
63036
63016
|
let changed = false;
|
|
63037
63017
|
for (let i = 0; i < v.length; i++) {
|
|
63038
|
-
out[i] = await walk(v[i]
|
|
63018
|
+
out[i] = await walk(v[i]);
|
|
63039
63019
|
if (out[i] !== v[i]) changed = true;
|
|
63040
63020
|
}
|
|
63041
63021
|
return changed ? out : v;
|
|
@@ -63044,14 +63024,14 @@ async function pinCrossRegionSecrets(bag, stackName, ctx) {
|
|
|
63044
63024
|
const out = Object.create(null);
|
|
63045
63025
|
let changed = false;
|
|
63046
63026
|
for (const [k, val] of Object.entries(v)) {
|
|
63047
|
-
out[k] = await walk(val
|
|
63027
|
+
out[k] = await walk(val);
|
|
63048
63028
|
if (out[k] !== val) changed = true;
|
|
63049
63029
|
}
|
|
63050
63030
|
return changed ? out : v;
|
|
63051
63031
|
}
|
|
63052
63032
|
return v;
|
|
63053
63033
|
};
|
|
63054
|
-
return await walk(bag
|
|
63034
|
+
return await walk(bag);
|
|
63055
63035
|
}
|
|
63056
63036
|
/**
|
|
63057
63037
|
* Order scrub targets PRODUCER-BEFORE-CONSUMER (issue
|
|
@@ -63460,8 +63440,11 @@ function storedValueCarriesNoPlaintext(stored) {
|
|
|
63460
63440
|
* {@link buildExportOwnerIndex} and `producerTemplates` are keyed by stack
|
|
63461
63441
|
* NAME alone, so the walk reads that name's template regardless of `Region`;
|
|
63462
63442
|
* - a secret reachable only through the TRUE branch of an `Fn::If` in a
|
|
63463
|
-
* producer's output: see {@link
|
|
63464
|
-
* branch for a condition it cannot evaluate
|
|
63443
|
+
* producer's output: see {@link selectTakenConditionalBranches}, which takes
|
|
63444
|
+
* the FALSE branch for a condition it cannot evaluate -- and which, since
|
|
63445
|
+
* issue [#2150](https://github.com/go-to-k/cdkd/issues/2150), feeds BOTH the
|
|
63446
|
+
* literal scan below and {@link collectReExportHops} from that one
|
|
63447
|
+
* selection.
|
|
63465
63448
|
*/
|
|
63466
63449
|
function producerPublishesSecretExpression(templates, exportOwners, producerStack, key) {
|
|
63467
63450
|
const seen = /* @__PURE__ */ new Set([`${producerStack}\u0000${key}`]);
|
|
@@ -63489,7 +63472,8 @@ function producerPublishesSecretExpression(templates, exportOwners, producerStac
|
|
|
63489
63472
|
widened = true;
|
|
63490
63473
|
subjects = Object.values(outputs).map((o) => o?.Value);
|
|
63491
63474
|
} else continue;
|
|
63492
|
-
for (const
|
|
63475
|
+
for (const rawSubject of subjects) {
|
|
63476
|
+
const subject = selectTakenConditionalBranches(rawSubject);
|
|
63493
63477
|
if (JSON.stringify(subject ?? null).includes("{{resolve:")) {
|
|
63494
63478
|
if (widened) return {
|
|
63495
63479
|
kind: "widened",
|
|
@@ -63518,6 +63502,64 @@ function producerPublishesSecretExpression(templates, exportOwners, producerStac
|
|
|
63518
63502
|
return NO_SECRET_EXPRESSION;
|
|
63519
63503
|
}
|
|
63520
63504
|
/**
|
|
63505
|
+
* One node with every `Fn::If` collapsed to the branch scrub would TAKE
|
|
63506
|
+
* (issues #2146 and [#2150](https://github.com/go-to-k/cdkd/issues/2150)).
|
|
63507
|
+
*
|
|
63508
|
+
* ONE SPELLING, consumed verbatim by both halves of "does this producer publish
|
|
63509
|
+
* a secret expression?" — {@link collectReExportHops}, which follows the value
|
|
63510
|
+
* onward, and {@link producerPublishesSecretExpression}'s literal
|
|
63511
|
+
* `{{resolve:` scan, which tests the value itself. Those two are one question
|
|
63512
|
+
* asked twice, and they DISAGREED between #2146 and #2150: the hop walk took
|
|
63513
|
+
* the false branch while the scan `JSON.stringify`d the whole node and saw
|
|
63514
|
+
* both. A producer whose output is
|
|
63515
|
+
* `{"Fn::If": ["IsProd", "{{resolve:...}}", "plain"]}` deployed with `IsProd`
|
|
63516
|
+
* false therefore verdicted `declared`, the discriminator read the deployed
|
|
63517
|
+
* value `plain`, and scrub refused with `SCRUB_CROSS_STACK_PRODUCER_PLAINTEXT`
|
|
63518
|
+
* — an UNCLEARABLE refusal, because no `cdkd scrub` of the producer can turn
|
|
63519
|
+
* `plain` into an expression and there is no bypass flag, so every other secret
|
|
63520
|
+
* in the consumer stack was stranded. A second spelling of the selection would
|
|
63521
|
+
* only move where the two can drift apart again, so there is exactly one.
|
|
63522
|
+
*
|
|
63523
|
+
* WHICH BRANCH: the FALSE one, unconditionally, mirroring `resolveIf`'s
|
|
63524
|
+
* unknown-condition arm. Every condition met on this path belongs to ANOTHER
|
|
63525
|
+
* stack, and scrub evaluates conditions only for the stack it is scrubbing,
|
|
63526
|
+
* from that template's parameter DEFAULTS (it takes no `--parameters`), so a
|
|
63527
|
+
* producer's condition is not evaluable here.
|
|
63528
|
+
*
|
|
63529
|
+
* TWO SHAPES COLLAPSE MORE THAN THE SELECTED BRANCH, both fail-OPEN, both
|
|
63530
|
+
* stated rather than implied because the old whole-node `JSON.stringify` saw
|
|
63531
|
+
* them and this does not. Neither is CloudFormation-emittable, which is why
|
|
63532
|
+
* they are accepted rather than fixed:
|
|
63533
|
+
*
|
|
63534
|
+
* - a MALFORMED `Fn::If` (args not a 3-tuple) collapses to `null` -- NO branch.
|
|
63535
|
+
* Which branch is live is unknowable, so neither a hop nor a `{{resolve:`
|
|
63536
|
+
* sighting may be claimed from it. CloudFormation refuses such a template, so
|
|
63537
|
+
* a producer that reads this way cannot have been deployed carrying one.
|
|
63538
|
+
* - an object carrying `Fn::If` ALONGSIDE sibling keys keeps only the selected
|
|
63539
|
+
* branch, so a `{{resolve:` under a sibling is not seen. An intrinsic node has
|
|
63540
|
+
* exactly one key in any template CloudFormation accepts, and the hop walk
|
|
63541
|
+
* dropped those siblings before this function existed (its `Fn::If` arm
|
|
63542
|
+
* `return`ed rather than falling through), so this preserves that behaviour
|
|
63543
|
+
* rather than introducing it.
|
|
63544
|
+
*
|
|
63545
|
+
* Residual, and it is the direction this file accepts elsewhere: a secret
|
|
63546
|
+
* reachable only through the TRUE branch is not detected, so that reference
|
|
63547
|
+
* keeps its pre-#2133 outcome. The hop walk already took this trade for the
|
|
63548
|
+
* chain it follows; #2150 extends it to the scan so that the two agree.
|
|
63549
|
+
*/
|
|
63550
|
+
function selectTakenConditionalBranches(node) {
|
|
63551
|
+
if (Array.isArray(node)) return node.map(selectTakenConditionalBranches);
|
|
63552
|
+
if (node === null || typeof node !== "object") return node;
|
|
63553
|
+
const obj = node;
|
|
63554
|
+
if ("Fn::If" in obj) {
|
|
63555
|
+
const args = obj["Fn::If"];
|
|
63556
|
+
return Array.isArray(args) && args.length === 3 ? selectTakenConditionalBranches(args[2]) : null;
|
|
63557
|
+
}
|
|
63558
|
+
const out = Object.create(null);
|
|
63559
|
+
for (const [key, child] of Object.entries(obj)) out[key] = selectTakenConditionalBranches(child);
|
|
63560
|
+
return out;
|
|
63561
|
+
}
|
|
63562
|
+
/**
|
|
63521
63563
|
* The re-export hops one output VALUE carries (issue #2146).
|
|
63522
63564
|
*
|
|
63523
63565
|
* Only STATICALLY resolvable references produce a hop, mirroring
|
|
@@ -63536,8 +63578,9 @@ function producerPublishesSecretExpression(templates, exportOwners, producerStac
|
|
|
63536
63578
|
* routinely wrapped — `Fn::Join`ing an imported password into a URL is the
|
|
63537
63579
|
* shape the live fixture uses.
|
|
63538
63580
|
*
|
|
63539
|
-
* `Fn::If`
|
|
63540
|
-
* tidy (issue #2146 review). `makeCrossStackPrePass`
|
|
63581
|
+
* `Fn::If` NEVER REACHES THIS WALK, and the selection that used to live here
|
|
63582
|
+
* is load-bearing rather than tidy (issue #2146 review). `makeCrossStackPrePass`
|
|
63583
|
+
* deliberately mirrors
|
|
63541
63584
|
* `resolveIf` — selected branch only — so that a conditional import of a
|
|
63542
63585
|
* not-yet-deployed producer cannot refuse a whole stack, unbypassably, over a
|
|
63543
63586
|
* reference the deploy never read. A hop walk that descended into both arms
|
|
@@ -63549,24 +63592,24 @@ function producerPublishesSecretExpression(templates, exportOwners, producerStac
|
|
|
63549
63592
|
* flag. On main the same input answered `no`, so it would have been a
|
|
63550
63593
|
* regression this change introduced.
|
|
63551
63594
|
*
|
|
63552
|
-
*
|
|
63553
|
-
*
|
|
63554
|
-
*
|
|
63555
|
-
*
|
|
63556
|
-
*
|
|
63557
|
-
*
|
|
63558
|
-
*
|
|
63559
|
-
*
|
|
63560
|
-
*
|
|
63561
|
-
*
|
|
63562
|
-
*
|
|
63563
|
-
*
|
|
63564
|
-
*
|
|
63565
|
-
*
|
|
63566
|
-
*
|
|
63567
|
-
*
|
|
63568
|
-
*/
|
|
63569
|
-
function collectReExportHops(
|
|
63595
|
+
* `selectedValue` IS THE CONTRACT, and it is why this function carries no
|
|
63596
|
+
* `Fn::If` handling of its own: the only caller,
|
|
63597
|
+
* {@link producerPublishesSecretExpression}, applies
|
|
63598
|
+
* {@link selectTakenConditionalBranches} ONCE and hands the result to BOTH the
|
|
63599
|
+
* literal `{{resolve:` scan and this walk, so a node reaching here holds no
|
|
63600
|
+
* `Fn::If` at all. Issue [#2150](https://github.com/go-to-k/cdkd/issues/2150)
|
|
63601
|
+
* collapsed the two to one site deliberately: the scan used to `JSON.stringify`
|
|
63602
|
+
* the whole subject and therefore see BOTH arms while this walk took only the
|
|
63603
|
+
* false one, and two spellings of one question are exactly how they came to
|
|
63604
|
+
* disagree. A second selection HERE would restore that, and be worse than
|
|
63605
|
+
* useless — measured on the #2150 lane, a per-site copy was invisible to every
|
|
63606
|
+
* test in the suite, because the caller had already pruned, so deleting it
|
|
63607
|
+
* changed nothing and no probe could fence it.
|
|
63608
|
+
* {@link selectTakenConditionalBranches}'s own doc carries which branch is
|
|
63609
|
+
* selected, what a malformed `Fn::If` yields, and the TRUE-branch residual both
|
|
63610
|
+
* halves accept.
|
|
63611
|
+
*/
|
|
63612
|
+
function collectReExportHops(selectedValue, templates, exportOwners) {
|
|
63570
63613
|
const hops = [];
|
|
63571
63614
|
const walk = (node) => {
|
|
63572
63615
|
if (Array.isArray(node)) {
|
|
@@ -63575,11 +63618,6 @@ function collectReExportHops(value, templates, exportOwners) {
|
|
|
63575
63618
|
}
|
|
63576
63619
|
if (node === null || typeof node !== "object") return;
|
|
63577
63620
|
const obj = node;
|
|
63578
|
-
if ("Fn::If" in obj) {
|
|
63579
|
-
const args = obj["Fn::If"];
|
|
63580
|
-
if (Array.isArray(args) && args.length === 3) walk(args[2]);
|
|
63581
|
-
return;
|
|
63582
|
-
}
|
|
63583
63621
|
const imported = obj["Fn::ImportValue"];
|
|
63584
63622
|
if (typeof imported === "string") for (const owner of exportOwners.get(imported) ?? []) hops.push({
|
|
63585
63623
|
stack: owner,
|
|
@@ -63597,7 +63635,7 @@ function collectReExportHops(value, templates, exportOwners) {
|
|
|
63597
63635
|
}
|
|
63598
63636
|
for (const child of Object.values(obj)) walk(child);
|
|
63599
63637
|
};
|
|
63600
|
-
walk(
|
|
63638
|
+
walk(selectedValue);
|
|
63601
63639
|
return hops;
|
|
63602
63640
|
}
|
|
63603
63641
|
/**
|
|
@@ -63637,6 +63675,44 @@ function buildExportOwnerIndex(templates) {
|
|
|
63637
63675
|
return owners;
|
|
63638
63676
|
}
|
|
63639
63677
|
/**
|
|
63678
|
+
* Is this the issue #2134 refusal -- a `{{resolve:...}}` reference whose region
|
|
63679
|
+
* cdkd cannot establish?
|
|
63680
|
+
*
|
|
63681
|
+
* WHY IT IS NOT BEST-EFFORT, which is the whole reason this predicate exists.
|
|
63682
|
+
* `scrubStack` wraps each resolution pass in a `catch { logger.debug }` so one
|
|
63683
|
+
* unresolvable resource does not abandon the rest of the scrub. Every other
|
|
63684
|
+
* error reaching those catches means "this resource resolved partially, carry
|
|
63685
|
+
* on". This one means cdkd could not tell which region owns a secret reference,
|
|
63686
|
+
* so NO NEEDLE was recorded for it and the plaintext is still in `state.json` --
|
|
63687
|
+
* and swallowed, the command then reports `No plaintext secrets found` and
|
|
63688
|
+
* exits 0 over exactly the state it exists to clean. That is why the pre-pass
|
|
63689
|
+
* refusals are placed OUTSIDE those catches; this one is raised from inside
|
|
63690
|
+
* `resolveDynamicReferences`, so it cannot be placed and has to be re-raised.
|
|
63691
|
+
*
|
|
63692
|
+
* By CAUSE CHAIN rather than a bare `instanceof`, for the reason
|
|
63693
|
+
* {@link isByDesignRefusal} states: the refusal reaches those catches through
|
|
63694
|
+
* `resolver.resolve`, which wraps in several places, so a top-link-only test
|
|
63695
|
+
* silently swallows a wrapped one.
|
|
63696
|
+
*
|
|
63697
|
+
* TWO SITES IN THIS CALL GRAPH STILL SWALLOW IT, named rather than left to be
|
|
63698
|
+
* rediscovered (issue #2134 review round 2):
|
|
63699
|
+
*
|
|
63700
|
+
* - `evaluateConditions` (`intrinsic-function-resolver.ts`) warns and degrades
|
|
63701
|
+
* the condition to `false`. Accepted for now: a condition evaluates to a
|
|
63702
|
+
* boolean and carries no secret INTO state, and this file's own note above
|
|
63703
|
+
* `resolveCrossStackReads` records why the conditions pass is deliberately
|
|
63704
|
+
* not refusal-armed -- it runs BEFORE scrub knows which branch the deploy
|
|
63705
|
+
* took, so refusing there can reject a whole stack over a branch never taken.
|
|
63706
|
+
* The residual is that the FALSE branch is then selected, which that note
|
|
63707
|
+
* already states.
|
|
63708
|
+
* - the `!nodeCanRefuse` arm of the cross-stack pre-pass, which debugs and
|
|
63709
|
+
* returns. Defensible: a SUPPRESSED output writes no state key, so there is
|
|
63710
|
+
* no persisted plaintext for a missing needle to leave behind.
|
|
63711
|
+
*/
|
|
63712
|
+
function isRegionAmbiguousRefusal(err) {
|
|
63713
|
+
return err instanceof Error && errorCauseChain(err).some((link) => link instanceof DynamicReferenceRegionAmbiguousError);
|
|
63714
|
+
}
|
|
63715
|
+
/**
|
|
63640
63716
|
* Build the CROSS-STACK pre-pass for ONE stack's scrub (issue
|
|
63641
63717
|
* [#2133](https://github.com/go-to-k/cdkd/issues/2133)).
|
|
63642
63718
|
*
|
|
@@ -63952,7 +64028,6 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
|
|
|
63952
64028
|
const resolvers = new ScrubResolvers(region);
|
|
63953
64029
|
const resolver = resolvers.primary;
|
|
63954
64030
|
const producerRegions = producerRegionsFromState(state);
|
|
63955
|
-
const foreignProducerRegions = foreignRegionsOf(producerRegions, region);
|
|
63956
64031
|
let parameters = {};
|
|
63957
64032
|
let conditions = {};
|
|
63958
64033
|
try {
|
|
@@ -63969,6 +64044,7 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
|
|
|
63969
64044
|
stackName: stack.stackName,
|
|
63970
64045
|
stateBackend: resolverStateBackend,
|
|
63971
64046
|
...recordedSecretValues && { recordedSecretValues },
|
|
64047
|
+
producerRegions,
|
|
63972
64048
|
bestEffort: true
|
|
63973
64049
|
});
|
|
63974
64050
|
const resolveCrossStackReads = makeCrossStackPrePass({
|
|
@@ -63992,7 +64068,6 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
|
|
|
63992
64068
|
const resolveInput = await pinCrossRegionSecrets(templateResource.Properties, stack.stackName, {
|
|
63993
64069
|
stackRegion: region,
|
|
63994
64070
|
producerRegions,
|
|
63995
|
-
foreignProducerRegions,
|
|
63996
64071
|
resolvers,
|
|
63997
64072
|
recordedSecretValues,
|
|
63998
64073
|
origin: `resource '${logicalId}'`
|
|
@@ -64002,6 +64077,7 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
|
|
|
64002
64077
|
try {
|
|
64003
64078
|
await resolver.resolve(resolveInput, resourceContext);
|
|
64004
64079
|
} catch (err) {
|
|
64080
|
+
if (isRegionAmbiguousRefusal(err)) throw err;
|
|
64005
64081
|
logger.debug(`Resolution of ${logicalId} during scrub was partial: ${maskSecretsInText(err instanceof Error ? err.message : String(err), recordedSecretValues)}`);
|
|
64006
64082
|
}
|
|
64007
64083
|
perResourceTemplateProps.set(logicalId, templateResource.Properties);
|
|
@@ -64019,7 +64095,6 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
|
|
|
64019
64095
|
const nameSource = await pinCrossRegionSecrets(declaredExportName, stack.stackName, {
|
|
64020
64096
|
stackRegion: region,
|
|
64021
64097
|
producerRegions,
|
|
64022
|
-
foreignProducerRegions,
|
|
64023
64098
|
resolvers,
|
|
64024
64099
|
recordedSecretValues: outputSecrets,
|
|
64025
64100
|
origin: `Export.Name of output '${name}'`
|
|
@@ -64029,6 +64104,7 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
|
|
|
64029
64104
|
try {
|
|
64030
64105
|
exportName = await resolver.resolve(nameSource, nameContext);
|
|
64031
64106
|
} catch (err) {
|
|
64107
|
+
if (isRegionAmbiguousRefusal(err)) throw err;
|
|
64032
64108
|
outputsSourceUntrusted = true;
|
|
64033
64109
|
logger.warn(`Export.Name of output ${name} could not be resolved during scrub (${maskSecretsInText(err instanceof Error ? err.message : String(err), outputSecrets)}) — redacting this stack's outputs by value match instead of by template position, since state may be keyed under a name this run cannot reproduce.`);
|
|
64034
64110
|
}
|
|
@@ -64053,7 +64129,6 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
|
|
|
64053
64129
|
const valueSource = await pinCrossRegionSecrets(value, stack.stackName, {
|
|
64054
64130
|
stackRegion: region,
|
|
64055
64131
|
producerRegions,
|
|
64056
|
-
foreignProducerRegions,
|
|
64057
64132
|
resolvers,
|
|
64058
64133
|
recordedSecretValues: outputSecrets,
|
|
64059
64134
|
origin: `output '${name}'`
|
|
@@ -64063,6 +64138,7 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
|
|
|
64063
64138
|
try {
|
|
64064
64139
|
await resolver.resolve(valueSource, valueContext);
|
|
64065
64140
|
} catch (err) {
|
|
64141
|
+
if (isRegionAmbiguousRefusal(err)) throw err;
|
|
64066
64142
|
logger.debug(`Resolution of output ${name} during scrub was partial: ${maskSecretsInText(err instanceof Error ? err.message : String(err), outputSecrets)}`);
|
|
64067
64143
|
}
|
|
64068
64144
|
}
|
|
@@ -86049,7 +86125,7 @@ function createMigrateCommand() {
|
|
|
86049
86125
|
*/
|
|
86050
86126
|
function buildProgram() {
|
|
86051
86127
|
const program = new Command();
|
|
86052
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.284.
|
|
86128
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.284.37");
|
|
86053
86129
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
86054
86130
|
const { profile } = actionCommand.optsWithGlobals();
|
|
86055
86131
|
if (profile !== void 0) process.env["AWS_PROFILE"] = profile;
|