@go-to-k/cdkd 0.284.39 → 0.284.41
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-Db6lnsaA.js → asg-provider-D0lenV1M.js} +2 -2
- package/dist/{asg-provider-Db6lnsaA.js.map → asg-provider-D0lenV1M.js.map} +1 -1
- package/dist/cli.js +372 -195
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-rKBFVifY.js → deploy-engine-CCWl_tAH.js} +3 -3
- package/dist/{deploy-engine-rKBFVifY.js.map → deploy-engine-CCWl_tAH.js.map} +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as startInterruptWatch, $n as CdkdError, $t as WorkGraph, A as renderStatefulReason, An as resolveStateBucketWithDefault, At as redactSecretsForState, B as exportAliasCollisionScrubWarning, Bn as expectedOwnerParam, Bt as DiffCalculator, C as isFinalSnapshotError, Cn as synthesisStatusMessage, Cr as isMarkedNonRetryable, Ct as TEMPLATE_SOURCED_RULES, D as extractDeploymentEventError, Dn as resolveAutoAssetStorage, Dt as isSingleDynamicReferenceToken, E as makeCanonicalizePropertiesFn, En as resolveApp, Er as markNonRetryable, Et as errorCauseChain, F as green, Fn as CFN_TEMPLATE_BODY_LIMIT, Ft as s3BucketDomainName, G as collectInlinePolicyNamesManagedBySiblings, Gn as processStackMessages, Gt as TemplateParser, H as secretBearingStateKeyWarning, Hn as canonicalizeRegion, Ht as describeTypeWithThrottleRetry, I as red, In as CFN_TEMPLATE_URL_LIMIT, It as s3BucketDualStackDomainName, J as findActionableSilentDrops, Jn as AwsClients, Jt as S3StateBackend, K as clearOnUpdateRemoval, Kt as LockManager, L as yellow, Ln as MIGRATE_TMP_PREFIX, Lt as s3BucketRegionalDomainName, M as bold, Mn as resolveUseCdkBootstrapAssets, Mt as classifyReplaySecretRegion, N as cyan, Nn as stateBucketExistenceConfirmed, Nt as producerRegionsFromState, O as MULTI_REGION_RECREATE_BLOCKED_TYPES, On as resolveCaptureObservedState, Ot as maskSecretsInError, P as gray, Pn as warnDeprecatedNoPrefixCliFlag, Pt as s3BucketArn, Q as isInterruptedWaitError, Qt as stringifyValue, R as collectDeclaredOutputNames, Rn as findLargeInlineResources, Rt as s3BucketWebsiteUrl, S as createPreDeleteFinalSnapshot, Sn as Synthesizer, Sr as withErrorHandling, St as STATE_SOURCED_READBACK_RULES, T as unsupportedFinalSnapshotError, Tn as getLegacyStateBucketName, Tr as isThrottlingError, Tt as dynamicReferenceTokens, U as stateKeySecretExposure, Un as derivePartitionAndUrlSuffix, Ut as withRetry, V as isExportAliasCollision, Vn as PARTITION_TABLE, Vt as INTRINSIC_KEYS, W as IAMRoleProvider, Wn as AssemblyReader, Wt as DagBuilder, X as beginCommandInterruptScope, Xn as resetAwsClients, Xt as shouldRetainResource, Y as findSilentDropProperties, Yn as getAwsClients, Yt as rebuildClientForBucketRegion, Z as endCommandInterruptScope, Zn as setAwsClients, Zt as AssetPublisher, _ as computeImplicitDeleteEdges, _n as getDockerCmd, _t as replayWarn, a as DeploymentEventsStore, an as stripControlChars, ar as LocalInvokeBuildError$1, at as carriesDynamicReference, b as buildFinalSnapshotIdentifier, bn as AssetManifestLoader, bt as requireConfigString, c as replayFailedOperations, cn as ensureAssetStorage, ct as refStateLookupFromResource, d as updatePartialReason, dn as readBootstrapMarkerBody, dr as PartialFailureError, dt as resolveExplicitPhysicalId, en as buildAssetRedirectMap, et as CloudControlProvider, f as UNSPECIFIED_SKIP_REASON, fn as validateAssetBucketName, fr as ProvisioningError, ft as assertRegionMatch, g as IMPLICIT_DELETE_DEPENDENCIES, gn as formatDockerLoginError, gr as StackTerminationProtectionError, gt as readConfigString, h as maskingRetryLogger, hn as buildDockerImage, hr as StackHasActiveImportsError, ht as configStringRefusal, i as DeploymentEventsReader, in as escapeRegExp, ir as DynamicReferenceRegionAmbiguousError, it as IntrinsicFunctionResolver, j as formatResourceLine, jn as resolveStateBucketWithDefaultAndSource, jt as scrubResourceRecord, k as isStatefulRecreateTargetSync, kn as resolveSkipPrefix, kt as maskSecretsInText, l as replayRollback, ln as getBootstrapMarkerKey, lr as MissingCdkCliError, lt as WAFv2WebACLProvider, m as withResourceDeadline, mn as buildDenyExternalAccessPolicy, mr as ResourceUpdateNotSupportedError, mt as configBooleanRefusal, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as loadPublishableAssetManifest, nt as disableInstanceApiTermination, o as planFailedOps, on as AssetModeResolver, or as LocalMigrateError, ot as cfnRefValueFromPhysicalId, p as deleteSkipReason, pn as validateContainerRepoName, pr as ResourceTimeoutError, pt as coerceCfnBoolean, q as ProviderRegistry, qn as resolveBucketRegion, qt as displaySafe, r as DeployEngine, rn as rewriteTemplateAssetReferences, rr as DeployCancelledError, rt as isTerminationProtectionPropagationError, s as planRollback, sn as BOOTSTRAP_MARKER_PREFIX, sr as LocalStartServiceError, st as getAccountInfo, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as createAssetRedirectResolver, tr as CrossAccountSecretRefusalError, tt as slowCcOperationTimeoutMs, u as updatePartialMessage, un as parseBootstrapMarker, ur as NestedStackChildDirectDestroyError, ut as normalizeAwsTagsToCfn, v as ATOMIC_FINAL_SNAPSHOT_TYPES, vn as runDockerForeground, vt as requireConfigArray, w as refusesFinalSnapshot, wn as getDefaultStateBucketName, wr as isRetryableTransientError, wt as createSecretMasker, x as ccRoutedFinalSnapshotError, xn as getDockerImageBySourceHash, xr as normalizeAwsError, xt as STATE_SOURCED_CROSS_GENERATION_RULES, y as PRE_DELETE_SNAPSHOT_TYPES, yn as runDockerStreaming, yt as requireConfigObject, z as collectPublishedOutputNames, zn as uploadCfnTemplate, zt as applyRoleArnIfSet } from "./deploy-engine-
|
|
2
|
+
import { $ as startInterruptWatch, $n as CdkdError, $t as WorkGraph, A as renderStatefulReason, An as resolveStateBucketWithDefault, At as redactSecretsForState, B as exportAliasCollisionScrubWarning, Bn as expectedOwnerParam, Bt as DiffCalculator, C as isFinalSnapshotError, Cn as synthesisStatusMessage, Cr as isMarkedNonRetryable, Ct as TEMPLATE_SOURCED_RULES, D as extractDeploymentEventError, Dn as resolveAutoAssetStorage, Dt as isSingleDynamicReferenceToken, E as makeCanonicalizePropertiesFn, En as resolveApp, Er as markNonRetryable, Et as errorCauseChain, F as green, Fn as CFN_TEMPLATE_BODY_LIMIT, Ft as s3BucketDomainName, G as collectInlinePolicyNamesManagedBySiblings, Gn as processStackMessages, Gt as TemplateParser, H as secretBearingStateKeyWarning, Hn as canonicalizeRegion, Ht as describeTypeWithThrottleRetry, I as red, In as CFN_TEMPLATE_URL_LIMIT, It as s3BucketDualStackDomainName, J as findActionableSilentDrops, Jn as AwsClients, Jt as S3StateBackend, K as clearOnUpdateRemoval, Kt as LockManager, L as yellow, Ln as MIGRATE_TMP_PREFIX, Lt as s3BucketRegionalDomainName, M as bold, Mn as resolveUseCdkBootstrapAssets, Mt as classifyReplaySecretRegion, N as cyan, Nn as stateBucketExistenceConfirmed, Nt as producerRegionsFromState, O as MULTI_REGION_RECREATE_BLOCKED_TYPES, On as resolveCaptureObservedState, Ot as maskSecretsInError, P as gray, Pn as warnDeprecatedNoPrefixCliFlag, Pt as s3BucketArn, Q as isInterruptedWaitError, Qt as stringifyValue, R as collectDeclaredOutputNames, Rn as findLargeInlineResources, Rt as s3BucketWebsiteUrl, S as createPreDeleteFinalSnapshot, Sn as Synthesizer, Sr as withErrorHandling, St as STATE_SOURCED_READBACK_RULES, T as unsupportedFinalSnapshotError, Tn as getLegacyStateBucketName, Tr as isThrottlingError, Tt as dynamicReferenceTokens, U as stateKeySecretExposure, Un as derivePartitionAndUrlSuffix, Ut as withRetry, V as isExportAliasCollision, Vn as PARTITION_TABLE, Vt as INTRINSIC_KEYS, W as IAMRoleProvider, Wn as AssemblyReader, Wt as DagBuilder, X as beginCommandInterruptScope, Xn as resetAwsClients, Xt as shouldRetainResource, Y as findSilentDropProperties, Yn as getAwsClients, Yt as rebuildClientForBucketRegion, Z as endCommandInterruptScope, Zn as setAwsClients, Zt as AssetPublisher, _ as computeImplicitDeleteEdges, _n as getDockerCmd, _t as replayWarn, a as DeploymentEventsStore, an as stripControlChars, ar as LocalInvokeBuildError$1, at as carriesDynamicReference, b as buildFinalSnapshotIdentifier, bn as AssetManifestLoader, bt as requireConfigString, c as replayFailedOperations, cn as ensureAssetStorage, ct as refStateLookupFromResource, d as updatePartialReason, dn as readBootstrapMarkerBody, dr as PartialFailureError, dt as resolveExplicitPhysicalId, en as buildAssetRedirectMap, et as CloudControlProvider, f as UNSPECIFIED_SKIP_REASON, fn as validateAssetBucketName, fr as ProvisioningError, ft as assertRegionMatch, g as IMPLICIT_DELETE_DEPENDENCIES, gn as formatDockerLoginError, gr as StackTerminationProtectionError, gt as readConfigString, h as maskingRetryLogger, hn as buildDockerImage, hr as StackHasActiveImportsError, ht as configStringRefusal, i as DeploymentEventsReader, in as escapeRegExp, ir as DynamicReferenceRegionAmbiguousError, it as IntrinsicFunctionResolver, j as formatResourceLine, jn as resolveStateBucketWithDefaultAndSource, jt as scrubResourceRecord, k as isStatefulRecreateTargetSync, kn as resolveSkipPrefix, kt as maskSecretsInText, l as replayRollback, ln as getBootstrapMarkerKey, lr as MissingCdkCliError, lt as WAFv2WebACLProvider, m as withResourceDeadline, mn as buildDenyExternalAccessPolicy, mr as ResourceUpdateNotSupportedError, mt as configBooleanRefusal, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as loadPublishableAssetManifest, nt as disableInstanceApiTermination, o as planFailedOps, on as AssetModeResolver, or as LocalMigrateError, ot as cfnRefValueFromPhysicalId, p as deleteSkipReason, pn as validateContainerRepoName, pr as ResourceTimeoutError, pt as coerceCfnBoolean, q as ProviderRegistry, qn as resolveBucketRegion, qt as displaySafe, r as DeployEngine, rn as rewriteTemplateAssetReferences, rr as DeployCancelledError, rt as isTerminationProtectionPropagationError, s as planRollback, sn as BOOTSTRAP_MARKER_PREFIX, sr as LocalStartServiceError, st as getAccountInfo, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as createAssetRedirectResolver, tr as CrossAccountSecretRefusalError, tt as slowCcOperationTimeoutMs, u as updatePartialMessage, un as parseBootstrapMarker, ur as NestedStackChildDirectDestroyError, ut as normalizeAwsTagsToCfn, v as ATOMIC_FINAL_SNAPSHOT_TYPES, vn as runDockerForeground, vt as requireConfigArray, w as refusesFinalSnapshot, wn as getDefaultStateBucketName, wr as isRetryableTransientError, wt as createSecretMasker, x as ccRoutedFinalSnapshotError, xn as getDockerImageBySourceHash, xr as normalizeAwsError, xt as STATE_SOURCED_CROSS_GENERATION_RULES, y as PRE_DELETE_SNAPSHOT_TYPES, yn as runDockerStreaming, yt as requireConfigObject, z as collectPublishedOutputNames, zn as uploadCfnTemplate, zt as applyRoleArnIfSet } from "./deploy-engine-CCWl_tAH.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-D0lenV1M.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";
|
|
@@ -10324,6 +10324,121 @@ function isSqsQueueUrl(value) {
|
|
|
10324
10324
|
return value.startsWith("https://sqs.") && value.includes("/");
|
|
10325
10325
|
}
|
|
10326
10326
|
|
|
10327
|
+
//#endregion
|
|
10328
|
+
//#region src/provisioning/masked-retry-logger.ts
|
|
10329
|
+
/**
|
|
10330
|
+
* The masker a caller supplied, or the identity function when it supplied
|
|
10331
|
+
* none.
|
|
10332
|
+
*
|
|
10333
|
+
* ABSENT MEANS UNMASKED, and that is the back-compatible default the contract
|
|
10334
|
+
* mandates — `create()` / `update()` are also reached from the import path,
|
|
10335
|
+
* from `cdkd drift --revert`, and from tests, and a provider must not care
|
|
10336
|
+
* which caller it got. Centralised here so no call site re-spells the `??`
|
|
10337
|
+
* and accidentally makes the capability required.
|
|
10338
|
+
*/
|
|
10339
|
+
function maskerOrIdentity(maskSecrets) {
|
|
10340
|
+
return maskSecrets ?? ((text) => text);
|
|
10341
|
+
}
|
|
10342
|
+
/**
|
|
10343
|
+
* A {@link RetryLogger} whose every line is routed through `maskSecrets`.
|
|
10344
|
+
*
|
|
10345
|
+
* `warn` is ALWAYS provided, never omitted. It is optional on `RetryLogger`,
|
|
10346
|
+
* and dropping it would silence the give-up summary on the calling path only —
|
|
10347
|
+
* trading a disclosure for the reporting hole issue #2018 closed. It goes
|
|
10348
|
+
* through the SAME mask as `debug` precisely because it is the line that
|
|
10349
|
+
* survives a run without `--verbose`; forwarding it unmasked would defeat the
|
|
10350
|
+
* fence at a HIGHER log level than the one the fence was written for.
|
|
10351
|
+
*
|
|
10352
|
+
* `logger` is typed structurally rather than as the concrete `Logger` so this
|
|
10353
|
+
* module stays a leaf and a test can pass two spies. That matches the
|
|
10354
|
+
* precedent already in the tree (`buildMfaConfigRequest` in
|
|
10355
|
+
* `cognito-provider.ts` takes an injected `logger?: { warn }`).
|
|
10356
|
+
*/
|
|
10357
|
+
function createMaskedRetryLogger(logger, maskSecrets) {
|
|
10358
|
+
const mask = maskerOrIdentity(maskSecrets);
|
|
10359
|
+
return {
|
|
10360
|
+
debug: (message) => logger.debug(mask(message)),
|
|
10361
|
+
warn: (message) => logger.warn(mask(message))
|
|
10362
|
+
};
|
|
10363
|
+
}
|
|
10364
|
+
/**
|
|
10365
|
+
* Depth cap for {@link maskDeep}.
|
|
10366
|
+
*
|
|
10367
|
+
* What it actually bounds is unbounded WORK on a pathologically deep bag — see
|
|
10368
|
+
* the rationale at the cap itself, which is the authority. It is NOT primarily
|
|
10369
|
+
* a cycle guard: a template-derived bag cannot be cyclic, so that justification
|
|
10370
|
+
* over-claims (issue #2176 security review). It does still terminate one, which
|
|
10371
|
+
* is why the walk is safe to share with a caller that might hand it something
|
|
10372
|
+
* other than a template bag.
|
|
10373
|
+
*
|
|
10374
|
+
* Deliberately generous. Every shape these warnings exist to describe is a
|
|
10375
|
+
* scalar, a list of scalars, or a small record, so the walk does not reach
|
|
10376
|
+
* depth 2 in practice.
|
|
10377
|
+
*/
|
|
10378
|
+
const MASK_WALK_MAX_DEPTH = 8;
|
|
10379
|
+
/**
|
|
10380
|
+
* What {@link maskDeep} substitutes for a subtree it declines to descend into.
|
|
10381
|
+
*
|
|
10382
|
+
* MUST equal `SECRET_MASK` in `src/deployment/secret-redaction.ts`. It is
|
|
10383
|
+
* spelled here rather than imported so this module keeps its single-import leaf
|
|
10384
|
+
* property (see the module note above); the two are fenced against drift by a
|
|
10385
|
+
* test that imports both.
|
|
10386
|
+
*/
|
|
10387
|
+
const MASK_WALK_DEPTH_CAP_MARKER = "***";
|
|
10388
|
+
/**
|
|
10389
|
+
* Mask every string LEAF and KEY of an arbitrary value, returning a structure
|
|
10390
|
+
* safe to `JSON.stringify` into a log line or an error message (issue
|
|
10391
|
+
* [#2176](https://github.com/go-to-k/cdkd/issues/2176)).
|
|
10392
|
+
*
|
|
10393
|
+
* THE ORDERING IS THE WHOLE POINT, and it is why masking the finished message
|
|
10394
|
+
* is not a substitute. `maskSecretsInText` matches by literal occurrence, so:
|
|
10395
|
+
*
|
|
10396
|
+
* 1. ESCAPING. `JSON.stringify` escapes `"`, `\` and newlines, so a secret
|
|
10397
|
+
* containing any of them no longer OCCURS in the stringified text and a
|
|
10398
|
+
* mask applied afterwards cannot find it. That is not an exotic case — it
|
|
10399
|
+
* is every Secrets Manager JSON document, the commonest real secret shape.
|
|
10400
|
+
* Measured on the pre-fix tree for #2176: a plaintext of
|
|
10401
|
+
* `{"user":"admin","pw":"hunter2"}` interpolated as
|
|
10402
|
+
* `${JSON.stringify(value)}` came through a message-level mask COMPLETELY
|
|
10403
|
+
* unchanged, while masking the leaves first rendered it `"***"`.
|
|
10404
|
+
* 2. LENGTH. A finished message is always longer than the value inside it, so
|
|
10405
|
+
* it can only ever reach `maskSecretsInText`'s SUBSTRING arm, which ignores
|
|
10406
|
+
* needles below `MIN_NEEDLE_LENGTH` (4). Handing the masker each RAW leaf
|
|
10407
|
+
* reaches the WHOLE-VALUE arm, which has no floor. Measured the same way: a
|
|
10408
|
+
* 3-character secret survived `Value 'abc' at 'pin' failed ...` intact.
|
|
10409
|
+
*
|
|
10410
|
+
* Neither pass subsumes the other, so providers do BOTH — this walk on the
|
|
10411
|
+
* value, and the assembled message through the masker as well. The masker is
|
|
10412
|
+
* idempotent, so the overlap is free.
|
|
10413
|
+
*
|
|
10414
|
+
* Keys are masked as well as values: `JSON.stringify` renders them into the
|
|
10415
|
+
* same line, so a resolved secret used as a map key would otherwise escape.
|
|
10416
|
+
*
|
|
10417
|
+
* WHY THIS LIVES HERE rather than as a private walk per provider. SIX
|
|
10418
|
+
* hand-rolled copies had already accumulated — `elbv2-provider.ts`,
|
|
10419
|
+
* `cognito-provider.ts`, `sns-topic-provider.ts`, `dynamodb-table-provider.ts`,
|
|
10420
|
+
* `dynamodb-globaltable-provider.ts` and `apigatewayv2-provider.ts` — and
|
|
10421
|
+
* `elbv2-provider.ts` carried the standing instruction that "a THIRD site is
|
|
10422
|
+
* the point at which this should move into `../masked-retry-logger.ts` ... and
|
|
10423
|
+
* all three converge on it". That trigger had fired and been missed twice over,
|
|
10424
|
+
* and the copies had already diverged exactly as predicted: FOUR of the six
|
|
10425
|
+
* carried NO depth cap at all.
|
|
10426
|
+
*
|
|
10427
|
+
* Worth recording how the last two were nearly missed AGAIN, because it is the
|
|
10428
|
+
* same failure one level up: the first sweep for this issue grepped for
|
|
10429
|
+
* `maskDeep` / `MASK_WALK_MAX_DEPTH` — the SPELLINGS the known copies used —
|
|
10430
|
+
* and the two survivors spell it `maskLeaf` / `maskLeafValue` with no named
|
|
10431
|
+
* constant. Grep for the SHAPE, not the name. These copies encode a security
|
|
10432
|
+
* contract, and a hardening applied to one silently leaves the others behind.
|
|
10433
|
+
*/
|
|
10434
|
+
function maskDeep(value, mask, depth = 0) {
|
|
10435
|
+
if (typeof value === "string") return mask(value);
|
|
10436
|
+
if (depth >= 8) return "***";
|
|
10437
|
+
if (Array.isArray(value)) return value.map((entry) => maskDeep(entry, mask, depth + 1));
|
|
10438
|
+
if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [mask(k), maskDeep(v, mask, depth + 1)]));
|
|
10439
|
+
return value;
|
|
10440
|
+
}
|
|
10441
|
+
|
|
10327
10442
|
//#endregion
|
|
10328
10443
|
//#region src/provisioning/providers/sns-topic-provider.ts
|
|
10329
10444
|
/**
|
|
@@ -10979,12 +11094,7 @@ function buildDeliveryStatusAttributeMap(logging, logicalId, onMalformed, maskSe
|
|
|
10979
11094
|
const warn = (message) => {
|
|
10980
11095
|
getLogger().child("SNSTopicProvider").warn(maskSecrets(message));
|
|
10981
11096
|
};
|
|
10982
|
-
const maskLeaf = (value) =>
|
|
10983
|
-
if (typeof value === "string") return maskSecrets(value);
|
|
10984
|
-
if (Array.isArray(value)) return value.map(maskLeaf);
|
|
10985
|
-
if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [maskSecrets(k), maskLeaf(v)]));
|
|
10986
|
-
return value;
|
|
10987
|
-
};
|
|
11097
|
+
const maskLeaf = (value) => maskDeep(value, maskSecrets);
|
|
10988
11098
|
const seenProtocols = /* @__PURE__ */ new Set();
|
|
10989
11099
|
if (!Array.isArray(logging)) {
|
|
10990
11100
|
if (onMalformed === "throw") throw new Error(`SNS topic ${logicalId}: DeliveryStatusLogging must be an array of {Protocol, ...} objects, got ${JSON.stringify(maskLeaf(logging))}`);
|
|
@@ -13955,10 +14065,11 @@ function idShape(segments) {
|
|
|
13955
14065
|
* mis-names one value frequently mis-names its sibling too, and reporting them
|
|
13956
14066
|
* one deploy at a time is the shape of remedy nobody finishes.
|
|
13957
14067
|
*/
|
|
13958
|
-
function compositeIdSeparatorRefusal(resourceType, logicalId, segments) {
|
|
14068
|
+
function compositeIdSeparatorRefusal(resourceType, logicalId, segments, maskSecrets) {
|
|
13959
14069
|
const offending = segments.filter((segment) => String(segment.value).includes("|"));
|
|
13960
14070
|
if (offending.length === 0) return void 0;
|
|
13961
|
-
|
|
14071
|
+
const mask = maskerOrIdentity(maskSecrets);
|
|
14072
|
+
return `${resourceType} ${logicalId}: ${offending.map((segment) => `${segment.name} '${mask(String(segment.value))}'`).join(" and ")} contains '${"|"}', which cdkd uses as the separator in this type's physical id (${idShape(segments)}). Recording it would produce an id that decodes back to a DIFFERENT resource, so a later cdkd destroy / drift / update would target the wrong one — or silently skip it while the real resource stays alive. AWS accepts the name and CloudFormation manages such a resource fine; this is a cdkd limitation tracked in ${COMPOSITE_ID_ISSUE_URL}. Rename the resource so no segment contains '${"|"}', or manage it outside cdkd.`;
|
|
13962
14073
|
}
|
|
13963
14074
|
/**
|
|
13964
14075
|
* Join the segments into a composite physicalId, REFUSING one that would be
|
|
@@ -13970,7 +14081,7 @@ function compositeIdSeparatorRefusal(resourceType, logicalId, segments) {
|
|
|
13970
14081
|
* behavior.
|
|
13971
14082
|
*/
|
|
13972
14083
|
function packCompositeId(resourceType, logicalId, segments, options) {
|
|
13973
|
-
const refusal = compositeIdSeparatorRefusal(resourceType, logicalId, segments);
|
|
14084
|
+
const refusal = compositeIdSeparatorRefusal(resourceType, logicalId, segments, options?.maskSecrets);
|
|
13974
14085
|
if (refusal !== void 0) if (options?.onRefusal) options.onRefusal(`${refusal} Continuing with the ambiguous id because the value comes from a cdkd state record, which no template edit can repair; the same value is REFUSED on a template-path create.`);
|
|
13975
14086
|
else throw new ProvisioningError(refusal, resourceType, logicalId);
|
|
13976
14087
|
return segments.map((segment) => String(segment.value)).join("|");
|
|
@@ -14163,7 +14274,10 @@ var LambdaEventInvokeConfigProvider = class {
|
|
|
14163
14274
|
coerceNumber: true,
|
|
14164
14275
|
...replayWarn(this.logger, context)
|
|
14165
14276
|
});
|
|
14166
|
-
const physicalId = this.buildPhysicalId(logicalId, functionName, qualifier,
|
|
14277
|
+
const physicalId = this.buildPhysicalId(logicalId, functionName, qualifier, {
|
|
14278
|
+
maskSecrets: context?.maskSecrets,
|
|
14279
|
+
...context?.replayingState === true && { onRefusal: (message) => this.logger.warn(message) }
|
|
14280
|
+
});
|
|
14167
14281
|
try {
|
|
14168
14282
|
await this.lambdaClient.send(new PutFunctionEventInvokeConfigCommand(this.buildPutInput(properties)));
|
|
14169
14283
|
this.logger.debug(`Successfully created Lambda EventInvokeConfig ${logicalId}: ${physicalId}`);
|
|
@@ -16333,10 +16447,7 @@ const deleteTableRetryDelays$1 = {};
|
|
|
16333
16447
|
* leaf is stringified — and escaped — identically.
|
|
16334
16448
|
*/
|
|
16335
16449
|
function maskLeafValue$1(value, maskSecrets) {
|
|
16336
|
-
|
|
16337
|
-
if (Array.isArray(value)) return value.map((v) => maskLeafValue$1(v, maskSecrets));
|
|
16338
|
-
if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [maskSecrets(k), maskLeafValue$1(v, maskSecrets)]));
|
|
16339
|
-
return value;
|
|
16450
|
+
return maskDeep(value, maskSecrets);
|
|
16340
16451
|
}
|
|
16341
16452
|
var DynamoDBTableProvider = class {
|
|
16342
16453
|
dynamoDBClient;
|
|
@@ -17245,7 +17356,7 @@ var DynamoDBTableProvider = class {
|
|
|
17245
17356
|
const warn = (message) => this.logger.warn(maskSecrets(message));
|
|
17246
17357
|
const coerced = coerceWarmThroughput(value);
|
|
17247
17358
|
if (coerced === void 0) return void 0;
|
|
17248
|
-
if (coerced.dropped.length > 0) warn(`${scope}: WarmThroughput member(s) ${coerced.dropped.join(", ")} in ${JSON.stringify(maskLeafValue$1(value, maskSecrets))} are not a number DynamoDB accepts, so they were dropped from the request, which leaves ${JSON.stringify(coerced.spec)}. Check for an unresolved intrinsic or a non-numeric value.`);
|
|
17359
|
+
if (coerced.dropped.length > 0) warn(`${scope}: WarmThroughput member(s) ${coerced.dropped.join(", ")} in ${JSON.stringify(maskLeafValue$1(value, maskSecrets))} are not a number DynamoDB accepts, so they were dropped from the request, which leaves ${JSON.stringify(maskLeafValue$1(coerced.spec, maskSecrets))}. Check for an unresolved intrinsic or a non-numeric value.`);
|
|
17249
17360
|
return coerced.spec;
|
|
17250
17361
|
}
|
|
17251
17362
|
/**
|
|
@@ -17895,18 +18006,17 @@ const DRIFT_STRIPPED_INDEX_MEMBERS = [{
|
|
|
17895
18006
|
* by value rather than by position, so the two layers compose.
|
|
17896
18007
|
*
|
|
17897
18008
|
* Walks rather than testing the top level, because a secret nested in an object
|
|
17898
|
-
* or array leaf is stringified — and escaped — identically.
|
|
17899
|
-
*
|
|
17900
|
-
*
|
|
17901
|
-
*
|
|
17902
|
-
*
|
|
17903
|
-
*
|
|
18009
|
+
* or array leaf is stringified — and escaped — identically.
|
|
18010
|
+
*
|
|
18011
|
+
* Issue #2176 hoisted the walk into `../masked-retry-logger.js`. The previous
|
|
18012
|
+
* note here said the helper was "duplicated rather than shared because these two
|
|
18013
|
+
* files deliberately hold no common module today" — that rationale expired the
|
|
18014
|
+
* moment a shared LEAF module existed for exactly this, and it had already cost
|
|
18015
|
+
* something: this copy carried NO depth cap, so it was one of SIX copies that
|
|
18016
|
+
* had drifted apart on the one property that matters.
|
|
17904
18017
|
*/
|
|
17905
18018
|
function maskLeafValue(value, maskSecrets) {
|
|
17906
|
-
|
|
17907
|
-
if (Array.isArray(value)) return value.map((v) => maskLeafValue(v, maskSecrets));
|
|
17908
|
-
if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [maskSecrets(k), maskLeafValue(v, maskSecrets)]));
|
|
17909
|
-
return value;
|
|
18019
|
+
return maskDeep(value, maskSecrets);
|
|
17910
18020
|
}
|
|
17911
18021
|
var DynamoDBGlobalTableProvider = class {
|
|
17912
18022
|
dynamoDBClient;
|
|
@@ -22786,11 +22896,11 @@ var SSMParameterProvider = class {
|
|
|
22786
22896
|
* whole body is wrapped: an unexpected failure degrades to "no Arn recorded"
|
|
22787
22897
|
* exactly as the fabricated-account arm does.
|
|
22788
22898
|
*/
|
|
22789
|
-
async buildParameterArn(name) {
|
|
22899
|
+
async buildParameterArn(name, mask) {
|
|
22790
22900
|
try {
|
|
22791
|
-
return await this.buildParameterArnUnguarded(name);
|
|
22901
|
+
return await this.buildParameterArnUnguarded(name, mask);
|
|
22792
22902
|
} catch (error) {
|
|
22793
|
-
this.logger.warn(`Could not build the Arn attribute for SSM parameter ${name}: ${error instanceof Error ? error.message : String(error)}. The parameter itself is unaffected; the Arn is NOT recorded, so an Fn::GetAtt on it will fail until a later deploy records it.`);
|
|
22903
|
+
this.logger.warn(mask(`Could not build the Arn attribute for SSM parameter ${name}: ${error instanceof Error ? error.message : String(error)}. The parameter itself is unaffected; the Arn is NOT recorded, so an Fn::GetAtt on it will fail until a later deploy records it.`));
|
|
22794
22904
|
return;
|
|
22795
22905
|
}
|
|
22796
22906
|
}
|
|
@@ -22810,10 +22920,10 @@ var SSMParameterProvider = class {
|
|
|
22810
22920
|
* import RECORDED it, so the fix belongs at the import boundary — where
|
|
22811
22921
|
* `import()` now refuses it — and not here. Do not re-add it.
|
|
22812
22922
|
*/
|
|
22813
|
-
async buildParameterArnUnguarded(name) {
|
|
22923
|
+
async buildParameterArnUnguarded(name, mask) {
|
|
22814
22924
|
const accountInfo = await getAccountInfo(await this.ssmClient.config.region());
|
|
22815
22925
|
if (accountInfo.fabricated) {
|
|
22816
|
-
this.logger.warn(`Cannot determine the AWS account (STS is unreachable, and the resolved account id is a placeholder), so the Arn attribute for SSM parameter ${name} would be fabricated and is NOT recorded. An Fn::GetAtt on it will fail until a later deploy resolves the account.`);
|
|
22926
|
+
this.logger.warn(mask(`Cannot determine the AWS account (STS is unreachable, and the resolved account id is a placeholder), so the Arn attribute for SSM parameter ${name} would be fabricated and is NOT recorded. An Fn::GetAtt on it will fail until a later deploy resolves the account.`));
|
|
22817
22927
|
return;
|
|
22818
22928
|
}
|
|
22819
22929
|
const { partition } = derivePartitionAndUrlSuffix(accountInfo.region);
|
|
@@ -22823,8 +22933,11 @@ var SSMParameterProvider = class {
|
|
|
22823
22933
|
/**
|
|
22824
22934
|
* Create an SSM parameter
|
|
22825
22935
|
*/
|
|
22826
|
-
async create(logicalId, resourceType, properties) {
|
|
22827
|
-
|
|
22936
|
+
async create(logicalId, resourceType, properties, context) {
|
|
22937
|
+
const mask = maskerOrIdentity(context?.maskSecrets);
|
|
22938
|
+
const warn = (message) => this.logger.warn(mask(message));
|
|
22939
|
+
const debug = (message) => this.logger.debug(mask(message));
|
|
22940
|
+
debug(`Creating SSM parameter ${logicalId}`);
|
|
22828
22941
|
const name = properties["Name"] || `/${generateResourceName(logicalId, {
|
|
22829
22942
|
maxLength: 1023,
|
|
22830
22943
|
allowedPattern: /[^a-zA-Z0-9-/_]/g
|
|
@@ -22855,14 +22968,14 @@ var SSMParameterProvider = class {
|
|
|
22855
22968
|
} catch (innerError) {
|
|
22856
22969
|
try {
|
|
22857
22970
|
await this.ssmClient.send(new DeleteParameterCommand({ Name: name }));
|
|
22858
|
-
|
|
22971
|
+
debug(`Cleaned up partially-created SSM parameter ${logicalId} (${name}) after wiring failure`);
|
|
22859
22972
|
} catch (cleanupError) {
|
|
22860
|
-
|
|
22973
|
+
warn(`Failed to clean up partially-created SSM parameter ${logicalId} (${name}): ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}. Manual deletion may be required before the next deploy: aws ssm delete-parameter --name '${name}'`);
|
|
22861
22974
|
}
|
|
22862
22975
|
throw innerError;
|
|
22863
22976
|
}
|
|
22864
|
-
|
|
22865
|
-
const arn = await this.buildParameterArn(name);
|
|
22977
|
+
debug(`Successfully created SSM parameter ${logicalId}: ${name}`);
|
|
22978
|
+
const arn = await this.buildParameterArn(name, mask);
|
|
22866
22979
|
return {
|
|
22867
22980
|
physicalId: name,
|
|
22868
22981
|
attributes: {
|
|
@@ -22873,14 +22986,16 @@ var SSMParameterProvider = class {
|
|
|
22873
22986
|
};
|
|
22874
22987
|
} catch (error) {
|
|
22875
22988
|
const cause = error instanceof Error ? error : void 0;
|
|
22876
|
-
throw new ProvisioningError(`Failed to create SSM parameter ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, void 0, cause);
|
|
22989
|
+
throw new ProvisioningError(`Failed to create SSM parameter ${logicalId}: ${mask(error instanceof Error ? error.message : String(error))}`, resourceType, logicalId, void 0, cause);
|
|
22877
22990
|
}
|
|
22878
22991
|
}
|
|
22879
22992
|
/**
|
|
22880
22993
|
* Update an SSM parameter
|
|
22881
22994
|
*/
|
|
22882
|
-
async update(logicalId, physicalId, resourceType, properties, previousProperties) {
|
|
22883
|
-
|
|
22995
|
+
async update(logicalId, physicalId, resourceType, properties, previousProperties, context) {
|
|
22996
|
+
const mask = maskerOrIdentity(context?.maskSecrets);
|
|
22997
|
+
const debug = (message) => this.logger.debug(mask(message));
|
|
22998
|
+
debug(`Updating SSM parameter ${logicalId}: ${physicalId}`);
|
|
22884
22999
|
const type = properties["Type"] || "String";
|
|
22885
23000
|
const value = properties["Value"];
|
|
22886
23001
|
if (!value) throw new ProvisioningError(`Value is required for SSM parameter ${logicalId}`, resourceType, logicalId, physicalId);
|
|
@@ -22912,10 +23027,10 @@ var SSMParameterProvider = class {
|
|
|
22912
23027
|
ResourceId: physicalId,
|
|
22913
23028
|
Tags: newTags
|
|
22914
23029
|
}));
|
|
22915
|
-
|
|
23030
|
+
debug(`Updated tags for SSM parameter ${physicalId}`);
|
|
22916
23031
|
}
|
|
22917
|
-
|
|
22918
|
-
const arn = await this.buildParameterArn(physicalId);
|
|
23032
|
+
debug(`Successfully updated SSM parameter ${logicalId}`);
|
|
23033
|
+
const arn = await this.buildParameterArn(physicalId, mask);
|
|
22919
23034
|
return {
|
|
22920
23035
|
physicalId,
|
|
22921
23036
|
wasReplaced: false,
|
|
@@ -22927,7 +23042,7 @@ var SSMParameterProvider = class {
|
|
|
22927
23042
|
};
|
|
22928
23043
|
} catch (error) {
|
|
22929
23044
|
const cause = error instanceof Error ? error : void 0;
|
|
22930
|
-
throw new ProvisioningError(`Failed to update SSM parameter ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
|
|
23045
|
+
throw new ProvisioningError(`Failed to update SSM parameter ${logicalId}: ${mask(error instanceof Error ? error.message : String(error))}`, resourceType, logicalId, physicalId, cause);
|
|
22931
23046
|
}
|
|
22932
23047
|
}
|
|
22933
23048
|
/**
|
|
@@ -23919,9 +24034,10 @@ const generations = /* @__PURE__ */ new Map();
|
|
|
23919
24034
|
* long-lived process cannot grow the map without bound.
|
|
23920
24035
|
*/
|
|
23921
24036
|
const MAX_TRACKED_GENERATIONS = 1e5;
|
|
23922
|
-
const tokenKey = (scope, logicalId) => `${scope}\u0000${process.env["AWS_REGION"] ?? ""}\u0000${getCurrentStackName() ?? ""}\u0000${logicalId}`;
|
|
23923
|
-
const derive = (key, generation, maxLength) => {
|
|
23924
|
-
|
|
24037
|
+
const tokenKey = (scope, logicalId, maxLength, charset) => `${scope}\u0000${process.env["AWS_REGION"] ?? ""}\u0000${getCurrentStackName() ?? ""}\u0000${logicalId}\u0000${maxLength}\u0000${charset}`;
|
|
24038
|
+
const derive = (key, generation, maxLength, charset) => {
|
|
24039
|
+
const digest = createHash("sha256").update(`${PROCESS_NONCE}\u0000${key}\u0000${generation}`).digest("hex");
|
|
24040
|
+
return `cdkd${charset === "alphanumeric" ? "" : "-"}${digest}`.slice(0, maxLength);
|
|
23925
24041
|
};
|
|
23926
24042
|
/**
|
|
23927
24043
|
* Get the idempotency token for one logical create, minting it on first use and
|
|
@@ -23935,10 +24051,10 @@ const derive = (key, generation, maxLength) => {
|
|
|
23935
24051
|
* ```
|
|
23936
24052
|
*/
|
|
23937
24053
|
function acquireIdempotencyToken(options) {
|
|
23938
|
-
const { scope, logicalId, maxLength = 64 } = options;
|
|
23939
|
-
const key = tokenKey(scope, logicalId);
|
|
24054
|
+
const { scope, logicalId, maxLength = 64, charset = "default" } = options;
|
|
24055
|
+
const key = tokenKey(scope, logicalId, maxLength, charset);
|
|
23940
24056
|
const existing = inFlight.get(key);
|
|
23941
|
-
const value = existing ?? derive(key, generations.get(key) ?? 0, maxLength);
|
|
24057
|
+
const value = existing ?? derive(key, generations.get(key) ?? 0, maxLength, charset);
|
|
23942
24058
|
if (existing === void 0) inFlight.set(key, value);
|
|
23943
24059
|
return {
|
|
23944
24060
|
value,
|
|
@@ -24401,10 +24517,10 @@ var EC2Provider = class {
|
|
|
24401
24517
|
case "AWS::EC2::VPCGatewayAttachment": return this.createVpcGatewayAttachment(logicalId, resourceType, properties, context);
|
|
24402
24518
|
case "AWS::EC2::NatGateway": return this.createNatGateway(logicalId, resourceType, properties);
|
|
24403
24519
|
case "AWS::EC2::RouteTable": return this.createRouteTable(logicalId, resourceType, properties);
|
|
24404
|
-
case "AWS::EC2::Route": return this.createRoute(logicalId, resourceType, properties, context?.replayingState === true ? (message) => this.logger.warn(message) : void 0);
|
|
24520
|
+
case "AWS::EC2::Route": return this.createRoute(logicalId, resourceType, properties, context?.replayingState === true ? (message) => this.logger.warn(message) : void 0, context?.maskSecrets);
|
|
24405
24521
|
case "AWS::EC2::SubnetRouteTableAssociation": return this.createSubnetRouteTableAssociation(logicalId, resourceType, properties);
|
|
24406
24522
|
case "AWS::EC2::SecurityGroup": return this.createSecurityGroup(logicalId, resourceType, properties);
|
|
24407
|
-
case "AWS::EC2::SecurityGroupIngress": return this.createSecurityGroupIngress(logicalId, resourceType, properties, context?.replayingState === true ? (message) => this.logger.warn(message) : void 0);
|
|
24523
|
+
case "AWS::EC2::SecurityGroupIngress": return this.createSecurityGroupIngress(logicalId, resourceType, properties, context?.replayingState === true ? (message) => this.logger.warn(message) : void 0, context?.maskSecrets);
|
|
24408
24524
|
case "AWS::EC2::Instance": return this.createInstance(logicalId, resourceType, properties, context);
|
|
24409
24525
|
case "AWS::EC2::NetworkAcl": return this.createNetworkAcl(logicalId, resourceType, properties);
|
|
24410
24526
|
case "AWS::EC2::NetworkAclEntry": return this.createNetworkAclEntry(logicalId, resourceType, properties, context);
|
|
@@ -24412,16 +24528,16 @@ var EC2Provider = class {
|
|
|
24412
24528
|
default: throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId);
|
|
24413
24529
|
}
|
|
24414
24530
|
}
|
|
24415
|
-
async update(logicalId, physicalId, resourceType, properties, previousProperties) {
|
|
24531
|
+
async update(logicalId, physicalId, resourceType, properties, previousProperties, context) {
|
|
24416
24532
|
try {
|
|
24417
|
-
return await this.applyUpdate(logicalId, physicalId, resourceType, properties, previousProperties);
|
|
24533
|
+
return await this.applyUpdate(logicalId, physicalId, resourceType, properties, previousProperties, context);
|
|
24418
24534
|
} catch (error) {
|
|
24419
24535
|
if (error instanceof CdkdError) throw error;
|
|
24420
24536
|
const cause = error instanceof Error ? error : void 0;
|
|
24421
24537
|
throw new ProvisioningError(`Failed to update EC2 resource ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
|
|
24422
24538
|
}
|
|
24423
24539
|
}
|
|
24424
|
-
async applyUpdate(logicalId, physicalId, resourceType, properties, previousProperties) {
|
|
24540
|
+
async applyUpdate(logicalId, physicalId, resourceType, properties, previousProperties, context) {
|
|
24425
24541
|
switch (resourceType) {
|
|
24426
24542
|
case "AWS::EC2::VPC": return this.updateVpc(logicalId, physicalId, resourceType, properties, previousProperties);
|
|
24427
24543
|
case "AWS::EC2::Subnet": return this.updateSubnet(logicalId, physicalId, resourceType, properties, previousProperties);
|
|
@@ -24430,10 +24546,10 @@ var EC2Provider = class {
|
|
|
24430
24546
|
case "AWS::EC2::VPCGatewayAttachment": return this.updateVpcGatewayAttachment(logicalId, physicalId);
|
|
24431
24547
|
case "AWS::EC2::NatGateway": return this.updateNatGateway(logicalId, physicalId);
|
|
24432
24548
|
case "AWS::EC2::RouteTable": return this.updateRouteTable(logicalId, physicalId);
|
|
24433
|
-
case "AWS::EC2::Route": return this.updateRoute(logicalId, physicalId, resourceType, properties, previousProperties);
|
|
24549
|
+
case "AWS::EC2::Route": return this.updateRoute(logicalId, physicalId, resourceType, properties, previousProperties, context?.maskSecrets);
|
|
24434
24550
|
case "AWS::EC2::SubnetRouteTableAssociation": return this.updateSubnetRouteTableAssociation(logicalId, physicalId);
|
|
24435
24551
|
case "AWS::EC2::SecurityGroup": return this.updateSecurityGroup(logicalId, physicalId, resourceType, properties, previousProperties);
|
|
24436
|
-
case "AWS::EC2::SecurityGroupIngress": return this.updateSecurityGroupIngress(logicalId, physicalId, resourceType, properties, previousProperties);
|
|
24552
|
+
case "AWS::EC2::SecurityGroupIngress": return this.updateSecurityGroupIngress(logicalId, physicalId, resourceType, properties, previousProperties, context?.maskSecrets);
|
|
24437
24553
|
case "AWS::EC2::Instance": return this.updateInstance(logicalId, physicalId, resourceType, properties, previousProperties);
|
|
24438
24554
|
case "AWS::EC2::NetworkAcl":
|
|
24439
24555
|
case "AWS::EC2::NetworkAclEntry":
|
|
@@ -24897,7 +25013,10 @@ var EC2Provider = class {
|
|
|
24897
25013
|
throw new ProvisioningError(`Failed to create EIP ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, void 0, cause);
|
|
24898
25014
|
}
|
|
24899
25015
|
return {
|
|
24900
|
-
physicalId: this.eipPhysicalId(logicalId, publicIp, allocationId,
|
|
25016
|
+
physicalId: this.eipPhysicalId(logicalId, publicIp, allocationId, {
|
|
25017
|
+
maskSecrets: context?.maskSecrets,
|
|
25018
|
+
...context?.replayingState === true && { onRefusal: (message) => this.logger.warn(message) }
|
|
25019
|
+
}),
|
|
24901
25020
|
attributes: {
|
|
24902
25021
|
AllocationId: allocationId,
|
|
24903
25022
|
PublicIp: publicIp
|
|
@@ -24979,7 +25098,10 @@ var EC2Provider = class {
|
|
|
24979
25098
|
}, {
|
|
24980
25099
|
name: "vpcId",
|
|
24981
25100
|
value: vpcId
|
|
24982
|
-
}],
|
|
25101
|
+
}], {
|
|
25102
|
+
maskSecrets: context?.maskSecrets,
|
|
25103
|
+
...context?.replayingState === true && { onRefusal: (message) => this.logger.warn(message) }
|
|
25104
|
+
});
|
|
24983
25105
|
try {
|
|
24984
25106
|
await this.ec2Client.send(new AttachInternetGatewayCommand({
|
|
24985
25107
|
VpcId: vpcId,
|
|
@@ -25132,7 +25254,7 @@ var EC2Provider = class {
|
|
|
25132
25254
|
throw new ProvisioningError(`Failed to delete RouteTable ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
|
|
25133
25255
|
}
|
|
25134
25256
|
}
|
|
25135
|
-
async createRoute(logicalId, resourceType, properties, onMultipleDestinations) {
|
|
25257
|
+
async createRoute(logicalId, resourceType, properties, onMultipleDestinations, maskSecrets) {
|
|
25136
25258
|
this.logger.debug(`Creating Route ${logicalId}`);
|
|
25137
25259
|
const routeTableId = properties["RouteTableId"];
|
|
25138
25260
|
const destinationCidrBlock = properties["DestinationCidrBlock"];
|
|
@@ -25156,7 +25278,10 @@ var EC2Provider = class {
|
|
|
25156
25278
|
}, {
|
|
25157
25279
|
name: "destination",
|
|
25158
25280
|
value: destination
|
|
25159
|
-
}],
|
|
25281
|
+
}], {
|
|
25282
|
+
maskSecrets,
|
|
25283
|
+
...onMultipleDestinations && { onRefusal: onMultipleDestinations }
|
|
25284
|
+
});
|
|
25160
25285
|
try {
|
|
25161
25286
|
await this.ec2Client.send(new CreateRouteCommand({
|
|
25162
25287
|
RouteTableId: routeTableId,
|
|
@@ -25184,7 +25309,7 @@ var EC2Provider = class {
|
|
|
25184
25309
|
throw new ProvisioningError(`Failed to create Route ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, void 0, cause);
|
|
25185
25310
|
}
|
|
25186
25311
|
}
|
|
25187
|
-
async updateRoute(logicalId, physicalId, resourceType, properties, previousProperties) {
|
|
25312
|
+
async updateRoute(logicalId, physicalId, resourceType, properties, previousProperties, maskSecrets) {
|
|
25188
25313
|
this.logger.debug(`Updating Route ${logicalId}: ${physicalId}`);
|
|
25189
25314
|
if (JSON.stringify(properties) === JSON.stringify(previousProperties)) return {
|
|
25190
25315
|
physicalId,
|
|
@@ -25192,7 +25317,7 @@ var EC2Provider = class {
|
|
|
25192
25317
|
};
|
|
25193
25318
|
try {
|
|
25194
25319
|
await this.deleteRoute(logicalId, physicalId, resourceType);
|
|
25195
|
-
const createResult = await this.createRoute(logicalId, resourceType, properties, (message) => this.logger.warn(message));
|
|
25320
|
+
const createResult = await this.createRoute(logicalId, resourceType, properties, (message) => this.logger.warn(message), maskSecrets);
|
|
25196
25321
|
return {
|
|
25197
25322
|
physicalId: createResult.physicalId,
|
|
25198
25323
|
wasReplaced: true,
|
|
@@ -25475,7 +25600,7 @@ var EC2Provider = class {
|
|
|
25475
25600
|
* and on a rollback replay the value comes from a STATE record, which no
|
|
25476
25601
|
* template edit can fix. Absent (the plain CREATE dispatch) it throws.
|
|
25477
25602
|
*/
|
|
25478
|
-
async createSecurityGroupIngress(logicalId, resourceType, properties, onUnusableProtocol) {
|
|
25603
|
+
async createSecurityGroupIngress(logicalId, resourceType, properties, onUnusableProtocol, maskSecrets) {
|
|
25479
25604
|
this.logger.debug(`Creating SecurityGroupIngress ${logicalId}`);
|
|
25480
25605
|
const groupId = properties["GroupId"];
|
|
25481
25606
|
if (!groupId) throw new ProvisioningError(`GroupId is required for SecurityGroupIngress ${logicalId}`, resourceType, logicalId);
|
|
@@ -25500,7 +25625,10 @@ var EC2Provider = class {
|
|
|
25500
25625
|
name: "toPort",
|
|
25501
25626
|
value: toPort ?? "-1"
|
|
25502
25627
|
}
|
|
25503
|
-
],
|
|
25628
|
+
], {
|
|
25629
|
+
maskSecrets,
|
|
25630
|
+
...onUnusableProtocol && { onRefusal: onUnusableProtocol }
|
|
25631
|
+
});
|
|
25504
25632
|
try {
|
|
25505
25633
|
const ruleId = singleSecurityGroupRuleId((await this.ec2Client.send(new AuthorizeSecurityGroupIngressCommand({
|
|
25506
25634
|
GroupId: groupId,
|
|
@@ -25596,7 +25724,7 @@ var EC2Provider = class {
|
|
|
25596
25724
|
return;
|
|
25597
25725
|
}
|
|
25598
25726
|
}
|
|
25599
|
-
async updateSecurityGroupIngress(logicalId, physicalId, resourceType, properties, previousProperties) {
|
|
25727
|
+
async updateSecurityGroupIngress(logicalId, physicalId, resourceType, properties, previousProperties, maskSecrets) {
|
|
25600
25728
|
this.logger.debug(`Updating SecurityGroupIngress ${logicalId}: ${physicalId}`);
|
|
25601
25729
|
if (JSON.stringify(properties) === JSON.stringify(previousProperties)) return {
|
|
25602
25730
|
physicalId,
|
|
@@ -25604,7 +25732,7 @@ var EC2Provider = class {
|
|
|
25604
25732
|
};
|
|
25605
25733
|
try {
|
|
25606
25734
|
await this.deleteSecurityGroupIngress(logicalId, physicalId, resourceType, previousProperties);
|
|
25607
|
-
const createResult = await this.createSecurityGroupIngress(logicalId, resourceType, properties, (message) => this.logger.warn(message));
|
|
25735
|
+
const createResult = await this.createSecurityGroupIngress(logicalId, resourceType, properties, (message) => this.logger.warn(message), maskSecrets);
|
|
25608
25736
|
return {
|
|
25609
25737
|
physicalId: createResult.physicalId,
|
|
25610
25738
|
wasReplaced: true,
|
|
@@ -26288,7 +26416,10 @@ var EC2Provider = class {
|
|
|
26288
26416
|
name: "egress",
|
|
26289
26417
|
value: egress
|
|
26290
26418
|
}
|
|
26291
|
-
],
|
|
26419
|
+
], {
|
|
26420
|
+
maskSecrets: context?.maskSecrets,
|
|
26421
|
+
...context?.replayingState === true && { onRefusal: (message) => this.logger.warn(message) }
|
|
26422
|
+
});
|
|
26292
26423
|
try {
|
|
26293
26424
|
const cidrBlock = properties["CidrBlock"];
|
|
26294
26425
|
const ipv6CidrBlock = properties["Ipv6CidrBlock"];
|
|
@@ -28337,7 +28468,10 @@ var ApiGatewayProvider = class ApiGatewayProvider {
|
|
|
28337
28468
|
name: "httpMethod",
|
|
28338
28469
|
value: httpMethod
|
|
28339
28470
|
}
|
|
28340
|
-
],
|
|
28471
|
+
], {
|
|
28472
|
+
maskSecrets: context?.maskSecrets,
|
|
28473
|
+
...context?.replayingState === true && { onRefusal: (message) => this.logger.warn(message) }
|
|
28474
|
+
});
|
|
28341
28475
|
try {
|
|
28342
28476
|
await this.apiGatewayClient.send(new PutMethodCommand({
|
|
28343
28477
|
restApiId,
|
|
@@ -30381,12 +30515,7 @@ var ApiGatewayV2Provider = class {
|
|
|
30381
30515
|
*/
|
|
30382
30516
|
toSdkResponseParameters(value, maskSecrets = (text) => text) {
|
|
30383
30517
|
const warn = (message) => this.logger.warn(maskSecrets(message));
|
|
30384
|
-
const maskLeaf = (value) =>
|
|
30385
|
-
if (typeof value === "string") return maskSecrets(value);
|
|
30386
|
-
if (Array.isArray(value)) return value.map(maskLeaf);
|
|
30387
|
-
if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [maskSecrets(k), maskLeaf(v)]));
|
|
30388
|
-
return value;
|
|
30389
|
-
};
|
|
30518
|
+
const maskLeaf = (value) => maskDeep(value, maskSecrets);
|
|
30390
30519
|
if (value == null) return void 0;
|
|
30391
30520
|
if (typeof value !== "object" || Array.isArray(value)) return value;
|
|
30392
30521
|
const out = {};
|
|
@@ -34955,44 +35084,6 @@ var ECSProvider = class {
|
|
|
34955
35084
|
}
|
|
34956
35085
|
};
|
|
34957
35086
|
|
|
34958
|
-
//#endregion
|
|
34959
|
-
//#region src/provisioning/masked-retry-logger.ts
|
|
34960
|
-
/**
|
|
34961
|
-
* The masker a caller supplied, or the identity function when it supplied
|
|
34962
|
-
* none.
|
|
34963
|
-
*
|
|
34964
|
-
* ABSENT MEANS UNMASKED, and that is the back-compatible default the contract
|
|
34965
|
-
* mandates — `create()` / `update()` are also reached from the import path,
|
|
34966
|
-
* from `cdkd drift --revert`, and from tests, and a provider must not care
|
|
34967
|
-
* which caller it got. Centralised here so no call site re-spells the `??`
|
|
34968
|
-
* and accidentally makes the capability required.
|
|
34969
|
-
*/
|
|
34970
|
-
function maskerOrIdentity(maskSecrets) {
|
|
34971
|
-
return maskSecrets ?? ((text) => text);
|
|
34972
|
-
}
|
|
34973
|
-
/**
|
|
34974
|
-
* A {@link RetryLogger} whose every line is routed through `maskSecrets`.
|
|
34975
|
-
*
|
|
34976
|
-
* `warn` is ALWAYS provided, never omitted. It is optional on `RetryLogger`,
|
|
34977
|
-
* and dropping it would silence the give-up summary on the calling path only —
|
|
34978
|
-
* trading a disclosure for the reporting hole issue #2018 closed. It goes
|
|
34979
|
-
* through the SAME mask as `debug` precisely because it is the line that
|
|
34980
|
-
* survives a run without `--verbose`; forwarding it unmasked would defeat the
|
|
34981
|
-
* fence at a HIGHER log level than the one the fence was written for.
|
|
34982
|
-
*
|
|
34983
|
-
* `logger` is typed structurally rather than as the concrete `Logger` so this
|
|
34984
|
-
* module stays a leaf and a test can pass two spies. That matches the
|
|
34985
|
-
* precedent already in the tree (`buildMfaConfigRequest` in
|
|
34986
|
-
* `cognito-provider.ts` takes an injected `logger?: { warn }`).
|
|
34987
|
-
*/
|
|
34988
|
-
function createMaskedRetryLogger(logger, maskSecrets) {
|
|
34989
|
-
const mask = maskerOrIdentity(maskSecrets);
|
|
34990
|
-
return {
|
|
34991
|
-
debug: (message) => logger.debug(mask(message)),
|
|
34992
|
-
warn: (message) => logger.warn(mask(message))
|
|
34993
|
-
};
|
|
34994
|
-
}
|
|
34995
|
-
|
|
34996
35087
|
//#endregion
|
|
34997
35088
|
//#region src/provisioning/providers/elbv2-provider.ts
|
|
34998
35089
|
/**
|
|
@@ -35002,41 +35093,6 @@ function createMaskedRetryLogger(logger, maskSecrets) {
|
|
|
35002
35093
|
*/
|
|
35003
35094
|
const capacityReservationDelays = { sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)) };
|
|
35004
35095
|
/**
|
|
35005
|
-
* Depth cap for {@link maskDeep}. Bounds a self-referential bag rather than
|
|
35006
|
-
* hanging; `JSON.stringify` would throw on one anyway, and a throw is the
|
|
35007
|
-
* better failure of the two.
|
|
35008
|
-
*/
|
|
35009
|
-
const MASK_WALK_MAX_DEPTH$1 = 8;
|
|
35010
|
-
/**
|
|
35011
|
-
* Mask every string LEAF and KEY of an arbitrary value, returning a structure
|
|
35012
|
-
* safe to `JSON.stringify` into a log line (issue #2050).
|
|
35013
|
-
*
|
|
35014
|
-
* The ordering is the whole point. `maskSecretsInText` matches by literal
|
|
35015
|
-
* occurrence, and `JSON.stringify` escapes `"`, `\` and newlines — so a Secrets
|
|
35016
|
-
* Manager JSON document (the commonest real secret shape) does not occur in the
|
|
35017
|
-
* stringified text and survives a mask applied afterwards. Masking the leaves
|
|
35018
|
-
* first also hands the masker each RAW value, which reaches
|
|
35019
|
-
* `maskSecretsInText`'s whole-value arm at any length instead of only the
|
|
35020
|
-
* substring arm's 4-character floor. See the "three gaps" note in
|
|
35021
|
-
* `src/deployment/secret-redaction.ts`.
|
|
35022
|
-
*
|
|
35023
|
-
* Keys are masked as well as values: `JSON.stringify` renders them into the
|
|
35024
|
-
* same line, so a resolved secret used as a map key would otherwise escape.
|
|
35025
|
-
*
|
|
35026
|
-
* `cognito-provider.ts` carries an equivalent private walk for its own
|
|
35027
|
-
* stringified warnings. Two copies is the current cost of keeping this a leaf;
|
|
35028
|
-
* a THIRD site is the point at which this should move into
|
|
35029
|
-
* `../masked-retry-logger.ts` (already the shared provider-side masking module)
|
|
35030
|
-
* and all three converge on it.
|
|
35031
|
-
*/
|
|
35032
|
-
function maskDeep(value, mask, depth = 0) {
|
|
35033
|
-
if (typeof value === "string") return mask(value);
|
|
35034
|
-
if (depth >= MASK_WALK_MAX_DEPTH$1) return value;
|
|
35035
|
-
if (Array.isArray(value)) return value.map((entry) => maskDeep(entry, mask, depth + 1));
|
|
35036
|
-
if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [mask(k), maskDeep(v, mask, depth + 1)]));
|
|
35037
|
-
return value;
|
|
35038
|
-
}
|
|
35039
|
-
/**
|
|
35040
35096
|
* Documented AWS defaults for TargetGroup attributes, used to reset a
|
|
35041
35097
|
* removed `TargetGroupAttributes` entry. Unlike ModifyLoadBalancerAttributes
|
|
35042
35098
|
* / ModifyListenerAttributes, ModifyTargetGroupAttributes REJECTS an empty
|
|
@@ -39580,10 +39636,10 @@ var Route53Provider = class {
|
|
|
39580
39636
|
default: throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId);
|
|
39581
39637
|
}
|
|
39582
39638
|
}
|
|
39583
|
-
async update(logicalId, physicalId, resourceType, properties, previousProperties) {
|
|
39639
|
+
async update(logicalId, physicalId, resourceType, properties, previousProperties, context) {
|
|
39584
39640
|
switch (resourceType) {
|
|
39585
39641
|
case "AWS::Route53::HostedZone": return this.updateHostedZone(logicalId, physicalId, resourceType, properties, previousProperties);
|
|
39586
|
-
case "AWS::Route53::RecordSet": return this.updateRecordSet(logicalId, physicalId, resourceType, properties);
|
|
39642
|
+
case "AWS::Route53::RecordSet": return this.updateRecordSet(logicalId, physicalId, resourceType, properties, context);
|
|
39587
39643
|
default: throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId, physicalId);
|
|
39588
39644
|
}
|
|
39589
39645
|
}
|
|
@@ -39901,7 +39957,10 @@ var Route53Provider = class {
|
|
|
39901
39957
|
name: "recordType",
|
|
39902
39958
|
value: recordType
|
|
39903
39959
|
}
|
|
39904
|
-
],
|
|
39960
|
+
], {
|
|
39961
|
+
maskSecrets: context?.maskSecrets,
|
|
39962
|
+
...context?.replayingState === true && { onRefusal: (message) => this.logger.warn(message) }
|
|
39963
|
+
});
|
|
39905
39964
|
try {
|
|
39906
39965
|
const resourceRecordSet = this.buildResourceRecordSet(properties);
|
|
39907
39966
|
const comment = properties["Comment"];
|
|
@@ -39926,7 +39985,7 @@ var Route53Provider = class {
|
|
|
39926
39985
|
throw new ProvisioningError(`Failed to create record set ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, void 0, cause);
|
|
39927
39986
|
}
|
|
39928
39987
|
}
|
|
39929
|
-
async updateRecordSet(logicalId, physicalId, resourceType, properties) {
|
|
39988
|
+
async updateRecordSet(logicalId, physicalId, resourceType, properties, context) {
|
|
39930
39989
|
this.logger.debug(`Updating Route 53 record set ${logicalId}: ${physicalId}`);
|
|
39931
39990
|
const hostedZoneId = await this.resolveHostedZoneId(properties, logicalId, resourceType, physicalId);
|
|
39932
39991
|
const recordName = properties["Name"];
|
|
@@ -39944,7 +40003,10 @@ var Route53Provider = class {
|
|
|
39944
40003
|
name: "recordType",
|
|
39945
40004
|
value: recordType
|
|
39946
40005
|
}
|
|
39947
|
-
], {
|
|
40006
|
+
], {
|
|
40007
|
+
onRefusal: (message) => this.logger.warn(message),
|
|
40008
|
+
maskSecrets: context?.maskSecrets
|
|
40009
|
+
});
|
|
39948
40010
|
try {
|
|
39949
40011
|
const resourceRecordSet = this.buildResourceRecordSet(properties);
|
|
39950
40012
|
const comment = properties["Comment"];
|
|
@@ -40977,15 +41039,6 @@ function readDeclaredMfaConfiguration(raw, options) {
|
|
|
40977
41039
|
return value.trim() === "" ? "" : value;
|
|
40978
41040
|
}
|
|
40979
41041
|
/**
|
|
40980
|
-
* Depth cap for the pre-stringify secret walk in {@link buildMfaConfigRequest}.
|
|
40981
|
-
*
|
|
40982
|
-
* Only a guard against a self-referential bag, not a real bound: a resolved
|
|
40983
|
-
* `EnabledMfas` is a list of factor-name scalars, so the walk never reaches
|
|
40984
|
-
* depth 2 on any shape this warning exists to describe. Deliberately generous
|
|
40985
|
-
* so no plausible mis-shape is left unmasked by it.
|
|
40986
|
-
*/
|
|
40987
|
-
const MASK_WALK_MAX_DEPTH = 8;
|
|
40988
|
-
/**
|
|
40989
41042
|
* Read `EnabledMfas` as the pair (recognized list, "the template asked for a
|
|
40990
41043
|
* factor at all").
|
|
40991
41044
|
*
|
|
@@ -41119,15 +41172,9 @@ function buildMfaConfigRequest(physicalId, properties, logger, declaredMfaConfig
|
|
|
41119
41172
|
request.MfaConfiguration = declaredMfaConfiguration || (enablesMfaFactor ? "OPTIONAL" : "OFF");
|
|
41120
41173
|
if (enabledMfasBlank) warn(`UserPool ${physicalId}: EnabledMfas is an empty string, which declares no MFA factor and is treated as absent — a mangled Fn::Join or an empty String parameter collapses to this shape. Nothing is enabled from it and MfaConfiguration is ${request.MfaConfiguration}; declare the factor names as a list (e.g. ["${MFA_FACTOR_SOFTWARE_TOKEN}"]) if a factor was intended.`);
|
|
41121
41174
|
const anyFactorBlock = hasAnyMfaFactorBlock(request);
|
|
41122
|
-
const
|
|
41123
|
-
|
|
41124
|
-
|
|
41125
|
-
if (Array.isArray(v)) return v.map((e) => maskDeep(e, depth + 1));
|
|
41126
|
-
if (v !== null && typeof v === "object") return Object.fromEntries(Object.entries(v).map(([k, x]) => [maskSecrets(k), maskDeep(x, depth + 1)]));
|
|
41127
|
-
return v;
|
|
41128
|
-
};
|
|
41129
|
-
const dropped = unrecognizedFactors.map((f) => JSON.stringify(maskDeep(f)) ?? String(f));
|
|
41130
|
-
if (enabledMfasMalformed) dropped.push(`${JSON.stringify(maskDeep(properties["EnabledMfas"]))} (not a list)`);
|
|
41175
|
+
const maskLeaves = (v) => maskDeep(v, maskSecrets);
|
|
41176
|
+
const dropped = unrecognizedFactors.map((f) => JSON.stringify(maskLeaves(f)) ?? String(f));
|
|
41177
|
+
if (enabledMfasMalformed) dropped.push(`${JSON.stringify(maskLeaves(properties["EnabledMfas"]))} (not a list)`);
|
|
41131
41178
|
if (dropped.length > 0) {
|
|
41132
41179
|
const consequence = request.MfaConfiguration === "OFF" ? `MfaConfiguration is OFF, so this pool deploys with MFA DISABLED` : anyFactorBlock ? `MfaConfiguration is ${request.MfaConfiguration} and another factor block IS sent, so these entries are silently ignored rather than failing the call on their own` : `MfaConfiguration is ${request.MfaConfiguration} with no factor enabled, so AWS rejects this call rather than deploying the pool with MFA disabled`;
|
|
41133
41180
|
warn(`UserPool ${physicalId}: EnabledMfas entries ${dropped.join(", ")} do not map to an MFA factor block (known: ${MFA_FACTOR_SMS}, ${MFA_FACTOR_SOFTWARE_TOKEN}, ${MFA_FACTOR_EMAIL_OTP}); no factor is enabled from them. ${consequence}.`);
|
|
@@ -44885,7 +44932,10 @@ var AppSyncProvider = class {
|
|
|
44885
44932
|
}, {
|
|
44886
44933
|
name: "name",
|
|
44887
44934
|
value: name
|
|
44888
|
-
}],
|
|
44935
|
+
}], {
|
|
44936
|
+
maskSecrets: context?.maskSecrets,
|
|
44937
|
+
...context?.replayingState === true && { onRefusal: (message) => this.logger.warn(message) }
|
|
44938
|
+
});
|
|
44889
44939
|
let reportedArn;
|
|
44890
44940
|
try {
|
|
44891
44941
|
const input = {
|
|
@@ -44951,7 +45001,10 @@ var AppSyncProvider = class {
|
|
|
44951
45001
|
name: "fieldName",
|
|
44952
45002
|
value: fieldName
|
|
44953
45003
|
}
|
|
44954
|
-
],
|
|
45004
|
+
], {
|
|
45005
|
+
maskSecrets: context?.maskSecrets,
|
|
45006
|
+
...context?.replayingState === true && { onRefusal: (message) => this.logger.warn(message) }
|
|
45007
|
+
});
|
|
44955
45008
|
let reportedArn;
|
|
44956
45009
|
try {
|
|
44957
45010
|
const input = {
|
|
@@ -45018,7 +45071,10 @@ var AppSyncProvider = class {
|
|
|
45018
45071
|
}, {
|
|
45019
45072
|
name: "apiKeyId",
|
|
45020
45073
|
value: apiKeyId
|
|
45021
|
-
}],
|
|
45074
|
+
}], {
|
|
45075
|
+
maskSecrets: context?.maskSecrets,
|
|
45076
|
+
...context?.replayingState === true && { onRefusal: (message) => this.logger.warn(message) }
|
|
45077
|
+
});
|
|
45022
45078
|
const apiKeyArn = await this.buildAppSyncArnOrWarn(`apis/${apiId}/apikey/${apiKeyId}`, logicalId, physicalId);
|
|
45023
45079
|
return {
|
|
45024
45080
|
physicalId,
|
|
@@ -45704,7 +45760,10 @@ var GlueProvider = class {
|
|
|
45704
45760
|
}, {
|
|
45705
45761
|
name: "tableName",
|
|
45706
45762
|
value: tableName
|
|
45707
|
-
}],
|
|
45763
|
+
}], {
|
|
45764
|
+
maskSecrets: context?.maskSecrets,
|
|
45765
|
+
...context?.replayingState === true && { onRefusal: (message) => this.logger.warn(message) }
|
|
45766
|
+
});
|
|
45708
45767
|
try {
|
|
45709
45768
|
await this.getClient().send(new CreateTableCommand$1({
|
|
45710
45769
|
CatalogId: catalogId,
|
|
@@ -57184,7 +57243,10 @@ var S3TablesProvider = class {
|
|
|
57184
57243
|
}, {
|
|
57185
57244
|
name: "namespace",
|
|
57186
57245
|
value: namespaceName
|
|
57187
|
-
}],
|
|
57246
|
+
}], {
|
|
57247
|
+
maskSecrets: context?.maskSecrets,
|
|
57248
|
+
...context?.replayingState === true && { onRefusal: (message) => this.logger.warn(message) }
|
|
57249
|
+
});
|
|
57188
57250
|
try {
|
|
57189
57251
|
await this.getClient().send(new CreateNamespaceCommand({
|
|
57190
57252
|
tableBucketARN,
|
|
@@ -57244,7 +57306,10 @@ var S3TablesProvider = class {
|
|
|
57244
57306
|
name: "name",
|
|
57245
57307
|
value: name
|
|
57246
57308
|
}
|
|
57247
|
-
],
|
|
57309
|
+
], {
|
|
57310
|
+
maskSecrets: context?.maskSecrets,
|
|
57311
|
+
...context?.replayingState === true && { onRefusal: (message) => this.logger.warn(message) }
|
|
57312
|
+
});
|
|
57248
57313
|
try {
|
|
57249
57314
|
const response = await this.getClient().send(new CreateTableCommand$2({
|
|
57250
57315
|
tableBucketARN,
|
|
@@ -59380,6 +59445,19 @@ function isCertificateInUseError(error) {
|
|
|
59380
59445
|
* consumers (CloudFront, ALB) will fail to start if they reach the cert
|
|
59381
59446
|
* before it issues, but that's the documented trade-off.
|
|
59382
59447
|
*
|
|
59448
|
+
* **A create that fails after requesting DELETES its certificate** (issue
|
|
59449
|
+
* [#2169](https://github.com/go-to-k/cdkd/issues/2169)). `RequestCertificate`
|
|
59450
|
+
* materializes the certificate immediately and everything after it can fail —
|
|
59451
|
+
* most commonly the ISSUED wait, which a DNS-validated certificate whose
|
|
59452
|
+
* validation records are not live yet exhausts by construction. Before this,
|
|
59453
|
+
* the ARN was thrown away with the error: the certificate existed in AWS with
|
|
59454
|
+
* nothing naming it (absent from state, so invisible to `cdkd state show` and
|
|
59455
|
+
* unreachable by `cdkd destroy`), and the next `cdkd deploy` requested another
|
|
59456
|
+
* one — an orphan per attempt. `create()` now retires the certificate it just
|
|
59457
|
+
* requested before re-throwing, so a failed deploy leaves nothing behind and a
|
|
59458
|
+
* retry starts clean. See {@link ACMCertificateProvider.cleanupRequestedCertificate}
|
|
59459
|
+
* for why deleting is safe and why it is preferred over recording the remnant.
|
|
59460
|
+
*
|
|
59383
59461
|
* **CloudFront cross-region note**: ACM certificates referenced by a
|
|
59384
59462
|
* CloudFront Distribution MUST live in `us-east-1`. cdkd does not enforce
|
|
59385
59463
|
* this — it's the developer's responsibility to deploy the certificate
|
|
@@ -59429,12 +59507,22 @@ var ACMCertificateProvider = class {
|
|
|
59429
59507
|
if (Object.keys(options).length > 0) input["Options"] = options;
|
|
59430
59508
|
const tags = properties["Tags"];
|
|
59431
59509
|
if (tags && Array.isArray(tags) && tags.length > 0) input["Tags"] = tags;
|
|
59510
|
+
const idempotencyToken = acquireIdempotencyToken({
|
|
59511
|
+
scope: "RequestCertificate",
|
|
59512
|
+
logicalId,
|
|
59513
|
+
maxLength: 32,
|
|
59514
|
+
charset: "alphanumeric"
|
|
59515
|
+
});
|
|
59516
|
+
input["IdempotencyToken"] = idempotencyToken.value;
|
|
59517
|
+
let requestedArn;
|
|
59432
59518
|
try {
|
|
59433
59519
|
const certificateArn = (await this.acmClient.send(new RequestCertificateCommand(input))).CertificateArn;
|
|
59434
59520
|
if (!certificateArn) throw new ProvisioningError(`RequestCertificate succeeded but no CertificateArn returned for ${logicalId}`, resourceType, logicalId);
|
|
59521
|
+
requestedArn = certificateArn;
|
|
59435
59522
|
this.logger.debug(`Requested ACM certificate: ${certificateArn}`);
|
|
59436
|
-
if (!(process.env["CDKD_NO_WAIT"] === "true")) await this.waitForCertificateIssued(certificateArn, logicalId);
|
|
59523
|
+
if (!(process.env["CDKD_NO_WAIT"] === "true")) await this.waitForCertificateIssued(certificateArn, logicalId, resourceType);
|
|
59437
59524
|
else this.logger.warn(`Skipping wait for ACM certificate ${logicalId} (CDKD_NO_WAIT=true). Downstream consumers (CloudFront / ALB) will fail until the cert reaches ISSUED.`);
|
|
59525
|
+
idempotencyToken.release();
|
|
59438
59526
|
return {
|
|
59439
59527
|
physicalId: certificateArn,
|
|
59440
59528
|
attributes: {
|
|
@@ -59443,9 +59531,84 @@ var ACMCertificateProvider = class {
|
|
|
59443
59531
|
}
|
|
59444
59532
|
};
|
|
59445
59533
|
} catch (error) {
|
|
59446
|
-
|
|
59534
|
+
const survivorNote = requestedArn ? await this.cleanupRequestedCertificate(requestedArn, logicalId, idempotencyToken) : void 0;
|
|
59535
|
+
if (error instanceof CdkdError) {
|
|
59536
|
+
if (survivorNote === void 0) throw error;
|
|
59537
|
+
throw new ProvisioningError(`${error.message} ${survivorNote}`, resourceType, logicalId, requestedArn, error);
|
|
59538
|
+
}
|
|
59447
59539
|
const cause = error instanceof Error ? error : void 0;
|
|
59448
|
-
throw new ProvisioningError(`Failed to create ACM certificate ${logicalId}: ${error instanceof Error ? error.message : String(error)}
|
|
59540
|
+
throw new ProvisioningError(`Failed to create ACM certificate ${logicalId}: ${error instanceof Error ? error.message : String(error)}` + (survivorNote === void 0 ? "" : ` ${survivorNote}`), resourceType, logicalId, requestedArn, cause);
|
|
59541
|
+
}
|
|
59542
|
+
}
|
|
59543
|
+
/**
|
|
59544
|
+
* Retire the certificate a FAILED `create()` just requested, so the failure
|
|
59545
|
+
* leaves nothing behind (issue
|
|
59546
|
+
* [#2169](https://github.com/go-to-k/cdkd/issues/2169)).
|
|
59547
|
+
*
|
|
59548
|
+
* Returns `undefined` when the certificate is gone, or a one-line note naming
|
|
59549
|
+
* the survivor when it could not be deleted. Never throws: the original
|
|
59550
|
+
* create error is what the user needs to see, and a cleanup failure must
|
|
59551
|
+
* degrade that message rather than replace it.
|
|
59552
|
+
*
|
|
59553
|
+
* ## Why DELETING is right, when the obvious objection is that it throws away
|
|
59554
|
+
* the user's DNS work
|
|
59555
|
+
*
|
|
59556
|
+
* It does not. ACM derives a domain's validation CNAME from the domain and
|
|
59557
|
+
* the account, not from the certificate, and documents that the record "must
|
|
59558
|
+
* be added to your DNS database only once": *"Without the need to repeat
|
|
59559
|
+
* validation, you can request additional ACM certificates for your fully
|
|
59560
|
+
* qualified domain name (FQDN) for as long as the CNAME record remains in
|
|
59561
|
+
* place ... You can also replace a deleted certificate."*
|
|
59562
|
+
* (https://docs.aws.amazon.com/acm/latest/userguide/dns-validation.html)
|
|
59563
|
+
* So the CNAMEs a user adds after seeing this failure validate the NEXT
|
|
59564
|
+
* certificate too, and the retry issues normally. The intuition that a
|
|
59565
|
+
* `PENDING_VALIDATION` certificate is precious does not hold for ACM.
|
|
59566
|
+
*
|
|
59567
|
+
* ## Why not RECORD the remnant in cdkd state instead
|
|
59568
|
+
*
|
|
59569
|
+
* That was this fix's first shape, and it does not work. A state record makes
|
|
59570
|
+
* the next deploy diff the resource as `NO_CHANGE` -- the recorded properties
|
|
59571
|
+
* ARE the template's -- so cdkd would never touch that certificate again:
|
|
59572
|
+
* `cdkd deploy` would print "No changes detected" and exit 0 over a
|
|
59573
|
+
* certificate that is still unusable, and the CloudFront / ALB consuming it
|
|
59574
|
+
* would fail with no explanation. Turning a loud failure into a silent one is
|
|
59575
|
+
* worse than the orphan it fixes. Making the diff re-provision it instead
|
|
59576
|
+
* needs a marker on the state record -- a schema bump -- to buy behaviour
|
|
59577
|
+
* that deleting gives for free.
|
|
59578
|
+
*
|
|
59579
|
+
* ## The "keep it" escape hatch already exists
|
|
59580
|
+
*
|
|
59581
|
+
* A user who WANTS the certificate to outlive the deploy has
|
|
59582
|
+
* `CDKD_NO_WAIT=true` (`cdkd deploy --no-wait`), which returns success with
|
|
59583
|
+
* the certificate recorded in state and never reaches this path. "Wait for
|
|
59584
|
+
* it" and "keep it even if the wait fails" are different requests, and cdkd
|
|
59585
|
+
* already has a flag for the second.
|
|
59586
|
+
*/
|
|
59587
|
+
/**
|
|
59588
|
+
* The region an ACM ARN names, for the manual retire command. Taken from the
|
|
59589
|
+
* ARN rather than from the client so the command a user pastes matches the
|
|
59590
|
+
* certificate it names even when their shell default differs. Falls back to
|
|
59591
|
+
* the literal placeholder for a shape this cannot parse -- a wrong region
|
|
59592
|
+
* would make the command silently address a different account/region.
|
|
59593
|
+
*/
|
|
59594
|
+
regionOfArn(arn) {
|
|
59595
|
+
return arn.split(":")[3] || "<region>";
|
|
59596
|
+
}
|
|
59597
|
+
async cleanupRequestedCertificate(certificateArn, logicalId, idempotencyToken) {
|
|
59598
|
+
try {
|
|
59599
|
+
await this.acmClient.send(new DeleteCertificateCommand({ CertificateArn: certificateArn }));
|
|
59600
|
+
this.logger.info(`Deleted the ACM certificate ${certificateArn} that the failed create of ${logicalId} requested, so nothing is left behind. DNS validation records you have already added stay valid for the retry.`);
|
|
59601
|
+
idempotencyToken.release();
|
|
59602
|
+
return;
|
|
59603
|
+
} catch (cleanupError) {
|
|
59604
|
+
if (cleanupError instanceof ResourceNotFoundException$7 || cleanupError instanceof Error && cleanupError.name === "ResourceNotFoundException") {
|
|
59605
|
+
this.logger.info(`The ACM certificate ${certificateArn} that the failed create of ${logicalId} requested was already gone; nothing to clean up.`);
|
|
59606
|
+
idempotencyToken.release();
|
|
59607
|
+
return;
|
|
59608
|
+
}
|
|
59609
|
+
const detail = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
|
|
59610
|
+
this.logger.warn(`Could not delete the ACM certificate ${certificateArn} that the failed create of ${logicalId} requested: ${detail}`);
|
|
59611
|
+
return `The certificate ${certificateArn} this attempt created could NOT be deleted, and cdkd is not tracking it -- retire it with \`aws acm delete-certificate --certificate-arn ${certificateArn} --region ${this.regionOfArn(certificateArn)}\` (the reason is in the warning above).`;
|
|
59449
59612
|
}
|
|
59450
59613
|
}
|
|
59451
59614
|
async update(logicalId, physicalId, resourceType, properties, previousProperties) {
|
|
@@ -59618,11 +59781,25 @@ var ACMCertificateProvider = class {
|
|
|
59618
59781
|
* Throws on `VALIDATION_TIMED_OUT` / `FAILED` (terminal failures) and on
|
|
59619
59782
|
* polling-cap exhaustion (treated as timeout). SIGINT short-circuits the
|
|
59620
59783
|
* loop and returns control to the deploy engine's cleanup path.
|
|
59784
|
+
*
|
|
59785
|
+
* Both throws are `ProvisioningError`s carrying `certificateArn` as their
|
|
59786
|
+
* `physicalId` (issue
|
|
59787
|
+
* [#2169](https://github.com/go-to-k/cdkd/issues/2169)). They used to be
|
|
59788
|
+
* plain `Error`s whose only reference to the certificate was the message
|
|
59789
|
+
* TEXT, which is not something a caller can act on. The certificate is real
|
|
59790
|
+
* by the time either fires, so the error that reports the failure is the one
|
|
59791
|
+
* piece of the failure path that should still be able to name it.
|
|
59792
|
+
*
|
|
59793
|
+
* Says nothing about what happens to the certificate afterwards: the caller
|
|
59794
|
+
* owns that. `create()`'s catch retires it (see
|
|
59795
|
+
* {@link ACMCertificateProvider.cleanupRequestedCertificate}) and appends a
|
|
59796
|
+
* note to this message only when that cleanup could NOT.
|
|
59621
59797
|
*/
|
|
59622
|
-
async waitForCertificateIssued(certificateArn, logicalId) {
|
|
59798
|
+
async waitForCertificateIssued(certificateArn, logicalId, resourceType) {
|
|
59623
59799
|
this.logger.debug(`Waiting for ACM certificate ${certificateArn} to reach ISSUED status...`);
|
|
59624
59800
|
let interrupted = false;
|
|
59625
|
-
|
|
59801
|
+
/** The last validation-record block printed, so we re-print only on a CHANGE. */
|
|
59802
|
+
let lastValidationOptions;
|
|
59626
59803
|
const sigintHandler = () => {
|
|
59627
59804
|
interrupted = true;
|
|
59628
59805
|
};
|
|
@@ -59640,17 +59817,20 @@ var ACMCertificateProvider = class {
|
|
|
59640
59817
|
this.logger.debug(`ACM certificate ${certificateArn} is ISSUED`);
|
|
59641
59818
|
return;
|
|
59642
59819
|
}
|
|
59643
|
-
if (status === "FAILED" || status === "VALIDATION_TIMED_OUT" || status === "INACTIVE" || status === "REVOKED" || status === "EXPIRED") throw new
|
|
59644
|
-
if (status === "PENDING_VALIDATION"
|
|
59645
|
-
this.
|
|
59646
|
-
|
|
59820
|
+
if (status === "FAILED" || status === "VALIDATION_TIMED_OUT" || status === "INACTIVE" || status === "REVOKED" || status === "EXPIRED") throw new ProvisioningError(`ACM certificate ${logicalId} (${certificateArn}) entered terminal status ${status} during validation. Check ACM console / DNS records to diagnose.`, resourceType, logicalId, certificateArn);
|
|
59821
|
+
if (status === "PENDING_VALIDATION") {
|
|
59822
|
+
const rendered = this.renderValidationOptions(validations);
|
|
59823
|
+
if (rendered !== void 0 && rendered !== lastValidationOptions) {
|
|
59824
|
+
this.logger.info(rendered);
|
|
59825
|
+
lastValidationOptions = rendered;
|
|
59826
|
+
}
|
|
59647
59827
|
}
|
|
59648
59828
|
this.logger.debug(`ACM certificate ${certificateArn} status: ${status} (attempt ${attempt}/${this.maxPollAttempts})`);
|
|
59649
59829
|
const sleepEnd = Date.now() + this.pollIntervalMs;
|
|
59650
59830
|
const tickMs = Math.min(1e3, this.pollIntervalMs);
|
|
59651
59831
|
while (Date.now() < sleepEnd && !interrupted) await new Promise((resolve) => setTimeout(resolve, tickMs));
|
|
59652
59832
|
}
|
|
59653
|
-
throw new
|
|
59833
|
+
throw new ProvisioningError(`ACM certificate ${logicalId} (${certificateArn}) did not reach ISSUED status within ${this.maxPollAttempts * this.pollIntervalMs / 1e3}s. ` + (lastValidationOptions === void 0 ? "AWS had not published this certificate's validation records before the wait ran out, so none were printed — re-run the deploy to request them again. " : `Add the validation records printed above to your DNS zone and re-run the deploy. `) + "ACM reuses a domain's validation CNAME across certificates, so records you add now validate the next attempt. To wait LONGER, raise CDKD_ACM_POLL_ATTEMPTS / CDKD_ACM_POLL_INTERVAL_MS — this cap is the provider's OWN and is what just fired; --resource-timeout alone will not lengthen it. Past ~30m also raise --resource-timeout AWS::CertificateManager::Certificate=<duration>, or the engine's per-resource deadline becomes the shorter of the two. Or set CDKD_NO_WAIT=true to keep the certificate and validate it out of band.", resourceType, logicalId, certificateArn);
|
|
59654
59834
|
} finally {
|
|
59655
59835
|
process.removeListener("SIGINT", sigintHandler);
|
|
59656
59836
|
}
|
|
@@ -59660,16 +59840,13 @@ var ACMCertificateProvider = class {
|
|
|
59660
59840
|
* user can copy / paste them into Route 53 / Cloudflare / etc. while the
|
|
59661
59841
|
* cert is still PENDING_VALIDATION.
|
|
59662
59842
|
*/
|
|
59663
|
-
|
|
59843
|
+
renderValidationOptions(validations) {
|
|
59664
59844
|
const lines = ["ACM certificate is PENDING_VALIDATION. Add the following DNS records to validate:"];
|
|
59665
59845
|
for (const v of validations) if (v.ValidationMethod === "DNS" && v.ResourceRecord) {
|
|
59666
59846
|
const r = v.ResourceRecord;
|
|
59667
59847
|
lines.push(` ${v.DomainName} — ${r.Type} ${r.Name} -> ${r.Value}`);
|
|
59668
|
-
} else if (v.ValidationMethod === "EMAIL") {
|
|
59669
|
-
|
|
59670
|
-
lines.push(` ${v.DomainName} — confirmation email sent to: ${emails || "<none>"}`);
|
|
59671
|
-
}
|
|
59672
|
-
this.logger.info(lines.join("\n"));
|
|
59848
|
+
} else if (v.ValidationMethod === "EMAIL" && (v.ValidationEmails ?? []).length > 0) lines.push(` ${v.DomainName} — confirmation email sent to: ${v.ValidationEmails.join(", ")}`);
|
|
59849
|
+
return lines.length === 1 ? void 0 : lines.join("\n");
|
|
59673
59850
|
}
|
|
59674
59851
|
async updateTags(certificateArn, newTags, oldTags) {
|
|
59675
59852
|
const newTagMap = new Map((newTags || []).map((t) => [t.Key, t.Value]));
|
|
@@ -86442,7 +86619,7 @@ function createMigrateCommand() {
|
|
|
86442
86619
|
*/
|
|
86443
86620
|
function buildProgram() {
|
|
86444
86621
|
const program = new Command();
|
|
86445
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.284.
|
|
86622
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.284.41");
|
|
86446
86623
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
86447
86624
|
const { profile } = actionCommand.optsWithGlobals();
|
|
86448
86625
|
if (profile !== void 0) process.env["AWS_PROFILE"] = profile;
|