@go-to-k/cdkd 0.284.12 → 0.284.14
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-CkIFzX7p.js → asg-provider-DZAeGqBC.js} +2 -2
- package/dist/{asg-provider-CkIFzX7p.js.map → asg-provider-DZAeGqBC.js.map} +1 -1
- package/dist/cli.js +499 -46
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-nwoIJsLn.js → deploy-engine-C2OBi4mp.js} +4 -4
- package/dist/{deploy-engine-nwoIJsLn.js.map → deploy-engine-C2OBi4mp.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 isTerminationProtectionPropagationError, $n as ResourceUpdateNotSupportedError, $t as validateAssetBucketName, A as renderStatefulReason, An as derivePartitionAndUrlSuffix, At as INTRINSIC_KEYS, B as exportAliasCollisionScrubWarning, Bn as CdkdError, Bt as stringifyValue, C as isFinalSnapshotError, Cn as CFN_TEMPLATE_URL_LIMIT, Ct as s3BucketArn, D as extractDeploymentEventError, Dn as expectedOwnerParam, Dt as s3BucketWebsiteUrl, E as makeCanonicalizePropertiesFn, En as uploadCfnTemplate, Et as s3BucketRegionalDomainName, F as green, Fn as AwsClients, Ft as LockManager, G as collectInlinePolicyNamesManagedBySiblings, Gn as LocalMigrateError, Gt as rewriteTemplateAssetReferences, H as secretBearingStateKeyWarning, Ht as buildAssetRedirectMap, I as red, In as getAwsClients, It as S3StateBackend, J as findActionableSilentDrops, Jn as MissingCdkCliError, Jt as AssetModeResolver, K as clearOnUpdateRemoval, Kn as LocalStartServiceError, Kt as escapeRegExp, L as yellow, Ln as resetAwsClients, Lt as rebuildClientForBucketRegion, M as bold, Mn as processStackMessages, Mt as withRetry, N as cyan, Nt as DagBuilder, O as MULTI_REGION_RECREATE_BLOCKED_TYPES, On as PARTITION_TABLE, Ot as applyRoleArnIfSet, P as gray, Pn as resolveBucketRegion, Pt as TemplateParser, Q as disableInstanceApiTermination, Qn as ResourceTimeoutError, Qt as parseBootstrapMarker, R as collectDeclaredOutputNames, Rn as setAwsClients, Rt as shouldRetainResource, S as createPreDeleteFinalSnapshot, Sn as CFN_TEMPLATE_BODY_LIMIT, St as scrubResourceRecord, T as unsupportedFinalSnapshotError, Tn as findLargeInlineResources, Tt as s3BucketDualStackDomainName, U as stateKeySecretExposure, Un as DeployCancelledError, Ut as createAssetRedirectResolver, V as isExportAliasCollision, Vt as WorkGraph, W as IAMRoleProvider, Wn as LocalInvokeBuildError$1, Wt as loadPublishableAssetManifest, X as CloudControlProvider, Xn as PartialFailureError, Xt as ensureAssetStorage, Y as findSilentDropProperties, Yn as NestedStackChildDirectDestroyError, Yt as BOOTSTRAP_MARKER_PREFIX, Z as slowCcOperationTimeoutMs, Zn as ProvisioningError, Zt as getBootstrapMarkerKey, _ as computeImplicitDeleteEdges, _n as resolveStateBucketWithDefault, _t as STATE_SOURCED_READBACK_RULES, a as DeploymentEventsStore, an as runDockerForeground, at as normalizeAwsTagsToCfn, b as buildFinalSnapshotIdentifier, bn as stateBucketExistenceConfirmed, bt as maskSecretsInText, c as replayFailedOperations, cn as getDockerImageBySourceHash, cr as isMarkedNonRetryable, ct as coerceCfnBoolean, d as updatePartialReason, dn as getDefaultStateBucketName, dr as markNonRetryable, dt as readConfigString, en as validateContainerRepoName, er as StackHasActiveImportsError, et as IntrinsicFunctionResolver, f as UNSPECIFIED_SKIP_REASON, fn as getLegacyStateBucketName, ft as replayWarn, g as IMPLICIT_DELETE_DEPENDENCIES, gn as resolveSkipPrefix, gt as STATE_SOURCED_CROSS_GENERATION_RULES, h as maskingRetryLogger, hn as resolveCaptureObservedState, ht as requireConfigString, i as DeploymentEventsReader, in as getDockerCmd, it as WAFv2WebACLProvider, j as formatResourceLine, jn as AssemblyReader, jt as describeTypeWithThrottleRetry, k as isStatefulRecreateTargetSync, kn as canonicalizeRegion, kt as DiffCalculator, l as replayRollback, ln as Synthesizer, lr as isRetryableTransientError, lt as configBooleanRefusal, m as withResourceDeadline, mn as resolveAutoAssetStorage, mt as requireConfigObject, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as buildDockerImage, nt as getAccountInfo, o as planFailedOps, on as runDockerStreaming, or as normalizeAwsError, ot as resolveExplicitPhysicalId, p as deleteSkipReason, pn as resolveApp, pt as requireConfigArray, q as ProviderRegistry, qt as stripControlChars, r as DeployEngine, rn as formatDockerLoginError, rt as refStateLookupFromResource, s as planRollback, sn as AssetManifestLoader, sr as withErrorHandling, st as assertRegionMatch, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as buildDenyExternalAccessPolicy, tr as StackTerminationProtectionError, tt as cfnRefValueFromPhysicalId, u as updatePartialMessage, un as synthesisStatusMessage, ur as isThrottlingError, ut as configStringRefusal, v as ATOMIC_FINAL_SNAPSHOT_TYPES, vn as resolveStateBucketWithDefaultAndSource, vt as TEMPLATE_SOURCED_RULES, w as refusesFinalSnapshot, wn as MIGRATE_TMP_PREFIX, wt as s3BucketDomainName, x as ccRoutedFinalSnapshotError, xn as warnDeprecatedNoPrefixCliFlag, xt as redactSecretsForState, y as PRE_DELETE_SNAPSHOT_TYPES, yn as resolveUseCdkBootstrapAssets, yt as createSecretMasker, z as collectPublishedOutputNames, zt as AssetPublisher } from "./deploy-engine-
|
|
2
|
+
import { $ as isTerminationProtectionPropagationError, $n as ResourceUpdateNotSupportedError, $t as validateAssetBucketName, A as renderStatefulReason, An as derivePartitionAndUrlSuffix, At as INTRINSIC_KEYS, B as exportAliasCollisionScrubWarning, Bn as CdkdError, Bt as stringifyValue, C as isFinalSnapshotError, Cn as CFN_TEMPLATE_URL_LIMIT, Ct as s3BucketArn, D as extractDeploymentEventError, Dn as expectedOwnerParam, Dt as s3BucketWebsiteUrl, E as makeCanonicalizePropertiesFn, En as uploadCfnTemplate, Et as s3BucketRegionalDomainName, F as green, Fn as AwsClients, Ft as LockManager, G as collectInlinePolicyNamesManagedBySiblings, Gn as LocalMigrateError, Gt as rewriteTemplateAssetReferences, H as secretBearingStateKeyWarning, Ht as buildAssetRedirectMap, I as red, In as getAwsClients, It as S3StateBackend, J as findActionableSilentDrops, Jn as MissingCdkCliError, Jt as AssetModeResolver, K as clearOnUpdateRemoval, Kn as LocalStartServiceError, Kt as escapeRegExp, L as yellow, Ln as resetAwsClients, Lt as rebuildClientForBucketRegion, M as bold, Mn as processStackMessages, Mt as withRetry, N as cyan, Nt as DagBuilder, O as MULTI_REGION_RECREATE_BLOCKED_TYPES, On as PARTITION_TABLE, Ot as applyRoleArnIfSet, P as gray, Pn as resolveBucketRegion, Pt as TemplateParser, Q as disableInstanceApiTermination, Qn as ResourceTimeoutError, Qt as parseBootstrapMarker, R as collectDeclaredOutputNames, Rn as setAwsClients, Rt as shouldRetainResource, S as createPreDeleteFinalSnapshot, Sn as CFN_TEMPLATE_BODY_LIMIT, St as scrubResourceRecord, T as unsupportedFinalSnapshotError, Tn as findLargeInlineResources, Tt as s3BucketDualStackDomainName, U as stateKeySecretExposure, Un as DeployCancelledError, Ut as createAssetRedirectResolver, V as isExportAliasCollision, Vt as WorkGraph, W as IAMRoleProvider, Wn as LocalInvokeBuildError$1, Wt as loadPublishableAssetManifest, X as CloudControlProvider, Xn as PartialFailureError, Xt as ensureAssetStorage, Y as findSilentDropProperties, Yn as NestedStackChildDirectDestroyError, Yt as BOOTSTRAP_MARKER_PREFIX, Z as slowCcOperationTimeoutMs, Zn as ProvisioningError, Zt as getBootstrapMarkerKey, _ as computeImplicitDeleteEdges, _n as resolveStateBucketWithDefault, _t as STATE_SOURCED_READBACK_RULES, a as DeploymentEventsStore, an as runDockerForeground, at as normalizeAwsTagsToCfn, b as buildFinalSnapshotIdentifier, bn as stateBucketExistenceConfirmed, bt as maskSecretsInText, c as replayFailedOperations, cn as getDockerImageBySourceHash, cr as isMarkedNonRetryable, ct as coerceCfnBoolean, d as updatePartialReason, dn as getDefaultStateBucketName, dr as markNonRetryable, dt as readConfigString, en as validateContainerRepoName, er as StackHasActiveImportsError, et as IntrinsicFunctionResolver, f as UNSPECIFIED_SKIP_REASON, fn as getLegacyStateBucketName, ft as replayWarn, g as IMPLICIT_DELETE_DEPENDENCIES, gn as resolveSkipPrefix, gt as STATE_SOURCED_CROSS_GENERATION_RULES, h as maskingRetryLogger, hn as resolveCaptureObservedState, ht as requireConfigString, i as DeploymentEventsReader, in as getDockerCmd, it as WAFv2WebACLProvider, j as formatResourceLine, jn as AssemblyReader, jt as describeTypeWithThrottleRetry, k as isStatefulRecreateTargetSync, kn as canonicalizeRegion, kt as DiffCalculator, l as replayRollback, ln as Synthesizer, lr as isRetryableTransientError, lt as configBooleanRefusal, m as withResourceDeadline, mn as resolveAutoAssetStorage, mt as requireConfigObject, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as buildDockerImage, nt as getAccountInfo, o as planFailedOps, on as runDockerStreaming, or as normalizeAwsError, ot as resolveExplicitPhysicalId, p as deleteSkipReason, pn as resolveApp, pt as requireConfigArray, q as ProviderRegistry, qt as stripControlChars, r as DeployEngine, rn as formatDockerLoginError, rt as refStateLookupFromResource, s as planRollback, sn as AssetManifestLoader, sr as withErrorHandling, st as assertRegionMatch, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as buildDenyExternalAccessPolicy, tr as StackTerminationProtectionError, tt as cfnRefValueFromPhysicalId, u as updatePartialMessage, un as synthesisStatusMessage, ur as isThrottlingError, ut as configStringRefusal, v as ATOMIC_FINAL_SNAPSHOT_TYPES, vn as resolveStateBucketWithDefaultAndSource, vt as TEMPLATE_SOURCED_RULES, w as refusesFinalSnapshot, wn as MIGRATE_TMP_PREFIX, wt as s3BucketDomainName, x as ccRoutedFinalSnapshotError, xn as warnDeprecatedNoPrefixCliFlag, xt as redactSecretsForState, y as PRE_DELETE_SNAPSHOT_TYPES, yn as resolveUseCdkBootstrapAssets, yt as createSecretMasker, z as collectPublishedOutputNames, zt as AssetPublisher } from "./deploy-engine-C2OBi4mp.js";
|
|
3
3
|
import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, 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-DZAeGqBC.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";
|
|
@@ -33995,6 +33995,44 @@ var ECSProvider = class {
|
|
|
33995
33995
|
}
|
|
33996
33996
|
};
|
|
33997
33997
|
|
|
33998
|
+
//#endregion
|
|
33999
|
+
//#region src/provisioning/masked-retry-logger.ts
|
|
34000
|
+
/**
|
|
34001
|
+
* The masker a caller supplied, or the identity function when it supplied
|
|
34002
|
+
* none.
|
|
34003
|
+
*
|
|
34004
|
+
* ABSENT MEANS UNMASKED, and that is the back-compatible default the contract
|
|
34005
|
+
* mandates — `create()` / `update()` are also reached from the import path,
|
|
34006
|
+
* from `cdkd drift --revert`, and from tests, and a provider must not care
|
|
34007
|
+
* which caller it got. Centralised here so no call site re-spells the `??`
|
|
34008
|
+
* and accidentally makes the capability required.
|
|
34009
|
+
*/
|
|
34010
|
+
function maskerOrIdentity(maskSecrets) {
|
|
34011
|
+
return maskSecrets ?? ((text) => text);
|
|
34012
|
+
}
|
|
34013
|
+
/**
|
|
34014
|
+
* A {@link RetryLogger} whose every line is routed through `maskSecrets`.
|
|
34015
|
+
*
|
|
34016
|
+
* `warn` is ALWAYS provided, never omitted. It is optional on `RetryLogger`,
|
|
34017
|
+
* and dropping it would silence the give-up summary on the calling path only —
|
|
34018
|
+
* trading a disclosure for the reporting hole issue #2018 closed. It goes
|
|
34019
|
+
* through the SAME mask as `debug` precisely because it is the line that
|
|
34020
|
+
* survives a run without `--verbose`; forwarding it unmasked would defeat the
|
|
34021
|
+
* fence at a HIGHER log level than the one the fence was written for.
|
|
34022
|
+
*
|
|
34023
|
+
* `logger` is typed structurally rather than as the concrete `Logger` so this
|
|
34024
|
+
* module stays a leaf and a test can pass two spies. That matches the
|
|
34025
|
+
* precedent already in the tree (`buildMfaConfigRequest` in
|
|
34026
|
+
* `cognito-provider.ts` takes an injected `logger?: { warn }`).
|
|
34027
|
+
*/
|
|
34028
|
+
function createMaskedRetryLogger(logger, maskSecrets) {
|
|
34029
|
+
const mask = maskerOrIdentity(maskSecrets);
|
|
34030
|
+
return {
|
|
34031
|
+
debug: (message) => logger.debug(mask(message)),
|
|
34032
|
+
warn: (message) => logger.warn(mask(message))
|
|
34033
|
+
};
|
|
34034
|
+
}
|
|
34035
|
+
|
|
33998
34036
|
//#endregion
|
|
33999
34037
|
//#region src/provisioning/providers/elbv2-provider.ts
|
|
34000
34038
|
/**
|
|
@@ -34004,6 +34042,41 @@ var ECSProvider = class {
|
|
|
34004
34042
|
*/
|
|
34005
34043
|
const capacityReservationDelays = { sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)) };
|
|
34006
34044
|
/**
|
|
34045
|
+
* Depth cap for {@link maskDeep}. Bounds a self-referential bag rather than
|
|
34046
|
+
* hanging; `JSON.stringify` would throw on one anyway, and a throw is the
|
|
34047
|
+
* better failure of the two.
|
|
34048
|
+
*/
|
|
34049
|
+
const MASK_WALK_MAX_DEPTH$1 = 8;
|
|
34050
|
+
/**
|
|
34051
|
+
* Mask every string LEAF and KEY of an arbitrary value, returning a structure
|
|
34052
|
+
* safe to `JSON.stringify` into a log line (issue #2050).
|
|
34053
|
+
*
|
|
34054
|
+
* The ordering is the whole point. `maskSecretsInText` matches by literal
|
|
34055
|
+
* occurrence, and `JSON.stringify` escapes `"`, `\` and newlines — so a Secrets
|
|
34056
|
+
* Manager JSON document (the commonest real secret shape) does not occur in the
|
|
34057
|
+
* stringified text and survives a mask applied afterwards. Masking the leaves
|
|
34058
|
+
* first also hands the masker each RAW value, which reaches
|
|
34059
|
+
* `maskSecretsInText`'s whole-value arm at any length instead of only the
|
|
34060
|
+
* substring arm's 4-character floor. See the "three gaps" note in
|
|
34061
|
+
* `src/deployment/secret-redaction.ts`.
|
|
34062
|
+
*
|
|
34063
|
+
* Keys are masked as well as values: `JSON.stringify` renders them into the
|
|
34064
|
+
* same line, so a resolved secret used as a map key would otherwise escape.
|
|
34065
|
+
*
|
|
34066
|
+
* `cognito-provider.ts` carries an equivalent private walk for its own
|
|
34067
|
+
* stringified warnings. Two copies is the current cost of keeping this a leaf;
|
|
34068
|
+
* a THIRD site is the point at which this should move into
|
|
34069
|
+
* `../masked-retry-logger.ts` (already the shared provider-side masking module)
|
|
34070
|
+
* and all three converge on it.
|
|
34071
|
+
*/
|
|
34072
|
+
function maskDeep(value, mask, depth = 0) {
|
|
34073
|
+
if (typeof value === "string") return mask(value);
|
|
34074
|
+
if (depth >= MASK_WALK_MAX_DEPTH$1) return value;
|
|
34075
|
+
if (Array.isArray(value)) return value.map((entry) => maskDeep(entry, mask, depth + 1));
|
|
34076
|
+
if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [mask(k), maskDeep(v, mask, depth + 1)]));
|
|
34077
|
+
return value;
|
|
34078
|
+
}
|
|
34079
|
+
/**
|
|
34007
34080
|
* Documented AWS defaults for TargetGroup attributes, used to reset a
|
|
34008
34081
|
* removed `TargetGroupAttributes` entry. Unlike ModifyLoadBalancerAttributes
|
|
34009
34082
|
* / ModifyListenerAttributes, ModifyTargetGroupAttributes REJECTS an empty
|
|
@@ -34177,28 +34250,86 @@ var ELBv2Provider = class {
|
|
|
34177
34250
|
if (!this.elbv2Client) this.elbv2Client = new ElasticLoadBalancingV2Client(this.providerRegion ? { region: this.providerRegion } : {});
|
|
34178
34251
|
return this.elbv2Client;
|
|
34179
34252
|
}
|
|
34180
|
-
|
|
34253
|
+
/**
|
|
34254
|
+
* A {@link RetryLogger} for this provider's `withRetry` calls, bound to the
|
|
34255
|
+
* caller's secret masker (issue #2050).
|
|
34256
|
+
*
|
|
34257
|
+
* Thin wrapper over the shared {@link createMaskedRetryLogger} so the two
|
|
34258
|
+
* providers that need it cannot drift apart — see that module for why the
|
|
34259
|
+
* factory is shared rather than hand-rolled per file, and for the full
|
|
34260
|
+
* rationale on why `warn` is threaded rather than omitted.
|
|
34261
|
+
*
|
|
34262
|
+
* WHY NOT A `maskSecrets` OPTION ON `withRetry` (issue #2050 acceptance
|
|
34263
|
+
* item 3): `RetryLogger` is a structural type any caller can already satisfy
|
|
34264
|
+
* with a masking object — `drift.ts`'s revert has done exactly that since
|
|
34265
|
+
* issue #1914 — so the option would be a SECOND spelling of one intent
|
|
34266
|
+
* across ~50 existing call sites, and two spellings is how a later author
|
|
34267
|
+
* reaches for the unmasked one.
|
|
34268
|
+
*/
|
|
34269
|
+
maskedRetryLogger(maskSecrets) {
|
|
34270
|
+
return createMaskedRetryLogger(this.logger, maskSecrets);
|
|
34271
|
+
}
|
|
34272
|
+
/**
|
|
34273
|
+
* Mask a caught AWS error's message before it is interpolated into the
|
|
34274
|
+
* `ProvisioningError` this provider throws (issue #2050, review round 2).
|
|
34275
|
+
*
|
|
34276
|
+
* NOT a duplicate of {@link maskedRetryLogger}, and strictly WIDER than it.
|
|
34277
|
+
* `withRetry` rethrows the RAW error, and `deploy-engine.ts` prints the
|
|
34278
|
+
* resulting message at ERROR — i.e. at DEFAULT verbosity — so a masked
|
|
34279
|
+
* give-up `warn` was being followed one line later by the identical text
|
|
34280
|
+
* unmasked. It is also the ONLY disclosure surface for a NON-RETRYABLE
|
|
34281
|
+
* rejection, where `withRetry` emits nothing at all: its give-up summary is
|
|
34282
|
+
* gated on `propagationRetries > 0 || serverErrorRetries > 0`, and a
|
|
34283
|
+
* validation error that fails on attempt 0 satisfies neither.
|
|
34284
|
+
*
|
|
34285
|
+
* Masks `error.message` rather than the assembled sentence ON PURPOSE. The
|
|
34286
|
+
* two are NOT equivalent: handing the masker the raw message can reach
|
|
34287
|
+
* `maskSecretsInText`'s WHOLE-VALUE arm, which matches at ANY length, while
|
|
34288
|
+
* a longer assembled sentence can only ever reach the SUBSTRING arm, which
|
|
34289
|
+
* ignores needles below 4 characters. See `SecretMaskingContext` in
|
|
34290
|
+
* `src/types/resource.ts`.
|
|
34291
|
+
*
|
|
34292
|
+
* The `cause` chain is deliberately left UNMASKED and unwrapped by every
|
|
34293
|
+
* caller of this helper: `isRetryableTransientError` walks `cause` for
|
|
34294
|
+
* `$metadata.httpStatusCode`, and rewriting or dropping it would silently
|
|
34295
|
+
* change retry classification. Only the human-readable message is rewritten.
|
|
34296
|
+
*/
|
|
34297
|
+
maskErrorMessage(error, maskSecrets) {
|
|
34298
|
+
return maskerOrIdentity(maskSecrets)(error instanceof Error ? error.message : String(error));
|
|
34299
|
+
}
|
|
34300
|
+
/**
|
|
34301
|
+
* `context` is read for ONE thing today: `maskSecrets` (issue #2050). The
|
|
34302
|
+
* Listener create path runs a post-create `ModifyListenerAttributes` through
|
|
34303
|
+
* `withRetry`, whose per-attempt `debug` line and give-up `warn` summary
|
|
34304
|
+
* interpolate the AWS message verbatim — and that payload is built from
|
|
34305
|
+
* RESOLVED template properties. See {@link maskedRetryLogger}.
|
|
34306
|
+
*/
|
|
34307
|
+
async create(logicalId, resourceType, properties, context) {
|
|
34181
34308
|
switch (resourceType) {
|
|
34182
34309
|
case "AWS::ElasticLoadBalancingV2::LoadBalancer": return this.createLoadBalancer(logicalId, resourceType, properties);
|
|
34183
|
-
case "AWS::ElasticLoadBalancingV2::TargetGroup": return this.createTargetGroup(logicalId, resourceType, properties);
|
|
34184
|
-
case "AWS::ElasticLoadBalancingV2::Listener": return this.createListener(logicalId, resourceType, properties);
|
|
34310
|
+
case "AWS::ElasticLoadBalancingV2::TargetGroup": return this.createTargetGroup(logicalId, resourceType, properties, context?.maskSecrets);
|
|
34311
|
+
case "AWS::ElasticLoadBalancingV2::Listener": return this.createListener(logicalId, resourceType, properties, context?.maskSecrets);
|
|
34185
34312
|
default: throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId);
|
|
34186
34313
|
}
|
|
34187
34314
|
}
|
|
34188
|
-
|
|
34315
|
+
/**
|
|
34316
|
+
* `context` is read for ONE thing today: `maskSecrets` (issue #2050) — the
|
|
34317
|
+
* update-path twin of the `create()` note above.
|
|
34318
|
+
*/
|
|
34319
|
+
async update(logicalId, physicalId, resourceType, properties, previousProperties, context) {
|
|
34189
34320
|
try {
|
|
34190
|
-
return await this.applyUpdate(logicalId, physicalId, resourceType, properties, previousProperties);
|
|
34321
|
+
return await this.applyUpdate(logicalId, physicalId, resourceType, properties, previousProperties, context);
|
|
34191
34322
|
} catch (error) {
|
|
34192
34323
|
if (error instanceof CdkdError) throw error;
|
|
34193
34324
|
const cause = error instanceof Error ? error : void 0;
|
|
34194
34325
|
throw new ProvisioningError(`Failed to update ELBv2 resource ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
|
|
34195
34326
|
}
|
|
34196
34327
|
}
|
|
34197
|
-
async applyUpdate(logicalId, physicalId, resourceType, properties, previousProperties) {
|
|
34328
|
+
async applyUpdate(logicalId, physicalId, resourceType, properties, previousProperties, context) {
|
|
34198
34329
|
switch (resourceType) {
|
|
34199
34330
|
case "AWS::ElasticLoadBalancingV2::LoadBalancer": return this.updateLoadBalancer(logicalId, physicalId, resourceType, properties, previousProperties);
|
|
34200
|
-
case "AWS::ElasticLoadBalancingV2::TargetGroup": return this.updateTargetGroup(logicalId, physicalId, resourceType, properties, previousProperties);
|
|
34201
|
-
case "AWS::ElasticLoadBalancingV2::Listener": return this.updateListener(logicalId, physicalId, resourceType, properties, previousProperties);
|
|
34331
|
+
case "AWS::ElasticLoadBalancingV2::TargetGroup": return this.updateTargetGroup(logicalId, physicalId, resourceType, properties, previousProperties, context?.maskSecrets);
|
|
34332
|
+
case "AWS::ElasticLoadBalancingV2::Listener": return this.updateListener(logicalId, physicalId, resourceType, properties, previousProperties, context?.maskSecrets);
|
|
34202
34333
|
default: throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId, physicalId);
|
|
34203
34334
|
}
|
|
34204
34335
|
}
|
|
@@ -34443,7 +34574,7 @@ var ELBv2Provider = class {
|
|
|
34443
34574
|
throw new ProvisioningError(`Failed to delete LoadBalancer ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
|
|
34444
34575
|
}
|
|
34445
34576
|
}
|
|
34446
|
-
async createTargetGroup(logicalId, resourceType, properties) {
|
|
34577
|
+
async createTargetGroup(logicalId, resourceType, properties, maskSecrets) {
|
|
34447
34578
|
this.logger.debug(`Creating TargetGroup ${logicalId}`);
|
|
34448
34579
|
try {
|
|
34449
34580
|
const tags = this.extractTags(properties);
|
|
@@ -34481,7 +34612,7 @@ var ELBv2Provider = class {
|
|
|
34481
34612
|
}));
|
|
34482
34613
|
this.logger.debug(`Applied ${tgAttributes.length} TargetGroup attribute(s) for ${logicalId}`);
|
|
34483
34614
|
}
|
|
34484
|
-
const targets = this.convertTargets(properties["Targets"]);
|
|
34615
|
+
const targets = this.convertTargets(properties["Targets"], void 0, maskSecrets);
|
|
34485
34616
|
if (targets.length > 0) {
|
|
34486
34617
|
await this.getClient().send(new RegisterTargetsCommand({
|
|
34487
34618
|
TargetGroupArn: tgArn,
|
|
@@ -34494,7 +34625,7 @@ var ELBv2Provider = class {
|
|
|
34494
34625
|
await this.getClient().send(new DeleteTargetGroupCommand({ TargetGroupArn: tgArn }));
|
|
34495
34626
|
this.logger.debug(`Cleaned up partially-created TargetGroup ${logicalId} (${tgArn}) after wiring failure`);
|
|
34496
34627
|
} catch (cleanupError) {
|
|
34497
|
-
this.logger.warn(`Failed to clean up partially-created TargetGroup ${logicalId} (${tgArn}): ${
|
|
34628
|
+
this.logger.warn(`Failed to clean up partially-created TargetGroup ${logicalId} (${tgArn}): ${this.maskErrorMessage(cleanupError, maskSecrets)}. Manual deletion may be required before the next deploy: aws elbv2 delete-target-group --target-group-arn ${tgArn}`);
|
|
34498
34629
|
}
|
|
34499
34630
|
throw innerError;
|
|
34500
34631
|
}
|
|
@@ -34508,10 +34639,10 @@ var ELBv2Provider = class {
|
|
|
34508
34639
|
};
|
|
34509
34640
|
} catch (error) {
|
|
34510
34641
|
const cause = error instanceof Error ? error : void 0;
|
|
34511
|
-
throw new ProvisioningError(`Failed to create TargetGroup ${logicalId}: ${
|
|
34642
|
+
throw new ProvisioningError(`Failed to create TargetGroup ${logicalId}: ${this.maskErrorMessage(error, maskSecrets)}`, resourceType, logicalId, void 0, cause);
|
|
34512
34643
|
}
|
|
34513
34644
|
}
|
|
34514
|
-
async updateTargetGroup(logicalId, physicalId, resourceType, properties, previousProperties) {
|
|
34645
|
+
async updateTargetGroup(logicalId, physicalId, resourceType, properties, previousProperties, maskSecrets) {
|
|
34515
34646
|
this.logger.debug(`Updating TargetGroup ${logicalId}: ${physicalId}`);
|
|
34516
34647
|
const immutableChanged = (key, coerce) => {
|
|
34517
34648
|
const next = properties[key];
|
|
@@ -34539,7 +34670,7 @@ var ELBv2Provider = class {
|
|
|
34539
34670
|
const tgAttrDiff = this.diffAttributes(this.normalizeAttributes(properties["TargetGroupAttributes"]), this.normalizeAttributes(previousProperties["TargetGroupAttributes"]), (key, currentValue) => {
|
|
34540
34671
|
const fallback = Object.hasOwn(TARGET_GROUP_ATTRIBUTE_DEFAULTS, key) ? TARGET_GROUP_ATTRIBUTE_DEFAULTS[key] : void 0;
|
|
34541
34672
|
if (fallback === void 0) {
|
|
34542
|
-
this.logger.warn(`TargetGroup attribute ${key} was removed from the template but has no documented default cdkd can reset it to — the live value is retained. Set the attribute explicitly to change it.`);
|
|
34673
|
+
this.logger.warn(`TargetGroup attribute ${maskerOrIdentity(maskSecrets)(key)} was removed from the template but has no documented default cdkd can reset it to — the live value is retained. Set the attribute explicitly to change it.`);
|
|
34543
34674
|
return;
|
|
34544
34675
|
}
|
|
34545
34676
|
return currentValue === fallback ? void 0 : fallback;
|
|
@@ -34551,8 +34682,8 @@ var ELBv2Provider = class {
|
|
|
34551
34682
|
}));
|
|
34552
34683
|
this.logger.debug(`Applied ${tgAttrDiff.length} TargetGroupAttributes change(s) for ${logicalId}`);
|
|
34553
34684
|
}
|
|
34554
|
-
const newTargets = this.convertTargets(properties["Targets"], properties["Port"]);
|
|
34555
|
-
const oldTargets = this.convertTargets(previousProperties["Targets"], properties["Port"]);
|
|
34685
|
+
const newTargets = this.convertTargets(properties["Targets"], properties["Port"], maskSecrets);
|
|
34686
|
+
const oldTargets = this.convertTargets(previousProperties["Targets"], properties["Port"], maskSecrets);
|
|
34556
34687
|
const targetKey = (t) => JSON.stringify([
|
|
34557
34688
|
t.Id,
|
|
34558
34689
|
t.Port ?? null,
|
|
@@ -34590,7 +34721,7 @@ var ELBv2Provider = class {
|
|
|
34590
34721
|
};
|
|
34591
34722
|
} catch (error) {
|
|
34592
34723
|
const cause = error instanceof Error ? error : void 0;
|
|
34593
|
-
throw new ProvisioningError(`Failed to update TargetGroup ${logicalId}: ${
|
|
34724
|
+
throw new ProvisioningError(`Failed to update TargetGroup ${logicalId}: ${this.maskErrorMessage(error, maskSecrets)}`, resourceType, logicalId, physicalId, cause);
|
|
34594
34725
|
}
|
|
34595
34726
|
}
|
|
34596
34727
|
async deleteTargetGroup(logicalId, physicalId, resourceType, context) {
|
|
@@ -34608,7 +34739,7 @@ var ELBv2Provider = class {
|
|
|
34608
34739
|
throw new ProvisioningError(`Failed to delete TargetGroup ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
|
|
34609
34740
|
}
|
|
34610
34741
|
}
|
|
34611
|
-
async createListener(logicalId, resourceType, properties) {
|
|
34742
|
+
async createListener(logicalId, resourceType, properties, maskSecrets) {
|
|
34612
34743
|
this.logger.debug(`Creating Listener ${logicalId}`);
|
|
34613
34744
|
try {
|
|
34614
34745
|
const tags = this.extractTags(properties);
|
|
@@ -34636,7 +34767,7 @@ var ELBv2Provider = class {
|
|
|
34636
34767
|
await withRetry(() => this.getClient().send(new ModifyListenerAttributesCommand({
|
|
34637
34768
|
ListenerArn: listenerArn,
|
|
34638
34769
|
Attributes: listenerAttributes
|
|
34639
|
-
})), logicalId, { logger: this.
|
|
34770
|
+
})), logicalId, { logger: this.maskedRetryLogger(maskSecrets) });
|
|
34640
34771
|
this.logger.debug(`Applied ${listenerAttributes.length} ListenerAttribute(s) for ${logicalId}`);
|
|
34641
34772
|
}
|
|
34642
34773
|
} catch (innerError) {
|
|
@@ -34644,7 +34775,7 @@ var ELBv2Provider = class {
|
|
|
34644
34775
|
await this.getClient().send(new DeleteListenerCommand({ ListenerArn: listenerArn }));
|
|
34645
34776
|
this.logger.debug(`Cleaned up partially-created Listener ${logicalId} (${listenerArn}) after attributes-wiring failure`);
|
|
34646
34777
|
} catch (cleanupError) {
|
|
34647
|
-
this.logger.warn(`Failed to clean up partially-created Listener ${logicalId} (${listenerArn}): ${
|
|
34778
|
+
this.logger.warn(`Failed to clean up partially-created Listener ${logicalId} (${listenerArn}): ${this.maskErrorMessage(cleanupError, maskSecrets)}. Manual deletion may be required before the next deploy: aws elbv2 delete-listener --listener-arn ${listenerArn}`);
|
|
34648
34779
|
}
|
|
34649
34780
|
throw innerError;
|
|
34650
34781
|
}
|
|
@@ -34654,10 +34785,10 @@ var ELBv2Provider = class {
|
|
|
34654
34785
|
};
|
|
34655
34786
|
} catch (error) {
|
|
34656
34787
|
const cause = error instanceof Error ? error : void 0;
|
|
34657
|
-
throw new ProvisioningError(`Failed to create Listener ${logicalId}: ${
|
|
34788
|
+
throw new ProvisioningError(`Failed to create Listener ${logicalId}: ${this.maskErrorMessage(error, maskSecrets)}`, resourceType, logicalId, void 0, cause);
|
|
34658
34789
|
}
|
|
34659
34790
|
}
|
|
34660
|
-
async updateListener(logicalId, physicalId, resourceType, properties, previousProperties) {
|
|
34791
|
+
async updateListener(logicalId, physicalId, resourceType, properties, previousProperties, maskSecrets) {
|
|
34661
34792
|
this.logger.debug(`Updating Listener ${logicalId}: ${physicalId}`);
|
|
34662
34793
|
try {
|
|
34663
34794
|
const defaultActions = this.convertActions(properties["DefaultActions"]);
|
|
@@ -34679,7 +34810,7 @@ var ELBv2Provider = class {
|
|
|
34679
34810
|
await withRetry(() => this.getClient().send(new ModifyListenerAttributesCommand({
|
|
34680
34811
|
ListenerArn: physicalId,
|
|
34681
34812
|
Attributes: submittedAttrs
|
|
34682
|
-
})), logicalId, { logger: this.
|
|
34813
|
+
})), logicalId, { logger: this.maskedRetryLogger(maskSecrets) });
|
|
34683
34814
|
this.logger.debug(`Applied ${submittedAttrs.length} ListenerAttributes change(s) for ${logicalId}`);
|
|
34684
34815
|
}
|
|
34685
34816
|
await this.applyTagDiff(physicalId, previousProperties["Tags"], properties["Tags"]);
|
|
@@ -34691,7 +34822,7 @@ var ELBv2Provider = class {
|
|
|
34691
34822
|
};
|
|
34692
34823
|
} catch (error) {
|
|
34693
34824
|
const cause = error instanceof Error ? error : void 0;
|
|
34694
|
-
throw new ProvisioningError(`Failed to update Listener ${logicalId}: ${
|
|
34825
|
+
throw new ProvisioningError(`Failed to update Listener ${logicalId}: ${this.maskErrorMessage(error, maskSecrets)}`, resourceType, logicalId, physicalId, cause);
|
|
34695
34826
|
}
|
|
34696
34827
|
}
|
|
34697
34828
|
async deleteListener(logicalId, physicalId, resourceType, context) {
|
|
@@ -34844,13 +34975,29 @@ var ELBv2Provider = class {
|
|
|
34844
34975
|
* Absent for a `lambda` target group (no port), where both sides stay
|
|
34845
34976
|
* undefined and therefore still key identically.
|
|
34846
34977
|
*/
|
|
34847
|
-
|
|
34978
|
+
/**
|
|
34979
|
+
* `maskSecrets` (issue #2050) exists for the drop-warning below, which
|
|
34980
|
+
* stringifies a whole REJECTED `Targets` element at DEFAULT verbosity. That
|
|
34981
|
+
* element came out of the resolved `properties` bag, so a
|
|
34982
|
+
* `{{resolve:secretsmanager:...}}` scalar anywhere inside it — an `Id` built
|
|
34983
|
+
* by `Fn::Sub` from a secret, say — is already plaintext by the time this
|
|
34984
|
+
* runs.
|
|
34985
|
+
*
|
|
34986
|
+
* OPTIONAL, but all three call sites supply it today (`createTargetGroup` and
|
|
34987
|
+
* both `updateTargetGroup` sides). It stays optional because that is the
|
|
34988
|
+
* `SecretMaskingContext` contract — absent means unmasked — not because some
|
|
34989
|
+
* caller is known to omit it. An earlier revision of this comment claimed it
|
|
34990
|
+
* protected "the `readCurrentState` / diff callers that have no masker";
|
|
34991
|
+
* there are no such callers, and describing a defence by a consumer that does
|
|
34992
|
+
* not exist is how the next author concludes the parameter is dead.
|
|
34993
|
+
*/
|
|
34994
|
+
convertTargets(raw, groupPort, maskSecrets) {
|
|
34848
34995
|
const defaultPort = groupPort === void 0 || groupPort === null || Number.isNaN(Number(groupPort)) ? void 0 : Number(groupPort);
|
|
34849
34996
|
if (!Array.isArray(raw)) return [];
|
|
34850
34997
|
const out = [];
|
|
34851
34998
|
for (const entry of raw) {
|
|
34852
34999
|
if (entry === null || typeof entry !== "object" || typeof entry.Id !== "string" || entry.Id.length === 0) {
|
|
34853
|
-
this.logger.warn(`Dropping malformed TargetGroup Targets entry (missing string Id): ${JSON.stringify(entry)}`);
|
|
35000
|
+
this.logger.warn(`Dropping malformed TargetGroup Targets entry (missing string Id): ${JSON.stringify(maskDeep(entry, maskerOrIdentity(maskSecrets)))}`);
|
|
34854
35001
|
continue;
|
|
34855
35002
|
}
|
|
34856
35003
|
const e = entry;
|
|
@@ -39887,8 +40034,7 @@ function buildMfaConfigRequest(physicalId, properties, logger, declaredMfaConfig
|
|
|
39887
40034
|
const enablesMfaFactor = declaresFactor || request.EmailMfaConfiguration !== void 0;
|
|
39888
40035
|
request.MfaConfiguration = declaredMfaConfiguration || (enablesMfaFactor ? "OPTIONAL" : "OFF");
|
|
39889
40036
|
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.`);
|
|
39890
|
-
const anyFactorBlock = request
|
|
39891
|
-
if (request.MfaConfiguration === "OFF" && anyFactorBlock) warn(`UserPool ${physicalId}: MfaConfiguration is pinned to OFF while an MFA factor block is configured. AWS REJECTS that combination ("Invalid MFA configuration given, can't turn off MFA and configure an MFA together"), so this deploy FAILS rather than producing a pool with MFA disabled. Remove MfaConfiguration (or set it to ON / OPTIONAL) to enable the declared factor.`);
|
|
40037
|
+
const anyFactorBlock = hasAnyMfaFactorBlock(request);
|
|
39892
40038
|
const maskDeep = (v, depth = 0) => {
|
|
39893
40039
|
if (typeof v === "string") return maskSecrets(v);
|
|
39894
40040
|
if (depth >= MASK_WALK_MAX_DEPTH) return v;
|
|
@@ -39899,7 +40045,7 @@ function buildMfaConfigRequest(physicalId, properties, logger, declaredMfaConfig
|
|
|
39899
40045
|
const dropped = unrecognizedFactors.map((f) => JSON.stringify(maskDeep(f)) ?? String(f));
|
|
39900
40046
|
if (enabledMfasMalformed) dropped.push(`${JSON.stringify(maskDeep(properties["EnabledMfas"]))} (not a list)`);
|
|
39901
40047
|
if (dropped.length > 0) {
|
|
39902
|
-
const consequence = request.MfaConfiguration === "OFF"
|
|
40048
|
+
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`;
|
|
39903
40049
|
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}.`);
|
|
39904
40050
|
}
|
|
39905
40051
|
return request;
|
|
@@ -39928,6 +40074,248 @@ function resolveSentMfaConfiguration(properties, declaredMfaConfiguration) {
|
|
|
39928
40074
|
return declaredMfaConfiguration || void 0;
|
|
39929
40075
|
}
|
|
39930
40076
|
/**
|
|
40077
|
+
* "This `SetUserPoolMfaConfig` request carries an MFA FACTOR sub-block."
|
|
40078
|
+
*
|
|
40079
|
+
* ONE definition, shared by `buildMfaConfigRequest`'s dropped-entry warning and
|
|
40080
|
+
* by the pre-flight refusal below, so the message and the refusal cannot
|
|
40081
|
+
* disagree about the same request. It is precisely
|
|
40082
|
+
* `{Sms, SoftwareToken, Email}MfaConfiguration` -- `WebAuthnConfiguration` is
|
|
40083
|
+
* deliberately NOT a member, because WebAuthn is not an MFA factor and rides
|
|
40084
|
+
* alongside `MfaConfiguration: OFF` on every passkey-only pool (issue #1920).
|
|
40085
|
+
*/
|
|
40086
|
+
function hasAnyMfaFactorBlock(request) {
|
|
40087
|
+
return request.SmsMfaConfiguration !== void 0 || request.SoftwareTokenMfaConfiguration !== void 0 || request.EmailMfaConfiguration !== void 0;
|
|
40088
|
+
}
|
|
40089
|
+
/**
|
|
40090
|
+
* The `Policies.SignInPolicy.AllowedFirstAuthFactors` members AWS refuses to
|
|
40091
|
+
* combine with MFA (issue #1975).
|
|
40092
|
+
*
|
|
40093
|
+
* A DENY-list, deliberately, even though the AWS message states an ALLOW-list
|
|
40094
|
+
* ("Only PASSWORD and WEB_AUTHN (if configured) can be enabled as an auth
|
|
40095
|
+
* factor if MFA is enabled"). Written from the allow-list, a member AWS adds
|
|
40096
|
+
* LATER would be refused by cdkd on the day it starts working -- the same
|
|
40097
|
+
* reason the unrecognized-`EnabledMfas` case in `buildMfaConfigRequest` warns
|
|
40098
|
+
* rather than throws, since that set is a hardcoded mirror of an AWS enum. The
|
|
40099
|
+
* cost of the deny-list is the reverse and is the cheaper one: a future
|
|
40100
|
+
* incompatible factor is simply not pre-flighted, which is the pre-#1975
|
|
40101
|
+
* behavior (a loud AWS rejection), not a regression.
|
|
40102
|
+
*
|
|
40103
|
+
* The two members ARE the whole of today's `AuthFactorType` enum outside the
|
|
40104
|
+
* allowed pair (`EMAIL_OTP`, `PASSWORD`, `SMS_OTP`, `WEB_AUTHN` --
|
|
40105
|
+
* `@aws-sdk/client-cognito-identity-provider`), so the deny-list and the
|
|
40106
|
+
* allow-list agree on every value that exists right now.
|
|
40107
|
+
*
|
|
40108
|
+
* BOTH members are MEASURED (us-east-1, 2026-08-19, issue #1975) -- neither is
|
|
40109
|
+
* extrapolated from the other, which is the failure mode this file's own
|
|
40110
|
+
* unrecognized-factor comment warns about:
|
|
40111
|
+
*
|
|
40112
|
+
* [PASSWORD, EMAIL_OTP] + SetUserPoolMfaConfig(ON) -> REJECTED
|
|
40113
|
+
* [PASSWORD, SMS_OTP] + SetUserPoolMfaConfig(ON) -> REJECTED, same message
|
|
40114
|
+
*
|
|
40115
|
+
* The `SMS_OTP` probe needed a precondition worth recording, because without it
|
|
40116
|
+
* the interesting call is never reached: on a pool with NO `SmsConfiguration`,
|
|
40117
|
+
* `CreateUserPool` refuses the factor outright ("SMS_OTP can not be configured
|
|
40118
|
+
* as AuthFactor as user pool is missing valid SMS configuration"), so the probe
|
|
40119
|
+
* had to stand up an SNS-caller IAM role + ExternalId first. That earlier,
|
|
40120
|
+
* different refusal is AWS's own and is NOT something this pre-flight
|
|
40121
|
+
* duplicates.
|
|
40122
|
+
*
|
|
40123
|
+
* Typed `satisfies AuthFactorType[]` rather than left as `string[]`: the values
|
|
40124
|
+
* mirror an SDK enum, so a rename upstream should be a COMPILE error here
|
|
40125
|
+
* rather than a silently-never-matching deny-list.
|
|
40126
|
+
*/
|
|
40127
|
+
const MFA_INCOMPATIBLE_FIRST_AUTH_FACTORS = ["EMAIL_OTP", "SMS_OTP"];
|
|
40128
|
+
/**
|
|
40129
|
+
* `Policies.SignInPolicy.AllowedFirstAuthFactors` as a list of STRINGS, or an
|
|
40130
|
+
* empty list when the template declares nothing usable there.
|
|
40131
|
+
*
|
|
40132
|
+
* Every mis-shape reads as ABSENT rather than as a violation: this feeds a
|
|
40133
|
+
* REFUSAL, so guessing at a container cdkd could not read would block a deploy
|
|
40134
|
+
* over a value it never understood. A genuinely broken `Policies` blob is AWS's
|
|
40135
|
+
* to reject, with its own message.
|
|
40136
|
+
*
|
|
40137
|
+
* The two containers are traversed with `?.` rather than with the
|
|
40138
|
+
* `typeof x === 'object' && !Array.isArray(x)` guard `config-shape.ts` uses,
|
|
40139
|
+
* and that is deliberate rather than sloppy: those guards are load-bearing
|
|
40140
|
+
* THERE because the helper REFUSES a non-object, while here the only outcome is
|
|
40141
|
+
* "no factors", which is exactly what indexing a string / number / array
|
|
40142
|
+
* already yields. The version carrying them was written first and MEASURED
|
|
40143
|
+
* inert -- deleting either clause failed no test and could not be made to,
|
|
40144
|
+
* since no template value indexes to anything at those keys. `?.` keeps the one
|
|
40145
|
+
* behaviour that is real (a declared `null` throws on a bare index) and leaves
|
|
40146
|
+
* nothing unfalsifiable behind.
|
|
40147
|
+
*
|
|
40148
|
+
* The two filters below are NOT the same kind of thing, and saying so matters
|
|
40149
|
+
* because an earlier revision of this comment claimed both were load-bearing and
|
|
40150
|
+
* "each fenced by a test" -- which was false for one of them, and a false fence
|
|
40151
|
+
* claim is what stops the next reader re-checking.
|
|
40152
|
+
*
|
|
40153
|
+
* - `Array.isArray` is a RUNTIME guard and IS fenced: without it a scalar
|
|
40154
|
+
* `AllowedFirstAuthFactors: 'EMAIL_OTP'` is read as a declaration and
|
|
40155
|
+
* refused, and the test for that shape reds when it is dropped.
|
|
40156
|
+
* - The string filter is a TYPE NARROWING and is INERT at runtime. `includes`
|
|
40157
|
+
* compares by SameValueZero, so a non-string member can never equal a
|
|
40158
|
+
* deny-list entry however it is spelled; deleting the filter fails NO test,
|
|
40159
|
+
* and no test could be written that it would fail. It earns its place by
|
|
40160
|
+
* giving the caller a `string[]`, and by keeping this function honest if a
|
|
40161
|
+
* future caller ever COERCES a member instead of comparing it. The
|
|
40162
|
+
* nested-list case in the suite fences that hypothetical `String(member)`
|
|
40163
|
+
* implementation -- i.e. the mistake, not this code.
|
|
40164
|
+
*/
|
|
40165
|
+
function readAllowedFirstAuthFactors(properties) {
|
|
40166
|
+
const factors = (properties["Policies"]?.["SignInPolicy"])?.["AllowedFirstAuthFactors"];
|
|
40167
|
+
if (!Array.isArray(factors)) return [];
|
|
40168
|
+
return factors.filter((factor) => typeof factor === "string");
|
|
40169
|
+
}
|
|
40170
|
+
/**
|
|
40171
|
+
* The PRE-FLIGHT: the reason this user-pool configuration cannot be applied at
|
|
40172
|
+
* all, or `undefined` when nothing is known to refuse it.
|
|
40173
|
+
*
|
|
40174
|
+
* Two rules, both for combinations AWS rejects 100% of the time, and both
|
|
40175
|
+
* raised BEFORE the first AWS call on BOTH the create and the update path. The
|
|
40176
|
+
* ordering is what makes them worth refusing rather than warning about: on the
|
|
40177
|
+
* UPDATE path `UpdateUserPool` lands FIRST and `SetUserPoolMfaConfig` is
|
|
40178
|
+
* refused afterwards, so sending the request leaves a PARTIAL APPLY behind --
|
|
40179
|
+
* with the security-relevant half being the half that did NOT apply (the MFA
|
|
40180
|
+
* configuration), while the half that did is the one loosening authentication.
|
|
40181
|
+
* Nothing unwinds it, and a retry cannot succeed until the user reverse-engineers
|
|
40182
|
+
* an AWS-worded error about a field they did not think they were changing.
|
|
40183
|
+
*
|
|
40184
|
+
* Rule 1 (issue #1977) -- `MfaConfiguration` resolves to OFF while the request
|
|
40185
|
+
* carries an MFA factor block. MEASURED us-east-1 2026-08-19 (issue #1968):
|
|
40186
|
+
*
|
|
40187
|
+
* {ON, SoftwareTokenMfaConfiguration: {Enabled: true}} -> ACCEPTED (control)
|
|
40188
|
+
* {OFF, SoftwareTokenMfaConfiguration: {Enabled: true}} -> rejected
|
|
40189
|
+
* {OFF, SoftwareTokenMfaConfiguration: {Enabled: false}} -> rejected
|
|
40190
|
+
* {OFF, EmailMfaConfiguration: {Message, Subject}} -> rejected
|
|
40191
|
+
*
|
|
40192
|
+
* all with `InvalidParameterException: Invalid MFA configuration given, can't
|
|
40193
|
+
* turn off MFA and configure an MFA together`. The `Enabled: false` arm settles
|
|
40194
|
+
* the KEY: a block that enables NOTHING is still refused, so the rule is about
|
|
40195
|
+
* the block's PRESENCE -- exactly what `hasAnyMfaFactorBlock` tests -- and
|
|
40196
|
+
* since that predicate is precisely the three factor sub-blocks, no accepted
|
|
40197
|
+
* case survives. (Bound on the Email arm: an OPTIONAL control with that block
|
|
40198
|
+
* failed with a DIFFERENT error, "Cannot set EmailMfaConfiguration when user
|
|
40199
|
+
* pool EmailConfiguration contains an EmailSendingAccount of COGNITO_DEFAULT",
|
|
40200
|
+
* so it has no isolated control of its own -- informative in itself, since it
|
|
40201
|
+
* shows the exclusion is evaluated BEFORE the email-sending-account check.)
|
|
40202
|
+
*
|
|
40203
|
+
* Rule 2 (issue #1975) -- the `SetUserPoolMfaConfig` request resolves
|
|
40204
|
+
* `MfaConfiguration` to ON while `Policies.SignInPolicy.AllowedFirstAuthFactors`
|
|
40205
|
+
* allows `EMAIL_OTP` or `SMS_OTP`. AWS refuses with `Only PASSWORD and WEB_AUTHN
|
|
40206
|
+
* (if configured) can be enabled as an auth factor if MFA is enabled`. MEASURED
|
|
40207
|
+
* us-east-1 2026-08-19, both members and both MFA modes:
|
|
40208
|
+
*
|
|
40209
|
+
* {[PASSWORD, EMAIL_OTP], ON} -> rejected
|
|
40210
|
+
* {[PASSWORD, EMAIL_OTP], OPTIONAL} -> ACCEPTED
|
|
40211
|
+
* {[PASSWORD, SMS_OTP], ON} -> rejected (pool WITH a valid SmsConfiguration)
|
|
40212
|
+
* {[PASSWORD, SMS_OTP], OPTIONAL} -> ACCEPTED
|
|
40213
|
+
*
|
|
40214
|
+
* So `=== 'ON'` is MEASURED-CORRECT rather than merely conservative: OPTIONAL is
|
|
40215
|
+
* ACCEPTED, and widening this to `!== 'OFF'` would REFUSE A WORKING DEPLOY. An
|
|
40216
|
+
* earlier revision of this comment called OPTIONAL unmeasured; it is not, and
|
|
40217
|
+
* the measurement is what settles the narrowing rather than a cost argument.
|
|
40218
|
+
*
|
|
40219
|
+
* WHY THE MESSAGE DOES NOT OFFER `WEB_AUTHN` AS THE ESCAPE HATCH, even though
|
|
40220
|
+
* AWS's own error text names it as accepted. MEASURED us-east-1 2026-08-20: a
|
|
40221
|
+
* pool allowing `WEB_AUTHN` as a first auth factor is REJECTED by
|
|
40222
|
+
* `SetUserPoolMfaConfig(ON, SoftwareTokenMfa)` --
|
|
40223
|
+
*
|
|
40224
|
+
* InvalidParameterException: Cannot set WebAuthn factor configuration to
|
|
40225
|
+
* SINGLE_FACTOR if MFA is required and WebAuthn is an allowed first auth factor
|
|
40226
|
+
*
|
|
40227
|
+
* -- with NO WebAuthn block at all, with `UserVerification: preferred`, and with
|
|
40228
|
+
* `required`. The only accepted shape is
|
|
40229
|
+
* `WebAuthnConfiguration.FactorConfiguration = MULTI_FACTOR_WITH_USER_VERIFICATION`,
|
|
40230
|
+
* and the PINNED SDK (`@aws-sdk/client-cognito-identity-provider` 3.1018.0)
|
|
40231
|
+
* does not carry that field -- `WebAuthnConfigurationType` is exactly
|
|
40232
|
+
* `{RelyingPartyId?, UserVerification?}` -- so cdkd cannot reach it today.
|
|
40233
|
+
* Telling the user to switch to `WEB_AUTHN` would therefore be advice that
|
|
40234
|
+
* cannot be followed, which is why the remedy names removing the factor or
|
|
40235
|
+
* lowering `MfaConfiguration` instead.
|
|
40236
|
+
*
|
|
40237
|
+
* That same measurement means `WEB_AUTHN` + ON reproduces this very
|
|
40238
|
+
* partial-apply shape while NOT being on the deny-list. Deliberately left
|
|
40239
|
+
* uncovered: the correct rule there is CONDITIONAL on
|
|
40240
|
+
* `WebAuthnConfiguration.FactorConfiguration`, which the pinned SDK cannot
|
|
40241
|
+
* express, so a flat deny-list entry would be an OVER-REFUSAL of the shape that
|
|
40242
|
+
* becomes valid the moment the SDK gains the field. Tracked as issue #2064.
|
|
40243
|
+
*
|
|
40244
|
+
* SCOPE LIMIT, deliberate and load-bearing: rule 2 fires only when a
|
|
40245
|
+
* `SetUserPoolMfaConfig` request is actually BUILT (`hasMfaConfigProps`), i.e.
|
|
40246
|
+
* the template declares `EnabledMfas` / an email-OTP message or subject /
|
|
40247
|
+
* WebAuthn. With NONE of those, `MfaConfiguration` rides on the SINGLE
|
|
40248
|
+
* `CreateUserPool` / `UpdateUserPool` call and no second call follows -- so
|
|
40249
|
+
* there is no two-call window and structurally NOTHING to partly apply, which
|
|
40250
|
+
* is the entire harm this pre-flight exists to prevent. Refusing there would
|
|
40251
|
+
* ALSO be an over-refusal on a shape AWS accepts: a pool created with
|
|
40252
|
+
* `AllowedFirstAuthFactors: [PASSWORD, EMAIL_OTP]` and no MFA configuration
|
|
40253
|
+
* call at all is ACCEPTED (measured, same session -- the pool came up fine and
|
|
40254
|
+
* only the later `SetUserPoolMfaConfig(ON)` failed). Such a template is left to
|
|
40255
|
+
* AWS, which answers it atomically.
|
|
40256
|
+
*
|
|
40257
|
+
* CLOUDFORMATION PARITY, settled by A/B rather than assumed -- both issues made
|
|
40258
|
+
* this an explicit precondition. CloudFormation hits the SAME rejections and
|
|
40259
|
+
* rolls the stack back:
|
|
40260
|
+
*
|
|
40261
|
+
* {MfaConfiguration: OFF, EnabledMfas: [SOFTWARE_TOKEN_MFA]}
|
|
40262
|
+
* -> ROLLBACK_COMPLETE, "Invalid MFA configuration given, can't turn off
|
|
40263
|
+
* MFA and configure an MFA together"
|
|
40264
|
+
* {MfaConfiguration: ON, EnabledMfas: [SOFTWARE_TOKEN_MFA],
|
|
40265
|
+
* AllowedFirstAuthFactors: [PASSWORD, EMAIL_OTP]} (ESSENTIALS tier)
|
|
40266
|
+
* -> ROLLBACK_COMPLETE, "Only PASSWORD and WEB_AUTHN..."
|
|
40267
|
+
*
|
|
40268
|
+
* So refusing is PARITY-PRESERVING, not a divergence: cdkd refuses the same SET
|
|
40269
|
+
* of templates CloudFormation refuses, only EARLIER (before the first API call
|
|
40270
|
+
* instead of after a partial apply) and in cdkd's own wording. Nothing that
|
|
40271
|
+
* deploys under CloudFormation is blocked here.
|
|
40272
|
+
*
|
|
40273
|
+
* Both rules read the value cdkd would SEND rather than the raw template value,
|
|
40274
|
+
* from ONE `buildMfaConfigRequest` call, so the refusal and the request can
|
|
40275
|
+
* never disagree about what OFF / ON means for a given bag. The build runs with
|
|
40276
|
+
* NO logger, so the pre-flight is silent and `applyMfaConfig`'s own warnings
|
|
40277
|
+
* still fire exactly once.
|
|
40278
|
+
*
|
|
40279
|
+
* WHAT THIS CANNOT SEE, stated because the message says "refuses it before
|
|
40280
|
+
* sending anything" and that reads as a completeness claim: the pre-flight
|
|
40281
|
+
* inspects the TEMPLATE, never the live pool, so two template edits reach the
|
|
40282
|
+
* same partial apply without tripping it -- DELETING the `SignInPolicy` block
|
|
40283
|
+
* while setting `MfaConfiguration: ON`, and OMITTING `MfaConfiguration` while
|
|
40284
|
+
* adding `EMAIL_OTP` to a pool whose live MFA is already ON. Both need a
|
|
40285
|
+
* live-state guard and are tracked in #2051.
|
|
40286
|
+
*
|
|
40287
|
+
* Deliberately UNCONDITIONAL -- no `CreateContext.replayingState` downgrade.
|
|
40288
|
+
* `.claude/rules/providers.md` says a create-path pre-flight refusal MUST
|
|
40289
|
+
* downgrade on a replay, so this is a STATED EXCEPTION rather than an
|
|
40290
|
+
* oversight, and the rule's own reasoning is what licenses it: the downgrade
|
|
40291
|
+
* exists because a refusal against a STATE record leaves a resource
|
|
40292
|
+
* un-rollbackable with no template-side remedy. That presupposes the replay
|
|
40293
|
+
* could otherwise SUCCEED. Here it cannot -- both combinations are rejected by
|
|
40294
|
+
* AWS 100% of the time (measured above, and CloudFormation rolls back on the
|
|
40295
|
+
* same templates), so downgrading would trade a clear cdkd-worded refusal for
|
|
40296
|
+
* the identical failure arriving later from AWS, on the create path with a
|
|
40297
|
+
* pool to roll back and on the update path with a partial apply. A state record
|
|
40298
|
+
* additionally cannot legitimately HOLD either combination: state is written
|
|
40299
|
+
* only after a SUCCESSFUL apply, and neither can be applied. Revisit this if
|
|
40300
|
+
* AWS ever starts accepting one of them.
|
|
40301
|
+
*/
|
|
40302
|
+
function describeUnsupportedMfaCombination(properties, declaredMfaConfiguration) {
|
|
40303
|
+
const request = buildMfaConfigRequest("", properties, void 0, declaredMfaConfiguration);
|
|
40304
|
+
if (request && request.MfaConfiguration === "OFF" && hasAnyMfaFactorBlock(request)) {
|
|
40305
|
+
const blocks = [
|
|
40306
|
+
...request.SmsMfaConfiguration !== void 0 ? ["SmsMfaConfiguration"] : [],
|
|
40307
|
+
...request.SoftwareTokenMfaConfiguration !== void 0 ? ["SoftwareTokenMfaConfiguration"] : [],
|
|
40308
|
+
...request.EmailMfaConfiguration !== void 0 ? ["EmailMfaConfiguration"] : []
|
|
40309
|
+
];
|
|
40310
|
+
return `${MFA_CONFIGURATION_PATH} is OFF while an MFA factor is configured (SetUserPoolMfaConfig would carry ${blocks.join(", ")}, built from EnabledMfas / EmailAuthenticationMessage / EmailAuthenticationSubject). AWS rejects that combination ("Invalid MFA configuration given, can't turn off MFA and configure an MFA together"), so cdkd refuses it before sending anything rather than leaving a partly-applied update behind. Set MfaConfiguration to ON or OPTIONAL (or remove it) to enable the declared factor. (If MFA is genuinely not wanted, remove EnabledMfas / EmailAuthenticationMessage / EmailAuthenticationSubject instead.)`;
|
|
40311
|
+
}
|
|
40312
|
+
if (request && request.MfaConfiguration === "ON") {
|
|
40313
|
+
const denied = MFA_INCOMPATIBLE_FIRST_AUTH_FACTORS;
|
|
40314
|
+
const offending = readAllowedFirstAuthFactors(properties).filter((factor) => denied.includes(factor));
|
|
40315
|
+
if (offending.length > 0) return `${MFA_CONFIGURATION_PATH} is ON while Policies.SignInPolicy.AllowedFirstAuthFactors allows ${offending.join(", ")}. AWS rejects that combination ("Only PASSWORD and WEB_AUTHN (if configured) can be enabled as an auth factor if MFA is enabled"), and on an update the sign-in policy has already been applied by the time it does, so cdkd refuses it before sending anything rather than leaving a partly-applied update behind. Remove ${offending.join(", ")} from Policies.SignInPolicy.AllowedFirstAuthFactors, leaving PASSWORD. (Setting MfaConfiguration to OPTIONAL or OFF also clears the conflict, but weakens MFA.)`;
|
|
40316
|
+
}
|
|
40317
|
+
}
|
|
40318
|
+
/**
|
|
39931
40319
|
* Replace a DECLARED-but-unusable `MfaConfiguration` with the value cdkd
|
|
39932
40320
|
* actually sends, or drop the key when nothing is sent. Returns the input
|
|
39933
40321
|
* object unchanged when nothing applies, so the common case compares
|
|
@@ -40011,7 +40399,7 @@ var CognitoUserPoolProvider = class {
|
|
|
40011
40399
|
"WebAuthnRelyingPartyID",
|
|
40012
40400
|
"WebAuthnUserVerification"
|
|
40013
40401
|
])]]);
|
|
40014
|
-
unhandledByDesign = /* @__PURE__ */ new Map([["AWS::Cognito::UserPool", /* @__PURE__ */ new Map([["WebAuthnFactorConfiguration", "No
|
|
40402
|
+
unhandledByDesign = /* @__PURE__ */ new Map([["AWS::Cognito::UserPool", /* @__PURE__ */ new Map([["WebAuthnFactorConfiguration", "No wire path in the PINNED SDK (@aws-sdk/client-cognito-identity-provider 3.1018.0): WebAuthnConfigurationType is {RelyingPartyId?, UserVerification?} and no CreateUserPool/UpdateUserPool field accepts SINGLE_FACTOR | MULTI_FACTOR_WITH_USER_VERIFICATION. This is an SDK-VERSION limit, NOT an API one -- the live API does honour FactorConfiguration (measured us-east-1 2026-08-20: SetUserPoolMfaConfig(ON) on a pool allowing WEB_AUTHN as a first auth factor is rejected with \"Cannot set WebAuthn factor configuration to SINGLE_FACTOR if MFA is required and WebAuthn is an allowed first auth factor\", i.e. the service reads a field the SDK cannot send). RE-EVALUATE THIS ENTRY ON AN SDK BUMP: if WebAuthnConfigurationType gains FactorConfiguration, the property becomes handleable and this entry must go"]])]]);
|
|
40015
40403
|
/**
|
|
40016
40404
|
* Warn when `MfaConfiguration` is declared as a BLANK string (issue #1925
|
|
40017
40405
|
* item 2, review round 2).
|
|
@@ -40040,6 +40428,26 @@ var CognitoUserPoolProvider = class {
|
|
|
40040
40428
|
this.logger.warn("AWS::Cognito::UserPool MfaConfiguration is an empty string, which declares no MFA mode and is treated as absent -- a collapsed Fn::Join or an empty String parameter produces this shape. The pool deploys with the default MFA configuration instead; declare ON / OPTIONAL / OFF explicitly, or omit the property, to say which was meant.");
|
|
40041
40429
|
}
|
|
40042
40430
|
/**
|
|
40431
|
+
* Raise {@link describeUnsupportedMfaCombination}'s verdict as a typed
|
|
40432
|
+
* refusal, or return silently when there is nothing to refuse.
|
|
40433
|
+
*
|
|
40434
|
+
* A `ProvisioningError` so both call sites behave the way each already does
|
|
40435
|
+
* with the sibling `MfaConfiguration` shape refusal: on the create path it
|
|
40436
|
+
* surfaces as the provider's own error rather than an AWS one, and on the
|
|
40437
|
+
* update path `update()`'s catch re-throws an already-wrapped
|
|
40438
|
+
* `ProvisioningError` unchanged, so it is not double-wrapped. Both call sites
|
|
40439
|
+
* are OUTSIDE their method's try, so the throw travels untouched either way.
|
|
40440
|
+
*
|
|
40441
|
+
* `target` is the pool NAME on create (there is no physical id yet) and the
|
|
40442
|
+
* physical id on update, matching what each path passes to its other
|
|
40443
|
+
* `ProvisioningError`s.
|
|
40444
|
+
*/
|
|
40445
|
+
assertMfaCombinationApplicable(logicalId, resourceType, target, properties, declaredMfaConfiguration) {
|
|
40446
|
+
const reason = describeUnsupportedMfaCombination(properties, declaredMfaConfiguration);
|
|
40447
|
+
if (reason === void 0) return;
|
|
40448
|
+
throw new ProvisioningError(reason, resourceType, logicalId, target);
|
|
40449
|
+
}
|
|
40450
|
+
/**
|
|
40043
40451
|
* The DIFF-side half of the `MfaConfiguration` substitution record.
|
|
40044
40452
|
*
|
|
40045
40453
|
* Applied by `DiffCalculator` to BOTH comparison sides, so a template that
|
|
@@ -40129,6 +40537,7 @@ var CognitoUserPoolProvider = class {
|
|
|
40129
40537
|
} catch (error) {
|
|
40130
40538
|
throw new ProvisioningError(error instanceof Error ? error.message : String(error), resourceType, logicalId, poolName, error instanceof Error ? error : void 0);
|
|
40131
40539
|
}
|
|
40540
|
+
this.assertMfaCombinationApplicable(logicalId, resourceType, poolName, properties, mfaConfiguration);
|
|
40132
40541
|
let createdUserPoolId;
|
|
40133
40542
|
try {
|
|
40134
40543
|
const createParams = { PoolName: poolName };
|
|
@@ -40298,7 +40707,8 @@ var CognitoUserPoolProvider = class {
|
|
|
40298
40707
|
* sub-block in the request AWS REJECTS the call; with none it accepts and
|
|
40299
40708
|
* resets the pool to MFA-disabled. An EXPLICIT OFF beside a block is
|
|
40300
40709
|
* rejected the same way (2026-08-19, issue #1968; the transcript lives at
|
|
40301
|
-
*
|
|
40710
|
+
* `describeUnsupportedMfaCombination`, which since issue #1977 REFUSES that
|
|
40711
|
+
* combination before any call rather than warning about it).
|
|
40302
40712
|
*
|
|
40303
40713
|
* Nothing here licenses a claim about a field on neither list, or about one
|
|
40304
40714
|
* API from the other's section. Measure it.
|
|
@@ -40376,6 +40786,7 @@ var CognitoUserPoolProvider = class {
|
|
|
40376
40786
|
this.logger.debug(`Updating Cognito User Pool ${logicalId}: ${physicalId}`);
|
|
40377
40787
|
const mfaConfiguration = readDeclaredMfaConfiguration(properties["MfaConfiguration"], { onUnusable: (message) => this.logger.warn(message) });
|
|
40378
40788
|
this.warnOnBlankMfaConfiguration(properties["MfaConfiguration"]);
|
|
40789
|
+
this.assertMfaCombinationApplicable(logicalId, resourceType, physicalId, properties, mfaConfiguration);
|
|
40379
40790
|
const liveMfa = mfaConfiguration === "" && hasMfaConfigProps(properties) ? await this.readLiveMfaConfiguration(physicalId) : void 0;
|
|
40380
40791
|
try {
|
|
40381
40792
|
const updateParams = { UserPoolId: physicalId };
|
|
@@ -41238,21 +41649,63 @@ var ServiceDiscoveryProvider = class ServiceDiscoveryProvider {
|
|
|
41238
41649
|
if (!this.stsClient) this.stsClient = new STSClient(this.providerRegion ? { region: this.providerRegion } : {});
|
|
41239
41650
|
return this.stsClient;
|
|
41240
41651
|
}
|
|
41241
|
-
|
|
41652
|
+
/**
|
|
41653
|
+
* A {@link RetryLogger} for this provider's `withRetry` calls, bound to the
|
|
41654
|
+
* caller's secret masker (issue #2050).
|
|
41655
|
+
*
|
|
41656
|
+
* Thin wrapper over the shared {@link createMaskedRetryLogger} — see that
|
|
41657
|
+
* module for why the factory is shared with `elbv2-provider.ts` rather than
|
|
41658
|
+
* hand-rolled per file, and for why `warn` is threaded rather than omitted.
|
|
41659
|
+
*
|
|
41660
|
+
* NOT applied to `deleteNamespace`'s retry: that one sends a
|
|
41661
|
+
* `DeleteNamespaceCommand` carrying a physical id only, and `delete()` has no
|
|
41662
|
+
* `maskSecrets` to thread anyway (`DeleteContext` deliberately carries none).
|
|
41663
|
+
*/
|
|
41664
|
+
maskedRetryLogger(maskSecrets) {
|
|
41665
|
+
return createMaskedRetryLogger(this.logger, maskSecrets);
|
|
41666
|
+
}
|
|
41667
|
+
/**
|
|
41668
|
+
* Mask a caught AWS error's message before it is interpolated into the
|
|
41669
|
+
* `ProvisioningError` this provider throws (issue #2050, review round 2).
|
|
41670
|
+
*
|
|
41671
|
+
* The twin of `ELBv2Provider.maskErrorMessage` — see that method for the full
|
|
41672
|
+
* rationale. In short: `withRetry` rethrows the RAW error and the deploy
|
|
41673
|
+
* engine prints the resulting message at ERROR (DEFAULT verbosity), so this
|
|
41674
|
+
* is a wider disclosure surface than the retry logger, and the ONLY one for a
|
|
41675
|
+
* NON-RETRYABLE rejection, where `withRetry` emits nothing at all.
|
|
41676
|
+
*
|
|
41677
|
+
* Masks `error.message` rather than the assembled sentence so the masker can
|
|
41678
|
+
* reach `maskSecretsInText`'s WHOLE-VALUE arm (any length) instead of only
|
|
41679
|
+
* the SUBSTRING arm (needles of 4+ characters). The `cause` chain is left
|
|
41680
|
+
* untouched so `isRetryableTransientError`'s `$metadata` walk is unaffected.
|
|
41681
|
+
*/
|
|
41682
|
+
maskErrorMessage(error, maskSecrets) {
|
|
41683
|
+
return maskerOrIdentity(maskSecrets)(error instanceof Error ? error.message : String(error));
|
|
41684
|
+
}
|
|
41685
|
+
/**
|
|
41686
|
+
* `context` is read for ONE thing today: `maskSecrets` (issue #2050). The
|
|
41687
|
+
* Service create path runs a post-create `UpdateServiceAttributes` through
|
|
41688
|
+
* `withRetry` — see {@link maskedRetryLogger}.
|
|
41689
|
+
*/
|
|
41690
|
+
async create(logicalId, resourceType, properties, context) {
|
|
41242
41691
|
switch (resourceType) {
|
|
41243
41692
|
case "AWS::ServiceDiscovery::PrivateDnsNamespace": return this.createNamespace(logicalId, resourceType, properties);
|
|
41244
41693
|
case "AWS::ServiceDiscovery::HttpNamespace": return this.createHttpNamespace(logicalId, resourceType, properties);
|
|
41245
41694
|
case "AWS::ServiceDiscovery::PublicDnsNamespace": return this.createPublicDnsNamespace(logicalId, resourceType, properties);
|
|
41246
|
-
case "AWS::ServiceDiscovery::Service": return this.createService(logicalId, resourceType, properties);
|
|
41695
|
+
case "AWS::ServiceDiscovery::Service": return this.createService(logicalId, resourceType, properties, context?.maskSecrets);
|
|
41247
41696
|
default: throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId);
|
|
41248
41697
|
}
|
|
41249
41698
|
}
|
|
41250
|
-
|
|
41699
|
+
/**
|
|
41700
|
+
* `context` is read for ONE thing today: `maskSecrets` (issue #2050) — the
|
|
41701
|
+
* update-path twin of the `create()` note above.
|
|
41702
|
+
*/
|
|
41703
|
+
update(logicalId, physicalId, resourceType, properties, previousProperties, context) {
|
|
41251
41704
|
switch (resourceType) {
|
|
41252
41705
|
case "AWS::ServiceDiscovery::PrivateDnsNamespace": return this.updateNamespace(logicalId, physicalId, resourceType, properties, previousProperties);
|
|
41253
41706
|
case "AWS::ServiceDiscovery::HttpNamespace": return this.updateHttpNamespace(logicalId, physicalId, resourceType, properties, previousProperties);
|
|
41254
41707
|
case "AWS::ServiceDiscovery::PublicDnsNamespace": return this.updatePublicDnsNamespace(logicalId, physicalId, resourceType, properties, previousProperties);
|
|
41255
|
-
case "AWS::ServiceDiscovery::Service": return this.updateService(logicalId, physicalId, resourceType, properties, previousProperties);
|
|
41708
|
+
case "AWS::ServiceDiscovery::Service": return this.updateService(logicalId, physicalId, resourceType, properties, previousProperties, context?.maskSecrets);
|
|
41256
41709
|
default: throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId, physicalId);
|
|
41257
41710
|
}
|
|
41258
41711
|
}
|
|
@@ -41540,7 +41993,7 @@ var ServiceDiscoveryProvider = class ServiceDiscoveryProvider {
|
|
|
41540
41993
|
throw new ProvisioningError(`Failed to update public DNS namespace ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
|
|
41541
41994
|
}
|
|
41542
41995
|
}
|
|
41543
|
-
async createService(logicalId, resourceType, properties) {
|
|
41996
|
+
async createService(logicalId, resourceType, properties, maskSecrets) {
|
|
41544
41997
|
this.logger.debug(`Creating service discovery service ${logicalId}`);
|
|
41545
41998
|
const client = this.getClient();
|
|
41546
41999
|
const name = properties["Name"];
|
|
@@ -41572,7 +42025,7 @@ var ServiceDiscoveryProvider = class ServiceDiscoveryProvider {
|
|
|
41572
42025
|
await withRetry(() => client.send(new UpdateServiceAttributesCommand({
|
|
41573
42026
|
ServiceId: serviceId,
|
|
41574
42027
|
Attributes: attrs
|
|
41575
|
-
})), logicalId, { logger: this.
|
|
42028
|
+
})), logicalId, { logger: this.maskedRetryLogger(maskSecrets) });
|
|
41576
42029
|
this.logger.debug(`Applied ${Object.keys(attrs).length} ServiceAttribute(s) for ${logicalId}`);
|
|
41577
42030
|
}
|
|
41578
42031
|
} catch (innerError) {
|
|
@@ -41580,7 +42033,7 @@ var ServiceDiscoveryProvider = class ServiceDiscoveryProvider {
|
|
|
41580
42033
|
await client.send(new DeleteServiceCommand$1({ Id: serviceId }));
|
|
41581
42034
|
this.logger.debug(`Cleaned up partially-created ServiceDiscovery Service ${logicalId} (${serviceId}) after ServiceAttributes wiring failure`);
|
|
41582
42035
|
} catch (cleanupError) {
|
|
41583
|
-
this.logger.warn(`Failed to clean up partially-created ServiceDiscovery Service ${logicalId} (${serviceId}) after ServiceAttributes wiring failure: ${
|
|
42036
|
+
this.logger.warn(`Failed to clean up partially-created ServiceDiscovery Service ${logicalId} (${serviceId}) after ServiceAttributes wiring failure: ${this.maskErrorMessage(cleanupError, maskSecrets)}. Manual deletion may be required before the next deploy: aws servicediscovery delete-service --id ${serviceId}`);
|
|
41584
42037
|
}
|
|
41585
42038
|
throw innerError;
|
|
41586
42039
|
}
|
|
@@ -41594,7 +42047,7 @@ var ServiceDiscoveryProvider = class ServiceDiscoveryProvider {
|
|
|
41594
42047
|
};
|
|
41595
42048
|
} catch (error) {
|
|
41596
42049
|
const cause = error instanceof Error ? error : void 0;
|
|
41597
|
-
throw new ProvisioningError(`Failed to create service discovery service ${logicalId}: ${
|
|
42050
|
+
throw new ProvisioningError(`Failed to create service discovery service ${logicalId}: ${this.maskErrorMessage(error, maskSecrets)}`, resourceType, logicalId, void 0, cause);
|
|
41598
42051
|
}
|
|
41599
42052
|
}
|
|
41600
42053
|
/**
|
|
@@ -41614,7 +42067,7 @@ var ServiceDiscoveryProvider = class ServiceDiscoveryProvider {
|
|
|
41614
42067
|
* full AWS-current snapshot as `properties`, so the round-trip is
|
|
41615
42068
|
* value-preserving.
|
|
41616
42069
|
*/
|
|
41617
|
-
async updateService(logicalId, physicalId, resourceType, properties, previousProperties) {
|
|
42070
|
+
async updateService(logicalId, physicalId, resourceType, properties, previousProperties, maskSecrets) {
|
|
41618
42071
|
this.logger.debug(`Updating service discovery service ${logicalId}: ${physicalId}`);
|
|
41619
42072
|
const client = this.getClient();
|
|
41620
42073
|
const serviceChange = {};
|
|
@@ -41649,14 +42102,14 @@ var ServiceDiscoveryProvider = class ServiceDiscoveryProvider {
|
|
|
41649
42102
|
await withRetry(() => client.send(new UpdateServiceAttributesCommand({
|
|
41650
42103
|
ServiceId: physicalId,
|
|
41651
42104
|
Attributes: upsertAttrs
|
|
41652
|
-
})), logicalId, { logger: this.
|
|
42105
|
+
})), logicalId, { logger: this.maskedRetryLogger(maskSecrets) });
|
|
41653
42106
|
this.logger.debug(`Applied ${Object.keys(upsertAttrs).length} ServiceAttribute change(s) for ${logicalId}`);
|
|
41654
42107
|
}
|
|
41655
42108
|
if (hasAttrRemove) {
|
|
41656
42109
|
await withRetry(() => client.send(new DeleteServiceAttributesCommand({
|
|
41657
42110
|
ServiceId: physicalId,
|
|
41658
42111
|
Attributes: removedAttrKeys
|
|
41659
|
-
})), logicalId, { logger: this.
|
|
42112
|
+
})), logicalId, { logger: this.maskedRetryLogger(maskSecrets) });
|
|
41660
42113
|
this.logger.debug(`Removed ${removedAttrKeys.length} ServiceAttribute(s) for ${logicalId}`);
|
|
41661
42114
|
}
|
|
41662
42115
|
this.logger.debug(`Successfully updated service discovery service ${logicalId}`);
|
|
@@ -41667,7 +42120,7 @@ var ServiceDiscoveryProvider = class ServiceDiscoveryProvider {
|
|
|
41667
42120
|
} catch (error) {
|
|
41668
42121
|
if (error instanceof ProvisioningError) throw error;
|
|
41669
42122
|
const cause = error instanceof Error ? error : void 0;
|
|
41670
|
-
throw new ProvisioningError(`Failed to update service discovery service ${logicalId}: ${
|
|
42123
|
+
throw new ProvisioningError(`Failed to update service discovery service ${logicalId}: ${this.maskErrorMessage(error, maskSecrets)}`, resourceType, logicalId, physicalId, cause);
|
|
41671
42124
|
}
|
|
41672
42125
|
}
|
|
41673
42126
|
async deleteService(logicalId, physicalId, resourceType, context) {
|
|
@@ -82709,7 +83162,7 @@ function createMigrateCommand() {
|
|
|
82709
83162
|
*/
|
|
82710
83163
|
function buildProgram() {
|
|
82711
83164
|
const program = new Command();
|
|
82712
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.284.
|
|
83165
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.284.14");
|
|
82713
83166
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
82714
83167
|
const { profile } = actionCommand.optsWithGlobals();
|
|
82715
83168
|
if (profile !== void 0) process.env["AWS_PROFILE"] = profile;
|