@go-to-k/cdkd 0.283.19 → 0.283.21
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/README.md +5 -0
- package/dist/{asg-provider-B260vt0k.js → asg-provider-DKjoomrX.js} +2 -2
- package/dist/{asg-provider-B260vt0k.js.map → asg-provider-DKjoomrX.js.map} +1 -1
- package/dist/cli.js +266 -5
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-UVLw029j.js → deploy-engine-ISSt01TI.js} +401 -46
- package/dist/deploy-engine-ISSt01TI.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-UVLw029j.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as coerceCfnBoolean, $t as
|
|
2
|
+
import { $ as coerceCfnBoolean, $t as resolveCaptureObservedState, A as cyan, An as LocalStartServiceError, At as rewriteTemplateAssetReferences, B as findSilentDropProperties, Bt as buildDockerImage, C as makeCanonicalizePropertiesFn, Ct as shouldRetainResource, D as renderStatefulReason, Dn as DeployCancelledError, Dt as buildAssetRedirectMap, E as isStatefulRecreateTargetSync, Et as WorkGraph, F as IAMRoleProvider, Fn as ProvisioningError, Ft as getBootstrapMarkerKey, G as IntrinsicFunctionResolver, Gn as withErrorHandling, Gt as AssetManifestLoader, H as slowCcOperationTimeoutMs, Ht as getDockerCmd, I as collectInlinePolicyNamesManagedBySiblings, In as ResourceTimeoutError, It as parseBootstrapMarker, J as refStateLookupFromResource, Jn as isThrottlingError, Jt as synthesisStatusMessage, K as cfnRefValueFromPhysicalId, Kn as isMarkedNonRetryable, Kt as getDockerImageBySourceHash, L as clearOnUpdateRemoval, Ln as ResourceUpdateNotSupportedError, Lt as validateAssetBucketName, M as green, Mn as MissingCdkCliError, Mt as AssetModeResolver, N as red, Nn as NestedStackChildDirectDestroyError, Nt as BOOTSTRAP_MARKER_PREFIX, O as formatResourceLine, On as LocalInvokeBuildError$1, Ot as createAssetRedirectResolver, P as yellow, Pn as PartialFailureError, Pt as ensureAssetStorage, Q as assertRegionMatch, Qt as resolveAutoAssetStorage, R as ProviderRegistry, Rn as StackHasActiveImportsError, Rt as validateContainerRepoName, S as unsupportedFinalSnapshotError, Sn as setAwsClients, St as rebuildClientForBucketRegion, T as MULTI_REGION_RECREATE_BLOCKED_TYPES, Tt as stringifyValue, U as disableInstanceApiTermination, Ut as runDockerForeground, V as CloudControlProvider, Vt as formatDockerLoginError, W as isTerminationProtectionPropagationError, Wn as normalizeAwsError, Wt as runDockerStreaming, X as normalizeAwsTagsToCfn, Xt as getLegacyStateBucketName, Y as WAFv2WebACLProvider, Yn as markNonRetryable, Yt as getDefaultStateBucketName, Z as resolveExplicitPhysicalId, Zt as resolveApp, _ as buildFinalSnapshotIdentifier, _t as withRetry, a as DeploymentEventsStore, an as warnDeprecatedNoPrefixCliFlag, at as requireConfigObject, b as isFinalSnapshotError, bn as getAwsClients, bt as LockManager, c as replayFailedOperations, cn as MIGRATE_TMP_PREFIX, ct as scrubResourceRecord, d as deleteSkipReason, dn as expectedOwnerParam, dt as s3BucketDualStackDomainName, en as resolveSkipPrefix, et as configBooleanRefusal, f as withResourceDeadline, fn as PARTITION_TABLE, ft as s3BucketRegionalDomainName, g as PRE_DELETE_SNAPSHOT_TYPES, gn as processStackMessages, gt as describeTypeWithThrottleRetry, h as ATOMIC_FINAL_SNAPSHOT_TYPES, hn as AssemblyReader, ht as DiffCalculator, i as DeploymentEventsReader, in as stateBucketExistenceConfirmed, it as requireConfigArray, j as gray, jt as escapeRegExp, k as bold, kn as LocalMigrateError, kt as loadPublishableAssetManifest, l as replayRollback, ln as findLargeInlineResources, lt as s3BucketArn, m as computeImplicitDeleteEdges, mn as derivePartitionAndUrlSuffix, mt as applyRoleArnIfSet, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as resolveStateBucketWithDefaultAndSource, nt as readConfigString, o as planFailedOps, on as CFN_TEMPLATE_BODY_LIMIT, ot as requireConfigString, p as IMPLICIT_DELETE_DEPENDENCIES, pn as canonicalizeRegion, pt as s3BucketWebsiteUrl, q as getAccountInfo, qn as isRetryableTransientError, qt as Synthesizer, r as DeployEngine, rn as resolveUseCdkBootstrapAssets, rt as replayWarn, s as planRollback, sn as CFN_TEMPLATE_URL_LIMIT, st as redactSecretsForState, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as resolveStateBucketWithDefault, tt as configStringRefusal, u as UNSPECIFIED_SKIP_REASON, un as uploadCfnTemplate, ut as s3BucketDomainName, v as ccRoutedFinalSnapshotError, vn as resolveBucketRegion, vt as DagBuilder, w as extractDeploymentEventError, wn as CdkdError, wt as AssetPublisher, x as refusesFinalSnapshot, xn as resetAwsClients, xt as S3StateBackend, y as createPreDeleteFinalSnapshot, yn as AwsClients, yt as TemplateParser, z as findActionableSilentDrops, zn as StackTerminationProtectionError, zt as buildDenyExternalAccessPolicy } from "./deploy-engine-ISSt01TI.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-DKjoomrX.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";
|
|
@@ -57746,7 +57746,8 @@ async function computeStackDiff(currentState, template, region, stackName, state
|
|
|
57746
57746
|
stackName,
|
|
57747
57747
|
bestEffort: true,
|
|
57748
57748
|
...mergedParameters && { parameters: mergedParameters },
|
|
57749
|
-
...conditions && { conditions }
|
|
57749
|
+
...conditions && { conditions },
|
|
57750
|
+
skipDynamicReferences: true
|
|
57750
57751
|
});
|
|
57751
57752
|
return diffCalculator.calculateDiff(currentState, effectiveTemplate, resolveFn, canonicalizeProperties);
|
|
57752
57753
|
}
|
|
@@ -58263,6 +58264,259 @@ function createDiffCommand() {
|
|
|
58263
58264
|
return cmd;
|
|
58264
58265
|
}
|
|
58265
58266
|
|
|
58267
|
+
//#endregion
|
|
58268
|
+
//#region src/cli/commands/scrub.ts
|
|
58269
|
+
/**
|
|
58270
|
+
* Signals `cdkd scrub` found stacks whose state still holds plaintext secrets.
|
|
58271
|
+
* Only thrown under `--dry-run --fail`; carries no message (the plan was
|
|
58272
|
+
* already printed) and maps to a non-zero exit so CI can gate on it.
|
|
58273
|
+
*/
|
|
58274
|
+
var ScrubNeededError = class ScrubNeededError extends CdkdError {
|
|
58275
|
+
silent = true;
|
|
58276
|
+
constructor() {
|
|
58277
|
+
super("scrub needed", "SCRUB_NEEDED");
|
|
58278
|
+
this.name = "ScrubNeededError";
|
|
58279
|
+
Object.setPrototypeOf(this, ScrubNeededError.prototype);
|
|
58280
|
+
}
|
|
58281
|
+
};
|
|
58282
|
+
/**
|
|
58283
|
+
* `cdkd scrub` — rewrite persisted state so any resolved secret dynamic
|
|
58284
|
+
* reference is stored as its UNRESOLVED expression rather than the plaintext
|
|
58285
|
+
* value (GHSA fix). "Secret" here is whatever the RESOLVER classifies as one,
|
|
58286
|
+
* which is the single source of truth this command shares with the deploy
|
|
58287
|
+
* path: every `{{resolve:secretsmanager:...}}`, plus a `{{resolve:ssm:...}}`
|
|
58288
|
+
* naming a `SecureString` parameter (issue #1901). scrub therefore gains a new
|
|
58289
|
+
* secret class automatically, with no second list to keep in sync.
|
|
58290
|
+
*
|
|
58291
|
+
* A normal `cdkd deploy` already scrubs state as a side effect (the deploy
|
|
58292
|
+
* engine redacts every persisted bag), so this command is for cleaning up
|
|
58293
|
+
* existing state WITHOUT a redeploy — e.g. after upgrading cdkd on a stack you
|
|
58294
|
+
* do not want to re-provision right now.
|
|
58295
|
+
*
|
|
58296
|
+
* It needs the CDK app (`--app`) because a state file records the RESOLVED
|
|
58297
|
+
* plaintext with no marker of which values are secrets: only the template
|
|
58298
|
+
* carries the `{{resolve:...}}` expressions. So scrub synthesizes the template,
|
|
58299
|
+
* re-resolves each resource's properties to learn the resolved secret VALUES
|
|
58300
|
+
* (recorded, never printed or re-persisted), and replaces those values in the
|
|
58301
|
+
* state record's `properties` / `attributes` / `observedProperties` with the
|
|
58302
|
+
* expression. No AWS resource is created, updated, or deleted; only state.json
|
|
58303
|
+
* is rewritten. This is why it is a top-level command and not `cdkd state
|
|
58304
|
+
* scrub` — the `cdkd state ...` family operates on the state bucket alone and
|
|
58305
|
+
* deliberately needs no CDK code.
|
|
58306
|
+
*
|
|
58307
|
+
* IMPORTANT: scrubbing does not un-expose an already-leaked secret. A value
|
|
58308
|
+
* that was stored in plaintext should be treated as compromised and ROTATED at
|
|
58309
|
+
* its source — in Secrets Manager, or by re-putting the `SecureString` SSM
|
|
58310
|
+
* parameter; scrub only stops it from being re-read out of state going
|
|
58311
|
+
* forward.
|
|
58312
|
+
*
|
|
58313
|
+
* ORDERING: scrub matches the CURRENT resolved secret value against what state
|
|
58314
|
+
* holds, so run it BEFORE rotating. Once the secret is rotated, the value in
|
|
58315
|
+
* state no longer matches the current one and scrub cannot find it (it reports
|
|
58316
|
+
* "nothing to scrub"). A rotated-away stale value in state is invalidated by
|
|
58317
|
+
* the rotation, but to remove it, redeploy the stack (which rewrites the record
|
|
58318
|
+
* with the expression).
|
|
58319
|
+
*/
|
|
58320
|
+
async function scrubCommand(stacks, options) {
|
|
58321
|
+
const logger = getLogger();
|
|
58322
|
+
if (options.verbose) logger.setLevel("debug");
|
|
58323
|
+
warnIfDeprecatedRegion(options);
|
|
58324
|
+
await applyRoleArnIfSet({
|
|
58325
|
+
roleArn: options.roleArn,
|
|
58326
|
+
region: options.region
|
|
58327
|
+
});
|
|
58328
|
+
const app = resolveApp(options.app);
|
|
58329
|
+
if (!app) throw new Error("CDK app is required (scrub needs the template to identify secret references). Pass --app, set CDKD_APP, or add \"app\" to cdk.json.");
|
|
58330
|
+
const region = options.region || process.env["AWS_REGION"] || "us-east-1";
|
|
58331
|
+
const stateBucket = await resolveStateBucketWithDefault(options.stateBucket, region);
|
|
58332
|
+
setAwsClients(new AwsClients({
|
|
58333
|
+
...options.region && { region: options.region },
|
|
58334
|
+
...options.profile && { profile: options.profile }
|
|
58335
|
+
}));
|
|
58336
|
+
logger.info(synthesisStatusMessage(app, "Synthesizing CDK app..."));
|
|
58337
|
+
const synthesizer = new Synthesizer();
|
|
58338
|
+
const context = parseContextOptions(options.context);
|
|
58339
|
+
const synthOptions = {
|
|
58340
|
+
app,
|
|
58341
|
+
output: options.output,
|
|
58342
|
+
...options.region && { region: options.region },
|
|
58343
|
+
...options.profile && { profile: options.profile },
|
|
58344
|
+
...Object.keys(context).length > 0 && { context },
|
|
58345
|
+
stateBucket,
|
|
58346
|
+
deferMacroExpansion: true
|
|
58347
|
+
};
|
|
58348
|
+
const allStacks = (await synthesizer.synthesize(synthOptions)).stacks;
|
|
58349
|
+
const stackPatterns = stacks.length > 0 ? stacks : options.stack ? [options.stack] : [];
|
|
58350
|
+
let targetStacks;
|
|
58351
|
+
if (options.all) targetStacks = allStacks;
|
|
58352
|
+
else if (stackPatterns.length > 0) targetStacks = matchStacks(allStacks, stackPatterns);
|
|
58353
|
+
else if (allStacks.length === 1) targetStacks = allStacks;
|
|
58354
|
+
else throw new Error(`Multiple stacks found: ${allStacks.map(describeStack).join(", ")}. Specify stack name(s) or use --all`);
|
|
58355
|
+
if (targetStacks.length === 0) throw new Error("No stacks matched.");
|
|
58356
|
+
await synthesizer.expandMacrosForStacks(targetStacks, synthOptions);
|
|
58357
|
+
const stateConfig = {
|
|
58358
|
+
bucket: stateBucket,
|
|
58359
|
+
prefix: options.statePrefix
|
|
58360
|
+
};
|
|
58361
|
+
const stateS3 = new AwsClients({
|
|
58362
|
+
region,
|
|
58363
|
+
...options.profile && { profile: options.profile }
|
|
58364
|
+
});
|
|
58365
|
+
const stateBackend = new S3StateBackend(stateS3.s3, stateConfig, {
|
|
58366
|
+
region,
|
|
58367
|
+
...options.profile && { profile: options.profile }
|
|
58368
|
+
});
|
|
58369
|
+
const lockManager = new LockManager(stateS3.s3, stateConfig);
|
|
58370
|
+
let totalStacksScrubbed = 0;
|
|
58371
|
+
for (const stack of targetStacks) {
|
|
58372
|
+
const scrubbed = await scrubStack(stack, stack.region || region, stateBackend, lockManager, {
|
|
58373
|
+
dryRun: options.dryRun ?? false,
|
|
58374
|
+
roleArn: options.roleArn,
|
|
58375
|
+
logger
|
|
58376
|
+
});
|
|
58377
|
+
if (scrubbed.recordsChanged > 0) {
|
|
58378
|
+
totalStacksScrubbed++;
|
|
58379
|
+
logger.info(`${options.dryRun ? "Would scrub" : "Scrubbed"} ${scrubbed.recordsChanged} resource record(s) in ${stack.stackName}`);
|
|
58380
|
+
} else logger.info(`No plaintext secrets found in ${stack.stackName}`);
|
|
58381
|
+
}
|
|
58382
|
+
if (totalStacksScrubbed === 0) {
|
|
58383
|
+
logger.info("\nNo plaintext secrets found in any target stack state. Nothing to scrub.");
|
|
58384
|
+
return;
|
|
58385
|
+
}
|
|
58386
|
+
if (options.dryRun) {
|
|
58387
|
+
logger.info(`\nPlan: ${totalStacksScrubbed} stack(s) hold plaintext secrets and would be scrubbed (--dry-run, no state written). ROTATE any exposed secret in Secrets Manager.`);
|
|
58388
|
+
if (options.fail) throw new ScrubNeededError();
|
|
58389
|
+
return;
|
|
58390
|
+
}
|
|
58391
|
+
logger.info(`\nDone: scrubbed ${totalStacksScrubbed} stack(s). The plaintext is no longer stored, but a value that was ever persisted should be treated as compromised — ROTATE it in Secrets Manager (scrub matches the current value, so scrub BEFORE rotating).`);
|
|
58392
|
+
}
|
|
58393
|
+
/**
|
|
58394
|
+
* Scrub one stack's state. Re-resolves the template's per-resource properties to
|
|
58395
|
+
* learn the resolved secret VALUES, then replaces those values in the state
|
|
58396
|
+
* record with their `{{resolve:...}}` expressions. Returns counts; performs no
|
|
58397
|
+
* AWS mutation. Acquires the stack lock for the read-modify-write unless
|
|
58398
|
+
* `dryRun`.
|
|
58399
|
+
*/
|
|
58400
|
+
async function scrubStack(stack, region, stateBackend, lockManager, opts) {
|
|
58401
|
+
const { logger } = opts;
|
|
58402
|
+
const acquired = !opts.dryRun;
|
|
58403
|
+
if (acquired) await lockManager.acquireLockWithRetry(stack.stackName, region, void 0, "scrub");
|
|
58404
|
+
try {
|
|
58405
|
+
const loaded = await stateBackend.getState(stack.stackName, region);
|
|
58406
|
+
if (!loaded) {
|
|
58407
|
+
logger.debug(`No state for ${stack.stackName} (${region}) — skipping`);
|
|
58408
|
+
return {
|
|
58409
|
+
recordsChanged: 0,
|
|
58410
|
+
secretsFound: 0
|
|
58411
|
+
};
|
|
58412
|
+
}
|
|
58413
|
+
const state = loaded.state;
|
|
58414
|
+
const perResourceSecrets = /* @__PURE__ */ new Map();
|
|
58415
|
+
const outputSecrets = /* @__PURE__ */ new Map();
|
|
58416
|
+
const resolver = new IntrinsicFunctionResolver(region);
|
|
58417
|
+
let parameters = {};
|
|
58418
|
+
let conditions = {};
|
|
58419
|
+
try {
|
|
58420
|
+
parameters = await resolver.resolveParameters(stack.template);
|
|
58421
|
+
} catch (err) {
|
|
58422
|
+
logger.debug(`Parameter resolution skipped for ${stack.stackName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
58423
|
+
}
|
|
58424
|
+
try {
|
|
58425
|
+
conditions = await resolver.evaluateConditions({
|
|
58426
|
+
template: stack.template,
|
|
58427
|
+
resources: state.resources,
|
|
58428
|
+
...Object.keys(parameters).length > 0 && { parameters },
|
|
58429
|
+
bestEffort: true
|
|
58430
|
+
});
|
|
58431
|
+
} catch (err) {
|
|
58432
|
+
logger.debug(`Condition evaluation skipped for ${stack.stackName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
58433
|
+
}
|
|
58434
|
+
const templateResources = stack.template.Resources ?? {};
|
|
58435
|
+
for (const logicalId of Object.keys(state.resources)) {
|
|
58436
|
+
const templateResource = templateResources[logicalId];
|
|
58437
|
+
if (!templateResource?.Properties) continue;
|
|
58438
|
+
const recordedSecretValues = /* @__PURE__ */ new Map();
|
|
58439
|
+
try {
|
|
58440
|
+
await resolver.resolve(templateResource.Properties, {
|
|
58441
|
+
template: stack.template,
|
|
58442
|
+
resources: state.resources,
|
|
58443
|
+
...Object.keys(parameters).length > 0 && { parameters },
|
|
58444
|
+
...Object.keys(conditions).length > 0 && { conditions },
|
|
58445
|
+
stackName: stack.stackName,
|
|
58446
|
+
recordedSecretValues,
|
|
58447
|
+
bestEffort: true
|
|
58448
|
+
});
|
|
58449
|
+
} catch (err) {
|
|
58450
|
+
logger.debug(`Resolution of ${logicalId} during scrub was partial: ${err instanceof Error ? err.message : String(err)}`);
|
|
58451
|
+
}
|
|
58452
|
+
if (recordedSecretValues.size > 0) perResourceSecrets.set(logicalId, recordedSecretValues);
|
|
58453
|
+
}
|
|
58454
|
+
const templateOutputs = stack.template.Outputs ?? {};
|
|
58455
|
+
for (const [name, output] of Object.entries(templateOutputs)) {
|
|
58456
|
+
const value = output.Value;
|
|
58457
|
+
if (value === void 0) continue;
|
|
58458
|
+
try {
|
|
58459
|
+
await resolver.resolve(value, {
|
|
58460
|
+
template: stack.template,
|
|
58461
|
+
resources: state.resources,
|
|
58462
|
+
...Object.keys(parameters).length > 0 && { parameters },
|
|
58463
|
+
...Object.keys(conditions).length > 0 && { conditions },
|
|
58464
|
+
stackName: stack.stackName,
|
|
58465
|
+
recordedSecretValues: outputSecrets,
|
|
58466
|
+
bestEffort: true
|
|
58467
|
+
});
|
|
58468
|
+
} catch (err) {
|
|
58469
|
+
logger.debug(`Resolution of output ${name} during scrub was partial: ${err instanceof Error ? err.message : String(err)}`);
|
|
58470
|
+
}
|
|
58471
|
+
}
|
|
58472
|
+
const totalSecrets = outputSecrets.size + [...perResourceSecrets.values()].reduce((n, m) => n + m.size, 0);
|
|
58473
|
+
if (totalSecrets === 0) return {
|
|
58474
|
+
recordsChanged: 0,
|
|
58475
|
+
secretsFound: 0
|
|
58476
|
+
};
|
|
58477
|
+
let recordsChanged = 0;
|
|
58478
|
+
const newResources = {};
|
|
58479
|
+
for (const [logicalId, record] of Object.entries(state.resources)) {
|
|
58480
|
+
const secrets = perResourceSecrets.get(logicalId);
|
|
58481
|
+
const scrubbed = secrets ? scrubResourceRecord(record, secrets) : record;
|
|
58482
|
+
if (JSON.stringify(scrubbed) !== JSON.stringify(record)) recordsChanged++;
|
|
58483
|
+
newResources[logicalId] = scrubbed;
|
|
58484
|
+
}
|
|
58485
|
+
const newOutputs = outputSecrets.size > 0 ? redactSecretsForState(state.outputs, outputSecrets) : state.outputs;
|
|
58486
|
+
if (JSON.stringify(newOutputs) !== JSON.stringify(state.outputs)) recordsChanged++;
|
|
58487
|
+
if (recordsChanged > 0 && !opts.dryRun) {
|
|
58488
|
+
const nextState = {
|
|
58489
|
+
...state,
|
|
58490
|
+
resources: newResources,
|
|
58491
|
+
outputs: newOutputs,
|
|
58492
|
+
lastModified: Date.now()
|
|
58493
|
+
};
|
|
58494
|
+
await stateBackend.saveState(stack.stackName, region, nextState, { expectedEtag: loaded.etag });
|
|
58495
|
+
}
|
|
58496
|
+
return {
|
|
58497
|
+
recordsChanged,
|
|
58498
|
+
secretsFound: totalSecrets
|
|
58499
|
+
};
|
|
58500
|
+
} finally {
|
|
58501
|
+
if (acquired) await lockManager.releaseLock(stack.stackName, region).catch((err) => {
|
|
58502
|
+
logger.warn(`Failed to release lock for ${stack.stackName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
58503
|
+
});
|
|
58504
|
+
}
|
|
58505
|
+
}
|
|
58506
|
+
function createScrubCommand() {
|
|
58507
|
+
const cmd = new Command("scrub").description("Rewrite persisted state so resolved secret dynamic references are stored as their {{resolve:...}} expression, not the plaintext value (no deploy).").argument("[stacks...]", "Stack name(s) to scrub (physical name or display path)").option("--all", "Scrub every stack in the synthesized app", false).option("--dry-run", "Report what would be scrubbed without writing state").option("--fail", "With --dry-run, exit non-zero if any plaintext secret is found (CI gate)");
|
|
58508
|
+
[
|
|
58509
|
+
...commonOptions,
|
|
58510
|
+
...appOptions,
|
|
58511
|
+
...stateOptions,
|
|
58512
|
+
...stackOptions,
|
|
58513
|
+
...contextOptions
|
|
58514
|
+
].forEach((opt) => cmd.addOption(opt));
|
|
58515
|
+
cmd.addOption(deprecatedRegionOption);
|
|
58516
|
+
cmd.action(withErrorHandling(scrubCommand));
|
|
58517
|
+
return cmd;
|
|
58518
|
+
}
|
|
58519
|
+
|
|
58266
58520
|
//#endregion
|
|
58267
58521
|
//#region src/analyzer/drift-normalize.ts
|
|
58268
58522
|
/**
|
|
@@ -58624,6 +58878,7 @@ function isIgnoredPath(path, ignorePaths) {
|
|
|
58624
58878
|
*/
|
|
58625
58879
|
function diffAt(path, stateValue, awsValue, out, ignorePaths, unionWalkObjects) {
|
|
58626
58880
|
if (deepEqual(stateValue, awsValue)) return;
|
|
58881
|
+
if (typeof stateValue === "string" && stateValue.includes("{{resolve:")) return;
|
|
58627
58882
|
if (isPlainObject(stateValue) && isPlainObject(awsValue) && !Array.isArray(stateValue) && !Array.isArray(awsValue)) {
|
|
58628
58883
|
const keys = unionWalkObjects ? /* @__PURE__ */ new Set([...Object.keys(stateValue), ...Object.keys(awsValue)]) : Object.keys(stateValue);
|
|
58629
58884
|
for (const key of keys) {
|
|
@@ -68655,7 +68910,12 @@ async function resolveImportedProperties(stackState, template, region, stateBack
|
|
|
68655
68910
|
stackName: stackState.stackName
|
|
68656
68911
|
};
|
|
68657
68912
|
for (const [logicalId, resource] of entries) try {
|
|
68658
|
-
|
|
68913
|
+
const recordedSecretValues = /* @__PURE__ */ new Map();
|
|
68914
|
+
const resolved = await resolver.resolve(resource.properties ?? {}, {
|
|
68915
|
+
...baseContext,
|
|
68916
|
+
recordedSecretValues
|
|
68917
|
+
});
|
|
68918
|
+
resource.properties = recordedSecretValues.size > 0 ? redactSecretsForState(resolved, recordedSecretValues) : resolved;
|
|
68659
68919
|
} catch (err) {
|
|
68660
68920
|
logger.warn(`Failed to resolve intrinsics in Properties for imported resource '${logicalId}' (${resource.resourceType}): ${err instanceof Error ? err.message : String(err)}. State will be written with the raw intrinsic shape, which may cause 'cdkd destroy' to fail on this resource — re-import once every referenced sibling is in state, or remove this resource via 'cdkd state orphan'.`);
|
|
68661
68921
|
}
|
|
@@ -78814,7 +79074,7 @@ function createMigrateCommand() {
|
|
|
78814
79074
|
*/
|
|
78815
79075
|
function buildProgram() {
|
|
78816
79076
|
const program = new Command();
|
|
78817
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.283.
|
|
79077
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.283.21");
|
|
78818
79078
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
78819
79079
|
const { profile } = actionCommand.optsWithGlobals();
|
|
78820
79080
|
if (profile !== void 0) process.env["AWS_PROFILE"] = profile;
|
|
@@ -78827,6 +79087,7 @@ function buildProgram() {
|
|
|
78827
79087
|
program.addCommand(createDriftCommand());
|
|
78828
79088
|
program.addCommand(createDestroyCommand());
|
|
78829
79089
|
program.addCommand(createRollbackCommand());
|
|
79090
|
+
program.addCommand(createScrubCommand());
|
|
78830
79091
|
program.addCommand(createEventsCommand());
|
|
78831
79092
|
program.addCommand(createGcCommand());
|
|
78832
79093
|
program.addCommand(createOrphanCommand());
|