@go-to-k/cdkd 0.296.31 → 0.298.0
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-Ca7cV9tM.js → asg-provider-B1xRE8do.js} +5 -5
- package/dist/{asg-provider-Ca7cV9tM.js.map → asg-provider-B1xRE8do.js.map} +1 -1
- package/dist/{aws-failure-text-BaWeCWRz.js → aws-failure-text-DtvlUugx.js} +2 -2
- package/dist/{aws-failure-text-BaWeCWRz.js.map → aws-failure-text-DtvlUugx.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-5FFXg6ih.js → deploy-engine-CJTG3tft.js} +1944 -469
- package/dist/deploy-engine-CJTG3tft.js.map +1 -0
- package/dist/{iam-policy-targets-Cz2tPxXk.js → iam-policy-targets-CwKiK9nS.js} +2 -2
- package/dist/{iam-policy-targets-Cz2tPxXk.js.map → iam-policy-targets-CwKiK9nS.js.map} +1 -1
- package/dist/{iam-role-provider-syetbMnw.js → iam-role-provider-qxfS4sDT.js} +24 -4
- package/dist/{iam-role-provider-syetbMnw.js.map → iam-role-provider-qxfS4sDT.js.map} +1 -1
- package/dist/index.d.ts +21 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/{neptune-provider-B1D2CFBs.js → neptune-provider-CU_1O2FR.js} +4 -4
- package/dist/{neptune-provider-B1D2CFBs.js.map → neptune-provider-CU_1O2FR.js.map} +1 -1
- package/dist/{program-Db1eBDeq.js → program-BivXyHmd.js} +584 -83
- package/dist/program-BivXyHmd.js.map +1 -0
- package/dist/{provider-classes-B7Z4-Zsc.js → provider-classes-1EHrZ847.js} +73 -23
- package/dist/provider-classes-1EHrZ847.js.map +1 -0
- package/dist/{rds-provider-B4sFJ8YW.js → rds-provider-J40R0ysf.js} +6 -6
- package/dist/{rds-provider-B4sFJ8YW.js.map → rds-provider-J40R0ysf.js.map} +1 -1
- package/dist/{version-D0cK9V1A.js → version-D96I6uiO.js} +2 -2
- package/dist/{version-D0cK9V1A.js.map → version-D96I6uiO.js.map} +1 -1
- package/package.json +3 -1
- package/dist/deploy-engine-5FFXg6ih.js.map +0 -1
- package/dist/program-Db1eBDeq.js.map +0 -1
- package/dist/provider-classes-B7Z4-Zsc.js.map +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { r as getAwsClients, s as __exportAll } from "./aws-clients-CnmtbrxD.js";
|
|
2
|
-
import { A as UNRENDERABLE, C as looksLikeCdkdGeneratedName, E as withoutGeneratedFallbackName, F as displayStackName, I as isPasteableIdent, M as displayAwsMessage, N as displayIdent, O as SECRET_REF_MAX_CODE_POINTS, P as displaySafe, R as plainIdentOr, S as getCurrentStackName, T as withStackName, V as
|
|
3
|
-
import { $ as protectedReplacementAdvice, $n as isAmbiguousOutcomeError, $t as credentialFingerprint, A as isQuotableImportValue, An as UNSHOWABLE_VALUE, At as isAuxiliaryMarkOf, B as isTerminalDeleteFailure, Bn as withheldTargetClause, Bt as recoveryCommandFlags, Ct as RETRY_AUXILIARY_OWNER, Dt as createdResourceIdentityBeforeFailure, E as assertRegionMatch, Et as createdBeforeFailure, F as resolveExplicitPhysicalId, Fn as physicalIdShownBesideCommand, G as runDeleteAttempt, Gn as isNameCollisionErrorFrom, H as observeThenDisableProtection, Ht as CUSTOM_RESOURCE_RESPONSE_PREFIX, In as plainOrDescribed, J as DELETION_PROTECTION_DOC_POINTER, Jn as isReplayedNameCollisionFrom, Kn as isNameCooldownError, L as ProtectionFlipRegistry, Ln as quotedOrDescribed, Lt as formatLockExpiry, M as quotedRemedyLogicalId, Mt as markCreatedBeforeFailure, N as refusalTypeShown, Nn as isInertUnquoted, Nt as UNREPRODUCIBLE_LOCK_CLAUSE, On as SHELL_ACTIVE, Ot as hasCreatedBeforeFailure, Pn as pasteableCommand, Pt as buildForceUnlockCommand, Q as pasteableAwsCommand, Qn as isAmbiguousCcHandlerErrorCode, Qt as clientDefaultsFor, R as compensateProtectionFlip, Rn as shellBoundedDisplay, Rt as formatLockOperation, Sn as SynthesisError, St as withDerivedNameMasks, T as RESOURCE_NOT_FOUND, Tn as normalizeAwsError, U as protectionFlipKey, Un as isUpdateUnsupportedError, Ut as DEFAULT_STATE_PREFIX, Vt as sanitizeRecoveryValue, Wn as hasReplayMayCollide, Wt as injectiveKey, Xn as markReplayMayCollide, Xt as ambientClientDefaults, Yt as withPasteableAwsProfile, Zn as formatRetryClassificationSignals, Zt as ambientCredentialConfig, _n as ResourceTimeoutError, _t as createMaskedLogSinks, an as DependencyError, ar as isMarkedNonRetryable, at as startInterruptWatch, bt as maskDeep, cn as IntrinsicResolutionRefusalError, cr as markRedactedCause, ct as canonicalizeRoute53QueryName, d as prepareRemovalForUpdate, dn as LockError, en as AccountIdUnavailableError, er as isIamPropagationError, f as removalWarning, fn as MacroExpansionError, ft as logicalIdShown, gn as ProvisioningError, gt as stateOrphanRecordRemedy, ht as segmentAfterAnchor, i as holdsSecretDerivedEntry, in as CrossAccountSecretRefusalError, it as isInterruptedWaitError, j as normalizeAwsTagsToCfn, jn as commandHole, jt as markAuxiliaryFailure, kn as SHELL_ACTIVE_WHY, kt as isAuxiliaryFailure, l as withUnchangedSecretPrincipalLists, lr as wrapMaskedAwsError, m as withoutInjectedRemovals, mt as resourceTypeShown, nn as CdkdError, nr as isTransientServerError, or as isThrottlingError, ot as ElapsedBudgetRegistry, pn as MalformedProducerRecordRefusalError, qn as isRecreateRetryableError, qt as producerRecordKey, rr as retryClassificationText, sn as DynamicReferenceRegionAmbiguousError, sr as markNonRetryable, st as monotonicNowMs, tn as AssetError, tr as isRetryableTransientError, ut as compositeIdSeparatorRefusal, vn as ResourceUpdateNotSupportedError, vt as createMaskedRetryLogger, w as withoutServerErrorRetries, wt as carryCreatedBeforeFailure, xn as StateError, xt as maskerOrIdentity, yt as isSecretDerivedValue, z as deleteWithProtectionCompensation, zn as shellQuote, zt as formatLockOwner } from "./iam-policy-targets-
|
|
2
|
+
import { A as UNRENDERABLE, C as looksLikeCdkdGeneratedName, E as withoutGeneratedFallbackName, F as displayStackName, H as truncateCodePoints, I as isPasteableIdent, M as displayAwsMessage, N as displayIdent, O as SECRET_REF_MAX_CODE_POINTS, P as displaySafe, R as plainIdentOr, S as getCurrentStackName, T as withStackName, V as terminalSafe, a as getLogger, b as generateResourceNameWithFallback, d as installLogLineMaskerSource, f as getLiveRenderer, g as applyDefaultNameForFallback, k as STACK_REF_MAX_CODE_POINTS, o as isStdoutReservedForPayload, r as safeStringify$1, t as describeAwsFailure, u as currentLogLineMasker, v as explicitNamePropertyFor, w as withSkipPrefix, x as getCurrentSkipPrefix, y as generateResourceName, z as safeMsg } from "./aws-failure-text-DtvlUugx.js";
|
|
3
|
+
import { $ as protectedReplacementAdvice, $n as isAmbiguousOutcomeError, $t as credentialFingerprint, A as isQuotableImportValue, An as UNSHOWABLE_VALUE, At as isAuxiliaryMarkOf, B as isTerminalDeleteFailure, Bn as withheldTargetClause, Bt as recoveryCommandFlags, Ct as RETRY_AUXILIARY_OWNER, Dt as createdResourceIdentityBeforeFailure, E as assertRegionMatch, Et as createdBeforeFailure, F as resolveExplicitPhysicalId, Fn as physicalIdShownBesideCommand, G as runDeleteAttempt, Gn as isNameCollisionErrorFrom, H as observeThenDisableProtection, Ht as CUSTOM_RESOURCE_RESPONSE_PREFIX, In as plainOrDescribed, J as DELETION_PROTECTION_DOC_POINTER, Jn as isReplayedNameCollisionFrom, Kn as isNameCooldownError, L as ProtectionFlipRegistry, Ln as quotedOrDescribed, Lt as formatLockExpiry, M as quotedRemedyLogicalId, Mt as markCreatedBeforeFailure, N as refusalTypeShown, Nn as isInertUnquoted, Nt as UNREPRODUCIBLE_LOCK_CLAUSE, On as SHELL_ACTIVE, Ot as hasCreatedBeforeFailure, Pn as pasteableCommand, Pt as buildForceUnlockCommand, Q as pasteableAwsCommand, Qn as isAmbiguousCcHandlerErrorCode, Qt as clientDefaultsFor, R as compensateProtectionFlip, Rn as shellBoundedDisplay, Rt as formatLockOperation, Sn as SynthesisError, St as withDerivedNameMasks, T as RESOURCE_NOT_FOUND, Tn as normalizeAwsError, U as protectionFlipKey, Un as isUpdateUnsupportedError, Ut as DEFAULT_STATE_PREFIX, Vt as sanitizeRecoveryValue, Wn as hasReplayMayCollide, Wt as injectiveKey, Xn as markReplayMayCollide, Xt as ambientClientDefaults, Yt as withPasteableAwsProfile, Zn as formatRetryClassificationSignals, Zt as ambientCredentialConfig, _n as ResourceTimeoutError, _t as createMaskedLogSinks, an as DependencyError, ar as isMarkedNonRetryable, at as startInterruptWatch, bt as maskDeep, cn as IntrinsicResolutionRefusalError, cr as markRedactedCause, ct as canonicalizeRoute53QueryName, d as prepareRemovalForUpdate, dn as LockError, en as AccountIdUnavailableError, er as isIamPropagationError, f as removalWarning, fn as MacroExpansionError, ft as logicalIdShown, gn as ProvisioningError, gt as stateOrphanRecordRemedy, ht as segmentAfterAnchor, i as holdsSecretDerivedEntry, in as CrossAccountSecretRefusalError, it as isInterruptedWaitError, j as normalizeAwsTagsToCfn, jn as commandHole, jt as markAuxiliaryFailure, kn as SHELL_ACTIVE_WHY, kt as isAuxiliaryFailure, l as withUnchangedSecretPrincipalLists, lr as wrapMaskedAwsError, m as withoutInjectedRemovals, mt as resourceTypeShown, nn as CdkdError, nr as isTransientServerError, or as isThrottlingError, ot as ElapsedBudgetRegistry, pn as MalformedProducerRecordRefusalError, qn as isRecreateRetryableError, qt as producerRecordKey, rr as retryClassificationText, sn as DynamicReferenceRegionAmbiguousError, sr as markNonRetryable, st as monotonicNowMs, tn as AssetError, tr as isRetryableTransientError, ut as compositeIdSeparatorRefusal, vn as ResourceUpdateNotSupportedError, vt as createMaskedRetryLogger, w as withoutServerErrorRetries, wt as carryCreatedBeforeFailure, xn as StateError, xt as maskerOrIdentity, yt as isSecretDerivedValue, z as deleteWithProtectionCompensation, zn as shellQuote, zt as formatLockOwner } from "./iam-policy-targets-CwKiK9nS.js";
|
|
4
4
|
import { a as setAssumedRoleCredentials, i as getPreAssumeEnvCredentials, n as awsClientDefaults, o as setPreAssumeEnvCredentials, r as getAssumedRoleCredentials, s as ambientRegion, t as SECOND_ROLE_PUBLISH_REFUSAL } from "./aws-client-defaults-D0q0z4dG.js";
|
|
5
|
-
import { t as getCdkdVersion } from "./version-
|
|
5
|
+
import { t as getCdkdVersion } from "./version-D96I6uiO.js";
|
|
6
6
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
7
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
8
|
-
import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetBucketReplicationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
8
|
+
import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketReplicationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketLifecycleConfigurationCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
|
|
9
9
|
import { CloudControlClient, CreateResourceCommand, DeleteResourceCommand, GetResourceCommand, GetResourceRequestStatusCommand, ListResourcesCommand, UpdateResourceCommand } from "@aws-sdk/client-cloudcontrol";
|
|
10
10
|
import { PublishCommand, SNSClient } from "@aws-sdk/client-sns";
|
|
11
11
|
import { GetFunctionCommand, GetFunctionUrlConfigCommand, InvokeCommand, LambdaClient, UpdateFunctionConfigurationCommand, waitUntilFunctionActiveV2, waitUntilFunctionUpdatedV2 } from "@aws-sdk/client-lambda";
|
|
@@ -204,16 +204,36 @@ var AppExecutor = class {
|
|
|
204
204
|
|
|
205
205
|
//#endregion
|
|
206
206
|
//#region src/types/assembly.ts
|
|
207
|
-
|
|
208
|
-
|
|
207
|
+
const ASSEMBLY_REGION_PREFIX = /^[a-z0-9-]+/;
|
|
208
|
+
const CDK_ENVIRONMENT_PATTERN = /aws:\/\/([a-zA-Z0-9@._-]+)\/([a-z0-9-]+)/;
|
|
209
|
+
const CANONICAL_ASSEMBLY_REGION = /^[a-z0-9-]+$/;
|
|
210
|
+
function parseEnvironment(env, stackName) {
|
|
211
|
+
const match = env.match(/^aws:\/\/([^/]+)\/([\s\S]+)$/);
|
|
209
212
|
if (!match) return {
|
|
210
213
|
account: "unknown-account",
|
|
211
214
|
region: "unknown-region"
|
|
212
215
|
};
|
|
213
|
-
|
|
216
|
+
const raw = match[2];
|
|
217
|
+
const region = ASSEMBLY_REGION_PREFIX.exec(raw)?.[0];
|
|
218
|
+
if (region !== void 0) return {
|
|
214
219
|
account: match[1] === "unknown-account" ? "unknown-account" : match[1],
|
|
215
|
-
region
|
|
220
|
+
region
|
|
216
221
|
};
|
|
222
|
+
const cdk = CDK_ENVIRONMENT_PATTERN.exec(env);
|
|
223
|
+
if (cdk === null) throw new SynthesisError(unparsableRegionMessage(raw, stackName));
|
|
224
|
+
return {
|
|
225
|
+
account: cdk[1],
|
|
226
|
+
region: cdk[2]
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function unparsableRegionMessage(region, stackName) {
|
|
230
|
+
const subject = stackName === void 0 ? "The cloud assembly" : `Stack ${displayStackName(stackName)} in the cloud assembly`;
|
|
231
|
+
const lower = region.toLowerCase();
|
|
232
|
+
const caseOnly = CANONICAL_ASSEMBLY_REGION.test(lower);
|
|
233
|
+
const fix = caseOnly ? `Re-synthesize with the region spelled ${displayIdent(lower)}.` : "Re-synthesize with a canonical region, e.g. env: { region: 'us-east-1' }.";
|
|
234
|
+
const head = `${subject} declares region ${displayIdent(region)}, which is not a canonical AWS region spelling (lowercase letters, digits and '-'). The AWS CDK CLI refuses this assembly too ("Unable to parse environment specification"). ${fix}`;
|
|
235
|
+
if (!caseOnly) return head;
|
|
236
|
+
return `${head}\nIf an earlier cdkd version deployed this stack from such an assembly, it recorded the stack's state under that raw region spelling, and a deploy using the canonical spelling will not find that state: it starts the stack from no state at all. 'cdkd state show' and 'cdkd state orphan' still reach that record when '--stack-region' is given the exact raw spelling; 'state orphan' removes the record and leaves its AWS resources in place, unmanaged by cdkd.`;
|
|
217
237
|
}
|
|
218
238
|
|
|
219
239
|
//#endregion
|
|
@@ -606,7 +626,7 @@ var AssemblyReader = class {
|
|
|
606
626
|
}
|
|
607
627
|
if (dependencyNames.length > 0) this.logger.debug(`Stack ${displayStackName(stackName)} depends on: [${dependencyNames.map((n) => displaySafe(n)).join(", ")}]`);
|
|
608
628
|
let env;
|
|
609
|
-
if (artifact.environment) env = parseEnvironment(artifact.environment);
|
|
629
|
+
if (artifact.environment) env = parseEnvironment(artifact.environment, stackName);
|
|
610
630
|
const nestedTemplates = nullPrototypeRecord();
|
|
611
631
|
for (const [logicalId, resource] of Object.entries(template.Resources ?? {})) {
|
|
612
632
|
if (resource?.Type !== "AWS::CloudFormation::Stack") continue;
|
|
@@ -617,6 +637,7 @@ var AssemblyReader = class {
|
|
|
617
637
|
if (!resolvedNested.contained) throw new SynthesisError(`Stack ${displayStackName(stackName)} nested-stack ${displayIdent(logicalId)} has Metadata['aws:asset:path']=${displayAssemblyPath(assetPath)} which ${renderAssemblyPathEscape(resolvedNested, assemblyDir)}`);
|
|
618
638
|
nestedTemplates[logicalId] = resolvedNested.path;
|
|
619
639
|
}
|
|
640
|
+
const lookupRoleArn = props?.lookupRole?.arn;
|
|
620
641
|
return {
|
|
621
642
|
stackName,
|
|
622
643
|
displayName: artifact.displayName ?? stackName,
|
|
@@ -629,6 +650,7 @@ var AssemblyReader = class {
|
|
|
629
650
|
account: env?.account !== "unknown-account" ? env?.account : void 0,
|
|
630
651
|
...props?.terminationProtection !== void 0 && { terminationProtection: props.terminationProtection },
|
|
631
652
|
...Object.keys(nestedTemplates).length > 0 && { nestedTemplates },
|
|
653
|
+
...typeof lookupRoleArn === "string" && { lookupRoleArn },
|
|
632
654
|
messages: collectStackMessages(assemblyDir, artifact)
|
|
633
655
|
};
|
|
634
656
|
}
|
|
@@ -5000,7 +5022,7 @@ function isNotImplemented(error) {
|
|
|
5000
5022
|
const status = error?.$metadata?.httpStatusCode;
|
|
5001
5023
|
return name === "NotImplemented" || status === 501;
|
|
5002
5024
|
}
|
|
5003
|
-
function isAccessDenied(error) {
|
|
5025
|
+
function isAccessDenied$1(error) {
|
|
5004
5026
|
let current = error;
|
|
5005
5027
|
for (let depth = 0; depth < 5 && current !== null && typeof current === "object"; depth++) {
|
|
5006
5028
|
const name = current.name;
|
|
@@ -5028,7 +5050,7 @@ async function scanOtherPrefixesForStack(target, stackName, region) {
|
|
|
5028
5050
|
try {
|
|
5029
5051
|
segments = await target.listTopLevelPrefixes();
|
|
5030
5052
|
} catch (error) {
|
|
5031
|
-
return isAccessDenied(error) ? {
|
|
5053
|
+
return isAccessDenied$1(error) ? {
|
|
5032
5054
|
kind: "denied",
|
|
5033
5055
|
error,
|
|
5034
5056
|
stage: "list"
|
|
@@ -5051,7 +5073,7 @@ async function scanOtherPrefixesForStack(target, stackName, region) {
|
|
|
5051
5073
|
if (held === "holder") found.push(prefix);
|
|
5052
5074
|
else if (held === "empty") stale.push(prefix);
|
|
5053
5075
|
} catch (error) {
|
|
5054
|
-
if (isAccessDenied(error)) denied ??= error;
|
|
5076
|
+
if (isAccessDenied$1(error)) denied ??= error;
|
|
5055
5077
|
else failed ??= error;
|
|
5056
5078
|
}
|
|
5057
5079
|
}
|
|
@@ -5491,7 +5513,7 @@ var S3StateBackend = class S3StateBackend {
|
|
|
5491
5513
|
async verifyBucketExists(options = {}) {
|
|
5492
5514
|
await this.ensureClientForBucket();
|
|
5493
5515
|
if (options.existenceAlreadyProbed) {
|
|
5494
|
-
this.logger.debug(`Skipping the redundant HeadBucket on '${this.config.bucket}' — the default-name resolution already confirmed it exists with these credentials
|
|
5516
|
+
this.logger.debug(`Skipping the redundant HeadBucket on '${this.config.bucket}' — the default-name resolution already confirmed it exists with these credentials.`);
|
|
5495
5517
|
return;
|
|
5496
5518
|
}
|
|
5497
5519
|
try {
|
|
@@ -8385,6 +8407,23 @@ function deriveReadbackNeedles(bag, source, secrets, rules) {
|
|
|
8385
8407
|
};
|
|
8386
8408
|
}
|
|
8387
8409
|
|
|
8410
|
+
//#endregion
|
|
8411
|
+
//#region src/deployment/secret-redaction/physical-name-copies.ts
|
|
8412
|
+
const physicalNameCopiesOf = /* @__PURE__ */ new WeakMap();
|
|
8413
|
+
function recordPhysicalNameCopy(secrets, physicalId) {
|
|
8414
|
+
if (physicalId === "") return;
|
|
8415
|
+
sideSetOf(physicalNameCopiesOf, secrets).add(physicalId);
|
|
8416
|
+
}
|
|
8417
|
+
function physicalNameSpans(names, value) {
|
|
8418
|
+
const spans = [];
|
|
8419
|
+
if (names === void 0) return spans;
|
|
8420
|
+
for (const name of names) for (let at = value.indexOf(name); at !== -1; at = value.indexOf(name, at + 1)) spans.push({
|
|
8421
|
+
start: at,
|
|
8422
|
+
end: at + name.length
|
|
8423
|
+
});
|
|
8424
|
+
return spans;
|
|
8425
|
+
}
|
|
8426
|
+
|
|
8388
8427
|
//#endregion
|
|
8389
8428
|
//#region src/deployment/secret-redaction/redact-state.ts
|
|
8390
8429
|
function redactSecretsForState(bag, secrets, source, rules = TEMPLATE_DERIVED_RULES) {
|
|
@@ -8404,17 +8443,22 @@ function redactSecretsForStatePasses(bag, secrets, source, rules) {
|
|
|
8404
8443
|
}
|
|
8405
8444
|
const regex = buildNeedleRegex(substringNeedlesOf(secrets));
|
|
8406
8445
|
const wholeValueExpr = (s) => s === "" ? void 0 : secrets.get(s);
|
|
8446
|
+
const physicalNames = physicalNameCopiesOf.get(secrets);
|
|
8407
8447
|
let vouched;
|
|
8408
8448
|
const vouchedExpressions = () => vouched ??= new Set(secrets.values());
|
|
8409
8449
|
const scanLeaf = (value, needles) => {
|
|
8410
8450
|
const spans = isDynamicReferenceString(value) ? resolvableReferenceSpans(value, vouchedExpressions()) : [];
|
|
8451
|
+
const names = physicalNameSpans(physicalNames, value);
|
|
8411
8452
|
return value.replace(needles, (match, offset) => {
|
|
8412
8453
|
if (isStrictlyInsideASpan(spans, offset, offset + match.length)) return match;
|
|
8454
|
+
const end = offset + match.length;
|
|
8455
|
+
if (names.some((n) => n.start <= offset && end <= n.end)) return match;
|
|
8413
8456
|
return secrets.get(match) ?? "***";
|
|
8414
8457
|
});
|
|
8415
8458
|
};
|
|
8416
8459
|
const walk = (value) => {
|
|
8417
8460
|
if (typeof value === "string") {
|
|
8461
|
+
if (physicalNames?.has(value) === true) return value;
|
|
8418
8462
|
const whole = wholeValueExpr(value);
|
|
8419
8463
|
if (whole !== void 0) return whole;
|
|
8420
8464
|
if (isSingleDynamicReferenceToken(value) && spanNamesResolvableService(value)) return value;
|
|
@@ -8447,6 +8491,133 @@ function scrubResourceRecord(record, secrets, sourceProperties, observedRules) {
|
|
|
8447
8491
|
if (record.observedProperties) next.observedProperties = redactSecretsForState(record.observedProperties, secrets, sourceProperties ?? next.properties, observedRules ?? (sourceProperties !== void 0 ? TEMPLATE_SOURCED_RULES : secrets.size === 0 && isSameGenerationBag(record.observedProperties) ? STATE_SOURCED_BASELINE_RULES : STATE_SOURCED_READBACK_RULES));
|
|
8448
8492
|
return next;
|
|
8449
8493
|
}
|
|
8494
|
+
function withoutPhysicalNameCopies(secrets, run) {
|
|
8495
|
+
const names = physicalNameCopiesOf.get(secrets);
|
|
8496
|
+
if (names === void 0) return run();
|
|
8497
|
+
physicalNameCopiesOf.delete(secrets);
|
|
8498
|
+
try {
|
|
8499
|
+
return run();
|
|
8500
|
+
} finally {
|
|
8501
|
+
physicalNameCopiesOf.set(secrets, names);
|
|
8502
|
+
}
|
|
8503
|
+
}
|
|
8504
|
+
function withPhysicalNameCopies(secrets, ids, run) {
|
|
8505
|
+
const previous = physicalNameCopiesOf.get(secrets);
|
|
8506
|
+
const names = new Set(previous);
|
|
8507
|
+
for (const id of ids) if (id !== "") names.add(id);
|
|
8508
|
+
physicalNameCopiesOf.set(secrets, names);
|
|
8509
|
+
try {
|
|
8510
|
+
return run();
|
|
8511
|
+
} finally {
|
|
8512
|
+
if (previous === void 0) physicalNameCopiesOf.delete(secrets);
|
|
8513
|
+
else physicalNameCopiesOf.set(secrets, previous);
|
|
8514
|
+
}
|
|
8515
|
+
}
|
|
8516
|
+
function repairPhysicalNameCopyLeaves(stored, legacy, current) {
|
|
8517
|
+
if (typeof stored === "string") return typeof legacy === "string" && typeof current === "string" && stored === legacy && current !== legacy ? current : stored;
|
|
8518
|
+
if (Array.isArray(stored)) {
|
|
8519
|
+
if (!Array.isArray(legacy) || !Array.isArray(current)) return stored;
|
|
8520
|
+
if (legacy.length !== stored.length || current.length !== stored.length) return stored;
|
|
8521
|
+
let changed = false;
|
|
8522
|
+
const out = stored.map((item, i) => {
|
|
8523
|
+
const next = repairPhysicalNameCopyLeaves(item, legacy[i], current[i]);
|
|
8524
|
+
if (next !== item) changed = true;
|
|
8525
|
+
return next;
|
|
8526
|
+
});
|
|
8527
|
+
return changed ? out : stored;
|
|
8528
|
+
}
|
|
8529
|
+
if (stored !== null && typeof stored === "object") {
|
|
8530
|
+
if (legacy === null || typeof legacy !== "object" || Array.isArray(legacy)) return stored;
|
|
8531
|
+
if (current === null || typeof current !== "object" || Array.isArray(current)) return stored;
|
|
8532
|
+
const l = legacy;
|
|
8533
|
+
const c = current;
|
|
8534
|
+
let out;
|
|
8535
|
+
for (const [key, value] of Object.entries(stored)) {
|
|
8536
|
+
if (!Object.hasOwn(l, key) || !Object.hasOwn(c, key)) continue;
|
|
8537
|
+
const next = repairPhysicalNameCopyLeaves(value, l[key], c[key]);
|
|
8538
|
+
if (next !== value) {
|
|
8539
|
+
out ??= { ...stored };
|
|
8540
|
+
out[key] = next;
|
|
8541
|
+
}
|
|
8542
|
+
}
|
|
8543
|
+
return out ?? stored;
|
|
8544
|
+
}
|
|
8545
|
+
return stored;
|
|
8546
|
+
}
|
|
8547
|
+
function embeddedPhysicalNameRewrites(secrets, legacySecrets = secrets) {
|
|
8548
|
+
const names = physicalNameCopiesOf.get(secrets);
|
|
8549
|
+
if (names === void 0) return [];
|
|
8550
|
+
const pairs = [];
|
|
8551
|
+
withoutPhysicalNameCopies(legacySecrets, () => {
|
|
8552
|
+
for (const name of names) {
|
|
8553
|
+
const legacy = redactSecretsForState(name, legacySecrets);
|
|
8554
|
+
if (typeof legacy === "string" && legacy !== name && legacy.includes("{{resolve:") && !isSingleDynamicReferenceToken(legacy) && !legacy.includes("***")) pairs.push([legacy, name]);
|
|
8555
|
+
}
|
|
8556
|
+
});
|
|
8557
|
+
return pairs;
|
|
8558
|
+
}
|
|
8559
|
+
function replaceEmbeddedPhysicalNameRewrites(value, pairs) {
|
|
8560
|
+
if (pairs.length === 0) return value;
|
|
8561
|
+
if (typeof value === "string") {
|
|
8562
|
+
let next = value;
|
|
8563
|
+
for (const [legacy, name] of pairs) next = next.split(legacy).join(name);
|
|
8564
|
+
return next;
|
|
8565
|
+
}
|
|
8566
|
+
if (Array.isArray(value)) {
|
|
8567
|
+
let changed = false;
|
|
8568
|
+
const out = value.map((item) => {
|
|
8569
|
+
const next = replaceEmbeddedPhysicalNameRewrites(item, pairs);
|
|
8570
|
+
if (next !== item) changed = true;
|
|
8571
|
+
return next;
|
|
8572
|
+
});
|
|
8573
|
+
return changed ? out : value;
|
|
8574
|
+
}
|
|
8575
|
+
if (value !== null && typeof value === "object") {
|
|
8576
|
+
let out;
|
|
8577
|
+
for (const [key, item] of Object.entries(value)) {
|
|
8578
|
+
const next = replaceEmbeddedPhysicalNameRewrites(item, pairs);
|
|
8579
|
+
if (next !== item) {
|
|
8580
|
+
out ??= { ...value };
|
|
8581
|
+
out[key] = next;
|
|
8582
|
+
}
|
|
8583
|
+
}
|
|
8584
|
+
return out ?? value;
|
|
8585
|
+
}
|
|
8586
|
+
return value;
|
|
8587
|
+
}
|
|
8588
|
+
function repairEmbeddedPhysicalNamesTowards(stored, target, pairs) {
|
|
8589
|
+
if (pairs.length === 0) return stored;
|
|
8590
|
+
if (typeof stored === "string") {
|
|
8591
|
+
if (typeof target !== "string" || stored === target) return stored;
|
|
8592
|
+
const repaired = replaceEmbeddedPhysicalNameRewrites(stored, pairs);
|
|
8593
|
+
return repaired === target ? repaired : stored;
|
|
8594
|
+
}
|
|
8595
|
+
if (Array.isArray(stored)) {
|
|
8596
|
+
if (!Array.isArray(target) || target.length !== stored.length) return stored;
|
|
8597
|
+
let changed = false;
|
|
8598
|
+
const out = stored.map((item, i) => {
|
|
8599
|
+
const next = repairEmbeddedPhysicalNamesTowards(item, target[i], pairs);
|
|
8600
|
+
if (next !== item) changed = true;
|
|
8601
|
+
return next;
|
|
8602
|
+
});
|
|
8603
|
+
return changed ? out : stored;
|
|
8604
|
+
}
|
|
8605
|
+
if (stored !== null && typeof stored === "object") {
|
|
8606
|
+
if (target === null || typeof target !== "object" || Array.isArray(target)) return stored;
|
|
8607
|
+
const t = target;
|
|
8608
|
+
let out;
|
|
8609
|
+
for (const [key, value] of Object.entries(stored)) {
|
|
8610
|
+
if (!Object.hasOwn(t, key)) continue;
|
|
8611
|
+
const next = repairEmbeddedPhysicalNamesTowards(value, t[key], pairs);
|
|
8612
|
+
if (next !== value) {
|
|
8613
|
+
out ??= { ...stored };
|
|
8614
|
+
out[key] = next;
|
|
8615
|
+
}
|
|
8616
|
+
}
|
|
8617
|
+
return out ?? stored;
|
|
8618
|
+
}
|
|
8619
|
+
return stored;
|
|
8620
|
+
}
|
|
8450
8621
|
|
|
8451
8622
|
//#endregion
|
|
8452
8623
|
//#region src/deployment/secret-redaction/positions.ts
|
|
@@ -13794,7 +13965,7 @@ const PROPERTY_COVERAGE_BY_TYPE = /* @__PURE__ */ new Map([
|
|
|
13794
13965
|
["Description", "CFn-only informational — no semantic effect on the recursive deploy"],
|
|
13795
13966
|
["DisableRollback", "CFn-only — cdkd controls rollback via the top-level deploy-engine --no-rollback flag, not per nested stack"],
|
|
13796
13967
|
["EnableTerminationProtection", "CFn-only per-nested-stack flag — cdkd records stack-level terminationProtection at CDK synth time (parent only) and `cdkd destroy` consults that for refusal"],
|
|
13797
|
-
["NotificationARNs", "CFn-only SNS-on-stack-event surface — cdkd has no equivalent
|
|
13968
|
+
["NotificationARNs", "CFn-only SNS-on-stack-event surface — cdkd has no equivalent"],
|
|
13798
13969
|
["RoleARN", "CFn-only role-assumption — cdkd uses the caller credentials directly, no per-resource role assumption"],
|
|
13799
13970
|
["StackName", "cdkd derives the child stack name as `<parent>~<logicalId>` per design §3 (state-key uniqueness); a user-provided StackName has no effect"],
|
|
13800
13971
|
["StackPolicyBody", "CFn-only stack-update policy — cdkd has no equivalent (per-resource diff replaces stack-level policy)"],
|
|
@@ -13802,7 +13973,7 @@ const PROPERTY_COVERAGE_BY_TYPE = /* @__PURE__ */ new Map([
|
|
|
13802
13973
|
["StackStatusReason", "CFn-only read-only output — never a real input property"],
|
|
13803
13974
|
["Tags", "CFn-only — cdkd does not tag the synthesized \"stack\" (the parent's synthesized ARN is a cdkd-local placeholder, not a real AWS resource)"],
|
|
13804
13975
|
["TemplateBody", "CFn-only inline template — cdkd reads the child template from the synth output via Metadata['aws:asset:path'] instead of accepting it inline"],
|
|
13805
|
-
["TimeoutInMinutes", "CFn-only stack-create deadline — cdkd uses per-resource --resource-timeout instead
|
|
13976
|
+
["TimeoutInMinutes", "CFn-only stack-create deadline — cdkd uses per-resource --resource-timeout instead"]
|
|
13806
13977
|
]),
|
|
13807
13978
|
createOnlyDrops: /* @__PURE__ */ new Set(["StackName"]),
|
|
13808
13979
|
readOnly: /* @__PURE__ */ new Set([
|
|
@@ -14578,7 +14749,7 @@ const PROPERTY_COVERAGE_BY_TYPE = /* @__PURE__ */ new Map([
|
|
|
14578
14749
|
"PosixUser",
|
|
14579
14750
|
"RootDirectory"
|
|
14580
14751
|
]),
|
|
14581
|
-
silentDrop: /* @__PURE__ */ new Map([["ClientToken", "cdkd supplies its own retry-stable ClientToken on CreateAccessPoint
|
|
14752
|
+
silentDrop: /* @__PURE__ */ new Map([["ClientToken", "cdkd supplies its own retry-stable ClientToken on CreateAccessPoint, so a template-supplied value would be overwritten. The SDK does not own this field either: an omitted ClientToken is auto-filled with a FRESH uuid per attempt, which leaves a retried 500 free to mint a second access point, while a caller-supplied value goes on the wire verbatim."]]),
|
|
14582
14753
|
createOnlyDrops: /* @__PURE__ */ new Set(["ClientToken"]),
|
|
14583
14754
|
readOnly: /* @__PURE__ */ new Set(["AccessPointId", "Arn"]),
|
|
14584
14755
|
ccRouteUnavailable: false
|
|
@@ -14598,7 +14769,7 @@ const PROPERTY_COVERAGE_BY_TYPE = /* @__PURE__ */ new Map([
|
|
|
14598
14769
|
"ProvisionedThroughputInMibps",
|
|
14599
14770
|
"ThroughputMode"
|
|
14600
14771
|
]),
|
|
14601
|
-
silentDrop: /* @__PURE__ */ new Map([["ReplicationConfiguration", "Cross-region EFS replication (CreateReplicationConfiguration) provisions a separate destination file system in another region with its own lifecycle, KMS key, and availability-zone placement; replicating + then tearing down the destination on destroy is a multi-resource, cross-region orchestration that is out of scope for the single-resource SDK provider.
|
|
14772
|
+
silentDrop: /* @__PURE__ */ new Map([["ReplicationConfiguration", "Cross-region EFS replication (CreateReplicationConfiguration) provisions a separate destination file system in another region with its own lifecycle, KMS key, and availability-zone placement; replicating + then tearing down the destination on destroy is a multi-resource, cross-region orchestration that is out of scope for the single-resource SDK provider."]]),
|
|
14602
14773
|
createOnlyDrops: /* @__PURE__ */ new Set(),
|
|
14603
14774
|
readOnly: /* @__PURE__ */ new Set(["Arn", "FileSystemId"]),
|
|
14604
14775
|
ccRouteUnavailable: false
|
|
@@ -15528,7 +15699,7 @@ const PROPERTY_COVERAGE_BY_TYPE = /* @__PURE__ */ new Map([
|
|
|
15528
15699
|
["DBClusterParameterGroupName", "not yet implemented by cdkd"],
|
|
15529
15700
|
["DBInstanceParameterGroupName", "not yet implemented by cdkd"],
|
|
15530
15701
|
["DBSystemId", "not yet implemented by cdkd"],
|
|
15531
|
-
["DeleteAutomatedBackups", "delete-time flag, sent on the delete when the record carries it
|
|
15702
|
+
["DeleteAutomatedBackups", "delete-time flag, sent on the delete when the record carries it; kept unhandled so a template declaring it keeps its Cloud Control route. Final snapshots are governed by DeletionPolicy: Snapshot, which this flag does not control"],
|
|
15532
15703
|
["Domain", "not yet implemented by cdkd"],
|
|
15533
15704
|
["DomainIAMRoleName", "not yet implemented by cdkd"],
|
|
15534
15705
|
["EnableCloudwatchLogsExports", "not yet implemented by cdkd"],
|
|
@@ -15628,7 +15799,7 @@ const PROPERTY_COVERAGE_BY_TYPE = /* @__PURE__ */ new Map([
|
|
|
15628
15799
|
["DBSnapshotIdentifier", "not yet implemented by cdkd"],
|
|
15629
15800
|
["DBSystemId", "not yet implemented by cdkd"],
|
|
15630
15801
|
["DedicatedLogVolume", "not yet implemented by cdkd"],
|
|
15631
|
-
["DeleteAutomatedBackups", "delete-time flag, sent on the delete when the record carries it
|
|
15802
|
+
["DeleteAutomatedBackups", "delete-time flag, sent on the delete when the record carries it; kept unhandled so a template declaring it keeps its Cloud Control route. Final snapshots are governed by DeletionPolicy: Snapshot, which this flag does not control"],
|
|
15632
15803
|
["Domain", "not yet implemented by cdkd"],
|
|
15633
15804
|
["DomainAuthSecretArn", "not yet implemented by cdkd"],
|
|
15634
15805
|
["DomainDnsIps", "not yet implemented by cdkd"],
|
|
@@ -17430,6 +17601,15 @@ function conditionsAssumedFalse(conditions) {
|
|
|
17430
17601
|
return assumedConditionNames.get(conditions) ?? /* @__PURE__ */ new Set();
|
|
17431
17602
|
}
|
|
17432
17603
|
|
|
17604
|
+
//#endregion
|
|
17605
|
+
//#region src/deployment/noecho-declared.ts
|
|
17606
|
+
function isNoEchoDeclared(noEcho) {
|
|
17607
|
+
return noEcho === true || noEcho === "true";
|
|
17608
|
+
}
|
|
17609
|
+
function declaresNoEcho(definition) {
|
|
17610
|
+
return definition !== null && typeof definition === "object" && isNoEchoDeclared(definition.NoEcho);
|
|
17611
|
+
}
|
|
17612
|
+
|
|
17433
17613
|
//#endregion
|
|
17434
17614
|
//#region src/deployment/masked-property-fingerprints.ts
|
|
17435
17615
|
const FINGERPRINT_LAYOUT$1 = 1;
|
|
@@ -17506,7 +17686,7 @@ function parameterInputsFor(args) {
|
|
|
17506
17686
|
continue;
|
|
17507
17687
|
}
|
|
17508
17688
|
const value = values[name];
|
|
17509
|
-
let secret = isPlainObject$2(definition) && (definition
|
|
17689
|
+
let secret = isPlainObject$2(definition) && declaresNoEcho(definition) || carriesSecretValue(value, []);
|
|
17510
17690
|
if (!secret && args.nestedChild === true && args.supplied !== void 0) {
|
|
17511
17691
|
if (Object.hasOwn(args.supplied, name)) {
|
|
17512
17692
|
const passed = args.passedClasses?.get(name);
|
|
@@ -17931,7 +18111,7 @@ function childParameterInputs(template, passed) {
|
|
|
17931
18111
|
const inputs = /* @__PURE__ */ new Map();
|
|
17932
18112
|
for (const name of Object.keys(declared)) {
|
|
17933
18113
|
const definition = declared[name];
|
|
17934
|
-
if (isPlainObject$2(definition) && (definition
|
|
18114
|
+
if (isPlainObject$2(definition) && declaresNoEcho(definition)) inputs.set(name, { kind: "secret" });
|
|
17935
18115
|
else if (passed.has(name)) {
|
|
17936
18116
|
const passedClass = passed.get(name);
|
|
17937
18117
|
inputs.set(name, passedClass === "clean" ? {
|
|
@@ -18940,7 +19120,7 @@ function inheritedSecretsCarriedBy(value, inherited) {
|
|
|
18940
19120
|
return carried;
|
|
18941
19121
|
}
|
|
18942
19122
|
function stringifyParameterForLog(paramDef, value) {
|
|
18943
|
-
if (paramDef
|
|
19123
|
+
if (declaresNoEcho(paramDef)) return "<redacted>";
|
|
18944
19124
|
return stringifyValue(value);
|
|
18945
19125
|
}
|
|
18946
19126
|
function boundAltered(original, shown, maxCodePoints) {
|
|
@@ -19561,7 +19741,7 @@ async function resolveGetAtt(getAtt, context) {
|
|
|
19561
19741
|
redacted: isSensitiveAttributeName(attributeName)
|
|
19562
19742
|
})}`);
|
|
19563
19743
|
if (reresolvesNestedReference) return await this.reresolveCrossStackValue(flatValue, nestedStackChildRegionFromLocalArn(resource.physicalId), context, `nested stack ${logicalId} ${this.displayLeaf(attributeName, context)}`, crossStackSourceKey({ "Fn::GetAtt": getAtt }));
|
|
19564
|
-
return notedFlatValue;
|
|
19744
|
+
return await this.reresolveRecordedReferences(notedFlatValue, logicalId, context, `${logicalId}.${this.displayLeaf(attributeName, context)}`, crossStackSourceKey({ "Fn::GetAtt": getAtt }));
|
|
19565
19745
|
}
|
|
19566
19746
|
if (attributeName.includes(".")) {
|
|
19567
19747
|
const parts = attributeName.split(".");
|
|
@@ -19577,7 +19757,7 @@ async function resolveGetAtt(getAtt, context) {
|
|
|
19577
19757
|
structured: isStructured(cursor),
|
|
19578
19758
|
redacted: isSensitiveAttributeName(attributeName)
|
|
19579
19759
|
})}`);
|
|
19580
|
-
return notedCursor;
|
|
19760
|
+
return await this.reresolveRecordedReferences(notedCursor, logicalId, context, `${logicalId}.${this.displayLeaf(attributeName, context)}`, crossStackSourceKey({ "Fn::GetAtt": getAtt }));
|
|
19581
19761
|
}
|
|
19582
19762
|
}
|
|
19583
19763
|
}
|
|
@@ -19649,9 +19829,73 @@ function recordSecretNamedRead(logicalId, value, context) {
|
|
|
19649
19829
|
break;
|
|
19650
19830
|
}
|
|
19651
19831
|
}
|
|
19832
|
+
async function reresolveRecordedReferences(value, logicalId, context, origin, sourceKey) {
|
|
19833
|
+
if (!carriesDynamicReference(value) || carriesSecretMask(value)) return value;
|
|
19834
|
+
if (context.recordedSecretValues === void 0) return value;
|
|
19835
|
+
if (!referencesProvenLocal(value, logicalId, context)) {
|
|
19836
|
+
this.logger.debug(safeMsg`Not re-resolving the reference(s) in ${this.displayMasked(origin, context)}: the template of ${this.displayMasked(logicalId, context)} does not spell them, so the region that resolved them is unknown`);
|
|
19837
|
+
return value;
|
|
19838
|
+
}
|
|
19839
|
+
if (!await this.referencesMatchPhysicalId(value, logicalId, context)) {
|
|
19840
|
+
this.logger.warn(safeMsg`Serving ${this.displayMasked(origin, context)} as recorded, with its {{resolve:...}} reference(s) unresolved: cdkd cannot confirm the secret still holds the value ${this.displayMasked(logicalId, context)} was deployed with, because its physical id does not contain it. Either the secret changed since then (update ${this.displayMasked(logicalId, context)} so cdkd deploys it with the new value), or the value is not part of its name (a custom resource's Data, a name shorter than four characters), and cdkd sends the recorded reference as before.`);
|
|
19841
|
+
return value;
|
|
19842
|
+
}
|
|
19843
|
+
return await this.reresolveCrossStackValue(value, void 0, context, origin, sourceKey);
|
|
19844
|
+
}
|
|
19845
|
+
async function referencesMatchPhysicalId(value, logicalId, context) {
|
|
19846
|
+
const record = Object.hasOwn(context.resources, logicalId) ? context.resources[logicalId] : void 0;
|
|
19847
|
+
const physicalId = typeof record?.physicalId === "string" ? record.physicalId.toLowerCase() : "";
|
|
19848
|
+
for (const leaf of wholeStringLeavesOf(value)) for (const token of dynamicReferenceTokens(leaf)) {
|
|
19849
|
+
const plaintext = await this.resolveDynamicReferences(token, context);
|
|
19850
|
+
if (plaintext.includes("{{resolve:")) continue;
|
|
19851
|
+
if (plaintext.length < 4) return false;
|
|
19852
|
+
if (!physicalId.includes(plaintext.toLowerCase())) return false;
|
|
19853
|
+
}
|
|
19854
|
+
return true;
|
|
19855
|
+
}
|
|
19856
|
+
function readsAnotherStack(node) {
|
|
19857
|
+
if (Object.hasOwn(node, "Fn::ImportValue") || Object.hasOwn(node, "Fn::GetStackOutput")) return true;
|
|
19858
|
+
if (!Object.hasOwn(node, "Fn::GetAtt")) return false;
|
|
19859
|
+
const getAtt = node["Fn::GetAtt"];
|
|
19860
|
+
const attributeName = typeof getAtt === "string" ? splitGetAttStringForm(getAtt)?.attributeName : Array.isArray(getAtt) ? getAtt[1] : void 0;
|
|
19861
|
+
return typeof attributeName !== "string" || attributeName.startsWith("Outputs.");
|
|
19862
|
+
}
|
|
19863
|
+
function takenIfBranch(node, context) {
|
|
19864
|
+
if (Array.isArray(node) || !Object.hasOwn(node, "Fn::If")) return void 0;
|
|
19865
|
+
const args = node["Fn::If"];
|
|
19866
|
+
if (!Array.isArray(args) || args.length !== 3 || typeof args[0] !== "string") return void 0;
|
|
19867
|
+
const conditions = context.conditions;
|
|
19868
|
+
if (conditions === void 0 || !Object.hasOwn(conditions, args[0])) return void 0;
|
|
19869
|
+
const verdict = conditions[args[0]];
|
|
19870
|
+
if (typeof verdict !== "boolean") return void 0;
|
|
19871
|
+
return { branch: verdict ? args[1] : args[2] };
|
|
19872
|
+
}
|
|
19873
|
+
function referencesProvenLocal(value, logicalId, context) {
|
|
19874
|
+
const resources = context.template.Resources ?? {};
|
|
19875
|
+
if (!Object.hasOwn(resources, logicalId)) return false;
|
|
19876
|
+
const literals = [];
|
|
19877
|
+
let crossStack = false;
|
|
19878
|
+
const walk = (node) => {
|
|
19879
|
+
if (typeof node === "string") {
|
|
19880
|
+
if (node.includes("{{resolve:")) literals.push(node);
|
|
19881
|
+
return;
|
|
19882
|
+
}
|
|
19883
|
+
if (node === null || typeof node !== "object") return;
|
|
19884
|
+
if (!Array.isArray(node) && readsAnotherStack(node)) crossStack = true;
|
|
19885
|
+
const taken = takenIfBranch(node, context);
|
|
19886
|
+
if (taken !== void 0) {
|
|
19887
|
+
walk(taken.branch);
|
|
19888
|
+
return;
|
|
19889
|
+
}
|
|
19890
|
+
for (const child of Object.values(node)) walk(child);
|
|
19891
|
+
};
|
|
19892
|
+
walk(resources[logicalId]?.Properties);
|
|
19893
|
+
if (crossStack) return false;
|
|
19894
|
+
return [...wholeStringLeavesOf(value)].every((leaf) => dynamicReferenceTokens(leaf).every((token) => literals.some((l) => l.includes(token))));
|
|
19895
|
+
}
|
|
19652
19896
|
function rejectPlaceholderArnAttribute(resource, attributeName, value, logicalId, context, healOutcome) {
|
|
19653
19897
|
if (!isStalePlaceholderArnAttribute(resource.resourceType, attributeName, value)) return;
|
|
19654
|
-
throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${this.displayMasked(logicalId, context)}, ${this.displayMasked(attributeName, context)}] for ${resource.resourceType}: the recorded value ${quotedRender(stringifyValue(this.maskValueLeaves(value, context)), "\"")} is a placeholder written by
|
|
19898
|
+
throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${this.displayMasked(logicalId, context)}, ${this.displayMasked(attributeName, context)}] for ${resource.resourceType}: the recorded value ${quotedRender(stringifyValue(this.maskValueLeaves(value, context)), "\"")} is a placeholder written by an older cdkd version — its region and account fields are literal wildcards, so it is not a usable ARN. ` + this.staleRecordRemedy(healOutcome, context)));
|
|
19655
19899
|
}
|
|
19656
19900
|
async function constructAttribute(resource, attributeName, context, logicalId, accountInfo) {
|
|
19657
19901
|
const { resourceType, physicalId } = resource;
|
|
@@ -20051,6 +20295,345 @@ function getCfnClient(rawRegion) {
|
|
|
20051
20295
|
return client;
|
|
20052
20296
|
}
|
|
20053
20297
|
|
|
20298
|
+
//#endregion
|
|
20299
|
+
//#region src/utils/caller-credentials.ts
|
|
20300
|
+
const AWS_CREDENTIAL_ENV_KEYS = [
|
|
20301
|
+
"AWS_ACCESS_KEY_ID",
|
|
20302
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
20303
|
+
"AWS_SESSION_TOKEN"
|
|
20304
|
+
];
|
|
20305
|
+
function readEnvCredentials(source = process.env) {
|
|
20306
|
+
const accessKeyId = source["AWS_ACCESS_KEY_ID"];
|
|
20307
|
+
const secretAccessKey = source["AWS_SECRET_ACCESS_KEY"];
|
|
20308
|
+
if (!accessKeyId || !secretAccessKey) return void 0;
|
|
20309
|
+
const sessionToken = source["AWS_SESSION_TOKEN"];
|
|
20310
|
+
return {
|
|
20311
|
+
accessKeyId,
|
|
20312
|
+
secretAccessKey,
|
|
20313
|
+
...sessionToken ? { sessionToken } : {}
|
|
20314
|
+
};
|
|
20315
|
+
}
|
|
20316
|
+
function callerEnvCredentials() {
|
|
20317
|
+
if (getAssumedRoleCredentials() === void 0) return readEnvCredentials();
|
|
20318
|
+
return getPreAssumeEnvCredentials();
|
|
20319
|
+
}
|
|
20320
|
+
function envCredentialsAreAssumedRole() {
|
|
20321
|
+
return getAssumedRoleCredentials() !== void 0;
|
|
20322
|
+
}
|
|
20323
|
+
function applyCallerIdentityCredentials(env) {
|
|
20324
|
+
if (getAssumedRoleCredentials() === void 0) return;
|
|
20325
|
+
const caller = getPreAssumeEnvCredentials();
|
|
20326
|
+
if (!caller) {
|
|
20327
|
+
for (const key of AWS_CREDENTIAL_ENV_KEYS) delete env[key];
|
|
20328
|
+
return;
|
|
20329
|
+
}
|
|
20330
|
+
env["AWS_ACCESS_KEY_ID"] = caller.accessKeyId;
|
|
20331
|
+
env["AWS_SECRET_ACCESS_KEY"] = caller.secretAccessKey;
|
|
20332
|
+
if (caller.sessionToken) env["AWS_SESSION_TOKEN"] = caller.sessionToken;
|
|
20333
|
+
else delete env["AWS_SESSION_TOKEN"];
|
|
20334
|
+
}
|
|
20335
|
+
|
|
20336
|
+
//#endregion
|
|
20337
|
+
//#region src/utils/role-arn.ts
|
|
20338
|
+
const crossAccountCredentialsCache = /* @__PURE__ */ new Map();
|
|
20339
|
+
const CRED_EXPIRY_SAFETY_MS = 6e4;
|
|
20340
|
+
const IAM_ROLE_ARN_RE = /^arn:(aws[a-z0-9-]*):iam::(\d{12}):role\/[\w+=,.@-]+(?:\/[\w+=,.@-]+)*$/;
|
|
20341
|
+
const IAM_ROLE_ARN_MAX_LENGTH = 2048;
|
|
20342
|
+
const IAM_ROLE_ARN_SHAPE_RE = /^arn:[A-Za-z0-9-]+:iam::[0-9]+:role\/[!-~]+$/;
|
|
20343
|
+
function isIamRoleArn(value) {
|
|
20344
|
+
if (typeof value !== "string") return false;
|
|
20345
|
+
if (value.length > 2048) return false;
|
|
20346
|
+
return IAM_ROLE_ARN_SHAPE_RE.test(value);
|
|
20347
|
+
}
|
|
20348
|
+
function refusedRoleArnMessage(value) {
|
|
20349
|
+
return `AssumeRole refused: the role ARN is not a well-formed IAM role ARN (expected arn:<partition>:iam::<account>:role/<name>, at most ${IAM_ROLE_ARN_MAX_LENGTH} characters): ${shownRefusedValue(value)}. Nothing was sent to STS.`;
|
|
20350
|
+
}
|
|
20351
|
+
function shownRefusedValue(value) {
|
|
20352
|
+
if (value === "") return "(empty)";
|
|
20353
|
+
return displayIdent(value, { maxCodePoints: 613 });
|
|
20354
|
+
}
|
|
20355
|
+
function assertSendableRoleArn(roleArn, makeError = (message) => new Error(message)) {
|
|
20356
|
+
if (typeof roleArn !== "string" || !isIamRoleArn(roleArn)) throw makeError(refusedRoleArnMessage(roleArn));
|
|
20357
|
+
}
|
|
20358
|
+
function sendableAssumeRoleCommand(input, makeError) {
|
|
20359
|
+
assertSendableRoleArn(input.RoleArn, makeError);
|
|
20360
|
+
return new AssumeRoleCommand(input);
|
|
20361
|
+
}
|
|
20362
|
+
function explicitRoleArnOrThrow(flag, raw, makeError = (message) => new Error(message)) {
|
|
20363
|
+
const trimmed = raw.trim();
|
|
20364
|
+
if (!isIamRoleArn(trimmed)) throw makeError(`Invalid ${flag} value: ${refusedRoleArnMessage(raw)}`);
|
|
20365
|
+
return trimmed;
|
|
20366
|
+
}
|
|
20367
|
+
function assertFlagRoleArn(flag, value, makeError = (message) => new Error(message)) {
|
|
20368
|
+
assertSendableRoleArn(value, (message) => makeError(`Invalid ${flag} value: ${message}`));
|
|
20369
|
+
}
|
|
20370
|
+
function parseIamRoleArn(roleArn) {
|
|
20371
|
+
if (roleArn.length > 2048) return null;
|
|
20372
|
+
const match = IAM_ROLE_ARN_RE.exec(roleArn);
|
|
20373
|
+
if (!match || !match[1] || !match[2]) return null;
|
|
20374
|
+
return {
|
|
20375
|
+
partition: match[1],
|
|
20376
|
+
accountId: match[2]
|
|
20377
|
+
};
|
|
20378
|
+
}
|
|
20379
|
+
async function assumeRoleForCrossAccountStateRead(roleArn) {
|
|
20380
|
+
const sourceConfig = ambientCredentialConfig();
|
|
20381
|
+
const cacheKey = JSON.stringify([credentialFingerprint(sourceConfig), roleArn]);
|
|
20382
|
+
const cached = crossAccountCredentialsCache.get(cacheKey);
|
|
20383
|
+
if (cached) {
|
|
20384
|
+
const cachedCreds = await cached;
|
|
20385
|
+
if (!cachedCreds.expiration || Date.now() < cachedCreds.expiration.getTime() - CRED_EXPIRY_SAFETY_MS) return cachedCreds;
|
|
20386
|
+
crossAccountCredentialsCache.delete(cacheKey);
|
|
20387
|
+
}
|
|
20388
|
+
const promise = (async () => {
|
|
20389
|
+
const logger = getLogger().child("role-arn");
|
|
20390
|
+
const displayRoleArn = displayIdent(roleArn, { maxCodePoints: 613 });
|
|
20391
|
+
logger.debug(`Assuming role for cross-account state read: ${displayRoleArn}`);
|
|
20392
|
+
assertSendableRoleArn(roleArn);
|
|
20393
|
+
const sts = new STSClient({ ...clientDefaultsFor(sourceConfig) });
|
|
20394
|
+
try {
|
|
20395
|
+
let response;
|
|
20396
|
+
try {
|
|
20397
|
+
response = await sts.send(sendableAssumeRoleCommand({
|
|
20398
|
+
RoleArn: roleArn,
|
|
20399
|
+
RoleSessionName: `cdkd-xacc-${Date.now()}`,
|
|
20400
|
+
DurationSeconds: 3600
|
|
20401
|
+
}));
|
|
20402
|
+
} catch (err) {
|
|
20403
|
+
const message = displayAwsMessage(err instanceof Error ? err.message : String(err));
|
|
20404
|
+
throw new Error(`AssumeRole into ${displayRoleArn} failed: ${message}. If this is a trust-policy issue, the producer's role must allow sts:AssumeRole from the consumer's principal. See https://cdkd.dev/cross-stack-internals/#iam-permissions for the trust-policy setup.`, { cause: err instanceof Error ? err : void 0 });
|
|
20405
|
+
}
|
|
20406
|
+
if (!response.Credentials) throw new Error(`AssumeRole for cross-account Fn::GetStackOutput returned no credentials (RoleArn=${displayRoleArn})`);
|
|
20407
|
+
const { AccessKeyId, SecretAccessKey, SessionToken, Expiration } = response.Credentials;
|
|
20408
|
+
if (!AccessKeyId || !SecretAccessKey || !SessionToken) throw new Error(`AssumeRole response missing required credentials fields for cross-account state read (RoleArn=${displayRoleArn})`);
|
|
20409
|
+
logger.info(`Assumed role for cross-account state read: ${displayRoleArn} (session expires ${Expiration?.toISOString() ?? "unknown"})`);
|
|
20410
|
+
return {
|
|
20411
|
+
accessKeyId: AccessKeyId,
|
|
20412
|
+
secretAccessKey: SecretAccessKey,
|
|
20413
|
+
sessionToken: SessionToken,
|
|
20414
|
+
...Expiration && { expiration: Expiration }
|
|
20415
|
+
};
|
|
20416
|
+
} finally {
|
|
20417
|
+
sts.destroy();
|
|
20418
|
+
}
|
|
20419
|
+
})().catch((err) => {
|
|
20420
|
+
if (crossAccountCredentialsCache.get(cacheKey) === promise) crossAccountCredentialsCache.delete(cacheKey);
|
|
20421
|
+
throw err;
|
|
20422
|
+
});
|
|
20423
|
+
crossAccountCredentialsCache.set(cacheKey, promise);
|
|
20424
|
+
return promise;
|
|
20425
|
+
}
|
|
20426
|
+
async function applyRoleArnIfSet(opts) {
|
|
20427
|
+
const roleArn = opts.roleArn !== void 0 ? opts.roleArn : process.env["CDKD_ROLE_ARN"] || void 0;
|
|
20428
|
+
if (roleArn === void 0) return;
|
|
20429
|
+
if (getAssumedRoleCredentials() !== void 0) throw new Error(SECOND_ROLE_PUBLISH_REFUSAL);
|
|
20430
|
+
const logger = getLogger().child("role-arn");
|
|
20431
|
+
const displayRoleArn = displayIdent(roleArn, { maxCodePoints: 613 });
|
|
20432
|
+
logger.debug(`Assuming role ${displayRoleArn}...`);
|
|
20433
|
+
assertSendableRoleArn(roleArn);
|
|
20434
|
+
const sts = new STSClient({
|
|
20435
|
+
...awsClientDefaults({ ignoreAssumedRole: true }),
|
|
20436
|
+
...opts.region && { region: opts.region }
|
|
20437
|
+
});
|
|
20438
|
+
try {
|
|
20439
|
+
let response;
|
|
20440
|
+
try {
|
|
20441
|
+
response = await sts.send(sendableAssumeRoleCommand({
|
|
20442
|
+
RoleArn: roleArn,
|
|
20443
|
+
RoleSessionName: `cdkd-${Date.now()}`,
|
|
20444
|
+
DurationSeconds: 3600
|
|
20445
|
+
}));
|
|
20446
|
+
} catch (err) {
|
|
20447
|
+
throw new Error(`AssumeRole for ${displayRoleArn} failed: ${displayAwsMessage(err instanceof Error ? err.message : String(err))}`, { cause: err instanceof Error ? err : void 0 });
|
|
20448
|
+
}
|
|
20449
|
+
if (!response.Credentials) throw new Error(`AssumeRole returned no credentials for role ${displayRoleArn}`);
|
|
20450
|
+
const { AccessKeyId, SecretAccessKey, SessionToken, Expiration } = response.Credentials;
|
|
20451
|
+
if (!AccessKeyId || !SecretAccessKey || !SessionToken) throw new Error(`AssumeRole response missing credentials fields for role ${displayRoleArn}`);
|
|
20452
|
+
setPreAssumeEnvCredentials(readEnvCredentials());
|
|
20453
|
+
process.env["AWS_ACCESS_KEY_ID"] = AccessKeyId;
|
|
20454
|
+
process.env["AWS_SECRET_ACCESS_KEY"] = SecretAccessKey;
|
|
20455
|
+
process.env["AWS_SESSION_TOKEN"] = SessionToken;
|
|
20456
|
+
setAssumedRoleCredentials({
|
|
20457
|
+
accessKeyId: AccessKeyId,
|
|
20458
|
+
secretAccessKey: SecretAccessKey,
|
|
20459
|
+
sessionToken: SessionToken,
|
|
20460
|
+
...Expiration && { expiration: Expiration }
|
|
20461
|
+
});
|
|
20462
|
+
logger.info(`Assumed role ${displayRoleArn} (session expires ${Expiration?.toISOString() ?? "unknown"})`);
|
|
20463
|
+
const selectedProfile = process.env["AWS_PROFILE"];
|
|
20464
|
+
if (selectedProfile !== void 0 && selectedProfile !== "") logger.warn("AWS_PROFILE is set and a role has been assumed. The AWS calls cdkd makes to deploy and read state run as the role. The profile still decides the region, it is what your CDK app resolves during synthesis, and — when you passed `--profile` rather than exporting it — it is the identity a `cdkd local` emulated function or task is given. See https://cdkd.dev/cli-reference/#profile-vs-role-arn");
|
|
20465
|
+
} finally {
|
|
20466
|
+
sts.destroy();
|
|
20467
|
+
}
|
|
20468
|
+
}
|
|
20469
|
+
|
|
20470
|
+
//#endregion
|
|
20471
|
+
//#region src/deployment/intrinsic-resolver/cdk-cross-account-ref.ts
|
|
20472
|
+
function producerLookupOver(stacks) {
|
|
20473
|
+
return (stackName, account, region) => stacks.find((s) => s.stackName === stackName && s.account === account && sameRegion(s.region, region));
|
|
20474
|
+
}
|
|
20475
|
+
const ROLE_NAME = "[\\w+=,.@-]{1,64}";
|
|
20476
|
+
const LITERAL_ROLE_ARN_RE = new RegExp(`^arn:aws[a-z0-9-]*:iam::(\\d{12}):role/(${ROLE_NAME})$`);
|
|
20477
|
+
const JOIN_TAIL_RE = new RegExp(`^:iam::(\\d{12}):role/(${ROLE_NAME})$`);
|
|
20478
|
+
function isExactlyKeyed(value, key) {
|
|
20479
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
20480
|
+
const keys = Object.keys(value);
|
|
20481
|
+
return keys.length === 1 && keys[0] === key;
|
|
20482
|
+
}
|
|
20483
|
+
function roleArnShape(raw) {
|
|
20484
|
+
if (typeof raw === "string") {
|
|
20485
|
+
const match = LITERAL_ROLE_ARN_RE.exec(raw);
|
|
20486
|
+
if (!match || !match[1] || !match[2] || !parseIamRoleArn(raw)) return void 0;
|
|
20487
|
+
return {
|
|
20488
|
+
form: "literal",
|
|
20489
|
+
accountId: match[1],
|
|
20490
|
+
roleName: match[2]
|
|
20491
|
+
};
|
|
20492
|
+
}
|
|
20493
|
+
if (!isExactlyKeyed(raw, "Fn::Join")) return void 0;
|
|
20494
|
+
const join = raw["Fn::Join"];
|
|
20495
|
+
if (!Array.isArray(join) || join.length !== 2 || join[0] !== "") return void 0;
|
|
20496
|
+
const parts = join[1];
|
|
20497
|
+
if (!Array.isArray(parts) || parts.length !== 3 || parts[0] !== "arn:") return void 0;
|
|
20498
|
+
const ref = parts[1];
|
|
20499
|
+
if (!isExactlyKeyed(ref, "Ref") || ref["Ref"] !== "AWS::Partition") return void 0;
|
|
20500
|
+
const tail = parts[2];
|
|
20501
|
+
if (typeof tail !== "string") return void 0;
|
|
20502
|
+
const match = JOIN_TAIL_RE.exec(tail);
|
|
20503
|
+
if (!match || !match[1] || !match[2]) return void 0;
|
|
20504
|
+
return {
|
|
20505
|
+
form: "join",
|
|
20506
|
+
accountId: match[1],
|
|
20507
|
+
roleName: match[2]
|
|
20508
|
+
};
|
|
20509
|
+
}
|
|
20510
|
+
function roleArnOf(shape, region) {
|
|
20511
|
+
const { partition } = derivePartitionAndUrlSuffix(region);
|
|
20512
|
+
return `arn:${partition}:iam::${shape.accountId}:role/${shape.roleName}`;
|
|
20513
|
+
}
|
|
20514
|
+
const CFN_EXEC_ROLE_PRINCIPAL_RE = /^arn:(?:\$\{AWS::Partition\}|aws[a-z0-9-]*):iam::\d{12}:role\/[^/]*cfn-exec-role[^/]*$/;
|
|
20515
|
+
function isCfnExecRolePrincipal(principal) {
|
|
20516
|
+
return isExactlyKeyed(principal, "Fn::Sub") && typeof principal["Fn::Sub"] === "string" && CFN_EXEC_ROLE_PRINCIPAL_RE.test(principal["Fn::Sub"]);
|
|
20517
|
+
}
|
|
20518
|
+
function trustsOnlyCfnExecRoles(document) {
|
|
20519
|
+
if (document === null || typeof document !== "object" || Array.isArray(document)) return false;
|
|
20520
|
+
const statements = document["Statement"];
|
|
20521
|
+
if (!Array.isArray(statements) || statements.length === 0) return false;
|
|
20522
|
+
for (const statement of statements) {
|
|
20523
|
+
if (statement === null || typeof statement !== "object" || Array.isArray(statement)) return false;
|
|
20524
|
+
const principal = statement["Principal"];
|
|
20525
|
+
if (!isExactlyKeyed(principal, "AWS")) return false;
|
|
20526
|
+
const aws = principal["AWS"];
|
|
20527
|
+
const list = Array.isArray(aws) ? aws : [aws];
|
|
20528
|
+
if (list.length === 0 || !list.every(isCfnExecRolePrincipal)) return false;
|
|
20529
|
+
}
|
|
20530
|
+
return true;
|
|
20531
|
+
}
|
|
20532
|
+
function isGeneratedReaderRole(template, roleName) {
|
|
20533
|
+
const resources = template.Resources;
|
|
20534
|
+
if (!resources || typeof resources !== "object") return false;
|
|
20535
|
+
for (const resource of Object.values(resources)) {
|
|
20536
|
+
if (resource === null || typeof resource !== "object") continue;
|
|
20537
|
+
const { Type: type, Properties: properties } = resource;
|
|
20538
|
+
if (type !== "AWS::IAM::Role") continue;
|
|
20539
|
+
if (properties === null || typeof properties !== "object") continue;
|
|
20540
|
+
const props = properties;
|
|
20541
|
+
if (props["RoleName"] !== roleName) continue;
|
|
20542
|
+
if (trustsOnlyCfnExecRoles(props["AssumeRolePolicyDocument"])) return true;
|
|
20543
|
+
}
|
|
20544
|
+
return false;
|
|
20545
|
+
}
|
|
20546
|
+
function producerLookupRoleArn(lookupRoleArn, producerRegion, producerAccount) {
|
|
20547
|
+
if (typeof lookupRoleArn !== "string" || lookupRoleArn === "") return void 0;
|
|
20548
|
+
const { partition } = derivePartitionAndUrlSuffix(producerRegion);
|
|
20549
|
+
const arn = lookupRoleArn.split("${AWS::Partition}").join(partition);
|
|
20550
|
+
const parsed = parseIamRoleArn(arn);
|
|
20551
|
+
if (!parsed || parsed.accountId !== producerAccount) return void 0;
|
|
20552
|
+
return arn;
|
|
20553
|
+
}
|
|
20554
|
+
function classifyCdkCrossAccountRef(shape, producerStackName, producerRegion, lookup) {
|
|
20555
|
+
if (lookup === void 0) return { kind: "not-generated" };
|
|
20556
|
+
const producer = lookup(producerStackName, shape.accountId, producerRegion);
|
|
20557
|
+
if (!producer) return shape.form === "join" ? {
|
|
20558
|
+
kind: "producer-absent",
|
|
20559
|
+
shape
|
|
20560
|
+
} : { kind: "not-generated" };
|
|
20561
|
+
if (!isGeneratedReaderRole(producer.template, shape.roleName)) return { kind: "not-generated" };
|
|
20562
|
+
return {
|
|
20563
|
+
kind: "generated",
|
|
20564
|
+
shape,
|
|
20565
|
+
lookupRoleArn: producerLookupRoleArn(producer.lookupRoleArn, producerRegion, shape.accountId)
|
|
20566
|
+
};
|
|
20567
|
+
}
|
|
20568
|
+
function quotedArg(rendered) {
|
|
20569
|
+
return rendered.startsWith("\"") ? rendered : `'${rendered}'`;
|
|
20570
|
+
}
|
|
20571
|
+
function cdkCrossAccountRefRefusal(parts) {
|
|
20572
|
+
const consumerAccount = parts.consumerAccount !== void 0 && /^\d{12}$/.test(parts.consumerAccount) ? parts.consumerAccount : "<consumer-account>";
|
|
20573
|
+
const reason = parts.reason.replace(/[\s.]+$/, "");
|
|
20574
|
+
const trust = `'cdk bootstrap --trust-for-lookup ${consumerAccount}' (or '--trust ${consumerAccount}')`;
|
|
20575
|
+
const remedyOne = parts.noLookupRole ? `(1) synthesize stack ${parts.producer} with a stack synthesizer that names a lookup role (the default DefaultStackSynthesizer), and in account ${parts.producerAccount} run ${trust} so the identity running cdkd can assume it; or ` : `(1) in account ${parts.producerAccount}, run ${trust} so the identity running cdkd can assume the lookup role; or `;
|
|
20576
|
+
return `Fn::GetStackOutput: cannot read output ${parts.output} of stack ${parts.producer} (region ${parts.region}, account ${parts.producerAccount})${parts.consumer ? ` for stack ${parts.consumer}` : ""}. Its RoleArn names ${parts.roleName}, a role the AWS CDK generates for a cross-account reference that trusts only the consumer account's CloudFormation execution role, which cdkd does not use; cdkd reads the producer's cdkd state through the producer account's CDK lookup role instead, but ${reason}. Fix it one of two ways: ` + remedyOne + `(2) write the reference as cdk.Fn.getStackOutput(${quotedArg(parts.producer)}, ${quotedArg(parts.output)}, ${quotedArg(parts.region)}, '<roleArn>') with a role in account ${parts.producerAccount} that trusts the identity running cdkd and allows s3:GetBucketLocation and s3:GetObject on cdkd-state-${parts.producerAccount}. See https://cdkd.dev/cross-stack-references/#reading-from-another-account`;
|
|
20577
|
+
}
|
|
20578
|
+
function producerAbsentReason(producerAccount) {
|
|
20579
|
+
return `this producer stack (account ${producerAccount}) is not in this app's cloud assembly, so cdkd can neither confirm the role is the generated one nor find the lookup role`;
|
|
20580
|
+
}
|
|
20581
|
+
function noLookupRoleReason(producerAccount) {
|
|
20582
|
+
return `the producer stack's artifact names no CDK lookup role in account ${producerAccount} (its stack synthesizer does not use the bootstrap lookup role)`;
|
|
20583
|
+
}
|
|
20584
|
+
function isLookupRoleDenial(error) {
|
|
20585
|
+
let current = error;
|
|
20586
|
+
for (let depth = 0; depth < 6 && current !== null && typeof current === "object"; depth++) {
|
|
20587
|
+
const name = current.name;
|
|
20588
|
+
if (name === "AccessDenied" || name === "AccessDeniedException" || name === "NoSuchEntity" || name === "NoSuchEntityException") return true;
|
|
20589
|
+
if (current.$metadata?.httpStatusCode === 403) return true;
|
|
20590
|
+
current = current.cause;
|
|
20591
|
+
}
|
|
20592
|
+
return false;
|
|
20593
|
+
}
|
|
20594
|
+
function describeInnermostAwsError(error) {
|
|
20595
|
+
let innermost = error;
|
|
20596
|
+
for (let depth = 0; depth < 6; depth++) {
|
|
20597
|
+
const cause = innermost !== null && typeof innermost === "object" ? innermost.cause : void 0;
|
|
20598
|
+
if (cause === null || typeof cause !== "object") break;
|
|
20599
|
+
innermost = cause;
|
|
20600
|
+
}
|
|
20601
|
+
if (!(innermost instanceof Error)) return displayAwsMessage(String(innermost));
|
|
20602
|
+
const code = innermost.Code;
|
|
20603
|
+
const name = typeof code === "string" && code !== "" ? code : innermost.name;
|
|
20604
|
+
const message = displayAwsMessage(innermost.message);
|
|
20605
|
+
return name && name !== "Error" ? `${displayIdent(name)}: ${message}` : message;
|
|
20606
|
+
}
|
|
20607
|
+
function refuseUnreadableCdkCrossAccountRefs(refs, deployed, consumerOf, lookup) {
|
|
20608
|
+
for (const { consumer, args } of refs) {
|
|
20609
|
+
if (!deployed.has(consumer)) continue;
|
|
20610
|
+
const shape = roleArnShape(args["RoleArn"]);
|
|
20611
|
+
if (shape === void 0) continue;
|
|
20612
|
+
const stackName = args["StackName"];
|
|
20613
|
+
const outputName = args["OutputName"];
|
|
20614
|
+
const consumerStack = consumerOf(consumer);
|
|
20615
|
+
const region = args["Region"] ?? consumerStack.region;
|
|
20616
|
+
if (typeof stackName !== "string" || typeof outputName !== "string") continue;
|
|
20617
|
+
if (typeof region !== "string" || region === "") continue;
|
|
20618
|
+
const verdict = classifyCdkCrossAccountRef(shape, stackName, region, lookup);
|
|
20619
|
+
let reason;
|
|
20620
|
+
if (verdict.kind === "producer-absent") reason = producerAbsentReason(shape.accountId);
|
|
20621
|
+
else if (verdict.kind === "generated" && verdict.lookupRoleArn === void 0) reason = noLookupRoleReason(shape.accountId);
|
|
20622
|
+
if (reason === void 0) continue;
|
|
20623
|
+
throw markNonRetryable(new Error(cdkCrossAccountRefRefusal({
|
|
20624
|
+
consumer: displayStackName(consumer),
|
|
20625
|
+
output: displayIdent(outputName),
|
|
20626
|
+
producer: displayStackName(stackName),
|
|
20627
|
+
region: displayIdent(region),
|
|
20628
|
+
producerAccount: shape.accountId,
|
|
20629
|
+
roleName: displayIdent(shape.roleName),
|
|
20630
|
+
consumerAccount: consumerStack.account,
|
|
20631
|
+
reason,
|
|
20632
|
+
noLookupRole: verdict.kind === "generated"
|
|
20633
|
+
})));
|
|
20634
|
+
}
|
|
20635
|
+
}
|
|
20636
|
+
|
|
20054
20637
|
//#endregion
|
|
20055
20638
|
//#region src/deployment/intrinsic-resolver/stack-output.ts
|
|
20056
20639
|
async function resolveGetStackOutput(arg, context) {
|
|
@@ -20074,10 +20657,29 @@ async function resolveGetStackOutput(arg, context) {
|
|
|
20074
20657
|
loggedRegionText = this.regionLogText(resolvedRegion, context);
|
|
20075
20658
|
}
|
|
20076
20659
|
let roleArn;
|
|
20660
|
+
let cdkRef;
|
|
20077
20661
|
if ("RoleArn" in args && args["RoleArn"] !== void 0 && args["RoleArn"] !== null) {
|
|
20078
20662
|
const raw = args["RoleArn"];
|
|
20079
|
-
|
|
20080
|
-
|
|
20663
|
+
const refuseNonLiteral = () => markNonRetryable(/* @__PURE__ */ new Error(`Fn::GetStackOutput: RoleArn must be a literal string in the template (no Ref / Fn::GetAtt / Fn::Sub allowed for cross-account references). Got ${raw === null ? "null" : Array.isArray(raw) ? "array" : typeof raw}${typeof raw === "object" ? ` (intrinsic shape: ${this.displayMasked(JSON.stringify(raw).slice(0, 80), context)})` : ""}.`));
|
|
20664
|
+
const shape = roleArnShape(raw);
|
|
20665
|
+
if ((typeof raw !== "string" || raw === "") && shape === void 0) throw refuseNonLiteral();
|
|
20666
|
+
if (shape !== void 0) {
|
|
20667
|
+
const key = JSON.stringify([
|
|
20668
|
+
shape.form,
|
|
20669
|
+
stackName,
|
|
20670
|
+
shape.accountId,
|
|
20671
|
+
canonicalizeRegion(region),
|
|
20672
|
+
shape.roleName
|
|
20673
|
+
]);
|
|
20674
|
+
let verdict = this.cdkCrossAccountRefVerdicts.get(key);
|
|
20675
|
+
if (verdict === void 0) {
|
|
20676
|
+
verdict = classifyCdkCrossAccountRef(shape, stackName, region, this.producerLookup);
|
|
20677
|
+
this.cdkCrossAccountRefVerdicts.set(key, verdict);
|
|
20678
|
+
}
|
|
20679
|
+
if (verdict.kind === "not-generated" && typeof raw !== "string") throw refuseNonLiteral();
|
|
20680
|
+
if (verdict.kind !== "not-generated") cdkRef = verdict;
|
|
20681
|
+
}
|
|
20682
|
+
roleArn = typeof raw === "string" ? raw : shape && roleArnOf(shape, region);
|
|
20081
20683
|
}
|
|
20082
20684
|
if (!roleArn && context.stackName && context.stackName === stackName && sameRegion(region, this.resolverRegion)) throw markNonRetryable(/* @__PURE__ */ new Error(`Fn::GetStackOutput: cannot reference own stack ${this.displayMaskedIdent(stackName, context, STACK_REF_MAX_CODE_POINTS)} in the same region ${this.displayMaskedIdent(loggedRegionText, context)}`));
|
|
20083
20685
|
const loggedStackName = this.displayMaskedIdent(stackName, context, STACK_REF_MAX_CODE_POINTS);
|
|
@@ -20085,11 +20687,35 @@ async function resolveGetStackOutput(arg, context) {
|
|
|
20085
20687
|
const loggedRegion = this.displayMasked(loggedRegionText, context);
|
|
20086
20688
|
const shownRoleArn = roleArn ? displayIdent(roleArn, { maxCodePoints: 613 }) : "";
|
|
20087
20689
|
this.logger.debug(`Resolving Fn::GetStackOutput: StackName=${loggedStackName}, Region=${loggedRegion}, OutputName=${loggedOutputName}${roleArn ? `, RoleArn=${shownRoleArn}` : ""}`);
|
|
20690
|
+
let readRoleArn = roleArn;
|
|
20691
|
+
let refuseCdkRead;
|
|
20692
|
+
if (cdkRef !== void 0) {
|
|
20693
|
+
const ref = cdkRef;
|
|
20694
|
+
const producerAccount = ref.shape.accountId;
|
|
20695
|
+
const refuseCdkRef = (reason, cause, noLookupRole = false) => markNonRetryable(new Error(cdkCrossAccountRefRefusal({
|
|
20696
|
+
consumer: context.stackName ? this.displayMaskedIdent(context.stackName, context, STACK_REF_MAX_CODE_POINTS) : void 0,
|
|
20697
|
+
output: loggedOutputName,
|
|
20698
|
+
producer: loggedStackName,
|
|
20699
|
+
region: displayIdent(loggedRegion),
|
|
20700
|
+
producerAccount,
|
|
20701
|
+
roleName: displayIdent(ref.shape.roleName),
|
|
20702
|
+
consumerAccount: this.consumerAccount,
|
|
20703
|
+
reason,
|
|
20704
|
+
noLookupRole
|
|
20705
|
+
}), cause === void 0 ? void 0 : { cause }));
|
|
20706
|
+
if (ref.kind === "producer-absent") throw refuseCdkRef(producerAbsentReason(producerAccount));
|
|
20707
|
+
if (ref.lookupRoleArn === void 0) throw refuseCdkRef(noLookupRoleReason(producerAccount), void 0, true);
|
|
20708
|
+
readRoleArn = ref.lookupRoleArn;
|
|
20709
|
+
const shownLookupRoleArn = displayIdent(readRoleArn, { maxCodePoints: 613 });
|
|
20710
|
+
refuseCdkRead = (error) => refuseCdkRef(`assuming the lookup role ${shownLookupRoleArn} or reading cdkd-state-${producerAccount} with it was denied: ${describeInnermostAwsError(error)}`, error);
|
|
20711
|
+
this.logger.debug(safeMsg`Fn::GetStackOutput: ${shownRoleArn} is the AWS CDK-generated cross-account reader role; reading through the producer account's lookup role ${shownLookupRoleArn}`);
|
|
20712
|
+
}
|
|
20713
|
+
const shownReadRoleArn = readRoleArn === roleArn ? shownRoleArn : displayIdent(readRoleArn, { maxCodePoints: 613 });
|
|
20088
20714
|
let stateData;
|
|
20089
20715
|
try {
|
|
20090
|
-
stateData =
|
|
20716
|
+
stateData = readRoleArn ? await this.getCrossAccountStackState(readRoleArn, stackName, region, context) : await this.getSameAccountStackState(stackName, region, context);
|
|
20091
20717
|
} catch (error) {
|
|
20092
|
-
this.maskStateReadError(error, [[stackName, this.displayMasked(stackName, context)], [region, loggedRegion]], context);
|
|
20718
|
+
this.maskStateReadError(refuseCdkRead && isLookupRoleDenial(error) ? refuseCdkRead(error) : error, [[stackName, this.displayMasked(stackName, context)], [region, loggedRegion]], context);
|
|
20093
20719
|
}
|
|
20094
20720
|
if (!stateData) {
|
|
20095
20721
|
if (!roleArn && this.cfnFallback) {
|
|
@@ -20104,7 +20730,7 @@ async function resolveGetStackOutput(arg, context) {
|
|
|
20104
20730
|
return value;
|
|
20105
20731
|
}
|
|
20106
20732
|
}
|
|
20107
|
-
throw new Error(`Fn::GetStackOutput: stack ${loggedStackName} not found in region ${displayIdent(loggedRegion)}${roleArn ? ` (cross-account via ${
|
|
20733
|
+
throw new Error(`Fn::GetStackOutput: stack ${loggedStackName} not found in region ${displayIdent(loggedRegion)}${roleArn ? ` (cross-account via ${shownReadRoleArn})` : ""}. ${!roleArn && this.cfnFallback ? "Searched cdkd state and CloudFormation stacks. Make sure the producer stack has been deployed (via cdkd or CloudFormation)." : `Make sure the producer stack has been deployed via cdkd.`}`);
|
|
20108
20734
|
}
|
|
20109
20735
|
if (!hasReadableOutputs(stateData.state)) throw markNonRetryable(new MalformedProducerRecordRefusalError(`Fn::GetStackOutput: the state record of producer stack ${loggedStackName} (${displayIdent(loggedRegion)}) has no readable 'outputs' map — the record is malformed or truncated. cdkd refuses to resolve from it rather than reading it as a map: 'Object.hasOwn' answers TRUE for '0' on a string and for an index on a list, so continuing would resolve ONE CHARACTER or element of the record as this reference's value and this deploy would send it to AWS. Inspect the producer's record with 'cdkd state show' --json, repair or remove it, then re-run.`));
|
|
20110
20736
|
const outputs = stateData.state.outputs ?? {};
|
|
@@ -20115,7 +20741,7 @@ async function resolveGetStackOutput(arg, context) {
|
|
|
20115
20741
|
const value = outputs[outputName];
|
|
20116
20742
|
this.logger.info(`Resolved Fn::GetStackOutput: StackName=${loggedStackName}, Region=${loggedRegion}, OutputName=${loggedOutputName}${roleArn ? `, RoleArn=${shownRoleArn}` : ""} (${carriesDynamicReference(value) ? "redacted dynamic reference" : "literal value"})`);
|
|
20117
20743
|
if (!roleArn) this.recordOutputRead(context, stackName, region, outputName);
|
|
20118
|
-
if (roleArn && !context.skipDynamicReferences && carriesDynamicReference(value)) throw markNonRetryable(new CrossAccountSecretRefusalError(`Fn::GetStackOutput: output ${loggedOutputName} of stack ${loggedStackName} (${displayIdent(loggedRegion)}) is a redacted dynamic reference, and this is a CROSS-ACCOUNT reference (RoleArn ${shownRoleArn}). cdkd will not resolve a producer account's secret with the consumer's credentials — a same-named secret in the consumer account would answer instead. Export a non-secret value (e.g. the secret's ARN) and resolve it in the consumer stack, or reference the producer stack from within its own account.`));
|
|
20744
|
+
if (roleArn && !context.skipDynamicReferences && carriesDynamicReference(value)) throw markNonRetryable(new CrossAccountSecretRefusalError(`Fn::GetStackOutput: output ${loggedOutputName} of stack ${loggedStackName} (${displayIdent(loggedRegion)}) is a redacted dynamic reference, and this is a CROSS-ACCOUNT reference (RoleArn ${shownRoleArn}${readRoleArn !== roleArn ? `, read through the producer account's CDK lookup role ${shownReadRoleArn}` : ""}). cdkd will not resolve a producer account's secret with the consumer's credentials — a same-named secret in the consumer account would answer instead. Export a non-secret value (e.g. the secret's ARN) and resolve it in the consumer stack, or reference the producer stack from within its own account.`));
|
|
20119
20745
|
return await this.reresolveCrossStackValue(value, region, context, `Fn::GetStackOutput ${quotedRender(this.displayLeaf(outputName, context), "'")} (producer ${this.displayLeaf(stackName, context)} / ${this.displayLeaf(loggedRegionText, context)})`, sourceKey, {
|
|
20120
20746
|
stackName,
|
|
20121
20747
|
region,
|
|
@@ -20188,178 +20814,6 @@ function namedRequestMasks(pairs, context) {
|
|
|
20188
20814
|
};
|
|
20189
20815
|
}
|
|
20190
20816
|
|
|
20191
|
-
//#endregion
|
|
20192
|
-
//#region src/utils/caller-credentials.ts
|
|
20193
|
-
const AWS_CREDENTIAL_ENV_KEYS = [
|
|
20194
|
-
"AWS_ACCESS_KEY_ID",
|
|
20195
|
-
"AWS_SECRET_ACCESS_KEY",
|
|
20196
|
-
"AWS_SESSION_TOKEN"
|
|
20197
|
-
];
|
|
20198
|
-
function readEnvCredentials(source = process.env) {
|
|
20199
|
-
const accessKeyId = source["AWS_ACCESS_KEY_ID"];
|
|
20200
|
-
const secretAccessKey = source["AWS_SECRET_ACCESS_KEY"];
|
|
20201
|
-
if (!accessKeyId || !secretAccessKey) return void 0;
|
|
20202
|
-
const sessionToken = source["AWS_SESSION_TOKEN"];
|
|
20203
|
-
return {
|
|
20204
|
-
accessKeyId,
|
|
20205
|
-
secretAccessKey,
|
|
20206
|
-
...sessionToken ? { sessionToken } : {}
|
|
20207
|
-
};
|
|
20208
|
-
}
|
|
20209
|
-
function callerEnvCredentials() {
|
|
20210
|
-
if (getAssumedRoleCredentials() === void 0) return readEnvCredentials();
|
|
20211
|
-
return getPreAssumeEnvCredentials();
|
|
20212
|
-
}
|
|
20213
|
-
function envCredentialsAreAssumedRole() {
|
|
20214
|
-
return getAssumedRoleCredentials() !== void 0;
|
|
20215
|
-
}
|
|
20216
|
-
function applyCallerIdentityCredentials(env) {
|
|
20217
|
-
if (getAssumedRoleCredentials() === void 0) return;
|
|
20218
|
-
const caller = getPreAssumeEnvCredentials();
|
|
20219
|
-
if (!caller) {
|
|
20220
|
-
for (const key of AWS_CREDENTIAL_ENV_KEYS) delete env[key];
|
|
20221
|
-
return;
|
|
20222
|
-
}
|
|
20223
|
-
env["AWS_ACCESS_KEY_ID"] = caller.accessKeyId;
|
|
20224
|
-
env["AWS_SECRET_ACCESS_KEY"] = caller.secretAccessKey;
|
|
20225
|
-
if (caller.sessionToken) env["AWS_SESSION_TOKEN"] = caller.sessionToken;
|
|
20226
|
-
else delete env["AWS_SESSION_TOKEN"];
|
|
20227
|
-
}
|
|
20228
|
-
|
|
20229
|
-
//#endregion
|
|
20230
|
-
//#region src/utils/role-arn.ts
|
|
20231
|
-
const crossAccountCredentialsCache = /* @__PURE__ */ new Map();
|
|
20232
|
-
const CRED_EXPIRY_SAFETY_MS = 6e4;
|
|
20233
|
-
const IAM_ROLE_ARN_RE = /^arn:(aws[a-z0-9-]*):iam::(\d{12}):role\/[\w+=,.@-]+(?:\/[\w+=,.@-]+)*$/;
|
|
20234
|
-
const IAM_ROLE_ARN_MAX_LENGTH = 2048;
|
|
20235
|
-
const IAM_ROLE_ARN_SHAPE_RE = /^arn:[A-Za-z0-9-]+:iam::[0-9]+:role\/[!-~]+$/;
|
|
20236
|
-
function isIamRoleArn(value) {
|
|
20237
|
-
if (typeof value !== "string") return false;
|
|
20238
|
-
if (value.length > 2048) return false;
|
|
20239
|
-
return IAM_ROLE_ARN_SHAPE_RE.test(value);
|
|
20240
|
-
}
|
|
20241
|
-
function refusedRoleArnMessage(value) {
|
|
20242
|
-
return `AssumeRole refused: the role ARN is not a well-formed IAM role ARN (expected arn:<partition>:iam::<account>:role/<name>, at most ${IAM_ROLE_ARN_MAX_LENGTH} characters): ${shownRefusedValue(value)}. Nothing was sent to STS.`;
|
|
20243
|
-
}
|
|
20244
|
-
function shownRefusedValue(value) {
|
|
20245
|
-
if (value === "") return "(empty)";
|
|
20246
|
-
return displayIdent(value, { maxCodePoints: 613 });
|
|
20247
|
-
}
|
|
20248
|
-
function assertSendableRoleArn(roleArn, makeError = (message) => new Error(message)) {
|
|
20249
|
-
if (typeof roleArn !== "string" || !isIamRoleArn(roleArn)) throw makeError(refusedRoleArnMessage(roleArn));
|
|
20250
|
-
}
|
|
20251
|
-
function sendableAssumeRoleCommand(input, makeError) {
|
|
20252
|
-
assertSendableRoleArn(input.RoleArn, makeError);
|
|
20253
|
-
return new AssumeRoleCommand(input);
|
|
20254
|
-
}
|
|
20255
|
-
function explicitRoleArnOrThrow(flag, raw, makeError = (message) => new Error(message)) {
|
|
20256
|
-
const trimmed = raw.trim();
|
|
20257
|
-
if (!isIamRoleArn(trimmed)) throw makeError(`Invalid ${flag} value: ${refusedRoleArnMessage(raw)}`);
|
|
20258
|
-
return trimmed;
|
|
20259
|
-
}
|
|
20260
|
-
function assertFlagRoleArn(flag, value, makeError = (message) => new Error(message)) {
|
|
20261
|
-
assertSendableRoleArn(value, (message) => makeError(`Invalid ${flag} value: ${message}`));
|
|
20262
|
-
}
|
|
20263
|
-
function parseIamRoleArn(roleArn) {
|
|
20264
|
-
if (roleArn.length > 2048) return null;
|
|
20265
|
-
const match = IAM_ROLE_ARN_RE.exec(roleArn);
|
|
20266
|
-
if (!match || !match[1] || !match[2]) return null;
|
|
20267
|
-
return {
|
|
20268
|
-
partition: match[1],
|
|
20269
|
-
accountId: match[2]
|
|
20270
|
-
};
|
|
20271
|
-
}
|
|
20272
|
-
async function assumeRoleForCrossAccountStateRead(roleArn) {
|
|
20273
|
-
const sourceConfig = ambientCredentialConfig();
|
|
20274
|
-
const cacheKey = JSON.stringify([credentialFingerprint(sourceConfig), roleArn]);
|
|
20275
|
-
const cached = crossAccountCredentialsCache.get(cacheKey);
|
|
20276
|
-
if (cached) {
|
|
20277
|
-
const cachedCreds = await cached;
|
|
20278
|
-
if (!cachedCreds.expiration || Date.now() < cachedCreds.expiration.getTime() - CRED_EXPIRY_SAFETY_MS) return cachedCreds;
|
|
20279
|
-
crossAccountCredentialsCache.delete(cacheKey);
|
|
20280
|
-
}
|
|
20281
|
-
const promise = (async () => {
|
|
20282
|
-
const logger = getLogger().child("role-arn");
|
|
20283
|
-
const displayRoleArn = displayIdent(roleArn, { maxCodePoints: 613 });
|
|
20284
|
-
logger.debug(`Assuming role for cross-account state read: ${displayRoleArn}`);
|
|
20285
|
-
assertSendableRoleArn(roleArn);
|
|
20286
|
-
const sts = new STSClient({ ...clientDefaultsFor(sourceConfig) });
|
|
20287
|
-
try {
|
|
20288
|
-
let response;
|
|
20289
|
-
try {
|
|
20290
|
-
response = await sts.send(sendableAssumeRoleCommand({
|
|
20291
|
-
RoleArn: roleArn,
|
|
20292
|
-
RoleSessionName: `cdkd-xacc-${Date.now()}`,
|
|
20293
|
-
DurationSeconds: 3600
|
|
20294
|
-
}));
|
|
20295
|
-
} catch (err) {
|
|
20296
|
-
const message = displayAwsMessage(err instanceof Error ? err.message : String(err));
|
|
20297
|
-
throw new Error(`AssumeRole into ${displayRoleArn} failed: ${message}. If this is a trust-policy issue, the producer's role must allow sts:AssumeRole from the consumer's principal. See https://cdkd.dev/cross-stack-internals/#iam-permissions for the trust-policy setup.`, { cause: err instanceof Error ? err : void 0 });
|
|
20298
|
-
}
|
|
20299
|
-
if (!response.Credentials) throw new Error(`AssumeRole for cross-account Fn::GetStackOutput returned no credentials (RoleArn=${displayRoleArn})`);
|
|
20300
|
-
const { AccessKeyId, SecretAccessKey, SessionToken, Expiration } = response.Credentials;
|
|
20301
|
-
if (!AccessKeyId || !SecretAccessKey || !SessionToken) throw new Error(`AssumeRole response missing required credentials fields for cross-account state read (RoleArn=${displayRoleArn})`);
|
|
20302
|
-
logger.info(`Assumed role for cross-account state read: ${displayRoleArn} (session expires ${Expiration?.toISOString() ?? "unknown"})`);
|
|
20303
|
-
return {
|
|
20304
|
-
accessKeyId: AccessKeyId,
|
|
20305
|
-
secretAccessKey: SecretAccessKey,
|
|
20306
|
-
sessionToken: SessionToken,
|
|
20307
|
-
...Expiration && { expiration: Expiration }
|
|
20308
|
-
};
|
|
20309
|
-
} finally {
|
|
20310
|
-
sts.destroy();
|
|
20311
|
-
}
|
|
20312
|
-
})().catch((err) => {
|
|
20313
|
-
if (crossAccountCredentialsCache.get(cacheKey) === promise) crossAccountCredentialsCache.delete(cacheKey);
|
|
20314
|
-
throw err;
|
|
20315
|
-
});
|
|
20316
|
-
crossAccountCredentialsCache.set(cacheKey, promise);
|
|
20317
|
-
return promise;
|
|
20318
|
-
}
|
|
20319
|
-
async function applyRoleArnIfSet(opts) {
|
|
20320
|
-
const roleArn = opts.roleArn !== void 0 ? opts.roleArn : process.env["CDKD_ROLE_ARN"] || void 0;
|
|
20321
|
-
if (roleArn === void 0) return;
|
|
20322
|
-
if (getAssumedRoleCredentials() !== void 0) throw new Error(SECOND_ROLE_PUBLISH_REFUSAL);
|
|
20323
|
-
const logger = getLogger().child("role-arn");
|
|
20324
|
-
const displayRoleArn = displayIdent(roleArn, { maxCodePoints: 613 });
|
|
20325
|
-
logger.debug(`Assuming role ${displayRoleArn}...`);
|
|
20326
|
-
assertSendableRoleArn(roleArn);
|
|
20327
|
-
const sts = new STSClient({
|
|
20328
|
-
...awsClientDefaults({ ignoreAssumedRole: true }),
|
|
20329
|
-
...opts.region && { region: opts.region }
|
|
20330
|
-
});
|
|
20331
|
-
try {
|
|
20332
|
-
let response;
|
|
20333
|
-
try {
|
|
20334
|
-
response = await sts.send(sendableAssumeRoleCommand({
|
|
20335
|
-
RoleArn: roleArn,
|
|
20336
|
-
RoleSessionName: `cdkd-${Date.now()}`,
|
|
20337
|
-
DurationSeconds: 3600
|
|
20338
|
-
}));
|
|
20339
|
-
} catch (err) {
|
|
20340
|
-
throw new Error(`AssumeRole for ${displayRoleArn} failed: ${displayAwsMessage(err instanceof Error ? err.message : String(err))}`, { cause: err instanceof Error ? err : void 0 });
|
|
20341
|
-
}
|
|
20342
|
-
if (!response.Credentials) throw new Error(`AssumeRole returned no credentials for role ${displayRoleArn}`);
|
|
20343
|
-
const { AccessKeyId, SecretAccessKey, SessionToken, Expiration } = response.Credentials;
|
|
20344
|
-
if (!AccessKeyId || !SecretAccessKey || !SessionToken) throw new Error(`AssumeRole response missing credentials fields for role ${displayRoleArn}`);
|
|
20345
|
-
setPreAssumeEnvCredentials(readEnvCredentials());
|
|
20346
|
-
process.env["AWS_ACCESS_KEY_ID"] = AccessKeyId;
|
|
20347
|
-
process.env["AWS_SECRET_ACCESS_KEY"] = SecretAccessKey;
|
|
20348
|
-
process.env["AWS_SESSION_TOKEN"] = SessionToken;
|
|
20349
|
-
setAssumedRoleCredentials({
|
|
20350
|
-
accessKeyId: AccessKeyId,
|
|
20351
|
-
secretAccessKey: SecretAccessKey,
|
|
20352
|
-
sessionToken: SessionToken,
|
|
20353
|
-
...Expiration && { expiration: Expiration }
|
|
20354
|
-
});
|
|
20355
|
-
logger.info(`Assumed role ${displayRoleArn} (session expires ${Expiration?.toISOString() ?? "unknown"})`);
|
|
20356
|
-
const selectedProfile = process.env["AWS_PROFILE"];
|
|
20357
|
-
if (selectedProfile !== void 0 && selectedProfile !== "") logger.warn("AWS_PROFILE is set and a role has been assumed. The AWS calls cdkd makes to deploy and read state run as the role. The profile still decides the region, it is what your CDK app resolves during synthesis, and — when you passed `--profile` rather than exporting it — it is the identity a `cdkd local` emulated function or task is given. See https://cdkd.dev/cli-reference/#profile-vs-role-arn");
|
|
20358
|
-
} finally {
|
|
20359
|
-
sts.destroy();
|
|
20360
|
-
}
|
|
20361
|
-
}
|
|
20362
|
-
|
|
20363
20817
|
//#endregion
|
|
20364
20818
|
//#region src/deployment/intrinsic-resolver/stack-state.ts
|
|
20365
20819
|
async function getSameAccountStackState(stackName, region, context) {
|
|
@@ -20922,7 +21376,7 @@ async function resolveSplit(splitArgs, context) {
|
|
|
20922
21376
|
const source = this.describeSplitValueSource(value);
|
|
20923
21377
|
const sourceClause = source ? ` (from ${this.displayMasked(source.label, context)})` : "";
|
|
20924
21378
|
if (Array.isArray(resolvedValue)) {
|
|
20925
|
-
const remedy = source?.kind === "ref" ? "A list-typed parameter — any List<...> type (List<AWS::EC2::Subnet::Id>, List<Number>, …) or CommaDelimitedList — is already a list." : source?.kind === "getatt" ? "A list-valued Fn::GetAtt (for example AWS::Route53::HostedZone.NameServers or AWS::EC2::VPC.Ipv6CidrBlocks) already returns a list. If you wrote the Fn::Split as a workaround for cdkd resolving that attribute to a comma-delimited string, that bug is fixed
|
|
21379
|
+
const remedy = source?.kind === "ref" ? "A list-typed parameter — any List<...> type (List<AWS::EC2::Subnet::Id>, List<Number>, …) or CommaDelimitedList — is already a list." : source?.kind === "getatt" ? "A list-valued Fn::GetAtt (for example AWS::Route53::HostedZone.NameServers or AWS::EC2::VPC.Ipv6CidrBlocks) already returns a list. If you wrote the Fn::Split as a workaround for cdkd resolving that attribute to a comma-delimited string, that bug is fixed and the workaround is no longer needed." : "Several intrinsics already return a list — among them a list-valued Fn::GetAtt, a Ref to a list-typed parameter (any List<...> type or CommaDelimitedList), Fn::GetAZs, Fn::Cidr, and Fn::Split itself.";
|
|
20926
21380
|
throw markNonRetryable(new IntrinsicResolutionRefusalError(`Fn::Split: the value to split${sourceClause} is ALREADY a list (an array of ${resolvedValue.length} item${resolvedValue.length === 1 ? "" : "s"}), not a string. CloudFormation rejects Fn::Split over a list too, so this template is not valid CloudFormation either. Remove the Fn::Split and use the value directly. ${remedy}`));
|
|
20927
21381
|
}
|
|
20928
21382
|
throw markNonRetryable(new IntrinsicResolutionRefusalError(`Fn::Split: the value to split${sourceClause} must be a string, got ${resolvedValue === null ? "null" : typeof resolvedValue}. Fn::Split accepts only a string; check the value or the intrinsic that produced it.`));
|
|
@@ -21837,8 +22291,9 @@ function recordInheritedNoEchoListElements(resolver, inherited, recorded, value,
|
|
|
21837
22291
|
}
|
|
21838
22292
|
function recordNoEchoParameterValue(paramDef, value, context) {
|
|
21839
22293
|
const bag = context.recordedSecretValues;
|
|
21840
|
-
if (paramDef
|
|
22294
|
+
if (!declaresNoEcho(paramDef) || bag === void 0) return;
|
|
21841
22295
|
recordLogOnlyParameterValue(bag, value);
|
|
22296
|
+
if (paramDef?.NoEcho !== true) return;
|
|
21842
22297
|
recordNoEchoParameterFreshValue(value, bag, this.publicNoEchoTokens(context));
|
|
21843
22298
|
}
|
|
21844
22299
|
function refuseCoercedInheritedSecret(name, paramDef, userValue, inherited) {
|
|
@@ -21876,6 +22331,7 @@ function lookupResourceRecord(logicalId, via, context) {
|
|
|
21876
22331
|
const loggedId = this.displayMasked(logicalId, context);
|
|
21877
22332
|
throw markNonRetryable(new IntrinsicResolutionRefusalError(`${via} ${loggedId}: ${isObject ? `the state record's physical id is ${physicalId === null ? "null" : typeof physicalId}, not a string` : `the state record is ${Array.isArray(resource) ? "an array" : typeof resource}, not an object`}. cdkd always records an object with a string id, so this record was edited by hand or written by another tool. ${isObject ? `Set the resource's "physicalId" in the stack's state.json back to the id AWS knows the resource by.` : `Restore the resource's record in the stack's state.json, or remove it and re-import the resource.`}`, void 0, "STATE_PHYSICAL_ID_NOT_STRING"));
|
|
21878
22333
|
}
|
|
22334
|
+
if (context.recordedSecretValues !== void 0) recordPhysicalNameCopy(context.recordedSecretValues, physicalId);
|
|
21879
22335
|
return resource;
|
|
21880
22336
|
}
|
|
21881
22337
|
async function resolveRef(logicalId, context, onParameter) {
|
|
@@ -21884,7 +22340,7 @@ async function resolveRef(logicalId, context, onParameter) {
|
|
|
21884
22340
|
const refValue = this.resolveRefValue(logicalId, resource, context);
|
|
21885
22341
|
recordSecretNamedRead(logicalId, refValue, context);
|
|
21886
22342
|
this.logger.debug(`Resolved Ref to resource: ${this.logRender(logicalId, context)} resolved to ${this.logRender(String(refValue), context)}`);
|
|
21887
|
-
return refValue;
|
|
22343
|
+
return await this.reresolveRecordedReferences(refValue, logicalId, context, `Ref ${logicalId}`, void 0);
|
|
21888
22344
|
}
|
|
21889
22345
|
if (context.parameters && Object.hasOwn(context.parameters, logicalId)) {
|
|
21890
22346
|
const value = context.parameters[logicalId];
|
|
@@ -21894,7 +22350,7 @@ async function resolveRef(logicalId, context, onParameter) {
|
|
|
21894
22350
|
this.recordInheritedParameterSecrets(logicalId, value, context);
|
|
21895
22351
|
this.logger.debug(`Resolved Ref to parameter: ${this.logRender(logicalId, context)} resolved to ${this.logRender(stringifyParameterForLog(paramDef, this.maskValueLeaves(value, context)), context, {
|
|
21896
22352
|
structured: isStructured(value),
|
|
21897
|
-
redacted: paramDef
|
|
22353
|
+
redacted: declaresNoEcho(paramDef)
|
|
21898
22354
|
})}`);
|
|
21899
22355
|
onParameter?.();
|
|
21900
22356
|
return value;
|
|
@@ -21976,6 +22432,9 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
21976
22432
|
explicitRegionLogText;
|
|
21977
22433
|
strictGetAtt;
|
|
21978
22434
|
cfnFallback;
|
|
22435
|
+
producerLookup;
|
|
22436
|
+
consumerAccount;
|
|
22437
|
+
cdkCrossAccountRefVerdicts = /* @__PURE__ */ new Map();
|
|
21979
22438
|
cfnClients = /* @__PURE__ */ new Map();
|
|
21980
22439
|
cfnExportsPromises = /* @__PURE__ */ new Map();
|
|
21981
22440
|
cfnStackOutputsCache = /* @__PURE__ */ new Map();
|
|
@@ -21987,6 +22446,8 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
|
|
|
21987
22446
|
this.explicitRegion = region || void 0;
|
|
21988
22447
|
this.strictGetAtt = options?.strictGetAtt ?? false;
|
|
21989
22448
|
this.cfnFallback = options?.cfnFallback ?? true;
|
|
22449
|
+
this.producerLookup = options?.producerLookup;
|
|
22450
|
+
this.consumerAccount = options?.consumerAccount;
|
|
21990
22451
|
}
|
|
21991
22452
|
getPhysicalIdFallbackCount() {
|
|
21992
22453
|
return this.physicalIdFallbackCount;
|
|
@@ -22181,6 +22642,8 @@ IntrinsicFunctionResolver.prototype.pushRedactedAttributeRead = pushRedactedAttr
|
|
|
22181
22642
|
IntrinsicFunctionResolver.prototype.resolveGetAtt = resolveGetAtt;
|
|
22182
22643
|
IntrinsicFunctionResolver.prototype.publicNoEchoTokens = publicNoEchoTokens;
|
|
22183
22644
|
IntrinsicFunctionResolver.prototype.noteAttributeSecrecy = noteAttributeSecrecy;
|
|
22645
|
+
IntrinsicFunctionResolver.prototype.reresolveRecordedReferences = reresolveRecordedReferences;
|
|
22646
|
+
IntrinsicFunctionResolver.prototype.referencesMatchPhysicalId = referencesMatchPhysicalId;
|
|
22184
22647
|
IntrinsicFunctionResolver.prototype.rejectPlaceholderArnAttribute = rejectPlaceholderArnAttribute;
|
|
22185
22648
|
IntrinsicFunctionResolver.prototype.staleRecordRemedy = staleRecordRemedy;
|
|
22186
22649
|
IntrinsicFunctionResolver.prototype.unenrichedRemedy = unenrichedRemedy;
|
|
@@ -22347,7 +22810,7 @@ var DiffCalculator = class DiffCalculator {
|
|
|
22347
22810
|
requiresReplacement: await this.maskedEditRequiresReplacement(desiredResource.Type, key, oldValue, newValue),
|
|
22348
22811
|
maskedExpressionChanged: true
|
|
22349
22812
|
});
|
|
22350
|
-
this.logger.debug(safeMsg`${logicalId}: ${key} is recorded masked and its template expression or a resolved input changed
|
|
22813
|
+
this.logger.debug(safeMsg`${logicalId}: ${key} is recorded masked and its template expression or a resolved input changed`);
|
|
22351
22814
|
}
|
|
22352
22815
|
const attributeChanges = this.compareAttributes(currentResource, desiredResource);
|
|
22353
22816
|
if (propertyChanges.length > 0 || attributeChanges.length > 0) {
|
|
@@ -23008,6 +23471,511 @@ function deleteIndeterminateGuards(result) {
|
|
|
23008
23471
|
return out;
|
|
23009
23472
|
}
|
|
23010
23473
|
|
|
23474
|
+
//#endregion
|
|
23475
|
+
//#region src/deployment/inline-policy-claims.ts
|
|
23476
|
+
function isInlinePolicyClaimedByCompletedWriter(args, kind, principal, policyName) {
|
|
23477
|
+
const { selfLogicalId, writers, changes, stateResources } = args;
|
|
23478
|
+
for (const [lid, write] of writers) {
|
|
23479
|
+
if (lid === selfLogicalId || !Object.hasOwn(stateResources, lid)) continue;
|
|
23480
|
+
const record = stateResources[lid];
|
|
23481
|
+
const standalone = standaloneInlinePolicyOf(record.resourceType, record.properties);
|
|
23482
|
+
if (standalone !== void 0) {
|
|
23483
|
+
if (standalone.kind === kind && sameIamName(standalone.principal, principal) && sameIamName(standalone.policyName, policyName)) return true;
|
|
23484
|
+
continue;
|
|
23485
|
+
}
|
|
23486
|
+
if (record.provisionedBy === "cc-api") continue;
|
|
23487
|
+
const props = record.properties ?? {};
|
|
23488
|
+
if (record.resourceType === "AWS::IAM::Policy") {
|
|
23489
|
+
if (!sameIamName(record.physicalId, policyName)) continue;
|
|
23490
|
+
const listed = props[POLICY_LIST_FIELDS[kind]];
|
|
23491
|
+
if (Array.isArray(listed) && listed.some((p) => sameIamName(p, principal))) return true;
|
|
23492
|
+
continue;
|
|
23493
|
+
}
|
|
23494
|
+
if (!Object.hasOwn(PRINCIPAL_KINDS, record.resourceType)) continue;
|
|
23495
|
+
if (PRINCIPAL_KINDS[record.resourceType] !== kind) continue;
|
|
23496
|
+
if (!sameIamName(record.physicalId, principal)) continue;
|
|
23497
|
+
if (write !== "create" && !ownPoliciesChange(changes.get(lid))) continue;
|
|
23498
|
+
const policies = props["Policies"];
|
|
23499
|
+
if (!Array.isArray(policies)) continue;
|
|
23500
|
+
if (policies.some((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry) && sameIamName(entry["PolicyName"], policyName))) return true;
|
|
23501
|
+
}
|
|
23502
|
+
return false;
|
|
23503
|
+
}
|
|
23504
|
+
var RollbackInlinePolicyWriters = class {
|
|
23505
|
+
entries = /* @__PURE__ */ new Map();
|
|
23506
|
+
removals = /* @__PURE__ */ new Map();
|
|
23507
|
+
unsettled = /* @__PURE__ */ new Map();
|
|
23508
|
+
notePending(ops) {
|
|
23509
|
+
for (const op of ops) if (!this.unsettled.has(op)) this.unsettled.set(op, {
|
|
23510
|
+
logicalId: op.logicalId,
|
|
23511
|
+
state: "pending"
|
|
23512
|
+
});
|
|
23513
|
+
}
|
|
23514
|
+
noteOutcome(op, completed) {
|
|
23515
|
+
if (!completed) {
|
|
23516
|
+
this.unsettled.set(op, {
|
|
23517
|
+
logicalId: op.logicalId,
|
|
23518
|
+
state: "failed"
|
|
23519
|
+
});
|
|
23520
|
+
return;
|
|
23521
|
+
}
|
|
23522
|
+
for (const [key, entry] of this.unsettled) if (key === op || entry.logicalId === op.logicalId) this.unsettled.delete(key);
|
|
23523
|
+
}
|
|
23524
|
+
record(logicalId, write, record, policiesChanged, via) {
|
|
23525
|
+
if (via === "cc-api" && !isStandaloneInlinePolicyType(record.resourceType)) {
|
|
23526
|
+
this.entries.delete(logicalId);
|
|
23527
|
+
return;
|
|
23528
|
+
}
|
|
23529
|
+
this.entries.set(logicalId, {
|
|
23530
|
+
write,
|
|
23531
|
+
record,
|
|
23532
|
+
policiesChanged
|
|
23533
|
+
});
|
|
23534
|
+
}
|
|
23535
|
+
claimedFor(resourceType, logicalId, stateResources) {
|
|
23536
|
+
if (resourceType !== "AWS::IAM::Policy" && !Object.hasOwn(PRINCIPAL_KINDS, resourceType) && !isStandaloneInlinePolicyType(resourceType)) return;
|
|
23537
|
+
return (kind, principal, policyName) => {
|
|
23538
|
+
const writers = /* @__PURE__ */ new Map();
|
|
23539
|
+
const changes = /* @__PURE__ */ new Map();
|
|
23540
|
+
for (const [lid, entry] of this.entries) {
|
|
23541
|
+
if (!Object.hasOwn(stateResources, lid) || stateResources[lid] !== entry.record) continue;
|
|
23542
|
+
writers.set(lid, entry.write);
|
|
23543
|
+
if (entry.policiesChanged) changes.set(lid, {
|
|
23544
|
+
logicalId: lid,
|
|
23545
|
+
changeType: "UPDATE",
|
|
23546
|
+
resourceType: entry.record.resourceType,
|
|
23547
|
+
propertyChanges: [{
|
|
23548
|
+
path: "Policies",
|
|
23549
|
+
oldValue: void 0,
|
|
23550
|
+
newValue: void 0,
|
|
23551
|
+
requiresReplacement: false
|
|
23552
|
+
}]
|
|
23553
|
+
});
|
|
23554
|
+
}
|
|
23555
|
+
const claimed = isInlinePolicyClaimedByCompletedWriter({
|
|
23556
|
+
selfLogicalId: logicalId,
|
|
23557
|
+
writers,
|
|
23558
|
+
changes,
|
|
23559
|
+
stateResources
|
|
23560
|
+
}, kind, principal, policyName);
|
|
23561
|
+
if (!claimed) {
|
|
23562
|
+
const key = removalKey(kind, principal, policyName);
|
|
23563
|
+
const noted = this.removals.get(key) ?? {
|
|
23564
|
+
kind,
|
|
23565
|
+
principal,
|
|
23566
|
+
policyName,
|
|
23567
|
+
removers: /* @__PURE__ */ new Map()
|
|
23568
|
+
};
|
|
23569
|
+
noted.removers.set(logicalId, stateResources[logicalId]);
|
|
23570
|
+
this.removals.set(key, noted);
|
|
23571
|
+
}
|
|
23572
|
+
return claimed;
|
|
23573
|
+
};
|
|
23574
|
+
}
|
|
23575
|
+
takeHeldRemovals(stateResources) {
|
|
23576
|
+
const held = [];
|
|
23577
|
+
for (const [key, { kind, principal, policyName, removers }] of this.removals) {
|
|
23578
|
+
const { holders, unreadable } = inlinePolicyHolders(stateResources, kind, principal, policyName);
|
|
23579
|
+
const others = holders.filter((h) => !removers.has(h.logicalId) || stateResources[h.logicalId] !== removers.get(h.logicalId));
|
|
23580
|
+
if (others.length === 0) continue;
|
|
23581
|
+
this.removals.delete(key);
|
|
23582
|
+
const open = new Set([...this.unsettled.values()].map((u) => u.logicalId));
|
|
23583
|
+
const unsettled = [...new Set(others.map((h) => h.logicalId))].filter((id) => open.has(id));
|
|
23584
|
+
held.push({
|
|
23585
|
+
kind,
|
|
23586
|
+
holders: others,
|
|
23587
|
+
unreadable,
|
|
23588
|
+
unsettled
|
|
23589
|
+
});
|
|
23590
|
+
}
|
|
23591
|
+
return held;
|
|
23592
|
+
}
|
|
23593
|
+
};
|
|
23594
|
+
function inlinePolicyHolders(stateResources, kind, principal, policyName) {
|
|
23595
|
+
const holders = [];
|
|
23596
|
+
const unreadable = [];
|
|
23597
|
+
const unnamed = (v) => typeof v !== "string" || !IAM_NAME.test(v);
|
|
23598
|
+
for (const [logicalId, record] of Object.entries(stateResources)) {
|
|
23599
|
+
if (record === null || typeof record !== "object") continue;
|
|
23600
|
+
const props = record.properties ?? {};
|
|
23601
|
+
const standalone = standaloneInlinePolicyOf(record.resourceType, props);
|
|
23602
|
+
if (standalone !== void 0) {
|
|
23603
|
+
if (standalone.kind !== kind) continue;
|
|
23604
|
+
const principalMatches = sameIamName(standalone.principal, principal);
|
|
23605
|
+
const nameMatches = sameIamName(standalone.policyName, policyName);
|
|
23606
|
+
if (principalMatches && nameMatches) holders.push({
|
|
23607
|
+
logicalId,
|
|
23608
|
+
principal: standalone.principal,
|
|
23609
|
+
policyName: standalone.policyName,
|
|
23610
|
+
document: props["PolicyDocument"]
|
|
23611
|
+
});
|
|
23612
|
+
else if ((principalMatches || unnamed(standalone.principal)) && (nameMatches || unnamed(standalone.policyName))) unreadable.push(logicalId);
|
|
23613
|
+
continue;
|
|
23614
|
+
}
|
|
23615
|
+
if (record.resourceType === "AWS::IAM::Policy") {
|
|
23616
|
+
const nameMatches = sameIamName(record.physicalId, policyName);
|
|
23617
|
+
if (!nameMatches && !unnamed(record.physicalId)) continue;
|
|
23618
|
+
const listed = props[POLICY_LIST_FIELDS[kind]];
|
|
23619
|
+
const entries = Array.isArray(listed) ? listed : [];
|
|
23620
|
+
const named = entries.find((p) => sameIamName(p, principal));
|
|
23621
|
+
if (nameMatches && typeof named === "string") holders.push({
|
|
23622
|
+
logicalId,
|
|
23623
|
+
principal: named,
|
|
23624
|
+
policyName: record.physicalId,
|
|
23625
|
+
document: props["PolicyDocument"]
|
|
23626
|
+
});
|
|
23627
|
+
else if (named !== void 0 || entries.some(unnamed)) unreadable.push(logicalId);
|
|
23628
|
+
continue;
|
|
23629
|
+
}
|
|
23630
|
+
if (!Object.hasOwn(PRINCIPAL_KINDS, record.resourceType)) continue;
|
|
23631
|
+
if (PRINCIPAL_KINDS[record.resourceType] !== kind) continue;
|
|
23632
|
+
const principalMatches = sameIamName(record.physicalId, principal);
|
|
23633
|
+
if (!principalMatches && !unnamed(record.physicalId)) continue;
|
|
23634
|
+
const policies = props["Policies"];
|
|
23635
|
+
if (!Array.isArray(policies)) continue;
|
|
23636
|
+
for (const entry of policies) {
|
|
23637
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) continue;
|
|
23638
|
+
const name = entry["PolicyName"];
|
|
23639
|
+
if (principalMatches && sameIamName(name, policyName)) holders.push({
|
|
23640
|
+
logicalId,
|
|
23641
|
+
principal: record.physicalId,
|
|
23642
|
+
policyName: name,
|
|
23643
|
+
document: entry["PolicyDocument"]
|
|
23644
|
+
});
|
|
23645
|
+
else if (unnamed(name) || sameIamName(name, policyName)) unreadable.push(logicalId);
|
|
23646
|
+
}
|
|
23647
|
+
}
|
|
23648
|
+
return {
|
|
23649
|
+
holders,
|
|
23650
|
+
unreadable: [...new Set(unreadable)]
|
|
23651
|
+
};
|
|
23652
|
+
}
|
|
23653
|
+
function sameIamName(a, b) {
|
|
23654
|
+
return typeof a === "string" && IAM_NAME.test(a) && a.toLowerCase() === b.toLowerCase();
|
|
23655
|
+
}
|
|
23656
|
+
function removalKey(kind, principal, policyName) {
|
|
23657
|
+
return JSON.stringify([
|
|
23658
|
+
kind,
|
|
23659
|
+
principal.toLowerCase(),
|
|
23660
|
+
policyName.toLowerCase()
|
|
23661
|
+
]);
|
|
23662
|
+
}
|
|
23663
|
+
const IAM_NAME = /^[\w+=,.@-]+$/;
|
|
23664
|
+
const POLICY_LIST_FIELDS = {
|
|
23665
|
+
role: "Roles",
|
|
23666
|
+
group: "Groups",
|
|
23667
|
+
user: "Users"
|
|
23668
|
+
};
|
|
23669
|
+
const PRINCIPAL_KINDS = {
|
|
23670
|
+
"AWS::IAM::Role": "role",
|
|
23671
|
+
"AWS::IAM::Group": "group",
|
|
23672
|
+
"AWS::IAM::User": "user"
|
|
23673
|
+
};
|
|
23674
|
+
const STANDALONE_POLICY_KINDS = {
|
|
23675
|
+
"AWS::IAM::RolePolicy": "role",
|
|
23676
|
+
"AWS::IAM::GroupPolicy": "group",
|
|
23677
|
+
"AWS::IAM::UserPolicy": "user"
|
|
23678
|
+
};
|
|
23679
|
+
function isStandaloneInlinePolicyType(resourceType) {
|
|
23680
|
+
return Object.hasOwn(STANDALONE_POLICY_KINDS, resourceType);
|
|
23681
|
+
}
|
|
23682
|
+
function standaloneInlinePolicyOf(resourceType, properties) {
|
|
23683
|
+
if (!isStandaloneInlinePolicyType(resourceType)) return void 0;
|
|
23684
|
+
const kind = STANDALONE_POLICY_KINDS[resourceType];
|
|
23685
|
+
const props = properties ?? {};
|
|
23686
|
+
return {
|
|
23687
|
+
kind,
|
|
23688
|
+
principal: props[PRINCIPAL_NAME_FIELDS[kind]],
|
|
23689
|
+
policyName: props["PolicyName"]
|
|
23690
|
+
};
|
|
23691
|
+
}
|
|
23692
|
+
function standaloneInlinePolicyHandedOff(resourceType, properties, claimed, physicalId) {
|
|
23693
|
+
if (claimed === void 0) return false;
|
|
23694
|
+
const target = standaloneInlinePolicyOf(resourceType, properties);
|
|
23695
|
+
if (target === void 0) return false;
|
|
23696
|
+
const { kind, principal, policyName } = target;
|
|
23697
|
+
if (typeof principal !== "string" || !IAM_NAME.test(principal)) return false;
|
|
23698
|
+
if (typeof policyName !== "string" || !IAM_NAME.test(policyName)) return false;
|
|
23699
|
+
if (physicalId !== void 0 && physicalId.toLowerCase() !== `${policyName}|${principal}`.toLowerCase()) return false;
|
|
23700
|
+
return claimed(kind, principal, policyName) === true;
|
|
23701
|
+
}
|
|
23702
|
+
function isInlinePolicyPrincipalType(resourceType) {
|
|
23703
|
+
return Object.hasOwn(PRINCIPAL_KINDS, resourceType);
|
|
23704
|
+
}
|
|
23705
|
+
function principalKindOf(resourceType) {
|
|
23706
|
+
return Object.hasOwn(PRINCIPAL_KINDS, resourceType) ? PRINCIPAL_KINDS[resourceType] : void 0;
|
|
23707
|
+
}
|
|
23708
|
+
const ANY = "*";
|
|
23709
|
+
const PRINCIPAL_NAME_FIELDS = {
|
|
23710
|
+
role: "RoleName",
|
|
23711
|
+
group: "GroupName",
|
|
23712
|
+
user: "UserName"
|
|
23713
|
+
};
|
|
23714
|
+
function nameKey(value) {
|
|
23715
|
+
return typeof value === "string" && IAM_NAME.test(value) ? value.toLowerCase() : ANY;
|
|
23716
|
+
}
|
|
23717
|
+
function templatePrincipalKeys(entry, kind, templateResources, stateResources) {
|
|
23718
|
+
if (typeof entry === "string") return [nameKey(entry)];
|
|
23719
|
+
const ref = typeof entry === "object" && entry !== null && !Array.isArray(entry) ? entry["Ref"] : void 0;
|
|
23720
|
+
if (typeof ref !== "string" || !Object.hasOwn(templateResources, ref)) return [ANY];
|
|
23721
|
+
const target = templateResources[ref];
|
|
23722
|
+
if (principalKindOf(target.Type) !== kind) return [ANY];
|
|
23723
|
+
return principalSelfKeys(ref, kind, target, stateResources);
|
|
23724
|
+
}
|
|
23725
|
+
function principalSelfKeys(logicalId, kind, template, stateResources) {
|
|
23726
|
+
const keys = [`ref:${logicalId}`];
|
|
23727
|
+
const declared = template?.Properties?.[PRINCIPAL_NAME_FIELDS[kind]];
|
|
23728
|
+
const declaredKey = nameKey(declared);
|
|
23729
|
+
if (declaredKey !== ANY) keys.push(declaredKey);
|
|
23730
|
+
const record = Object.hasOwn(stateResources, logicalId) ? stateResources[logicalId] : void 0;
|
|
23731
|
+
if (record !== void 0 && principalKindOf(record.resourceType) === kind) {
|
|
23732
|
+
const recordedKey = nameKey(record.physicalId);
|
|
23733
|
+
if (recordedKey !== ANY) keys.push(recordedKey);
|
|
23734
|
+
}
|
|
23735
|
+
return keys;
|
|
23736
|
+
}
|
|
23737
|
+
function policiesNames(policies) {
|
|
23738
|
+
if (policies === void 0 || policies === null) return [];
|
|
23739
|
+
if (!Array.isArray(policies)) return [ANY];
|
|
23740
|
+
return policies.map((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry) ? nameKey(entry["PolicyName"]) : ANY);
|
|
23741
|
+
}
|
|
23742
|
+
function standaloneClaimSides(logicalId, change, templateResources, stateResources) {
|
|
23743
|
+
const template = Object.hasOwn(templateResources, logicalId) ? templateResources[logicalId] : void 0;
|
|
23744
|
+
const desired = standaloneInlinePolicyOf(change.resourceType, template?.Properties ?? {});
|
|
23745
|
+
if (desired === void 0) return void 0;
|
|
23746
|
+
const { kind } = desired;
|
|
23747
|
+
const name = nameKey(desired.policyName);
|
|
23748
|
+
const sides = {
|
|
23749
|
+
desired: templatePrincipalKeys(desired.principal, kind, templateResources, stateResources).map((principal) => ({
|
|
23750
|
+
kind,
|
|
23751
|
+
principal,
|
|
23752
|
+
name
|
|
23753
|
+
})),
|
|
23754
|
+
old: []
|
|
23755
|
+
};
|
|
23756
|
+
const record = Object.hasOwn(stateResources, logicalId) ? stateResources[logicalId] : void 0;
|
|
23757
|
+
if (record?.resourceType === change.resourceType) {
|
|
23758
|
+
const old = standaloneInlinePolicyOf(change.resourceType, record.properties);
|
|
23759
|
+
sides.old.push({
|
|
23760
|
+
kind,
|
|
23761
|
+
principal: nameKey(old.principal),
|
|
23762
|
+
name: nameKey(old.policyName)
|
|
23763
|
+
});
|
|
23764
|
+
}
|
|
23765
|
+
return sides;
|
|
23766
|
+
}
|
|
23767
|
+
function inlinePolicyClaimsOf(logicalId, change, templateResources, stateResources) {
|
|
23768
|
+
const template = Object.hasOwn(templateResources, logicalId) ? templateResources[logicalId] : void 0;
|
|
23769
|
+
const record = Object.hasOwn(stateResources, logicalId) ? stateResources[logicalId] : void 0;
|
|
23770
|
+
const recorded = record?.resourceType === change.resourceType ? record : void 0;
|
|
23771
|
+
const claims = [];
|
|
23772
|
+
const cross = (kind, principals, names) => {
|
|
23773
|
+
for (const principal of new Set(principals)) for (const name of new Set(names)) claims.push({
|
|
23774
|
+
kind,
|
|
23775
|
+
principal,
|
|
23776
|
+
name
|
|
23777
|
+
});
|
|
23778
|
+
};
|
|
23779
|
+
const standalone = standaloneClaimSides(logicalId, change, templateResources, stateResources);
|
|
23780
|
+
if (standalone !== void 0) return [...standalone.desired, ...standalone.old];
|
|
23781
|
+
if (change.resourceType === "AWS::IAM::Policy") {
|
|
23782
|
+
const desired = template?.Properties ?? {};
|
|
23783
|
+
const old = recorded?.properties ?? {};
|
|
23784
|
+
const names = [];
|
|
23785
|
+
if (desired["PolicyName"] !== void 0) names.push(nameKey(desired["PolicyName"]));
|
|
23786
|
+
if (old["PolicyName"] !== void 0) names.push(nameKey(old["PolicyName"]));
|
|
23787
|
+
if (recorded !== void 0) names.push(nameKey(typeof recorded.physicalId === "string" ? recorded.physicalId.split(":")[0] : recorded.physicalId));
|
|
23788
|
+
if (names.length === 0) return claims;
|
|
23789
|
+
for (const kind of [
|
|
23790
|
+
"role",
|
|
23791
|
+
"group",
|
|
23792
|
+
"user"
|
|
23793
|
+
]) {
|
|
23794
|
+
const field = POLICY_LIST_FIELDS[kind];
|
|
23795
|
+
const principals = [];
|
|
23796
|
+
const listed = desired[field];
|
|
23797
|
+
if (Array.isArray(listed)) for (const entry of listed) principals.push(...templatePrincipalKeys(entry, kind, templateResources, stateResources));
|
|
23798
|
+
else if (listed !== void 0) principals.push(ANY);
|
|
23799
|
+
const held = old[field];
|
|
23800
|
+
if (Array.isArray(held)) principals.push(...held.map(nameKey));
|
|
23801
|
+
else if (held !== void 0 && held !== null) principals.push(ANY);
|
|
23802
|
+
cross(kind, principals, names);
|
|
23803
|
+
}
|
|
23804
|
+
return claims;
|
|
23805
|
+
}
|
|
23806
|
+
const kind = principalKindOf(change.resourceType);
|
|
23807
|
+
if (kind === void 0) return claims;
|
|
23808
|
+
if (!(change.changeType === "CREATE" || (change.propertyChanges ?? []).some((c) => c.requiresReplacement) || ownPoliciesChange(change))) return claims;
|
|
23809
|
+
cross(kind, principalSelfKeys(logicalId, kind, template, stateResources), [...policiesNames(template?.Properties?.["Policies"]), ...policiesNames(recorded?.properties?.["Policies"])]);
|
|
23810
|
+
return claims;
|
|
23811
|
+
}
|
|
23812
|
+
function orderInlinePolicyWriters(args) {
|
|
23813
|
+
const { writes, changes, templateResources, stateResources, dependencies, rank } = args;
|
|
23814
|
+
const writers = writes.filter((id) => {
|
|
23815
|
+
const type = changes.get(id)?.resourceType;
|
|
23816
|
+
return type === "AWS::IAM::Policy" || type !== void 0 && (isInlinePolicyPrincipalType(type) || isStandaloneInlinePolicyType(type));
|
|
23817
|
+
});
|
|
23818
|
+
if (writers.length < 2) return [];
|
|
23819
|
+
const exact = /* @__PURE__ */ new Map();
|
|
23820
|
+
const loose = [];
|
|
23821
|
+
const all = [];
|
|
23822
|
+
for (const id of writers) for (const claim of inlinePolicyClaimsOf(id, changes.get(id), templateResources, stateResources)) {
|
|
23823
|
+
all.push({
|
|
23824
|
+
id,
|
|
23825
|
+
claim
|
|
23826
|
+
});
|
|
23827
|
+
if (claim.principal === ANY || claim.name === ANY) {
|
|
23828
|
+
loose.push({
|
|
23829
|
+
id,
|
|
23830
|
+
claim
|
|
23831
|
+
});
|
|
23832
|
+
continue;
|
|
23833
|
+
}
|
|
23834
|
+
const key = JSON.stringify([
|
|
23835
|
+
claim.kind,
|
|
23836
|
+
claim.principal,
|
|
23837
|
+
claim.name
|
|
23838
|
+
]);
|
|
23839
|
+
const ids = exact.get(key) ?? /* @__PURE__ */ new Set();
|
|
23840
|
+
ids.add(id);
|
|
23841
|
+
exact.set(key, ids);
|
|
23842
|
+
}
|
|
23843
|
+
const principalRows = new Set(writers.filter((id) => isInlinePolicyPrincipalType(changes.get(id).resourceType)));
|
|
23844
|
+
const standaloneRows = new Set(writers.filter((id) => isStandaloneInlinePolicyType(changes.get(id).resourceType)));
|
|
23845
|
+
const sides = new Map([...standaloneRows].map((id) => [id, standaloneClaimSides(id, changes.get(id), templateResources, stateResources)]));
|
|
23846
|
+
const sameClaim = (p, q) => p.kind === q.kind && (p.principal === ANY || q.principal === ANY || p.principal === q.principal) && (p.name === ANY || q.name === ANY || p.name === q.name);
|
|
23847
|
+
const releasesFor = (a, b) => sides.get(a).old.some((o) => sides.get(b).desired.some((d) => sameClaim(o, d)));
|
|
23848
|
+
const pairs = /* @__PURE__ */ new Map();
|
|
23849
|
+
const pair = (a, b) => {
|
|
23850
|
+
if (a === b || principalRows.has(a) && principalRows.has(b)) return;
|
|
23851
|
+
const sa = standaloneRows.has(a);
|
|
23852
|
+
const sb = standaloneRows.has(b);
|
|
23853
|
+
let first;
|
|
23854
|
+
if (sa !== sb) first = sa ? b : a;
|
|
23855
|
+
else if (sa && sb) {
|
|
23856
|
+
const aReleases = releasesFor(a, b);
|
|
23857
|
+
if (aReleases !== releasesFor(b, a)) first = aReleases ? a : b;
|
|
23858
|
+
}
|
|
23859
|
+
first ??= rank(a) < rank(b) || rank(a) === rank(b) && a < b ? a : b;
|
|
23860
|
+
const [x, y] = first === a ? [a, b] : [b, a];
|
|
23861
|
+
pairs.set(JSON.stringify([x, y]), [x, y]);
|
|
23862
|
+
};
|
|
23863
|
+
for (const ids of exact.values()) {
|
|
23864
|
+
const list = [...ids];
|
|
23865
|
+
for (let i = 0; i < list.length; i++) for (let j = i + 1; j < list.length; j++) pair(list[i], list[j]);
|
|
23866
|
+
}
|
|
23867
|
+
const meets = (a, b) => a === ANY || b === ANY || a === b;
|
|
23868
|
+
for (const { id, claim } of loose) for (const other of all) {
|
|
23869
|
+
if (other.claim.kind !== claim.kind) continue;
|
|
23870
|
+
if (meets(claim.principal, other.claim.principal) && meets(claim.name, other.claim.name)) pair(id, other.id);
|
|
23871
|
+
}
|
|
23872
|
+
if (pairs.size === 0) return [];
|
|
23873
|
+
const reaches = (from, to) => {
|
|
23874
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23875
|
+
const stack = [from];
|
|
23876
|
+
while (stack.length > 0) {
|
|
23877
|
+
const id = stack.pop();
|
|
23878
|
+
if (id === to) return true;
|
|
23879
|
+
if (seen.has(id)) continue;
|
|
23880
|
+
seen.add(id);
|
|
23881
|
+
for (const dep of dependencies.get(id) ?? []) stack.push(dep);
|
|
23882
|
+
}
|
|
23883
|
+
return false;
|
|
23884
|
+
};
|
|
23885
|
+
const added = [];
|
|
23886
|
+
for (const [before, after] of pairs.values()) {
|
|
23887
|
+
if (reaches(after, before) || reaches(before, after)) continue;
|
|
23888
|
+
const deps = dependencies.get(after) ?? /* @__PURE__ */ new Set();
|
|
23889
|
+
deps.add(before);
|
|
23890
|
+
dependencies.set(after, deps);
|
|
23891
|
+
added.push({
|
|
23892
|
+
before,
|
|
23893
|
+
after
|
|
23894
|
+
});
|
|
23895
|
+
}
|
|
23896
|
+
return added;
|
|
23897
|
+
}
|
|
23898
|
+
function recordInlinePolicyKeys(record) {
|
|
23899
|
+
if (record === void 0 || record === null || typeof record !== "object") return [];
|
|
23900
|
+
const props = record.properties ?? {};
|
|
23901
|
+
const keys = [];
|
|
23902
|
+
const add = (kind, principal, name) => {
|
|
23903
|
+
keys.push(JSON.stringify([
|
|
23904
|
+
kind,
|
|
23905
|
+
nameKey(principal),
|
|
23906
|
+
nameKey(name)
|
|
23907
|
+
]));
|
|
23908
|
+
};
|
|
23909
|
+
const standalone = standaloneInlinePolicyOf(record.resourceType, props);
|
|
23910
|
+
if (standalone !== void 0) {
|
|
23911
|
+
add(standalone.kind, standalone.principal, standalone.policyName);
|
|
23912
|
+
return keys;
|
|
23913
|
+
}
|
|
23914
|
+
if (record.resourceType === "AWS::IAM::Policy") {
|
|
23915
|
+
const names = [props["PolicyName"], typeof record.physicalId === "string" ? record.physicalId.split(":")[0] : record.physicalId];
|
|
23916
|
+
for (const kind of [
|
|
23917
|
+
"role",
|
|
23918
|
+
"group",
|
|
23919
|
+
"user"
|
|
23920
|
+
]) {
|
|
23921
|
+
const listed = props[POLICY_LIST_FIELDS[kind]];
|
|
23922
|
+
if (listed === void 0 || listed === null) continue;
|
|
23923
|
+
const principals = Array.isArray(listed) ? listed : [void 0];
|
|
23924
|
+
for (const p of principals) for (const n of names) add(kind, p, n);
|
|
23925
|
+
}
|
|
23926
|
+
return keys;
|
|
23927
|
+
}
|
|
23928
|
+
const kind = principalKindOf(record.resourceType);
|
|
23929
|
+
if (kind === void 0) return keys;
|
|
23930
|
+
for (const name of policiesNames(props["Policies"])) add(kind, record.physicalId, name);
|
|
23931
|
+
return keys;
|
|
23932
|
+
}
|
|
23933
|
+
function orderInlinePolicyReversals(replay, stateResources) {
|
|
23934
|
+
const current = (op) => Object.hasOwn(stateResources, op.logicalId) ? stateResources[op.logicalId] : void 0;
|
|
23935
|
+
const isStandalone = (op) => [current(op), op.previousState].some((r) => r !== void 0 && r !== null && isStandaloneInlinePolicyType(r.resourceType));
|
|
23936
|
+
if (!replay.some(isStandalone)) return [...replay];
|
|
23937
|
+
const keysOf = /* @__PURE__ */ new Map();
|
|
23938
|
+
for (const op of replay) {
|
|
23939
|
+
const keys = [...recordInlinePolicyKeys(current(op)), ...recordInlinePolicyKeys(op.previousState)];
|
|
23940
|
+
if (keys.length > 0) keysOf.set(op, new Set(keys));
|
|
23941
|
+
}
|
|
23942
|
+
if (keysOf.size < 2) return [...replay];
|
|
23943
|
+
const meets = (a, b) => a === ANY || b === ANY || a === b;
|
|
23944
|
+
const collide = (a, b) => {
|
|
23945
|
+
for (const ka of a) {
|
|
23946
|
+
const [kindA, pA, nA] = JSON.parse(ka);
|
|
23947
|
+
for (const kb of b) {
|
|
23948
|
+
const [kindB, pB, nB] = JSON.parse(kb);
|
|
23949
|
+
if (kindA === kindB && meets(pA, pB) && meets(nA, nB)) return true;
|
|
23950
|
+
}
|
|
23951
|
+
}
|
|
23952
|
+
return false;
|
|
23953
|
+
};
|
|
23954
|
+
const dependsOn = (a, b) => [current(a), a.previousState].some((r) => Array.isArray(r?.dependencies) && r.dependencies.includes(b.logicalId));
|
|
23955
|
+
const order = [...replay];
|
|
23956
|
+
for (const op of [...replay].reverse()) {
|
|
23957
|
+
if (!isStandalone(op)) continue;
|
|
23958
|
+
const mine = keysOf.get(op);
|
|
23959
|
+
if (mine === void 0) continue;
|
|
23960
|
+
const at = order.indexOf(op);
|
|
23961
|
+
let last = -1;
|
|
23962
|
+
for (let j = at + 1; j < order.length; j++) {
|
|
23963
|
+
const other = order[j];
|
|
23964
|
+
const theirs = keysOf.get(other);
|
|
23965
|
+
if (dependsOn(op, other) || dependsOn(other, op)) break;
|
|
23966
|
+
if (theirs === void 0 || isStandalone(other)) continue;
|
|
23967
|
+
if (collide(mine, theirs)) last = j;
|
|
23968
|
+
}
|
|
23969
|
+
if (last < 0) continue;
|
|
23970
|
+
order.splice(at, 1);
|
|
23971
|
+
order.splice(last, 0, op);
|
|
23972
|
+
}
|
|
23973
|
+
return order;
|
|
23974
|
+
}
|
|
23975
|
+
function ownPoliciesChange(change) {
|
|
23976
|
+
return (change?.propertyChanges ?? []).some((c) => c.path === "Policies" && c.inPlacePropagated !== true && c.replacementPropagated !== true);
|
|
23977
|
+
}
|
|
23978
|
+
|
|
23011
23979
|
//#endregion
|
|
23012
23980
|
//#region src/provisioning/json-patch-generator.ts
|
|
23013
23981
|
var JsonPatchGenerator = class {
|
|
@@ -23125,15 +24093,200 @@ function escapeRegExp$1(text) {
|
|
|
23125
24093
|
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
23126
24094
|
}
|
|
23127
24095
|
|
|
24096
|
+
//#endregion
|
|
24097
|
+
//#region src/provisioning/getatt-attributes.generated.ts
|
|
24098
|
+
const WRITABLE_GETATT_ATTRIBUTES = {
|
|
24099
|
+
"AWS::Amplify::Branch": ["BranchName"],
|
|
24100
|
+
"AWS::Amplify::Domain": [
|
|
24101
|
+
"AutoSubDomainCreationPatterns",
|
|
24102
|
+
"AutoSubDomainIAMRole",
|
|
24103
|
+
"DomainName",
|
|
24104
|
+
"EnableAutoSubDomain"
|
|
24105
|
+
],
|
|
24106
|
+
"AWS::AppMesh::GatewayRoute": [
|
|
24107
|
+
"GatewayRouteName",
|
|
24108
|
+
"MeshName",
|
|
24109
|
+
"MeshOwner",
|
|
24110
|
+
"VirtualGatewayName"
|
|
24111
|
+
],
|
|
24112
|
+
"AWS::AppMesh::Mesh": ["MeshName"],
|
|
24113
|
+
"AWS::AppMesh::Route": [
|
|
24114
|
+
"MeshName",
|
|
24115
|
+
"MeshOwner",
|
|
24116
|
+
"RouteName",
|
|
24117
|
+
"VirtualRouterName"
|
|
24118
|
+
],
|
|
24119
|
+
"AWS::AppMesh::VirtualGateway": [
|
|
24120
|
+
"MeshName",
|
|
24121
|
+
"MeshOwner",
|
|
24122
|
+
"VirtualGatewayName"
|
|
24123
|
+
],
|
|
24124
|
+
"AWS::AppMesh::VirtualNode": [
|
|
24125
|
+
"MeshName",
|
|
24126
|
+
"MeshOwner",
|
|
24127
|
+
"VirtualNodeName"
|
|
24128
|
+
],
|
|
24129
|
+
"AWS::AppMesh::VirtualRouter": [
|
|
24130
|
+
"MeshName",
|
|
24131
|
+
"MeshOwner",
|
|
24132
|
+
"VirtualRouterName"
|
|
24133
|
+
],
|
|
24134
|
+
"AWS::AppMesh::VirtualService": [
|
|
24135
|
+
"MeshName",
|
|
24136
|
+
"MeshOwner",
|
|
24137
|
+
"VirtualServiceName"
|
|
24138
|
+
],
|
|
24139
|
+
"AWS::AppSync::DataSource": ["Name"],
|
|
24140
|
+
"AWS::AppSync::DomainName": ["DomainName"],
|
|
24141
|
+
"AWS::AppSync::FunctionConfiguration": ["DataSourceName", "Name"],
|
|
24142
|
+
"AWS::AppSync::Resolver": ["FieldName", "TypeName"],
|
|
24143
|
+
"AWS::Backup::BackupSelection": ["BackupPlanId"],
|
|
24144
|
+
"AWS::Backup::BackupVault": ["BackupVaultName"],
|
|
24145
|
+
"AWS::CleanRooms::AnalysisTemplate": ["Schema"],
|
|
24146
|
+
"AWS::Cloud9::EnvironmentEC2": ["Name"],
|
|
24147
|
+
"AWS::CloudWatch::InsightRule": ["RuleName"],
|
|
24148
|
+
"AWS::CodeArtifact::Domain": ["EncryptionKey"],
|
|
24149
|
+
"AWS::CodeArtifact::Repository": ["DomainName", "DomainOwner"],
|
|
24150
|
+
"AWS::EC2::CapacityReservation": [
|
|
24151
|
+
"AvailabilityZone",
|
|
24152
|
+
"InstanceType",
|
|
24153
|
+
"Tenancy"
|
|
24154
|
+
],
|
|
24155
|
+
"AWS::EC2::Instance": ["AvailabilityZone"],
|
|
24156
|
+
"AWS::EC2::SecurityGroup": ["VpcId"],
|
|
24157
|
+
"AWS::EC2::Subnet": [
|
|
24158
|
+
"AvailabilityZone",
|
|
24159
|
+
"AvailabilityZoneId",
|
|
24160
|
+
"CidrBlock",
|
|
24161
|
+
"OutpostArn",
|
|
24162
|
+
"VpcId"
|
|
24163
|
+
],
|
|
24164
|
+
"AWS::EC2::VPC": ["CidrBlock"],
|
|
24165
|
+
"AWS::EFS::MountTarget": ["IpAddress"],
|
|
24166
|
+
"AWS::EKS::Nodegroup": ["ClusterName", "NodegroupName"],
|
|
24167
|
+
"AWS::ElasticLoadBalancingV2::LoadBalancer": ["SecurityGroups"],
|
|
24168
|
+
"AWS::EventSchemas::Discoverer": ["CrossAccount"],
|
|
24169
|
+
"AWS::EventSchemas::Registry": ["RegistryName"],
|
|
24170
|
+
"AWS::EventSchemas::Schema": ["SchemaName"],
|
|
24171
|
+
"AWS::Events::EventBus": ["Name"],
|
|
24172
|
+
"AWS::GameLift::GameSessionQueue": ["Name"],
|
|
24173
|
+
"AWS::GameLift::MatchmakingConfiguration": ["Name"],
|
|
24174
|
+
"AWS::GameLift::MatchmakingRuleSet": ["Name"],
|
|
24175
|
+
"AWS::Grafana::Workspace": ["GrafanaVersion"],
|
|
24176
|
+
"AWS::Greengrass::ConnectorDefinition": ["Name"],
|
|
24177
|
+
"AWS::Greengrass::CoreDefinition": ["Name"],
|
|
24178
|
+
"AWS::Greengrass::DeviceDefinition": ["Name"],
|
|
24179
|
+
"AWS::Greengrass::FunctionDefinition": ["Name"],
|
|
24180
|
+
"AWS::Greengrass::Group": ["Name", "RoleArn"],
|
|
24181
|
+
"AWS::Greengrass::LoggerDefinition": ["Name"],
|
|
24182
|
+
"AWS::Greengrass::ResourceDefinition": ["Name"],
|
|
24183
|
+
"AWS::Greengrass::SubscriptionDefinition": ["Name"],
|
|
24184
|
+
"AWS::ImageBuilder::Component": ["Name"],
|
|
24185
|
+
"AWS::ImageBuilder::ContainerRecipe": ["Name"],
|
|
24186
|
+
"AWS::ImageBuilder::DistributionConfiguration": ["Name"],
|
|
24187
|
+
"AWS::ImageBuilder::ImagePipeline": ["Name"],
|
|
24188
|
+
"AWS::ImageBuilder::ImageRecipe": ["Name"],
|
|
24189
|
+
"AWS::ImageBuilder::InfrastructureConfiguration": ["Name"],
|
|
24190
|
+
"AWS::Kinesis::StreamConsumer": ["ConsumerName", "StreamARN"],
|
|
24191
|
+
"AWS::MSK::Cluster": ["CurrentVersion"],
|
|
24192
|
+
"AWS::ManagedBlockchain::Member": ["NetworkId"],
|
|
24193
|
+
"AWS::ManagedBlockchain::Node": ["MemberId", "NetworkId"],
|
|
24194
|
+
"AWS::MediaConvert::JobTemplate": ["Name"],
|
|
24195
|
+
"AWS::MediaConvert::Preset": ["Name"],
|
|
24196
|
+
"AWS::MediaConvert::Queue": ["Name"],
|
|
24197
|
+
"AWS::OpsWorks::Instance": ["AvailabilityZone"],
|
|
24198
|
+
"AWS::OpsWorks::UserProfile": ["SshUsername"],
|
|
24199
|
+
"AWS::QuickSight::Analysis": ["Errors", "Sheets"],
|
|
24200
|
+
"AWS::RDS::DBInstance": ["DBSystemId"],
|
|
24201
|
+
"AWS::RDS::DBParameterGroup": ["DBParameterGroupName"],
|
|
24202
|
+
"AWS::RedshiftServerless::Workgroup": ["Workgroup"],
|
|
24203
|
+
"AWS::RoboMaker::RobotApplication": ["CurrentRevisionId"],
|
|
24204
|
+
"AWS::RoboMaker::SimulationApplication": ["CurrentRevisionId"],
|
|
24205
|
+
"AWS::Route53Resolver::ResolverEndpoint": [
|
|
24206
|
+
"Direction",
|
|
24207
|
+
"Name",
|
|
24208
|
+
"OutpostArn",
|
|
24209
|
+
"PreferredInstanceType",
|
|
24210
|
+
"ResolverEndpointType"
|
|
24211
|
+
],
|
|
24212
|
+
"AWS::Route53Resolver::ResolverRule": [
|
|
24213
|
+
"DomainName",
|
|
24214
|
+
"Name",
|
|
24215
|
+
"ResolverEndpointId",
|
|
24216
|
+
"TargetIps"
|
|
24217
|
+
],
|
|
24218
|
+
"AWS::Route53Resolver::ResolverRuleAssociation": [
|
|
24219
|
+
"Name",
|
|
24220
|
+
"ResolverRuleId",
|
|
24221
|
+
"VPCId"
|
|
24222
|
+
],
|
|
24223
|
+
"AWS::S3::AccessPoint": ["Name"],
|
|
24224
|
+
"AWS::SNS::Topic": ["TopicName"],
|
|
24225
|
+
"AWS::SQS::Queue": ["QueueName"],
|
|
24226
|
+
"AWS::SSM::Parameter": ["Type"],
|
|
24227
|
+
"AWS::SSM::ResourceDataSync": ["SyncName"],
|
|
24228
|
+
"AWS::SageMaker::CodeRepository": ["CodeRepositoryName"],
|
|
24229
|
+
"AWS::SageMaker::Endpoint": ["EndpointName"],
|
|
24230
|
+
"AWS::SageMaker::EndpointConfig": ["EndpointConfigName"],
|
|
24231
|
+
"AWS::SageMaker::Model": ["ModelName"],
|
|
24232
|
+
"AWS::SageMaker::ModelPackage": ["LastModifiedTime"],
|
|
24233
|
+
"AWS::SageMaker::NotebookInstance": ["NotebookInstanceName"],
|
|
24234
|
+
"AWS::SageMaker::NotebookInstanceLifecycleConfig": ["NotebookInstanceLifecycleConfigName"],
|
|
24235
|
+
"AWS::SageMaker::Workteam": ["WorkteamName"],
|
|
24236
|
+
"AWS::ServiceDiscovery::Service": ["Name"],
|
|
24237
|
+
"AWS::Signer::SigningProfile": ["ProfileName"],
|
|
24238
|
+
"AWS::StepFunctions::Activity": ["Name"],
|
|
24239
|
+
"AWS::Transfer::User": ["ServerId", "UserName"]
|
|
24240
|
+
};
|
|
24241
|
+
const SHAPE_MISMATCHED_GETATT_ATTRIBUTES = {
|
|
24242
|
+
"AWS::DocDB::DBCluster": ["Port"],
|
|
24243
|
+
"AWS::Events::EventBus": ["Policy"],
|
|
24244
|
+
"AWS::MediaLive::Input": ["Destinations", "Sources"]
|
|
24245
|
+
};
|
|
24246
|
+
|
|
23128
24247
|
//#endregion
|
|
23129
24248
|
//#region src/provisioning/read-only-properties.ts
|
|
24249
|
+
function isDefinitiveRefusal(error) {
|
|
24250
|
+
if (isThrottlingError(error)) return false;
|
|
24251
|
+
let current = error;
|
|
24252
|
+
for (let depth = 0; depth < 5 && current != null; depth++) {
|
|
24253
|
+
const status = current.$metadata?.httpStatusCode;
|
|
24254
|
+
if (status !== void 0) return status >= 400 && status < 500;
|
|
24255
|
+
current = current.cause;
|
|
24256
|
+
}
|
|
24257
|
+
return false;
|
|
24258
|
+
}
|
|
23130
24259
|
const readOnlyPropertiesCache = /* @__PURE__ */ new Map();
|
|
24260
|
+
const attributeNamesCache = /* @__PURE__ */ new Map();
|
|
24261
|
+
async function getTopLevelAttributeNames(resourceType) {
|
|
24262
|
+
const readOnly = await getTopLevelReadOnlyProperties(resourceType);
|
|
24263
|
+
if (readOnly === void 0) return void 0;
|
|
24264
|
+
const writable = Object.hasOwn(WRITABLE_GETATT_ATTRIBUTES, resourceType) ? WRITABLE_GETATT_ATTRIBUTES[resourceType] : void 0;
|
|
24265
|
+
if (!writable || writable.length === 0) return readOnly;
|
|
24266
|
+
const cached = attributeNamesCache.get(resourceType);
|
|
24267
|
+
if (cached) return cached;
|
|
24268
|
+
const names = /* @__PURE__ */ new Set([...readOnly, ...writable]);
|
|
24269
|
+
attributeNamesCache.set(resourceType, names);
|
|
24270
|
+
return names;
|
|
24271
|
+
}
|
|
24272
|
+
const deployAttributeNamesCache = /* @__PURE__ */ new Map();
|
|
24273
|
+
async function getTopLevelDeployAttributeNames(resourceType) {
|
|
24274
|
+
const certified = await getTopLevelAttributeNames(resourceType);
|
|
24275
|
+
if (certified === void 0) return void 0;
|
|
24276
|
+
const mismatched = Object.hasOwn(SHAPE_MISMATCHED_GETATT_ATTRIBUTES, resourceType) ? SHAPE_MISMATCHED_GETATT_ATTRIBUTES[resourceType] : void 0;
|
|
24277
|
+
if (!mismatched || mismatched.length === 0) return certified;
|
|
24278
|
+
const cached = deployAttributeNamesCache.get(resourceType);
|
|
24279
|
+
if (cached) return cached;
|
|
24280
|
+
const names = /* @__PURE__ */ new Set([...certified, ...mismatched]);
|
|
24281
|
+
deployAttributeNamesCache.set(resourceType, names);
|
|
24282
|
+
return names;
|
|
24283
|
+
}
|
|
23131
24284
|
function getTopLevelReadOnlyProperties(resourceType) {
|
|
23132
24285
|
if (hasNoRegistrySchema(resourceType)) return Promise.resolve(void 0);
|
|
23133
24286
|
const cached = readOnlyPropertiesCache.get(resourceType);
|
|
23134
24287
|
if (cached) return cached;
|
|
23135
24288
|
const entry = fetchTopLevelReadOnlyProperties(resourceType).catch((error) => {
|
|
23136
|
-
readOnlyPropertiesCache.delete(resourceType);
|
|
24289
|
+
if (!isDefinitiveRefusal(error)) readOnlyPropertiesCache.delete(resourceType);
|
|
23137
24290
|
const message = describeAwsFailure(error).detail;
|
|
23138
24291
|
getLogger().child("ReadOnlyProperties").debug(`Failed to resolve read-only properties for ${displaySafe(resourceType, { asciiOnly: true })} via cloudformation:DescribeType (${message}).`);
|
|
23139
24292
|
});
|
|
@@ -23550,10 +24703,11 @@ var CloudControlProvider = class {
|
|
|
23550
24703
|
async create(logicalId, resourceType, properties, context) {
|
|
23551
24704
|
const mask = maskerOrIdentity(context?.maskSecrets);
|
|
23552
24705
|
const idLog = new IdScrubLog(this.logger, mask, holdsSecretDerivedLeaf(properties, mask));
|
|
23553
|
-
return inIdLogScope(idLog, () => this.createInIdLogScope(logicalId, resourceType, properties, idLog));
|
|
24706
|
+
return inIdLogScope(idLog, () => this.createInIdLogScope(logicalId, resourceType, properties, idLog, mask));
|
|
23554
24707
|
}
|
|
23555
|
-
async createInIdLogScope(logicalId, resourceType, properties, idLog) {
|
|
24708
|
+
async createInIdLogScope(logicalId, resourceType, properties, idLog, mask) {
|
|
23556
24709
|
this.logger.debug(`Creating resource ${logicalId} (${resourceType})`);
|
|
24710
|
+
const attributeNamesLookup = getTopLevelDeployAttributeNames(resourceType);
|
|
23557
24711
|
try {
|
|
23558
24712
|
const ccProperties = stringifyJsonProperties(resourceType, stripNullValues(properties));
|
|
23559
24713
|
const desiredState = JSON.stringify(ccProperties);
|
|
@@ -23571,7 +24725,9 @@ var CloudControlProvider = class {
|
|
|
23571
24725
|
const result = { physicalId: progressEvent.Identifier };
|
|
23572
24726
|
if (progressEvent.ResourceModel) result.attributes = this.parseResourceModel(progressEvent.ResourceModel);
|
|
23573
24727
|
result.attributes = await this.mergeSparseModelReadback(resourceType, progressEvent.Identifier, result.attributes || {});
|
|
24728
|
+
const modelKeys = Object.keys(result.attributes);
|
|
23574
24729
|
result.attributes = await this.enrichResourceAttributes(resourceType, progressEvent.Identifier, result.attributes);
|
|
24730
|
+
result.attributes = await this.maskDeployedNonAttributeKeys(result.attributes, modelKeys, resourceType, progressEvent.Identifier, attributeNamesLookup, properties, mask);
|
|
23575
24731
|
return result;
|
|
23576
24732
|
} catch (error) {
|
|
23577
24733
|
idLog.addId(identifierCarriedBy(error));
|
|
@@ -23642,6 +24798,7 @@ var CloudControlProvider = class {
|
|
|
23642
24798
|
sentNothing: true
|
|
23643
24799
|
};
|
|
23644
24800
|
}
|
|
24801
|
+
const attributeNamesLookup = getTopLevelDeployAttributeNames(resourceType);
|
|
23645
24802
|
const writeOnlyProperties = await getTopLevelWriteOnlyProperties(resourceType);
|
|
23646
24803
|
if (writeOnlyProperties.size > 0) {
|
|
23647
24804
|
const keptOnPreviousSide = getTopLevelKeysHoldingUnreadableCreateOnly(resourceType, cleanProperties);
|
|
@@ -23682,7 +24839,9 @@ var CloudControlProvider = class {
|
|
|
23682
24839
|
};
|
|
23683
24840
|
if (progressEvent.ResourceModel) result.attributes = this.parseResourceModel(progressEvent.ResourceModel, idLog);
|
|
23684
24841
|
result.attributes = await this.mergeSparseModelReadback(resourceType, physicalId, result.attributes || {}, idLog);
|
|
24842
|
+
const modelKeys = Object.keys(result.attributes);
|
|
23685
24843
|
result.attributes = await this.enrichResourceAttributes(resourceType, physicalId, result.attributes, idLog);
|
|
24844
|
+
result.attributes = await this.maskDeployedNonAttributeKeys(result.attributes, modelKeys, resourceType, physicalId, attributeNamesLookup, properties, maskerOrIdentity(context?.maskSecrets), idLog);
|
|
23686
24845
|
return result;
|
|
23687
24846
|
} catch (error) {
|
|
23688
24847
|
this.handleError(error, "UPDATE", resourceType, logicalId, physicalId);
|
|
@@ -23707,10 +24866,14 @@ var CloudControlProvider = class {
|
|
|
23707
24866
|
this.logger.debug(`Deleting resource ${logicalId} (${resourceType}), physical ID: ${physicalId}`);
|
|
23708
24867
|
if (context?.finalSnapshotIdentifier !== void 0) throw new ProvisioningError(`${logicalId} (${resourceType}) requires a final snapshot (DeletionPolicy: Snapshot), but the Cloud Control API delete route has no final-snapshot parameter. Re-run with --skip-final-snapshot after snapshotting manually, or retain the resource.`, resourceType, logicalId, physicalId);
|
|
23709
24868
|
await this.assertRecordedRegionAgainstClient("pre-delete", context?.expectedRegion, resourceType, logicalId, physicalId);
|
|
24869
|
+
if (standaloneInlinePolicyHandedOff(resourceType, properties, context?.inlinePolicyClaimed, physicalId)) {
|
|
24870
|
+
this.logger.info(` ${logicalId}: not deleting its inline policy, which another resource of this deploy now holds under the same name`);
|
|
24871
|
+
return;
|
|
24872
|
+
}
|
|
23710
24873
|
const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
|
|
23711
24874
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
23712
24875
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
23713
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
24876
|
+
const { ASGProvider } = await import("./asg-provider-B1xRE8do.js");
|
|
23714
24877
|
const delegateRegion = ambientRegion();
|
|
23715
24878
|
let asgProvider = this.asgDeleteDelegates.get(delegateRegion);
|
|
23716
24879
|
if (asgProvider === void 0) {
|
|
@@ -23816,7 +24979,7 @@ var CloudControlProvider = class {
|
|
|
23816
24979
|
const family = SDK_DELETE_FAMILIES.get(resourceType);
|
|
23817
24980
|
const cached = this.sdkDeleteDelegates.get(family);
|
|
23818
24981
|
if (cached !== void 0 && cached.region === ccRegion) return cached.provider;
|
|
23819
|
-
const provider = family === "rds" ? new (await (import("./rds-provider-
|
|
24982
|
+
const provider = family === "rds" ? new (await (import("./rds-provider-J40R0ysf.js"))).RDSProvider({ region: ccRegion }) : new (await (import("./neptune-provider-CU_1O2FR.js"))).NeptuneProvider({ region: ccRegion });
|
|
23820
24983
|
this.sdkDeleteDelegates.set(family, {
|
|
23821
24984
|
region: ccRegion,
|
|
23822
24985
|
provider
|
|
@@ -24461,6 +25624,25 @@ var CloudControlProvider = class {
|
|
|
24461
25624
|
throw error;
|
|
24462
25625
|
}
|
|
24463
25626
|
}
|
|
25627
|
+
async maskDeployedNonAttributeKeys(attributes, modelKeys, resourceType, physicalId, attributeNamesLookup, desired, mask, log = this.logger) {
|
|
25628
|
+
const safeType = displaySafe(resourceType, { asciiOnly: true });
|
|
25629
|
+
const attributeNames = await attributeNamesLookup;
|
|
25630
|
+
if (attributeNames === void 0) {
|
|
25631
|
+
log.debug(`Could not resolve the CloudFormation schema for ${safeType}; recording its Cloud Control model unnarrowed for ${displaySafe(physicalId, { asciiOnly: true })}.`);
|
|
25632
|
+
return attributes;
|
|
25633
|
+
}
|
|
25634
|
+
const narrowed = new Set(modelKeys.filter((key) => !attributeNames.has(key) && !isLiteralDeployOnlyKey(resourceType, key, desired, mask)));
|
|
25635
|
+
if (narrowed.size === 0) return attributes;
|
|
25636
|
+
const out = Object.create(null);
|
|
25637
|
+
for (const [key, value] of Object.entries(attributes)) Object.defineProperty(out, key, {
|
|
25638
|
+
value: narrowed.has(key) ? maskLeavesDeep(value) : value,
|
|
25639
|
+
enumerable: true,
|
|
25640
|
+
writable: true,
|
|
25641
|
+
configurable: true
|
|
25642
|
+
});
|
|
25643
|
+
log.debug(`Masked ${narrowed.size} non-attribute key(s) of the ${safeType} Cloud Control model for ${displaySafe(physicalId, { asciiOnly: true })}: they are not Fn::GetAtt attributes of the type.`);
|
|
25644
|
+
return out;
|
|
25645
|
+
}
|
|
24464
25646
|
async import(input) {
|
|
24465
25647
|
if (!input.knownPhysicalId) return null;
|
|
24466
25648
|
const identifier = toCloudControlIdentifier({
|
|
@@ -24501,7 +25683,7 @@ var CloudControlProvider = class {
|
|
|
24501
25683
|
async maskUncertifiedModelValues(model, resourceType, physicalId) {
|
|
24502
25684
|
const safeType = displaySafe(resourceType, { asciiOnly: true });
|
|
24503
25685
|
const safeId = displaySafe(physicalId, { asciiOnly: true });
|
|
24504
|
-
const attributeNames = await
|
|
25686
|
+
const attributeNames = await getTopLevelAttributeNames(resourceType);
|
|
24505
25687
|
if (attributeNames === void 0) {
|
|
24506
25688
|
if (!this.warnedUnresolvableSchemaTypes.has(resourceType)) {
|
|
24507
25689
|
this.warnedUnresolvableSchemaTypes.add(resourceType);
|
|
@@ -24515,7 +25697,7 @@ var CloudControlProvider = class {
|
|
|
24515
25697
|
masked[key] = maskLeavesDeep(value);
|
|
24516
25698
|
maskedCount++;
|
|
24517
25699
|
}
|
|
24518
|
-
if (maskedCount > 0 && attributeNames !== void 0) this.logger.debug(`Masked ${maskedCount} non-attribute key(s) out of the ${safeType} Cloud Control model for ${safeId}: they are not
|
|
25700
|
+
if (maskedCount > 0 && attributeNames !== void 0) this.logger.debug(`Masked ${maskedCount} non-attribute key(s) out of the ${safeType} Cloud Control model for ${safeId}: they are not Fn::GetAtt attributes of the type, so CloudFormation would reject an Fn::GetAtt naming them and cdkd has no evidence they are safe to persist.`);
|
|
24519
25701
|
return masked;
|
|
24520
25702
|
}
|
|
24521
25703
|
};
|
|
@@ -24616,6 +25798,11 @@ function holdsSecretDerivedLeaf(value, mask, depth = 0) {
|
|
|
24616
25798
|
return Object.entries(value).some(([key, entry]) => isSecretDerivedValue(key, mask) || holdsSecretDerivedLeaf(entry, mask, depth + 1));
|
|
24617
25799
|
}
|
|
24618
25800
|
const IMPORT_YIELDED_NO_ATTRIBUTES_CONSEQUENCE = "Attributes the stack's state already records for this physical id are kept; without them, an Fn::GetAtt against this resource falls back to a value constructed from its physical id.";
|
|
25801
|
+
function isLiteralDeployOnlyKey(resourceType, key, desired, mask) {
|
|
25802
|
+
if (resourceType !== "AWS::SSM::Parameter" || key !== "Value") return false;
|
|
25803
|
+
const declared = desired["Value"];
|
|
25804
|
+
return typeof declared === "string" && !isSecretDerivedValue(declared, mask);
|
|
25805
|
+
}
|
|
24619
25806
|
function maskLeavesDeep(value) {
|
|
24620
25807
|
if (Array.isArray(value)) return value.map((element) => maskLeavesDeep(element));
|
|
24621
25808
|
if (value !== null && typeof value === "object") {
|
|
@@ -24626,6 +25813,124 @@ function maskLeavesDeep(value) {
|
|
|
24626
25813
|
return "***";
|
|
24627
25814
|
}
|
|
24628
25815
|
|
|
25816
|
+
//#endregion
|
|
25817
|
+
//#region src/state/state-bucket-lifecycle.ts
|
|
25818
|
+
const CUSTOM_RESOURCE_RESPONSE_LIFECYCLE_RULE_ID = "cdkd-custom-resource-responses-expiration";
|
|
25819
|
+
const CUSTOM_RESOURCE_RESPONSE_EXPIRATION_DAYS = 1;
|
|
25820
|
+
const CUSTOM_RESOURCE_RESPONSE_LIFECYCLE_PREFIX = `${CUSTOM_RESOURCE_RESPONSE_PREFIX}/cdkd-1`;
|
|
25821
|
+
function buildCustomResourceResponseLifecycleRule(legacyPrefix) {
|
|
25822
|
+
const prefix = CUSTOM_RESOURCE_RESPONSE_LIFECYCLE_PREFIX;
|
|
25823
|
+
return {
|
|
25824
|
+
ID: CUSTOM_RESOURCE_RESPONSE_LIFECYCLE_RULE_ID,
|
|
25825
|
+
Status: "Enabled",
|
|
25826
|
+
...legacyPrefix ? { Prefix: prefix } : { Filter: { Prefix: prefix } },
|
|
25827
|
+
Expiration: { Days: 1 },
|
|
25828
|
+
NoncurrentVersionExpiration: { NoncurrentDays: 1 }
|
|
25829
|
+
};
|
|
25830
|
+
}
|
|
25831
|
+
async function applyCustomResourceResponseExpiration(s3, bucket, opts = {}) {
|
|
25832
|
+
const owner = opts.expectedBucketOwner ? { ExpectedBucketOwner: opts.expectedBucketOwner } : {};
|
|
25833
|
+
const first = await readLifecycle(s3, bucket, owner);
|
|
25834
|
+
if ("failed" in first) return first.failed;
|
|
25835
|
+
if (first.rules.some((rule) => rule.ID === "cdkd-custom-resource-responses-expiration")) return { status: "present" };
|
|
25836
|
+
let collision;
|
|
25837
|
+
try {
|
|
25838
|
+
collision = await findStateUnderResponsePrefix(s3, bucket, owner);
|
|
25839
|
+
} catch (error) {
|
|
25840
|
+
return failedOutcome("ListObjectsV2", error);
|
|
25841
|
+
}
|
|
25842
|
+
if (collision !== void 0) return {
|
|
25843
|
+
status: "refused",
|
|
25844
|
+
reason: `${CUSTOM_RESOURCE_RESPONSE_PREFIX}/${displaySafe(collision) || "<unrenderable>"} holds objects other than custom-resource responses (a stack deployed with --state-prefix ${CUSTOM_RESOURCE_RESPONSE_PREFIX}), and the rule would expire them`
|
|
25845
|
+
};
|
|
25846
|
+
const latest = await readLifecycle(s3, bucket, owner);
|
|
25847
|
+
if ("failed" in latest) return latest.failed;
|
|
25848
|
+
if (latest.rules.some((rule) => rule.ID === "cdkd-custom-resource-responses-expiration")) return { status: "present" };
|
|
25849
|
+
const existing = latest.rules;
|
|
25850
|
+
const minimumObjectSize = latest.minimumObjectSize;
|
|
25851
|
+
const legacyPrefix = existing.some((rule) => rule.Prefix !== void 0);
|
|
25852
|
+
try {
|
|
25853
|
+
await s3.send(new PutBucketLifecycleConfigurationCommand({
|
|
25854
|
+
Bucket: bucket,
|
|
25855
|
+
...owner,
|
|
25856
|
+
LifecycleConfiguration: { Rules: [...existing, buildCustomResourceResponseLifecycleRule(legacyPrefix)] },
|
|
25857
|
+
...minimumObjectSize !== void 0 && { TransitionDefaultMinimumObjectSize: minimumObjectSize }
|
|
25858
|
+
}));
|
|
25859
|
+
} catch (error) {
|
|
25860
|
+
return failedOutcome("PutBucketLifecycleConfiguration", error);
|
|
25861
|
+
}
|
|
25862
|
+
return { status: "added" };
|
|
25863
|
+
}
|
|
25864
|
+
async function readLifecycle(s3, bucket, owner) {
|
|
25865
|
+
try {
|
|
25866
|
+
const current = await s3.send(new GetBucketLifecycleConfigurationCommand({
|
|
25867
|
+
Bucket: bucket,
|
|
25868
|
+
...owner
|
|
25869
|
+
}));
|
|
25870
|
+
return {
|
|
25871
|
+
rules: current.Rules ?? [],
|
|
25872
|
+
minimumObjectSize: current.TransitionDefaultMinimumObjectSize
|
|
25873
|
+
};
|
|
25874
|
+
} catch (error) {
|
|
25875
|
+
if (error?.name === "NoSuchLifecycleConfiguration") return {
|
|
25876
|
+
rules: [],
|
|
25877
|
+
minimumObjectSize: void 0
|
|
25878
|
+
};
|
|
25879
|
+
return { failed: failedOutcome("GetBucketLifecycleConfiguration", error) };
|
|
25880
|
+
}
|
|
25881
|
+
}
|
|
25882
|
+
function isAccessDenied(error) {
|
|
25883
|
+
const e = error;
|
|
25884
|
+
return e?.name === "AccessDenied" || e?.$metadata?.httpStatusCode === 403;
|
|
25885
|
+
}
|
|
25886
|
+
function failedOutcome(operation, error) {
|
|
25887
|
+
return {
|
|
25888
|
+
status: "failed",
|
|
25889
|
+
reason: failureReason(operation, error),
|
|
25890
|
+
accessDenied: isAccessDenied(error)
|
|
25891
|
+
};
|
|
25892
|
+
}
|
|
25893
|
+
async function findStateUnderResponsePrefix(s3, bucket, owner) {
|
|
25894
|
+
let token;
|
|
25895
|
+
do {
|
|
25896
|
+
const page = await s3.send(new ListObjectsV2Command({
|
|
25897
|
+
Bucket: bucket,
|
|
25898
|
+
...owner,
|
|
25899
|
+
Prefix: CUSTOM_RESOURCE_RESPONSE_LIFECYCLE_PREFIX,
|
|
25900
|
+
Delimiter: "/",
|
|
25901
|
+
EncodingType: "url",
|
|
25902
|
+
...token !== void 0 && { ContinuationToken: token }
|
|
25903
|
+
}));
|
|
25904
|
+
const first = decodeListingKey(page.CommonPrefixes?.find((p) => p.Prefix)?.Prefix);
|
|
25905
|
+
if (first !== void 0) return first.slice(CUSTOM_RESOURCE_RESPONSE_PREFIX.length + 1);
|
|
25906
|
+
token = page.IsTruncated ? page.NextContinuationToken : void 0;
|
|
25907
|
+
} while (token !== void 0);
|
|
25908
|
+
}
|
|
25909
|
+
function failureReason(operation, error) {
|
|
25910
|
+
return `${operation} failed: ${displaySafe(describeAwsFailure(error).detail) || "<unrenderable>"}`;
|
|
25911
|
+
}
|
|
25912
|
+
function customResourceResponseExpirationWarning(bucket, outcome) {
|
|
25913
|
+
const remedy = outcome.status === "refused" ? `To add the rule, move that stack to a different --state-prefix.` : outcome.accessDenied ? "To add the rule, grant s3:GetLifecycleConfiguration, s3:PutLifecycleConfiguration and s3:ListBucket on the bucket, and check that this account owns it." : `To add the rule, review the bucket's lifecycle configuration against the reason above.`;
|
|
25914
|
+
return `Could not add the ${1}-day expiration lifecycle rule for ${CUSTOM_RESOURCE_RESPONSE_PREFIX}/ to state bucket ${displaySafe(bucket, { asciiOnly: true }) || "<unrenderable>"} (${outcome.reason}). The bucket's lifecycle configuration was left unchanged. Custom-resource response objects left by an interrupted run stay until 'cdkd gc' removes them. ${remedy}`;
|
|
25915
|
+
}
|
|
25916
|
+
const ensured = /* @__PURE__ */ new Map();
|
|
25917
|
+
function ensureCustomResourceResponseExpirationOnce(s3, bucket, log) {
|
|
25918
|
+
const memo = ensured.get(bucket);
|
|
25919
|
+
if (memo) return memo;
|
|
25920
|
+
const run = (async () => {
|
|
25921
|
+
try {
|
|
25922
|
+
const owner = await expectedOwnerParam(s3);
|
|
25923
|
+
const outcome = await applyCustomResourceResponseExpiration(s3, bucket, { ...owner.ExpectedBucketOwner && { expectedBucketOwner: owner.ExpectedBucketOwner } });
|
|
25924
|
+
if (outcome.status === "failed" || outcome.status === "refused") log.warn(customResourceResponseExpirationWarning(bucket, outcome));
|
|
25925
|
+
else if (outcome.status === "added") log.debug(`Added the ${1}-day expiration lifecycle rule for ${CUSTOM_RESOURCE_RESPONSE_PREFIX}/ to the state bucket.`);
|
|
25926
|
+
} catch (error) {
|
|
25927
|
+
log.warn(customResourceResponseExpirationWarning(bucket, failedOutcome("lifecycle check", error)));
|
|
25928
|
+
}
|
|
25929
|
+
})();
|
|
25930
|
+
ensured.set(bucket, run);
|
|
25931
|
+
return run;
|
|
25932
|
+
}
|
|
25933
|
+
|
|
24629
25934
|
//#endregion
|
|
24630
25935
|
//#region src/deployment/resource-secrets-scope.ts
|
|
24631
25936
|
const currentResourceSecretsStore = new AsyncLocalStorage();
|
|
@@ -24856,6 +26161,7 @@ var TemplateNoEchoReresolver = class TemplateNoEchoReresolver {
|
|
|
24856
26161
|
logger = getLogger().child("NoEchoDeleteReresolution");
|
|
24857
26162
|
resolver;
|
|
24858
26163
|
parametersPromise;
|
|
26164
|
+
printingNoEchoPromise;
|
|
24859
26165
|
noEchoParameters;
|
|
24860
26166
|
options;
|
|
24861
26167
|
constructor(options) {
|
|
@@ -24865,7 +26171,7 @@ var TemplateNoEchoReresolver = class TemplateNoEchoReresolver {
|
|
|
24865
26171
|
const section = options.template.Parameters;
|
|
24866
26172
|
if (section !== null && typeof section === "object") for (const [name, definition] of Object.entries(section)) {
|
|
24867
26173
|
const noEcho = definition?.NoEcho;
|
|
24868
|
-
if (noEcho
|
|
26174
|
+
if (isNoEchoDeclared(noEcho)) declared.add(name);
|
|
24869
26175
|
}
|
|
24870
26176
|
for (const name of options.inheritedNoEchoParameters ?? []) declared.add(name);
|
|
24871
26177
|
this.noEchoParameters = declared;
|
|
@@ -24917,6 +26223,33 @@ var TemplateNoEchoReresolver = class TemplateNoEchoReresolver {
|
|
|
24917
26223
|
for (const name of this.noEchoParameters) if (parameters !== void 0 && Object.hasOwn(parameters, name)) recordLogOnlyParameterValue(bag, parameters[name]);
|
|
24918
26224
|
return bag;
|
|
24919
26225
|
}
|
|
26226
|
+
printingNoEchoValues() {
|
|
26227
|
+
this.printingNoEchoPromise ??= this.printingNeedles().then((bag) => hasMaskableValues(bag) ? bag : void 0);
|
|
26228
|
+
return this.printingNoEchoPromise;
|
|
26229
|
+
}
|
|
26230
|
+
async printingNeedles() {
|
|
26231
|
+
const bag = this.newBag();
|
|
26232
|
+
if (this.options.printingSeed !== void 0) carryLogOnlyValues(this.options.printingSeed, bag);
|
|
26233
|
+
if (this.noEchoParameters.size === 0) return bag;
|
|
26234
|
+
const parameters = await this.boundParameters();
|
|
26235
|
+
const section = this.options.template.Parameters ?? {};
|
|
26236
|
+
const supplied = this.options.parameters;
|
|
26237
|
+
for (const name of this.noEchoParameters) {
|
|
26238
|
+
if (parameters !== void 0) {
|
|
26239
|
+
if (Object.hasOwn(parameters, name)) recordLogOnlyParameterValue(bag, parameters[name]);
|
|
26240
|
+
continue;
|
|
26241
|
+
}
|
|
26242
|
+
if (supplied !== void 0 && Object.hasOwn(supplied, name)) {
|
|
26243
|
+
const value = supplied[name];
|
|
26244
|
+
if (!this.options.maskedParameters?.has(name)) recordLogOnlyParameterValue(bag, value);
|
|
26245
|
+
continue;
|
|
26246
|
+
}
|
|
26247
|
+
const definition = Object.hasOwn(section, name) ? section[name] : void 0;
|
|
26248
|
+
if (typeof definition?.Type === "string" && definition.Type.startsWith("AWS::SSM::")) continue;
|
|
26249
|
+
if (definition?.Default !== void 0) recordLogOnlyParameterValue(bag, definition.Default);
|
|
26250
|
+
}
|
|
26251
|
+
return bag;
|
|
26252
|
+
}
|
|
24920
26253
|
async valuesFor(logicalId, record) {
|
|
24921
26254
|
const masked = maskedNoEchoCoordinates(record.properties, noEchoLeavesOf(record));
|
|
24922
26255
|
if (masked.length === 0) return void 0;
|
|
@@ -24991,8 +26324,10 @@ var TemplateNoEchoReresolver = class TemplateNoEchoReresolver {
|
|
|
24991
26324
|
const parameters0 = await this.boundParameters();
|
|
24992
26325
|
const rowTemplate = this.options.template.Resources?.[logicalId];
|
|
24993
26326
|
const passedClasses = parameters0 === void 0 ? void 0 : await classifyPassedParameters(rowTemplate?.Properties?.Parameters, this.inputSources(parameters0)).catch(() => void 0);
|
|
26327
|
+
const printingSeed = await this.printingNoEchoValues().catch(() => void 0);
|
|
24994
26328
|
return new TemplateNoEchoReresolver({
|
|
24995
26329
|
...passedClasses !== void 0 && { passedClasses },
|
|
26330
|
+
...printingSeed !== void 0 && { printingSeed },
|
|
24996
26331
|
template: child.template,
|
|
24997
26332
|
stackName: childStackName,
|
|
24998
26333
|
region: this.options.region,
|
|
@@ -25779,6 +27114,7 @@ var CustomResourceProvider = class CustomResourceProvider {
|
|
|
25779
27114
|
if (!this.responseBucket) return "https://localhost/cfn-response-not-configured";
|
|
25780
27115
|
await this.ensureResponseClient();
|
|
25781
27116
|
const bucket = this.responseBucket;
|
|
27117
|
+
if (this.responsePrefix === "custom-resource-responses") ensureCustomResourceResponseExpirationOnce(this.s3Client, bucket, this.logger);
|
|
25782
27118
|
try {
|
|
25783
27119
|
await withRetry(() => this.s3Client.send(new PutObjectCommand({
|
|
25784
27120
|
Bucket: bucket,
|
|
@@ -28434,191 +29770,6 @@ async function reserveStackCreateToken(options) {
|
|
|
28434
29770
|
};
|
|
28435
29771
|
}
|
|
28436
29772
|
|
|
28437
|
-
//#endregion
|
|
28438
|
-
//#region src/deployment/inline-policy-claims.ts
|
|
28439
|
-
function isInlinePolicyClaimedByCompletedWriter(args, kind, principal, policyName) {
|
|
28440
|
-
const { selfLogicalId, writers, changes, stateResources } = args;
|
|
28441
|
-
for (const [lid, write] of writers) {
|
|
28442
|
-
if (lid === selfLogicalId || !Object.hasOwn(stateResources, lid)) continue;
|
|
28443
|
-
const record = stateResources[lid];
|
|
28444
|
-
if (record.provisionedBy === "cc-api") continue;
|
|
28445
|
-
const props = record.properties ?? {};
|
|
28446
|
-
if (record.resourceType === "AWS::IAM::Policy") {
|
|
28447
|
-
if (!sameIamName(record.physicalId, policyName)) continue;
|
|
28448
|
-
const listed = props[POLICY_LIST_FIELDS[kind]];
|
|
28449
|
-
if (Array.isArray(listed) && listed.some((p) => sameIamName(p, principal))) return true;
|
|
28450
|
-
continue;
|
|
28451
|
-
}
|
|
28452
|
-
if (!Object.hasOwn(PRINCIPAL_KINDS, record.resourceType)) continue;
|
|
28453
|
-
if (PRINCIPAL_KINDS[record.resourceType] !== kind) continue;
|
|
28454
|
-
if (!sameIamName(record.physicalId, principal)) continue;
|
|
28455
|
-
if (write !== "create" && !ownPoliciesChange(changes.get(lid))) continue;
|
|
28456
|
-
const policies = props["Policies"];
|
|
28457
|
-
if (!Array.isArray(policies)) continue;
|
|
28458
|
-
if (policies.some((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry) && sameIamName(entry["PolicyName"], policyName))) return true;
|
|
28459
|
-
}
|
|
28460
|
-
return false;
|
|
28461
|
-
}
|
|
28462
|
-
var RollbackInlinePolicyWriters = class {
|
|
28463
|
-
entries = /* @__PURE__ */ new Map();
|
|
28464
|
-
removals = /* @__PURE__ */ new Map();
|
|
28465
|
-
unsettled = /* @__PURE__ */ new Map();
|
|
28466
|
-
notePending(ops) {
|
|
28467
|
-
for (const op of ops) if (!this.unsettled.has(op)) this.unsettled.set(op, {
|
|
28468
|
-
logicalId: op.logicalId,
|
|
28469
|
-
state: "pending"
|
|
28470
|
-
});
|
|
28471
|
-
}
|
|
28472
|
-
noteOutcome(op, completed) {
|
|
28473
|
-
if (!completed) {
|
|
28474
|
-
this.unsettled.set(op, {
|
|
28475
|
-
logicalId: op.logicalId,
|
|
28476
|
-
state: "failed"
|
|
28477
|
-
});
|
|
28478
|
-
return;
|
|
28479
|
-
}
|
|
28480
|
-
for (const [key, entry] of this.unsettled) if (key === op || entry.logicalId === op.logicalId) this.unsettled.delete(key);
|
|
28481
|
-
}
|
|
28482
|
-
record(logicalId, write, record, policiesChanged, via) {
|
|
28483
|
-
if (via === "cc-api") {
|
|
28484
|
-
this.entries.delete(logicalId);
|
|
28485
|
-
return;
|
|
28486
|
-
}
|
|
28487
|
-
this.entries.set(logicalId, {
|
|
28488
|
-
write,
|
|
28489
|
-
record,
|
|
28490
|
-
policiesChanged
|
|
28491
|
-
});
|
|
28492
|
-
}
|
|
28493
|
-
claimedFor(resourceType, logicalId, stateResources) {
|
|
28494
|
-
if (resourceType !== "AWS::IAM::Policy" && !Object.hasOwn(PRINCIPAL_KINDS, resourceType)) return;
|
|
28495
|
-
return (kind, principal, policyName) => {
|
|
28496
|
-
const writers = /* @__PURE__ */ new Map();
|
|
28497
|
-
const changes = /* @__PURE__ */ new Map();
|
|
28498
|
-
for (const [lid, entry] of this.entries) {
|
|
28499
|
-
if (!Object.hasOwn(stateResources, lid) || stateResources[lid] !== entry.record) continue;
|
|
28500
|
-
writers.set(lid, entry.write);
|
|
28501
|
-
if (entry.policiesChanged) changes.set(lid, {
|
|
28502
|
-
logicalId: lid,
|
|
28503
|
-
changeType: "UPDATE",
|
|
28504
|
-
resourceType: entry.record.resourceType,
|
|
28505
|
-
propertyChanges: [{
|
|
28506
|
-
path: "Policies",
|
|
28507
|
-
oldValue: void 0,
|
|
28508
|
-
newValue: void 0,
|
|
28509
|
-
requiresReplacement: false
|
|
28510
|
-
}]
|
|
28511
|
-
});
|
|
28512
|
-
}
|
|
28513
|
-
const claimed = isInlinePolicyClaimedByCompletedWriter({
|
|
28514
|
-
selfLogicalId: logicalId,
|
|
28515
|
-
writers,
|
|
28516
|
-
changes,
|
|
28517
|
-
stateResources
|
|
28518
|
-
}, kind, principal, policyName);
|
|
28519
|
-
if (!claimed) {
|
|
28520
|
-
const key = removalKey(kind, principal, policyName);
|
|
28521
|
-
const noted = this.removals.get(key) ?? {
|
|
28522
|
-
kind,
|
|
28523
|
-
principal,
|
|
28524
|
-
policyName,
|
|
28525
|
-
removers: /* @__PURE__ */ new Map()
|
|
28526
|
-
};
|
|
28527
|
-
noted.removers.set(logicalId, stateResources[logicalId]);
|
|
28528
|
-
this.removals.set(key, noted);
|
|
28529
|
-
}
|
|
28530
|
-
return claimed;
|
|
28531
|
-
};
|
|
28532
|
-
}
|
|
28533
|
-
takeHeldRemovals(stateResources) {
|
|
28534
|
-
const held = [];
|
|
28535
|
-
for (const [key, { kind, principal, policyName, removers }] of this.removals) {
|
|
28536
|
-
const { holders, unreadable } = inlinePolicyHolders(stateResources, kind, principal, policyName);
|
|
28537
|
-
const others = holders.filter((h) => !removers.has(h.logicalId) || stateResources[h.logicalId] !== removers.get(h.logicalId));
|
|
28538
|
-
if (others.length === 0) continue;
|
|
28539
|
-
this.removals.delete(key);
|
|
28540
|
-
const open = new Set([...this.unsettled.values()].map((u) => u.logicalId));
|
|
28541
|
-
const unsettled = [...new Set(others.map((h) => h.logicalId))].filter((id) => open.has(id));
|
|
28542
|
-
held.push({
|
|
28543
|
-
kind,
|
|
28544
|
-
holders: others,
|
|
28545
|
-
unreadable,
|
|
28546
|
-
unsettled
|
|
28547
|
-
});
|
|
28548
|
-
}
|
|
28549
|
-
return held;
|
|
28550
|
-
}
|
|
28551
|
-
};
|
|
28552
|
-
function inlinePolicyHolders(stateResources, kind, principal, policyName) {
|
|
28553
|
-
const holders = [];
|
|
28554
|
-
const unreadable = [];
|
|
28555
|
-
const unnamed = (v) => typeof v !== "string" || !IAM_NAME.test(v);
|
|
28556
|
-
for (const [logicalId, record] of Object.entries(stateResources)) {
|
|
28557
|
-
if (record === null || typeof record !== "object") continue;
|
|
28558
|
-
const props = record.properties ?? {};
|
|
28559
|
-
if (record.resourceType === "AWS::IAM::Policy") {
|
|
28560
|
-
const nameMatches = sameIamName(record.physicalId, policyName);
|
|
28561
|
-
if (!nameMatches && !unnamed(record.physicalId)) continue;
|
|
28562
|
-
const listed = props[POLICY_LIST_FIELDS[kind]];
|
|
28563
|
-
const entries = Array.isArray(listed) ? listed : [];
|
|
28564
|
-
const named = entries.find((p) => sameIamName(p, principal));
|
|
28565
|
-
if (nameMatches && typeof named === "string") holders.push({
|
|
28566
|
-
logicalId,
|
|
28567
|
-
principal: named,
|
|
28568
|
-
policyName: record.physicalId,
|
|
28569
|
-
document: props["PolicyDocument"]
|
|
28570
|
-
});
|
|
28571
|
-
else if (named !== void 0 || entries.some(unnamed)) unreadable.push(logicalId);
|
|
28572
|
-
continue;
|
|
28573
|
-
}
|
|
28574
|
-
if (!Object.hasOwn(PRINCIPAL_KINDS, record.resourceType)) continue;
|
|
28575
|
-
if (PRINCIPAL_KINDS[record.resourceType] !== kind) continue;
|
|
28576
|
-
const principalMatches = sameIamName(record.physicalId, principal);
|
|
28577
|
-
if (!principalMatches && !unnamed(record.physicalId)) continue;
|
|
28578
|
-
const policies = props["Policies"];
|
|
28579
|
-
if (!Array.isArray(policies)) continue;
|
|
28580
|
-
for (const entry of policies) {
|
|
28581
|
-
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) continue;
|
|
28582
|
-
const name = entry["PolicyName"];
|
|
28583
|
-
if (principalMatches && sameIamName(name, policyName)) holders.push({
|
|
28584
|
-
logicalId,
|
|
28585
|
-
principal: record.physicalId,
|
|
28586
|
-
policyName: name,
|
|
28587
|
-
document: entry["PolicyDocument"]
|
|
28588
|
-
});
|
|
28589
|
-
else if (unnamed(name) || sameIamName(name, policyName)) unreadable.push(logicalId);
|
|
28590
|
-
}
|
|
28591
|
-
}
|
|
28592
|
-
return {
|
|
28593
|
-
holders,
|
|
28594
|
-
unreadable: [...new Set(unreadable)]
|
|
28595
|
-
};
|
|
28596
|
-
}
|
|
28597
|
-
function sameIamName(a, b) {
|
|
28598
|
-
return typeof a === "string" && IAM_NAME.test(a) && a.toLowerCase() === b.toLowerCase();
|
|
28599
|
-
}
|
|
28600
|
-
function removalKey(kind, principal, policyName) {
|
|
28601
|
-
return JSON.stringify([
|
|
28602
|
-
kind,
|
|
28603
|
-
principal.toLowerCase(),
|
|
28604
|
-
policyName.toLowerCase()
|
|
28605
|
-
]);
|
|
28606
|
-
}
|
|
28607
|
-
const IAM_NAME = /^[\w+=,.@-]+$/;
|
|
28608
|
-
const POLICY_LIST_FIELDS = {
|
|
28609
|
-
role: "Roles",
|
|
28610
|
-
group: "Groups",
|
|
28611
|
-
user: "Users"
|
|
28612
|
-
};
|
|
28613
|
-
const PRINCIPAL_KINDS = {
|
|
28614
|
-
"AWS::IAM::Role": "role",
|
|
28615
|
-
"AWS::IAM::Group": "group",
|
|
28616
|
-
"AWS::IAM::User": "user"
|
|
28617
|
-
};
|
|
28618
|
-
function ownPoliciesChange(change) {
|
|
28619
|
-
return (change?.propertyChanges ?? []).some((c) => c.path === "Policies" && c.inPlacePropagated !== true && c.replacementPropagated !== true);
|
|
28620
|
-
}
|
|
28621
|
-
|
|
28622
29773
|
//#endregion
|
|
28623
29774
|
//#region src/analyzer/destructive-changes.ts
|
|
28624
29775
|
const IGNORED_RESOURCE_TYPES = /* @__PURE__ */ new Set(["AWS::CDK::Metadata"]);
|
|
@@ -29540,14 +30691,43 @@ function secretNamedRecordNeedles(resources, secrets, bag = /* @__PURE__ */ new
|
|
|
29540
30691
|
return bag;
|
|
29541
30692
|
}
|
|
29542
30693
|
function secretNamePrintingBag(logicalId, resources) {
|
|
29543
|
-
|
|
29544
|
-
|
|
29545
|
-
|
|
29546
|
-
|
|
29547
|
-
|
|
30694
|
+
return secretNamePrintingBags(resources, void 0)(logicalId);
|
|
30695
|
+
}
|
|
30696
|
+
function secretNamePrintingBags(resources, noEchoValues) {
|
|
30697
|
+
const secrets = noEchoValues !== void 0 && hasMaskableValues(noEchoValues) ? noEchoValues : void 0;
|
|
30698
|
+
const judged = /* @__PURE__ */ new Map();
|
|
30699
|
+
const needlesOf = (logicalId, record) => {
|
|
30700
|
+
const hit = judged.get(logicalId);
|
|
30701
|
+
if (hit !== void 0 && hit.record === record) return hit.needles;
|
|
30702
|
+
const needles = secretNameNeedlesOf(logicalId, record, secrets, { embedded: void 0 });
|
|
30703
|
+
judged.set(logicalId, {
|
|
30704
|
+
record,
|
|
30705
|
+
needles
|
|
30706
|
+
});
|
|
30707
|
+
return needles;
|
|
30708
|
+
};
|
|
30709
|
+
return (logicalId) => {
|
|
30710
|
+
const bag = /* @__PURE__ */ new Map();
|
|
30711
|
+
if (secrets !== void 0) carryLogOnlyValues(secrets, bag);
|
|
30712
|
+
const record = Object.hasOwn(resources, logicalId) ? resources[logicalId] : void 0;
|
|
30713
|
+
for (const needle of needlesOf(logicalId, record) ?? []) recordLogOnlyValue(bag, needle);
|
|
30714
|
+
const leaves = [...wholeStringLeavesOf(record?.properties)].filter((leaf) => leaf !== "");
|
|
30715
|
+
if (leaves.length === 0) return bag;
|
|
30716
|
+
for (const [otherId, other] of Object.entries(resources)) {
|
|
30717
|
+
if (otherId === logicalId) continue;
|
|
30718
|
+
for (const needle of needlesOf(otherId, other) ?? []) if (leavesHoldNeedle(leaves, needle)) recordLogOnlyValue(bag, needle);
|
|
30719
|
+
}
|
|
30720
|
+
return bag;
|
|
30721
|
+
};
|
|
29548
30722
|
}
|
|
29549
|
-
function journaledOrphanPrintingBag(ops, stateResources) {
|
|
30723
|
+
function journaledOrphanPrintingBag(ops, stateResources, noEchoValues) {
|
|
29550
30724
|
const bag = /* @__PURE__ */ new Map();
|
|
30725
|
+
const secrets = noEchoValues !== void 0 && hasMaskableValues(noEchoValues) ? noEchoValues : void 0;
|
|
30726
|
+
if (secrets !== void 0) carryLogOnlyValues(secrets, bag);
|
|
30727
|
+
const judge = {
|
|
30728
|
+
secrets,
|
|
30729
|
+
embedded: void 0
|
|
30730
|
+
};
|
|
29551
30731
|
for (const op of ops) {
|
|
29552
30732
|
const record = {
|
|
29553
30733
|
resourceType: op.resourceType,
|
|
@@ -29556,9 +30736,9 @@ function journaledOrphanPrintingBag(ops, stateResources) {
|
|
|
29556
30736
|
};
|
|
29557
30737
|
const replaced = Object.hasOwn(stateResources, op.logicalId) ? stateResources[op.logicalId] : void 0;
|
|
29558
30738
|
for (const needle of [
|
|
29559
|
-
...secretNameNeedlesOf(op.logicalId, record,
|
|
29560
|
-
...secretNameNeedlesOf(op.logicalId, replaced,
|
|
29561
|
-
...secretNamesReadBy(op.logicalId, record, stateResources)
|
|
30739
|
+
...secretNameNeedlesOf(op.logicalId, record, secrets, judge) ?? [],
|
|
30740
|
+
...secretNameNeedlesOf(op.logicalId, replaced, secrets, judge) ?? [],
|
|
30741
|
+
...secretNamesReadBy(op.logicalId, record, stateResources, () => judge)
|
|
29562
30742
|
]) recordLogOnlyValue(bag, needle);
|
|
29563
30743
|
}
|
|
29564
30744
|
return bag;
|
|
@@ -29578,13 +30758,15 @@ function completedReplayEntries(ops) {
|
|
|
29578
30758
|
attemptedProperties: op.previousState.properties
|
|
29579
30759
|
}]]);
|
|
29580
30760
|
}
|
|
29581
|
-
function orphanRecordsPrintingBag(records) {
|
|
30761
|
+
function orphanRecordsPrintingBag(records, noEchoValues) {
|
|
29582
30762
|
const bag = /* @__PURE__ */ new Map();
|
|
30763
|
+
const secrets = noEchoValues !== void 0 && hasMaskableValues(noEchoValues) ? noEchoValues : void 0;
|
|
30764
|
+
if (secrets !== void 0) carryLogOnlyValues(secrets, bag);
|
|
29583
30765
|
for (const entry of records) {
|
|
29584
30766
|
if (entry === null || typeof entry !== "object") continue;
|
|
29585
30767
|
const { logicalId, state } = entry;
|
|
29586
30768
|
if (typeof logicalId !== "string" || state === null || typeof state !== "object") continue;
|
|
29587
|
-
for (const needle of secretNameNeedlesOf(logicalId, state, void 0) ?? []) recordLogOnlyValue(bag, needle);
|
|
30769
|
+
for (const needle of secretNameNeedlesOf(logicalId, state, secrets, { embedded: void 0 }) ?? []) recordLogOnlyValue(bag, needle);
|
|
29588
30770
|
}
|
|
29589
30771
|
return bag;
|
|
29590
30772
|
}
|
|
@@ -29694,7 +30876,7 @@ function withNoEchoDefaultsMasked(parameters) {
|
|
|
29694
30876
|
const masked = Object.create(null);
|
|
29695
30877
|
for (const [name, decl] of Object.entries(parameters)) {
|
|
29696
30878
|
const body = decl;
|
|
29697
|
-
masked[name] = typeof body === "object" && body !== null && body
|
|
30879
|
+
masked[name] = typeof body === "object" && body !== null && declaresNoEcho(body) && "Default" in body ? {
|
|
29698
30880
|
...body,
|
|
29699
30881
|
Default: NO_ECHO_DEFAULT_MASK
|
|
29700
30882
|
} : decl;
|
|
@@ -29906,7 +31088,7 @@ function noEchoParameterValueSeed(parameters, values, inherited, userParameters)
|
|
|
29906
31088
|
if (values === void 0) return seed;
|
|
29907
31089
|
for (const [name, value] of Object.entries(values)) {
|
|
29908
31090
|
if (isWholeDynamicReferenceValue(value)) continue;
|
|
29909
|
-
if (parameters
|
|
31091
|
+
if (parameters !== void 0 && declaresNoEcho(parameters[name])) {
|
|
29910
31092
|
const type = parameters[name]?.Type;
|
|
29911
31093
|
const ssmTyped = typeof type === "string" && type.startsWith("AWS::SSM::Parameter::Value<");
|
|
29912
31094
|
const spelled = Object.prototype.hasOwnProperty.call(userParameters ?? {}, name) ? userParameters?.[name] : parameters[name]?.Default;
|
|
@@ -30694,13 +31876,52 @@ async function resolveLeafByRegion(leaf, propertyPath, logicalId, execCtx, resol
|
|
|
30694
31876
|
}
|
|
30695
31877
|
return out + leaf.slice(cursor);
|
|
30696
31878
|
}
|
|
30697
|
-
function redactRollbackRecord(record, secrets, journaledProps) {
|
|
31879
|
+
function redactRollbackRecord(record, secrets, journaledProps, journaledAttributes) {
|
|
30698
31880
|
if (secrets.size === 0) return record;
|
|
30699
31881
|
const positioned = journaledProps === void 0 ? record : {
|
|
30700
31882
|
...record,
|
|
30701
31883
|
properties: redactSecretsForState(record.properties, secrets, journaledProps, STATE_DERIVED_RULES)
|
|
30702
31884
|
};
|
|
30703
|
-
return scrubResourceRecord(positioned, secrets)
|
|
31885
|
+
return keepPersistedPlainRecordLeaves(scrubResourceRecord(positioned, secrets), record, {
|
|
31886
|
+
properties: journaledProps,
|
|
31887
|
+
attributes: journaledAttributes
|
|
31888
|
+
});
|
|
31889
|
+
}
|
|
31890
|
+
function keepPersistedPlainRecordLeaves(scrubbed, raw, persisted) {
|
|
31891
|
+
const properties = keepPersistedPlainLeaves(scrubbed.properties, raw.properties, persisted.properties);
|
|
31892
|
+
const attributes = scrubbed.attributes === void 0 ? void 0 : keepPersistedPlainLeaves(scrubbed.attributes, raw.attributes, persisted.attributes);
|
|
31893
|
+
if (properties === scrubbed.properties && attributes === scrubbed.attributes) return scrubbed;
|
|
31894
|
+
return {
|
|
31895
|
+
...scrubbed,
|
|
31896
|
+
properties,
|
|
31897
|
+
...attributes !== void 0 && { attributes }
|
|
31898
|
+
};
|
|
31899
|
+
}
|
|
31900
|
+
function keepPersistedPlainLeaves(redacted, raw, persisted) {
|
|
31901
|
+
if (typeof raw === "string") return typeof persisted === "string" && raw === persisted && redacted !== "***" && dynamicReferenceTokens(persisted).length === 0 ? persisted : redacted;
|
|
31902
|
+
if (Array.isArray(raw)) {
|
|
31903
|
+
if (!Array.isArray(persisted) || !Array.isArray(redacted)) return redacted;
|
|
31904
|
+
if (redacted.length !== raw.length) return redacted;
|
|
31905
|
+
let out;
|
|
31906
|
+
for (let i = 0; i < redacted.length; i++) {
|
|
31907
|
+
const kept = keepPersistedPlainLeaves(redacted[i], raw[i], persisted[i]);
|
|
31908
|
+
if (kept !== redacted[i]) (out ??= [...redacted])[i] = kept;
|
|
31909
|
+
}
|
|
31910
|
+
return out ?? redacted;
|
|
31911
|
+
}
|
|
31912
|
+
if (!isPlainRecord$1(raw) || !isPlainRecord$1(persisted) || !isPlainRecord$1(redacted)) return redacted;
|
|
31913
|
+
let out;
|
|
31914
|
+
for (const key of Object.keys(redacted)) {
|
|
31915
|
+
if (!hasOwnKey(raw, key) || !hasOwnKey(persisted, key)) continue;
|
|
31916
|
+
const kept = keepPersistedPlainLeaves(redacted[key], raw[key], persisted[key]);
|
|
31917
|
+
if (kept === redacted[key]) continue;
|
|
31918
|
+
out ??= { ...redacted };
|
|
31919
|
+
out[key] = kept;
|
|
31920
|
+
}
|
|
31921
|
+
return out ?? redacted;
|
|
31922
|
+
}
|
|
31923
|
+
function isPlainRecord$1(value) {
|
|
31924
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && hasPlainPrototype$1(value);
|
|
30704
31925
|
}
|
|
30705
31926
|
|
|
30706
31927
|
//#endregion
|
|
@@ -30749,7 +31970,7 @@ function renderNestedStackTypeChangeRefusal(typeChanges, stackName) {
|
|
|
30749
31970
|
const rows = typeChanges.map((tc) => {
|
|
30750
31971
|
return `${` - ${tc.logicalId}: Type changes from ${tc.currentType} to ${tc.desiredType} (the existing ${tc.currentType} is ${tc.physicalId}).`}\n${tc.direction === "into-nested-stack" ? ` Replacing a single resource BY a nested stack is not supported: the create half of the replacement would deploy a whole child stack "${stackName}~${tc.logicalId}" as one row, while ${tc.physicalId} is still recorded under this logical id.` : ` Replacing a nested stack BY a single resource is not supported: a replacement deletes its old half as a best-effort cleanup step whose failure is only a warning, so the child stack "${stackName}~${tc.logicalId}" and every resource it owns could be left behind, untracked, under a deploy that reports success.`}`;
|
|
30751
31972
|
});
|
|
30752
|
-
return `Refusing to deploy ${stackName}: ` + (typeChanges.length === 1 ? `a resource changes its Type ` : `${typeChanges.length} resources change their Type `) + `into or out of ${NESTED_STACK_RESOURCE_TYPE}, which cdkd does not replace in place
|
|
31973
|
+
return `Refusing to deploy ${stackName}: ` + (typeChanges.length === 1 ? `a resource changes its Type ` : `${typeChanges.length} resources change their Type `) + `into or out of ${NESTED_STACK_RESOURCE_TYPE}, which cdkd does not replace in place.\n${rows.join("\n")}\n Deploy this as two changes instead: give the new resource a DIFFERENT logical id (in CDK, rename the construct) so the existing row is deleted through its own type's provider and the new one is created under its own — or remove the resource in one deploy and add its replacement in the next. There is no flag that overrides this refusal.`;
|
|
30753
31974
|
}
|
|
30754
31975
|
|
|
30755
31976
|
//#endregion
|
|
@@ -31754,7 +32975,7 @@ async function replayRevert(s) {
|
|
|
31754
32975
|
}
|
|
31755
32976
|
], op.logicalId, logger, isInterrupted, secrets, mask));
|
|
31756
32977
|
if (revertResult) recordNoEchoAttributeValues(revertResult, secrets, desiredProps);
|
|
31757
|
-
stateResources[op.logicalId] = maskRestoredNoEchoRecord(redactRollbackRecord(recordAfterRollbackUpdate(previousState, revertResult), secrets, previousState.properties), previousState, noEcho.substituted);
|
|
32978
|
+
stateResources[op.logicalId] = maskRestoredNoEchoRecord(redactRollbackRecord(recordAfterRollbackUpdate(previousState, revertResult), secrets, previousState.properties, previousState.attributes), previousState, noEcho.substituted);
|
|
31758
32979
|
if (updatePartialReason(revertResult) === void 0) inlinePolicyWriters.record(op.logicalId, "update", stateResources[op.logicalId], !deepEqual$1(previousState.properties?.["Policies"], current.properties?.["Policies"]), revertVia);
|
|
31759
32980
|
const rollbackPartial = updatePartialReason(revertResult);
|
|
31760
32981
|
if (rollbackPartial !== void 0) logger.warn(mask(` Rollback: ${safe(op.logicalId)} restored, ${updatePartialMessage(rollbackPartial)}`));
|
|
@@ -31953,7 +33174,7 @@ async function replayReverseReplacement(s) {
|
|
|
31953
33174
|
physicalId: createResult.physicalId,
|
|
31954
33175
|
attributes: createResult.attributes ?? {},
|
|
31955
33176
|
properties: recordedPropertiesAfterReplayCreate(prevRecord, createResult)
|
|
31956
|
-
}, secrets, prevRecord.properties);
|
|
33177
|
+
}, secrets, prevRecord.properties, prevRecord.attributes);
|
|
31957
33178
|
if (!adoptedLiveNewResource) inlinePolicyWriters.record(op.logicalId, "create", stateResources[op.logicalId], false, createProvisionedBy);
|
|
31958
33179
|
await afterOp?.(op.logicalId);
|
|
31959
33180
|
let survivorReason;
|
|
@@ -32124,12 +33345,12 @@ async function replayRollbackUnbound(operations, stateResources, stackName, ctx,
|
|
|
32124
33345
|
inlinePolicyWriters.notePending(operations);
|
|
32125
33346
|
markDeleteFirstBlocked(operations, options.failedOperations);
|
|
32126
33347
|
const { createOps, otherOps } = partitionOps(operations);
|
|
32127
|
-
for (
|
|
33348
|
+
for (const op of orderInlinePolicyReversals([...otherOps].reverse(), stateResources)) {
|
|
32128
33349
|
if (options.isInterrupted?.()) {
|
|
32129
33350
|
result.interrupted = true;
|
|
32130
33351
|
break;
|
|
32131
33352
|
}
|
|
32132
|
-
await replaySingle(
|
|
33353
|
+
await replaySingle(op, stateResources, stackName, ctx, resolver, orphanLogicalIds, result, options.onOrphan, inlinePolicyWriters, options.afterOp, options.isInterrupted);
|
|
32133
33354
|
}
|
|
32134
33355
|
if (!result.interrupted && createOps.length > 0) {
|
|
32135
33356
|
const sorted = sortRollbackCreates(createOps, stateResources);
|
|
@@ -32513,7 +33734,7 @@ async function replayFailedOperationsUnbound(failedOps, stateResources, stackNam
|
|
|
32513
33734
|
}
|
|
32514
33735
|
], op.logicalId, logger, options.isInterrupted, secrets, mask));
|
|
32515
33736
|
if (revertFailedResult) recordNoEchoAttributeValues(revertFailedResult, secrets, desiredProps);
|
|
32516
|
-
stateResources[op.logicalId] = maskRestoredNoEchoRecord(redactRollbackRecord(recordAfterRollbackUpdate(prev, revertFailedResult), secrets, prev.properties), prev, noEcho.substituted);
|
|
33737
|
+
stateResources[op.logicalId] = maskRestoredNoEchoRecord(redactRollbackRecord(recordAfterRollbackUpdate(prev, revertFailedResult), secrets, prev.properties, prev.attributes), prev, noEcho.substituted);
|
|
32517
33738
|
if (updatePartialReason(revertFailedResult) === void 0) inlinePolicyWriters.record(op.logicalId, "update", stateResources[op.logicalId], !deepEqual$1(prev.properties?.["Policies"], (op.attemptedProperties ?? current.properties)?.["Policies"]), revertVia);
|
|
32518
33739
|
const revertFailedPartial = updatePartialReason(revertFailedResult);
|
|
32519
33740
|
if (revertFailedPartial !== void 0) logger.warn(mask(` Rollback: ${safe(op.logicalId)} reverted, ${updatePartialMessage(revertFailedPartial)}`));
|
|
@@ -32671,7 +33892,7 @@ async function deleteJournaledOrphans(orphans, stateResources, stackName, ctx, o
|
|
|
32671
33892
|
const inlinePolicyWriters = new RollbackInlinePolicyWriters();
|
|
32672
33893
|
const failedIds = total.failedLogicalIds;
|
|
32673
33894
|
const record = ctx.recordEvent;
|
|
32674
|
-
const printing = journaledOrphanPrintingBag(orphans.segments.flatMap(({ ops, companions }) => [...companions ?? [], ...ops]), stateResources);
|
|
33895
|
+
const printing = journaledOrphanPrintingBag(orphans.segments.flatMap(({ ops, companions }) => [...companions ?? [], ...ops]), stateResources, options.noEchoValues);
|
|
32675
33896
|
const maskedCtx = record === void 0 ? ctx : {
|
|
32676
33897
|
...ctx,
|
|
32677
33898
|
recordEvent: (event) => record(maskEventTextWithBoundBags(event))
|
|
@@ -33483,11 +34704,14 @@ function redactOperationsForJournal(operations) {
|
|
|
33483
34704
|
if (next.attemptedProperties) next.attemptedProperties = byPosition(redactSecretsForState(markSameGenerationBag({ ...next.attemptedProperties }), ownSecrets, templateProps));
|
|
33484
34705
|
if (next.previousState) {
|
|
33485
34706
|
const previous = next.previousState;
|
|
33486
|
-
|
|
34707
|
+
const adopted = adoptedOrphanRecords.get(this);
|
|
34708
|
+
const loaded = adopted?.has(op.logicalId) ? adopted.get(op.logicalId) : previous;
|
|
34709
|
+
next.previousState = this.applyNoEchoPersist(op.logicalId, previous, keepPersistedPlainRecordLeaves(scrubResourceRecord(previous, ownSecrets, void 0, STATE_SOURCED_READBACK_RULES), previous, loaded ?? {}), void 0, {}, void 0, true);
|
|
33487
34710
|
}
|
|
33488
34711
|
return next;
|
|
33489
34712
|
});
|
|
33490
34713
|
}
|
|
34714
|
+
const adoptedOrphanRecords = /* @__PURE__ */ new WeakMap();
|
|
33491
34715
|
async function adoptRollbackOrphans(currentState, effectiveTemplate) {
|
|
33492
34716
|
const records = currentState.orphans ?? [];
|
|
33493
34717
|
const named = orphanRecordsPrintingBag(records);
|
|
@@ -33514,8 +34738,11 @@ async function adoptRollbackOrphans(currentState, effectiveTemplate) {
|
|
|
33514
34738
|
logger: { debug: (m) => this.logger.debug(m) }
|
|
33515
34739
|
}));
|
|
33516
34740
|
for (const notice of plan.notices) this.logger.info(maskSecretsInText(notice, named));
|
|
34741
|
+
const adoptedFrom = /* @__PURE__ */ new Map();
|
|
34742
|
+
adoptedOrphanRecords.set(this, adoptedFrom);
|
|
33517
34743
|
if (plan.refusals.length > 0) throw new Error(`Deploy refused — cdkd left ${plan.refusals.length} resource(s) in AWS that it cannot safely re-adopt:\n ${plan.refusals.map((r) => maskSecretsInText(r, named)).join("\n ")}`);
|
|
33518
34744
|
for (const [logicalId, record] of Object.entries(plan.adopted)) {
|
|
34745
|
+
adoptedFrom.set(logicalId, records.findLast((r) => r.logicalId === logicalId)?.state);
|
|
33519
34746
|
currentState.resources[logicalId] = record;
|
|
33520
34747
|
this.logger.info(`Adopting ${displayIdent(logicalId)} (${displayIdent(record.resourceType)}) left in AWS by an earlier rollback as ${displaySafe(maskSecretsInText(record.physicalId, named))}`);
|
|
33521
34748
|
}
|
|
@@ -34129,6 +35356,20 @@ async function drainObservedCaptures(stateResources) {
|
|
|
34129
35356
|
}
|
|
34130
35357
|
return installed;
|
|
34131
35358
|
}
|
|
35359
|
+
const STANDALONE_SIBLING = {
|
|
35360
|
+
Roles: {
|
|
35361
|
+
type: "AWS::IAM::RolePolicy",
|
|
35362
|
+
field: "RoleName"
|
|
35363
|
+
},
|
|
35364
|
+
Users: {
|
|
35365
|
+
type: "AWS::IAM::UserPolicy",
|
|
35366
|
+
field: "UserName"
|
|
35367
|
+
},
|
|
35368
|
+
Groups: {
|
|
35369
|
+
type: "AWS::IAM::GroupPolicy",
|
|
35370
|
+
field: "GroupName"
|
|
35371
|
+
}
|
|
35372
|
+
};
|
|
34132
35373
|
async function buildObservedCaptureSiblings(resourceType, capturedLogicalId, capturedPhysicalId, template, stateResources, stackName, parameterValues, conditions) {
|
|
34133
35374
|
if (this.options.captureObservedState !== true) return void 0;
|
|
34134
35375
|
const attachmentField = resourceType === "AWS::IAM::Role" ? "Roles" : resourceType === "AWS::IAM::User" ? "Users" : resourceType === "AWS::IAM::Group" ? "Groups" : void 0;
|
|
@@ -34138,11 +35379,12 @@ async function buildObservedCaptureSiblings(resourceType, capturedLogicalId, cap
|
|
|
34138
35379
|
let resolverContext;
|
|
34139
35380
|
const isRefTo = (value, logicalId) => typeof value === "object" && value !== null && !Array.isArray(value) && value["Ref"] === logicalId;
|
|
34140
35381
|
const siblings = {};
|
|
35382
|
+
const standaloneType = STANDALONE_SIBLING[attachmentField];
|
|
34141
35383
|
for (const [lid, res] of Object.entries(resources)) {
|
|
34142
35384
|
if (lid === capturedLogicalId) continue;
|
|
34143
|
-
if (res.Type !== "AWS::IAM::Policy") continue;
|
|
35385
|
+
if (res.Type !== "AWS::IAM::Policy" && res.Type !== standaloneType.type) continue;
|
|
34144
35386
|
const props = res.Properties ?? {};
|
|
34145
|
-
const attachments = props[attachmentField];
|
|
35387
|
+
const attachments = res.Type === "AWS::IAM::Policy" ? props[attachmentField] : [props[standaloneType.field]];
|
|
34146
35388
|
if (!Array.isArray(attachments)) continue;
|
|
34147
35389
|
if (!attachments.some((a) => isRefTo(a, capturedLogicalId) || a === capturedPhysicalId)) continue;
|
|
34148
35390
|
let policyName = props["PolicyName"];
|
|
@@ -34211,7 +35453,7 @@ function kickOffAutoRefreshObservedProperties(stateResources, crossStackReads) {
|
|
|
34211
35453
|
resource
|
|
34212
35454
|
});
|
|
34213
35455
|
}
|
|
34214
|
-
if (refused > 0) this.logger.debug(`observed-properties auto-refresh SKIPPED for ${refused} resource(s) whose baseline a 'cdkd import' run refused
|
|
35456
|
+
if (refused > 0) this.logger.debug(`observed-properties auto-refresh SKIPPED for ${refused} resource(s) whose baseline a 'cdkd import' run refused: their recorded properties cannot position the redaction, so capturing an AWS readback against them could persist a resolved secret in plaintext. A deploy that actually CHANGES one of them restores its baseline, unless the refusal is an unverifiable-parameter one (only a replacement or a proving re-import discharges that); a NO_CHANGE deploy never does.`);
|
|
34215
35457
|
if (candidates.length === 0 && masked.length === 0) return;
|
|
34216
35458
|
const allSiblings = {};
|
|
34217
35459
|
for (const [lid, res] of Object.entries(stateResources)) allSiblings[lid] = {
|
|
@@ -34244,7 +35486,7 @@ function kickOffAutoRefreshObservedProperties(stateResources, crossStackReads) {
|
|
|
34244
35486
|
if (masked.length > 0) try {
|
|
34245
35487
|
producerRegions = producerRegionsFromState(crossStackReads);
|
|
34246
35488
|
} catch {
|
|
34247
|
-
this.logger.debug(safeMsg`Masked observed baseline re-capture skipped for ${masked.length} resource(s): the record's cross-stack reads could not be read
|
|
35489
|
+
this.logger.debug(safeMsg`Masked observed baseline re-capture skipped for ${masked.length} resource(s): the record's cross-stack reads could not be read.`);
|
|
34248
35490
|
masked.length = 0;
|
|
34249
35491
|
}
|
|
34250
35492
|
for (const { logicalId, resource } of masked) {
|
|
@@ -34271,7 +35513,7 @@ function kickOffMaskedBaselineRecapture(provider, logicalId, resource, producerR
|
|
|
34271
35513
|
const task = (async () => {
|
|
34272
35514
|
const secrets = await resolveRecordSecrets(properties, (token, own) => this.resolver.resolveDynamicReferences(token, persistedTokenResolverContext(own, producerRegions)));
|
|
34273
35515
|
if (secrets === void 0 || secrets.size === 0) {
|
|
34274
|
-
this.logger.debug(safeMsg`Masked observed baseline of ${logicalId} kept: its recorded references did not all resolve to distinct values
|
|
35516
|
+
this.logger.debug(safeMsg`Masked observed baseline of ${logicalId} kept: its recorded references did not all resolve to distinct values.`);
|
|
34275
35517
|
return;
|
|
34276
35518
|
}
|
|
34277
35519
|
const readback = await withCurrentResourceSecrets(secrets, () => readCurrentState(resource.physicalId, logicalId, resource.resourceType, properties, context));
|
|
@@ -34283,14 +35525,14 @@ function kickOffMaskedBaselineRecapture(provider, logicalId, resource, producerR
|
|
|
34283
35525
|
secrets
|
|
34284
35526
|
});
|
|
34285
35527
|
if (recaptured === void 0) {
|
|
34286
|
-
this.logger.debug(safeMsg`Masked observed baseline of ${logicalId} kept: no masked position could be certified, or the resource no longer reads back as its baseline records
|
|
35528
|
+
this.logger.debug(safeMsg`Masked observed baseline of ${logicalId} kept: no masked position could be certified, or the resource no longer reads back as its baseline records.`);
|
|
34287
35529
|
return;
|
|
34288
35530
|
}
|
|
34289
35531
|
this.recapturedBaselines.set(recaptured, previous);
|
|
34290
|
-
this.logger.debug(safeMsg`Re-captured the masked observed baseline of ${logicalId}
|
|
35532
|
+
this.logger.debug(safeMsg`Re-captured the masked observed baseline of ${logicalId}.`);
|
|
34291
35533
|
return recaptured;
|
|
34292
35534
|
})().catch(() => {
|
|
34293
|
-
this.logger.debug(safeMsg`Masked observed baseline of ${logicalId} kept: the re-capture failed
|
|
35535
|
+
this.logger.debug(safeMsg`Masked observed baseline of ${logicalId} kept: the re-capture failed.`);
|
|
34294
35536
|
});
|
|
34295
35537
|
this.observedCaptureTasks.set(logicalId, task);
|
|
34296
35538
|
}
|
|
@@ -34355,9 +35597,9 @@ function deepEqualValue(a, b) {
|
|
|
34355
35597
|
function diffLogMasker(diffSecrets, template, parameterValues, resources) {
|
|
34356
35598
|
const inherited = this.options.inheritedSecrets;
|
|
34357
35599
|
const noEchoValues = /* @__PURE__ */ new Map();
|
|
34358
|
-
const noEchoNames = new Set(Object.entries(template.Parameters ?? {}).filter(([, definition]) => definition
|
|
35600
|
+
const noEchoNames = new Set(Object.entries(template.Parameters ?? {}).filter(([, definition]) => declaresNoEcho(definition)).map(([name]) => name));
|
|
34359
35601
|
const splitDelimiters = literalSplitDelimitersOf(template, noEchoNames);
|
|
34360
|
-
for (const [name, definition] of Object.entries(template.Parameters ?? {})) if (definition
|
|
35602
|
+
for (const [name, definition] of Object.entries(template.Parameters ?? {})) if (declaresNoEcho(definition) && Object.hasOwn(parameterValues, name)) recordLogOnlyParameterValue(noEchoValues, parameterValues[name], splitDelimiters);
|
|
34361
35603
|
const stateNames = /* @__PURE__ */ new Map();
|
|
34362
35604
|
const union = createUnionSecretMasker([
|
|
34363
35605
|
diffSecrets,
|
|
@@ -34454,7 +35696,7 @@ function refuseRedactedAttributeReads(logicalId, resourceType, context) {
|
|
|
34454
35696
|
const producers = [...new Set(reads.map((read) => read.logicalId).filter((id) => !!id))];
|
|
34455
35697
|
throw new ProvisioningError(`Cannot resolve ${reads.map((read) => read.display).join(", ")} for ${logicalId}: ${producers.map((id) => displayIdent(id)).join(", ")} declared ${reads.length === 1 ? "that attribute" : "those attributes"} NoEcho, so cdkd's recorded state holds only the redaction mask for ${reads.length === 1 ? "it" : "them"}, and cdkd does not re-run a producer to recover a value. Remedy: deploy a change to the producer in the SAME run as ${logicalId} (for a custom resource, change one of its properties, e.g. a nonce / version property, so its handler runs again and supplies the value), or stop declaring the value NoEcho.`, resourceType, logicalId);
|
|
34456
35698
|
}
|
|
34457
|
-
throw new ProvisioningError(`Cannot resolve ${reads.map((read) => read.display).join(", ")} for ${logicalId}: cdkd's recorded state holds only the redaction mask there, and the value is not recoverable from state. There are three ways a record comes to hold the mask. (1) A custom resource handler declared its response NoEcho: true — the value is generated by the handler, so cdkd has nothing to re-derive it from and must not write the literal mask to AWS. Remedies: force that custom resource to update (change one of its properties, e.g. a nonce / version property) so its handler runs again and supplies the value in this same run; or stop setting NoEcho on that response. If the value comes from ANOTHER stack, the producer and this stack must deploy in ONE run (cdkd deploy --all, or cdkd deploy '**' when either is inside a CDK Stage) with the producer's custom resource actually running — re-deploying the producer by itself does not help, because it re-masks the value on the way into its own state. (2) The value holds the Fn::Base64 encoding of a secret value (a {{resolve:...}} dynamic reference under Fn::Base64, such as EC2 UserData), which cdkd never records — so a deploy that leaves the resource or output holding it unchanged reads the mask again, and a re-import does not recover it. Remedy: have this resource build the value itself from the SECRET's own reference (the {{resolve:secretsmanager:...}} or {{resolve:ssm-secure:...}} reference, or a {{resolve:ssm:...}} of a SecureString parameter, that the encoding was made from) under its own Fn::Base64, instead of reading it from another resource's attribute or another stack's output — not by a {{resolve:ssm:...}} read of a String parameter holding the encoding, which cdkd treats as public and would record in state in the clear. (3) The resource
|
|
35699
|
+
throw new ProvisioningError(`Cannot resolve ${reads.map((read) => read.display).join(", ")} for ${logicalId}: cdkd's recorded state holds only the redaction mask there, and the value is not recoverable from state. There are three ways a record comes to hold the mask. (1) A custom resource handler declared its response NoEcho: true — the value is generated by the handler, so cdkd has nothing to re-derive it from and must not write the literal mask to AWS. Remedies: force that custom resource to update (change one of its properties, e.g. a nonce / version property) so its handler runs again and supplies the value in this same run; or stop setting NoEcho on that response. If the value comes from ANOTHER stack, the producer and this stack must deploy in ONE run (cdkd deploy --all, or cdkd deploy '**' when either is inside a CDK Stage) with the producer's custom resource actually running — re-deploying the producer by itself does not help, because it re-masks the value on the way into its own state. (2) The value holds the Fn::Base64 encoding of a secret value (a {{resolve:...}} dynamic reference under Fn::Base64, such as EC2 UserData), which cdkd never records — so a deploy that leaves the resource or output holding it unchanged reads the mask again, and a re-import does not recover it. Remedy: have this resource build the value itself from the SECRET's own reference (the {{resolve:secretsmanager:...}} or {{resolve:ssm-secure:...}} reference, or a {{resolve:ssm:...}} of a SecureString parameter, that the encoding was made from) under its own Fn::Base64, instead of reading it from another resource's attribute or another stack's output — not by a {{resolve:ssm:...}} read of a String parameter holding the encoding, which cdkd treats as public and would record in state in the clear. (3) The resource is recorded through the Cloud Control fallback (by a deploy or by 'cdkd import'), which keeps in the clear only the Fn::GetAtt attributes it can vouch for and masks the rest. If the name above is not an Fn::GetAtt attribute CloudFormation accepts for that resource type, no record will ever hold it: stop reading it. If it is one, 'cdkd import' masked it because it could not vouch for the value, or could not read the schema and warned about it when it happened (if that warning named a missing cloudformation:DescribeType permission, grant it first); a deploy that creates or updates the resource records it, unless its value came from a secret. See https://github.com/go-to-k/cdkd/issues/2449. ${DeployEngine.maskedRecordRemedyFor(reads, context.resources, true)}`, resourceType, logicalId);
|
|
34458
35700
|
}
|
|
34459
35701
|
function allRecordedSecrets() {
|
|
34460
35702
|
const all = /* @__PURE__ */ new Map();
|
|
@@ -34763,6 +36005,7 @@ async function createFirstThenDeleteOld(input) {
|
|
|
34763
36005
|
async function deleteReplacedAfterCreate(logicalId, oldResourceType, currentResource, deleteProvider, deleteProperties, finalSnapshotIdentifier, updateReplacePolicy, secrets, deleteProvisionedBy, noEchoDeleteValues) {
|
|
34764
36006
|
let deleteResult = void 0;
|
|
34765
36007
|
let deleteFailed = false;
|
|
36008
|
+
const inlinePolicyClaimed = isStandaloneInlinePolicyType(oldResourceType) ? this.inlinePolicyClaimedFor(oldResourceType, logicalId, this.deployStateResources) : void 0;
|
|
34766
36009
|
try {
|
|
34767
36010
|
deleteResult = await deleteProvider.delete(logicalId, currentResource.physicalId, oldResourceType, deleteProperties, {
|
|
34768
36011
|
expectedRegion: this.stackRegion,
|
|
@@ -34771,7 +36014,8 @@ async function deleteReplacedAfterCreate(logicalId, oldResourceType, currentReso
|
|
|
34771
36014
|
...this.replacementDeleteContext(updateReplacePolicy),
|
|
34772
36015
|
recordedAttributes: currentResource.attributes,
|
|
34773
36016
|
recordedNoEchoLeaves: currentResource.noEchoLeaves,
|
|
34774
|
-
...noEchoDeleteValues !== void 0 && { noEchoDeleteValues }
|
|
36017
|
+
...noEchoDeleteValues !== void 0 && { noEchoDeleteValues },
|
|
36018
|
+
...inlinePolicyClaimed && { inlinePolicyClaimed }
|
|
34775
36019
|
});
|
|
34776
36020
|
} catch (deleteError) {
|
|
34777
36021
|
const deleteMsg = maskSecretsInText(deleteError instanceof Error ? deleteError.message : String(deleteError), secrets);
|
|
@@ -34839,7 +36083,7 @@ function createReadOnlyAttributeHealerFactory(options) {
|
|
|
34839
36083
|
region
|
|
34840
36084
|
})))().then((outcome) => {
|
|
34841
36085
|
if (outcome.kind === "read") try {
|
|
34842
|
-
logger.debug(safeMsg`Re-read the attributes of ${displaySafe(logicalId)} (${displaySafe(resource.resourceType)}) from AWS for this preview — its state record lacked one a Fn::GetAtt asked for
|
|
36086
|
+
logger.debug(safeMsg`Re-read the attributes of ${displaySafe(logicalId)} (${displaySafe(resource.resourceType)}) from AWS for this preview — its state record lacked one a Fn::GetAtt asked for; nothing is written: ${Object.keys(outcome.attributes).length} attribute(s) read`);
|
|
34843
36087
|
} catch {}
|
|
34844
36088
|
return outcome;
|
|
34845
36089
|
}).catch((error) => ({
|
|
@@ -34891,7 +36135,7 @@ async function readStaleAttributes(logicalId, resource, stackName) {
|
|
|
34891
36135
|
resourceType: resource.resourceType,
|
|
34892
36136
|
attributes
|
|
34893
36137
|
});
|
|
34894
|
-
this.logger.debug(`Re-read the attributes of ${displaySafe(logicalId)} (${displaySafe(resource.resourceType)}) from AWS — its state record lacked one a Fn::GetAtt asked for
|
|
36138
|
+
this.logger.debug(`Re-read the attributes of ${displaySafe(logicalId)} (${displaySafe(resource.resourceType)}) from AWS — its state record lacked one a Fn::GetAtt asked for: ${Object.keys(attributes).length} attribute(s) read`);
|
|
34895
36139
|
return outcome;
|
|
34896
36140
|
}
|
|
34897
36141
|
function withHealedAttributes(logicalId, record) {
|
|
@@ -35240,9 +36484,9 @@ async function refuseUnlicensedGeneratedName(input) {
|
|
|
35240
36484
|
}], recoveryCommandFlags(this.options.refusalRecovery).flags).command}\``;
|
|
35241
36485
|
if (verdict.ownIntent !== void 0) {
|
|
35242
36486
|
const why = verdict.ownIntent === "abandoned" ? "a deploy of this stack that was killed or force-unlocked" : "a create of this stack that came back failed (a timeout or a server error)";
|
|
35243
|
-
return refuse(`${subject} would be created with ${named}, which an existing resource (${displaySafe(verdict.holder)}) already holds. This stack's create-token ledger records that ${why} sent this name, but this type reports no creation time, so cdkd cannot tell that resource is the one it made rather than another deployment's created since. Since ${adoptsText}, creating it would take that resource over. ${displaySafe(logicalId)} was not created. The likely cause: the resource is this stack's own, from that deploy. If it is, ${importLine} and re-run; if it is not, this stack is also deployed under another state backend
|
|
36487
|
+
return refuse(`${subject} would be created with ${named}, which an existing resource (${displaySafe(verdict.holder)}) already holds. This stack's create-token ledger records that ${why} sent this name, but this type reports no creation time, so cdkd cannot tell that resource is the one it made rather than another deployment's created since. Since ${adoptsText}, creating it would take that resource over. ${displaySafe(logicalId)} was not created. The likely cause: the resource is this stack's own, from that deploy. If it is, ${importLine} and re-run; if it is not, this stack is also deployed under another state backend.`);
|
|
35244
36488
|
}
|
|
35245
|
-
return refuse(`${subject} would be created with ${named}, which an existing resource (${displaySafe(verdict.holder)}) already holds, and nothing this stack records names that resource: not its state, its rollback journal or create-token ledger, nor what a destroy of it under this state prefix kept. Since ${adoptsText}, creating it would take that resource over, as CloudFormation refuses with "already exists". ${displaySafe(logicalId)} was not created. The likely cause: this stack is also deployed under another state backend (another --state-prefix, --state-bucket or account), where cdkd generates the same names. One stack name per account and region is supported
|
|
36489
|
+
return refuse(`${subject} would be created with ${named}, which an existing resource (${displaySafe(verdict.holder)}) already holds, and nothing this stack records names that resource: not its state, its rollback journal or create-token ledger, nor what a destroy of it under this state prefix kept. Since ${adoptsText}, creating it would take that resource over, as CloudFormation refuses with "already exists". ${displaySafe(logicalId)} was not created. The likely cause: this stack is also deployed under another state backend (another --state-prefix, --state-bucket or account), where cdkd generates the same names. One stack name per account and region is supported: deploy it under one backend only, or give this stack another name. If the resource is this stack's own, ${importLine} and re-run.`);
|
|
35246
36490
|
}
|
|
35247
36491
|
}
|
|
35248
36492
|
}
|
|
@@ -36246,7 +37490,7 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
36246
37490
|
const droppedOverrides = [];
|
|
36247
37491
|
if (oldReplicaOverrides.OnDemandThroughputOverride && !replicaOverrides.OnDemandThroughputOverride) droppedOverrides.push("ReadOnDemandThroughputSettings");
|
|
36248
37492
|
if (oldReplicaOverrides.ProvisionedThroughputOverride && !replicaOverrides.ProvisionedThroughputOverride) droppedOverrides.push("ReadProvisionedThroughputSettings");
|
|
36249
|
-
if (droppedOverrides.length > 0) warn(`Cross-region replica ${region} of ${physicalId}: the template no longer declares ${droppedOverrides.map((o) => maskSecrets(o)).join(" / ")}, but DynamoDB offers no way to CLEAR a replica-level throughput override — a -1 sentinel is stored literally, and an empty override block wedges the table in UPDATING
|
|
37493
|
+
if (droppedOverrides.length > 0) warn(`Cross-region replica ${region} of ${physicalId}: the template no longer declares ${droppedOverrides.map((o) => maskSecrets(o)).join(" / ")}, but DynamoDB offers no way to CLEAR a replica-level throughput override — a -1 sentinel is stored literally, and an empty override block wedges the table in UPDATING. The previous override is STILL IN EFFECT on AWS. Set an explicit value, or remove and re-add the replica, to change it.`);
|
|
36250
37494
|
}
|
|
36251
37495
|
if (replica["TableClassOverride"]) updateAction.TableClassOverride = replica["TableClassOverride"];
|
|
36252
37496
|
if (!(updateAction.KMSMasterKeyId !== void 0 || updateAction.GlobalSecondaryIndexes !== void 0 || updateAction.TableClassOverride !== void 0 || updateAction.ProvisionedThroughputOverride !== void 0 || updateAction.OnDemandThroughputOverride !== void 0)) {
|
|
@@ -36749,7 +37993,7 @@ var DynamoDBGlobalTableProvider = class {
|
|
|
36749
37993
|
}
|
|
36750
37994
|
try {
|
|
36751
37995
|
if (hasTransitionalIndex(preDeleteIndexes)) {
|
|
36752
|
-
this.logger.debug(`Waiting for indexes on ${physicalId} to settle before DeleteTable
|
|
37996
|
+
this.logger.debug(`Waiting for indexes on ${physicalId} to settle before DeleteTable`);
|
|
36753
37997
|
await this.waitForIndexesActive(physicalId, logicalId, {
|
|
36754
37998
|
proceedNote: DELETE_INDEX_WAIT_PROCEED_NOTE,
|
|
36755
37999
|
budget
|
|
@@ -38153,10 +39397,7 @@ function noEchoParameterNamesOf(template) {
|
|
|
38153
39397
|
const names = /* @__PURE__ */ new Set();
|
|
38154
39398
|
const declared = template?.Parameters;
|
|
38155
39399
|
if (declared === null || typeof declared !== "object") return names;
|
|
38156
|
-
for (const [name, definition] of Object.entries(declared))
|
|
38157
|
-
const noEcho = definition?.NoEcho;
|
|
38158
|
-
if (noEcho === true || noEcho === "true") names.add(name);
|
|
38159
|
-
}
|
|
39400
|
+
for (const [name, definition] of Object.entries(declared)) if (declaresNoEcho(definition)) names.add(name);
|
|
38160
39401
|
return names;
|
|
38161
39402
|
}
|
|
38162
39403
|
function noEchoAttributeNamesOf(record) {
|
|
@@ -38393,6 +39634,185 @@ function noEchoComparisonForTemplate(template, knownConditions, values, resource
|
|
|
38393
39634
|
return (input) => compare(input);
|
|
38394
39635
|
}
|
|
38395
39636
|
|
|
39637
|
+
//#endregion
|
|
39638
|
+
//#region src/analyzer/security-diff.ts
|
|
39639
|
+
function isSyntheticChange(pc) {
|
|
39640
|
+
return pc.replacementPropagated === true || pc.inPlacePropagated === true || pc.noEchoPromoted === true;
|
|
39641
|
+
}
|
|
39642
|
+
function withChanges(base, changes, side) {
|
|
39643
|
+
const props = { ...base ?? {} };
|
|
39644
|
+
for (const pc of changes) {
|
|
39645
|
+
const value = pc[side];
|
|
39646
|
+
if (value === void 0) delete props[pc.path];
|
|
39647
|
+
else props[pc.path] = value;
|
|
39648
|
+
}
|
|
39649
|
+
return props;
|
|
39650
|
+
}
|
|
39651
|
+
function securityDiffTemplates(changes, records) {
|
|
39652
|
+
const oldResources = {};
|
|
39653
|
+
const newResources = {};
|
|
39654
|
+
const maskedChanges = [];
|
|
39655
|
+
for (const change of changes) {
|
|
39656
|
+
const id = change.logicalId;
|
|
39657
|
+
const record = Object.hasOwn(records, id) ? records[id] : void 0;
|
|
39658
|
+
switch (change.changeType) {
|
|
39659
|
+
case "CREATE":
|
|
39660
|
+
newResources[id] = {
|
|
39661
|
+
Type: change.resourceType,
|
|
39662
|
+
Properties: { ...change.desiredProperties ?? {} }
|
|
39663
|
+
};
|
|
39664
|
+
break;
|
|
39665
|
+
case "DELETE":
|
|
39666
|
+
oldResources[id] = {
|
|
39667
|
+
Type: record?.resourceType ?? change.resourceType,
|
|
39668
|
+
Properties: { ...change.currentProperties ?? record?.properties ?? {} }
|
|
39669
|
+
};
|
|
39670
|
+
break;
|
|
39671
|
+
case "UPDATE": {
|
|
39672
|
+
const pcs = change.propertyChanges ?? [];
|
|
39673
|
+
const typeChange = pcs.find((pc) => pc.path === "Type");
|
|
39674
|
+
const oldType = record?.resourceType ?? change.resourceType;
|
|
39675
|
+
if (typeChange !== void 0 || oldType !== change.resourceType) {
|
|
39676
|
+
oldResources[id] = {
|
|
39677
|
+
Type: typeof typeChange?.oldValue === "string" ? typeChange.oldValue : oldType,
|
|
39678
|
+
Properties: { ...change.currentProperties ?? record?.properties ?? {} }
|
|
39679
|
+
};
|
|
39680
|
+
newResources[id] = {
|
|
39681
|
+
Type: change.resourceType,
|
|
39682
|
+
Properties: { ...change.desiredProperties ?? {} }
|
|
39683
|
+
};
|
|
39684
|
+
break;
|
|
39685
|
+
}
|
|
39686
|
+
for (const pc of pcs) if (pc.maskedExpressionChanged === true && !isSyntheticChange(pc)) maskedChanges.push({
|
|
39687
|
+
logicalId: id,
|
|
39688
|
+
resourceType: change.resourceType,
|
|
39689
|
+
path: pc.path
|
|
39690
|
+
});
|
|
39691
|
+
const real = pcs.filter((pc) => !isSyntheticChange(pc) && pc.maskedExpressionChanged !== true);
|
|
39692
|
+
if (real.length === 0) break;
|
|
39693
|
+
const base = change.currentProperties ?? record?.properties;
|
|
39694
|
+
oldResources[id] = {
|
|
39695
|
+
Type: oldType,
|
|
39696
|
+
Properties: withChanges(base, real, "oldValue")
|
|
39697
|
+
};
|
|
39698
|
+
newResources[id] = {
|
|
39699
|
+
Type: change.resourceType,
|
|
39700
|
+
Properties: withChanges(base, real, "newValue")
|
|
39701
|
+
};
|
|
39702
|
+
break;
|
|
39703
|
+
}
|
|
39704
|
+
}
|
|
39705
|
+
}
|
|
39706
|
+
return {
|
|
39707
|
+
oldTemplate: { Resources: oldResources },
|
|
39708
|
+
newTemplate: { Resources: newResources },
|
|
39709
|
+
maskedChanges
|
|
39710
|
+
};
|
|
39711
|
+
}
|
|
39712
|
+
let cfnDiffModule;
|
|
39713
|
+
function loadCfnDiff() {
|
|
39714
|
+
cfnDiffModule ??= import("@aws-cdk/cloudformation-diff").then((mod) => mod.default ?? mod);
|
|
39715
|
+
return cfnDiffModule;
|
|
39716
|
+
}
|
|
39717
|
+
let cfnDiffUtilModule;
|
|
39718
|
+
function loadCfnDiffUtil() {
|
|
39719
|
+
cfnDiffUtilModule ??= import("@aws-cdk/cloudformation-diff/lib/diff/util.js").then((mod) => mod.default ?? mod);
|
|
39720
|
+
return cfnDiffUtilModule;
|
|
39721
|
+
}
|
|
39722
|
+
function isScrutinized(util, change) {
|
|
39723
|
+
const model = util.loadResourceModel(change.resourceType);
|
|
39724
|
+
const scrutinized = (value) => value !== void 0 && value !== "None";
|
|
39725
|
+
return scrutinized(model?.scrutinizable) || scrutinized(model?.properties?.[change.path]?.scrutinizable);
|
|
39726
|
+
}
|
|
39727
|
+
async function diffSecurity(args) {
|
|
39728
|
+
const templates = securityDiffTemplates(args.changes, args.records);
|
|
39729
|
+
const { oldTemplate, newTemplate } = templates;
|
|
39730
|
+
if (Object.keys(oldTemplate.Resources).length === 0 && Object.keys(newTemplate.Resources).length === 0 && templates.maskedChanges.length === 0) return {
|
|
39731
|
+
broadening: false,
|
|
39732
|
+
rendered: ""
|
|
39733
|
+
};
|
|
39734
|
+
try {
|
|
39735
|
+
const cfnDiff = await loadCfnDiff();
|
|
39736
|
+
const unjudged = templates.maskedChanges.length === 0 ? [] : await loadCfnDiffUtil().then((util) => templates.maskedChanges.filter((c) => isScrutinized(util, c)));
|
|
39737
|
+
const diff = cfnDiff.fullDiff(structuredClone(oldTemplate), structuredClone(newTemplate));
|
|
39738
|
+
const broadening = diff.permissionsBroadened || unjudged.length > 0;
|
|
39739
|
+
const unjudgedNote = unjudged.length === 0 ? "" : "These changed values are masked, so whether they broaden permissions could not be evaluated:\n" + unjudged.map((c) => ` ${c.logicalId} (${c.resourceType}): ${c.path}\n`).join("");
|
|
39740
|
+
if (!diff.permissionsBroadened && !diff.permissionsAnyChanges) return {
|
|
39741
|
+
broadening,
|
|
39742
|
+
rendered: terminalSafe(unjudgedNote)
|
|
39743
|
+
};
|
|
39744
|
+
let table;
|
|
39745
|
+
try {
|
|
39746
|
+
const shown = args.secretMask === void 0 ? void 0 : withholdSecrets(templates, args.secretMask());
|
|
39747
|
+
const shownDiff = shown === void 0 || shown.masked === 0 ? diff : cfnDiff.fullDiff(shown.templates.oldTemplate, shown.templates.newTemplate);
|
|
39748
|
+
table = "";
|
|
39749
|
+
cfnDiff.formatSecurityChanges({ write(chunk) {
|
|
39750
|
+
table += String(chunk);
|
|
39751
|
+
return true;
|
|
39752
|
+
} }, shownDiff, logicalIdToPath(args.records, args.template));
|
|
39753
|
+
if (shown !== void 0 && shown.masked > 0) table += "(Values that hold a secret are shown as ***.)\n";
|
|
39754
|
+
} catch {
|
|
39755
|
+
table = "The security changes are not shown: they could not be checked for secrets.\n";
|
|
39756
|
+
}
|
|
39757
|
+
return {
|
|
39758
|
+
broadening,
|
|
39759
|
+
rendered: terminalSafe(table + unjudgedNote)
|
|
39760
|
+
};
|
|
39761
|
+
} catch (error) {
|
|
39762
|
+
throw new CdkdError(safeMsg`Could not compute the security diff of stack ${args.stackName}, so whether its changes broaden permissions is unknown: ${error instanceof Error ? error.message : String(error)}`, "SECURITY_DIFF_FAILED");
|
|
39763
|
+
}
|
|
39764
|
+
}
|
|
39765
|
+
function holdsSecret(value, mask) {
|
|
39766
|
+
if (typeof value === "string") return mask(value) !== value;
|
|
39767
|
+
if (typeof value === "number" || typeof value === "boolean") return mask(String(value)) !== String(value);
|
|
39768
|
+
if (Array.isArray(value)) return value.some((v) => holdsSecret(v, mask));
|
|
39769
|
+
if (value !== null && typeof value === "object") return Object.entries(value).some(([k, v]) => mask(k) !== k || holdsSecret(v, mask));
|
|
39770
|
+
return false;
|
|
39771
|
+
}
|
|
39772
|
+
function maskLeaves(value, mask, keys = true) {
|
|
39773
|
+
if (Array.isArray(value)) return value.map((v) => maskLeaves(v, mask, keys));
|
|
39774
|
+
if (value !== null && typeof value === "object") {
|
|
39775
|
+
const entries = Object.entries(value).map(([k, v]) => [keys && mask(k) !== k ? "***" : k, maskLeaves(v, mask, keys)]);
|
|
39776
|
+
if (new Set(entries.map(([k]) => k)).size < entries.length) return "***";
|
|
39777
|
+
return Object.fromEntries(entries);
|
|
39778
|
+
}
|
|
39779
|
+
return holdsSecret(value, mask) ? "***" : value;
|
|
39780
|
+
}
|
|
39781
|
+
function withholdSecrets(templates, mask) {
|
|
39782
|
+
const copy = structuredClone(templates);
|
|
39783
|
+
let masked = 0;
|
|
39784
|
+
const ids = /* @__PURE__ */ new Set([...Object.keys(copy.oldTemplate.Resources), ...Object.keys(copy.newTemplate.Resources)]);
|
|
39785
|
+
for (const id of ids) {
|
|
39786
|
+
const oldProps = copy.oldTemplate.Resources[id]?.Properties;
|
|
39787
|
+
const newProps = copy.newTemplate.Resources[id]?.Properties;
|
|
39788
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(oldProps ?? {}), ...Object.keys(newProps ?? {})]);
|
|
39789
|
+
for (const key of keys) {
|
|
39790
|
+
const oldValue = oldProps?.[key];
|
|
39791
|
+
const newValue = newProps?.[key];
|
|
39792
|
+
const newHolds = holdsSecret(newValue, mask);
|
|
39793
|
+
if (!newHolds && !holdsSecret(oldValue, mask)) continue;
|
|
39794
|
+
masked++;
|
|
39795
|
+
if (newProps !== void 0 && newValue !== void 0) newProps[key] = maskLeaves(newValue, mask);
|
|
39796
|
+
if (oldProps === void 0 || oldValue === void 0) continue;
|
|
39797
|
+
if (newHolds && JSON.stringify(oldValue) !== JSON.stringify(newValue)) oldProps[key] = maskLeaves(oldValue, () => "***", false);
|
|
39798
|
+
else oldProps[key] = maskLeaves(oldValue, mask);
|
|
39799
|
+
}
|
|
39800
|
+
}
|
|
39801
|
+
return {
|
|
39802
|
+
templates: copy,
|
|
39803
|
+
masked
|
|
39804
|
+
};
|
|
39805
|
+
}
|
|
39806
|
+
function logicalIdToPath(records, template) {
|
|
39807
|
+
const map = {};
|
|
39808
|
+
for (const [id, record] of Object.entries(records)) if (typeof record?.constructPath === "string") map[id] = record.constructPath;
|
|
39809
|
+
for (const [id, resource] of Object.entries(template?.Resources ?? {})) {
|
|
39810
|
+
const path = (resource?.Metadata)?.["aws:cdk:path"];
|
|
39811
|
+
if (typeof path === "string") map[id] = path;
|
|
39812
|
+
}
|
|
39813
|
+
return map;
|
|
39814
|
+
}
|
|
39815
|
+
|
|
38396
39816
|
//#endregion
|
|
38397
39817
|
//#region src/deployment/resource-deadline.ts
|
|
38398
39818
|
var InvalidResourceDeadlineError = class extends Error {
|
|
@@ -38500,7 +39920,16 @@ async function requireDeploymentApproval(args) {
|
|
|
38500
39920
|
const changes = [...args.changes].filter((c) => c.changeType !== "NO_CHANGE" && !isNoEchoPromotionOnly(c));
|
|
38501
39921
|
const destructiveChanges = findDestructiveChanges(args.stackName, changes, args.records, args.template, new Set(args.recreateTargetIds ?? []));
|
|
38502
39922
|
const onlyNestedUpdates = changes.every((c) => c.changeType === "UPDATE" && c.resourceType === NESTED_STACK_TYPE$1);
|
|
38503
|
-
|
|
39923
|
+
const security = level === "broadening" ? await diffSecurity({
|
|
39924
|
+
stackName: args.stackName,
|
|
39925
|
+
changes,
|
|
39926
|
+
records: args.records,
|
|
39927
|
+
template: args.template,
|
|
39928
|
+
secretMask: args.secretMask
|
|
39929
|
+
}).catch((error) => {
|
|
39930
|
+
throw error instanceof Error ? markNonRetryable(error) : error;
|
|
39931
|
+
}) : void 0;
|
|
39932
|
+
if (!(level === "destructive" ? destructiveChanges.length > 0 : level === "broadening" ? security?.broadening === true : changes.length > 0 && !onlyNestedUpdates)) return false;
|
|
38504
39933
|
const count = (type) => changes.filter((c) => c.changeType === type).length;
|
|
38505
39934
|
if (enclosingDeadlineExpired()) throw approvalAfterTimeout(args.stackName);
|
|
38506
39935
|
let approved;
|
|
@@ -38513,7 +39942,8 @@ async function requireDeploymentApproval(args) {
|
|
|
38513
39942
|
update: count("UPDATE"),
|
|
38514
39943
|
delete: count("DELETE")
|
|
38515
39944
|
},
|
|
38516
|
-
destructiveChanges
|
|
39945
|
+
destructiveChanges,
|
|
39946
|
+
...security !== void 0 && { securityChanges: security.rendered }
|
|
38517
39947
|
}));
|
|
38518
39948
|
} catch (error) {
|
|
38519
39949
|
throw error instanceof Error ? markNonRetryable(error) : error;
|
|
@@ -38533,7 +39963,7 @@ async function checkDestructivePlan(args) {
|
|
|
38533
39963
|
async function approveLateReplacement(args) {
|
|
38534
39964
|
const level = args.options.requireApproval ?? "never";
|
|
38535
39965
|
const approve = args.options.approveDeployment;
|
|
38536
|
-
if (level === "never" || approve === void 0) return true;
|
|
39966
|
+
if (level === "never" || level === "broadening" || approve === void 0) return true;
|
|
38537
39967
|
if (enclosingDeadlineExpired()) return false;
|
|
38538
39968
|
try {
|
|
38539
39969
|
const approved = await whileEnclosingDeadlinesPaused(() => approve({
|
|
@@ -38680,14 +40110,14 @@ const REMEDY = "To move a custom resource to a different handler, give it a new
|
|
|
38680
40110
|
const UNJUDGEABLE_REMEDY = `Where the recorded ServiceToken is unreadable and the handler did not change, put the ARN of the handler that created this resource (the one it was last deployed with, not the one the template names now) back as ServiceToken in state.json (a ServiceToken fed by a NoEcho parameter is recorded only as '${"***"}': feed it from a plain value) and re-deploy.`;
|
|
38681
40111
|
function renderServiceTokenRefusal(refusals, stackName, mask = (text) => text) {
|
|
38682
40112
|
const rows = refusals.map((refusal) => ` - ${rowText(refusal, mask)}`);
|
|
38683
|
-
return `Refusing to deploy ${stackName}: ` + (refusals.length === 1 ? `a custom resource's ServiceToken changes` : `${refusals.length} custom resources' ServiceTokens change`) + `, which CloudFormation does not allow ("Modifying service token is not allowed")
|
|
40113
|
+
return `Refusing to deploy ${stackName}: ` + (refusals.length === 1 ? `a custom resource's ServiceToken changes` : `${refusals.length} custom resources' ServiceTokens change`) + `, which CloudFormation does not allow ("Modifying service token is not allowed"). Nothing was sent to either handler.
|
|
38684
40114
|
${rows.join("\n")}\n ${REMEDY}` + (refusals.some((refusal) => refusal.kind === "unjudgeable") ? `\n ${UNJUDGEABLE_REMEDY}` : "");
|
|
38685
40115
|
}
|
|
38686
40116
|
function serviceTokenBlockingReason(refusal, mask) {
|
|
38687
|
-
return `${rowText(refusal, mask)} cdkd deploy refuses a changed custom-resource ServiceToken, as CloudFormation does
|
|
40117
|
+
return `${rowText(refusal, mask)} cdkd deploy refuses a changed custom-resource ServiceToken, as CloudFormation does; give the custom resource a new logical id to move it to another handler.`;
|
|
38688
40118
|
}
|
|
38689
40119
|
function deferredServiceTokenWarning(row) {
|
|
38690
|
-
return `${displayIdent(row.logicalId)}: its ServiceToken reads a resource this deploy replaces, creates or updates, or a value this preview cannot resolve. If it resolves to a different ARN than the one recorded, cdkd deploy refuses the update before invoking any handler and rolls back
|
|
40120
|
+
return `${displayIdent(row.logicalId)}: its ServiceToken reads a resource this deploy replaces, creates or updates, or a value this preview cannot resolve. If it resolves to a different ARN than the one recorded, cdkd deploy refuses the update before invoking any handler and rolls back. A replaced backing function is already deleted by then and the rollback re-creates it; if that fails, the record names a function that no longer exists.`;
|
|
38691
40121
|
}
|
|
38692
40122
|
|
|
38693
40123
|
//#endregion
|
|
@@ -38792,6 +40222,24 @@ async function provisionUpdate(logicalId, change, stateResources, stackName, tem
|
|
|
38792
40222
|
if (resourceType === "AWS::CloudFormation::Stack") recordPassedNoEchoParameters(updateSecrets, desiredProps["Parameters"], this.noEchoPositionSources(stateResources));
|
|
38793
40223
|
if (fingerprintSources !== void 0 && resourceType === "AWS::CloudFormation::Stack") recordPassedParameterClasses(updateSecrets, await classifyPassedParameters(desiredProps["Parameters"], fingerprintSources));
|
|
38794
40224
|
const desiredForSkipCheck = maskAtCoordinates(redactSecretsForState(markSameGenerationBag({ ...resolvedProps }), updateSecrets, desiredProps), comparisonCoordinates);
|
|
40225
|
+
const legacyDesired = withoutPhysicalNameCopies(updateSecrets, () => maskAtCoordinates(redactSecretsForState(markSameGenerationBag({ ...resolvedProps }), updateSecrets, desiredProps), comparisonCoordinates));
|
|
40226
|
+
const copies = physicalNameCopiesOf.get(updateSecrets);
|
|
40227
|
+
const regionEvidence = getCurrentProducerRegions()?.();
|
|
40228
|
+
const recordOwnSecrets = copies !== void 0 && copies.size > 0 && regionEvidence?.complete === true && recordHoldsEmbeddedReference(currentResource) ? await resolveRecordSecrets({
|
|
40229
|
+
properties: currentResource.properties,
|
|
40230
|
+
attributes: currentResource.attributes
|
|
40231
|
+
}, (token, own) => this.resolver.resolveDynamicReferences(token, persistedTokenResolverContext(own, regionEvidence.regions))) : void 0;
|
|
40232
|
+
const embeddedRewrites = [...embeddedPhysicalNameRewrites(updateSecrets), ...recordOwnSecrets === void 0 || recordOwnSecrets.size === 0 ? [] : embeddedPhysicalNameRewrites(updateSecrets, recordOwnSecrets)];
|
|
40233
|
+
const repairable = [currentProps, currentResource.properties].filter((bag) => bag !== null && typeof bag === "object");
|
|
40234
|
+
for (const bag of new Set(repairable)) for (const key of Object.keys(bag)) {
|
|
40235
|
+
const repaired = repairEmbeddedPhysicalNamesTowards(repairPhysicalNameCopyLeaves(bag[key], legacyDesired[key], desiredForSkipCheck[key]), desiredForSkipCheck[key], embeddedRewrites);
|
|
40236
|
+
if (repaired !== bag[key]) bag[key] = repaired;
|
|
40237
|
+
}
|
|
40238
|
+
const attributes = currentResource.attributes;
|
|
40239
|
+
if (attributes !== void 0 && attributes !== null && embeddedRewrites.length > 0) for (const key of Object.keys(attributes)) {
|
|
40240
|
+
const repaired = replaceEmbeddedPhysicalNameRewrites(attributes[key], embeddedRewrites);
|
|
40241
|
+
if (repaired !== attributes[key]) attributes[key] = repaired;
|
|
40242
|
+
}
|
|
38795
40243
|
const allowedSilentDrops = this.providerRegistry.getAllowedUnsupportedProperties?.();
|
|
38796
40244
|
const desiredForSkipCheckAsWritten = currentResource.provisionedBy !== "cc-api" && allowedSilentDrops ? withoutAcceptedSilentDropProperties(resourceType, desiredForSkipCheck, allowedSilentDrops, currentResource.properties) : desiredForSkipCheck;
|
|
38797
40245
|
const allowedForRecord = allowedSilentDrops ?? /* @__PURE__ */ new Set();
|
|
@@ -39213,6 +40661,14 @@ function recordPreviousNoEchoLeaves(registry, stored, desiredLeaves, resolution,
|
|
|
39213
40661
|
if (partlyMasked !== leaf && partlyMasked !== "***") recordLogOnlyValue(registry, partlyMasked);
|
|
39214
40662
|
}
|
|
39215
40663
|
}
|
|
40664
|
+
function recordHoldsEmbeddedReference(record) {
|
|
40665
|
+
const walk = (node) => {
|
|
40666
|
+
if (typeof node === "string") return node.includes("{{resolve:") && !isSingleDynamicReferenceToken(node);
|
|
40667
|
+
if (node === null || typeof node !== "object") return false;
|
|
40668
|
+
return Object.values(node).some(walk);
|
|
40669
|
+
};
|
|
40670
|
+
return walk(record.properties) || walk(record.attributes);
|
|
40671
|
+
}
|
|
39216
40672
|
|
|
39217
40673
|
//#endregion
|
|
39218
40674
|
//#region src/deployment/deploy-engine/update-replace.ts
|
|
@@ -40246,6 +41702,7 @@ async function executeDeployment(template, currentState, changes, dag, execution
|
|
|
40246
41702
|
const concurrency = this.options.concurrency;
|
|
40247
41703
|
this.deployChanges = changes;
|
|
40248
41704
|
const newResources = { ...currentState.resources };
|
|
41705
|
+
this.deployStateResources = newResources;
|
|
40249
41706
|
const actualCounts = {
|
|
40250
41707
|
created: 0,
|
|
40251
41708
|
updated: 0,
|
|
@@ -40304,16 +41761,29 @@ async function executeDeployment(template, currentState, changes, dag, execution
|
|
|
40304
41761
|
this.logger.info(`${cyan("Deploying")} ${cyan(createUpdateIds.length)} resource(s) (DAG: ${executionLevels.length} levels, max parallel: ${concurrency})`);
|
|
40305
41762
|
const createUpdateExecutor = new DagExecutor();
|
|
40306
41763
|
const provisionable = new Set(createUpdateIds);
|
|
41764
|
+
const createUpdateDeps = /* @__PURE__ */ new Map();
|
|
40307
41765
|
for (const id of createUpdateIds) {
|
|
40308
41766
|
const allDeps = this.dagBuilder.getDirectDependencies(dag, id);
|
|
40309
|
-
|
|
40310
|
-
createUpdateExecutor.add({
|
|
40311
|
-
id,
|
|
40312
|
-
dependencies: deps,
|
|
40313
|
-
state: "pending",
|
|
40314
|
-
data: changes.get(id)
|
|
40315
|
-
});
|
|
41767
|
+
createUpdateDeps.set(id, new Set(allDeps.filter((d) => provisionable.has(d))));
|
|
40316
41768
|
}
|
|
41769
|
+
const levelOf = /* @__PURE__ */ new Map();
|
|
41770
|
+
executionLevels.forEach((level, at) => {
|
|
41771
|
+
for (const id of level) levelOf.set(id, at);
|
|
41772
|
+
});
|
|
41773
|
+
for (const { before, after } of orderInlinePolicyWriters({
|
|
41774
|
+
writes: createUpdateIds,
|
|
41775
|
+
changes,
|
|
41776
|
+
templateResources: template.Resources ?? {},
|
|
41777
|
+
stateResources: currentState.resources,
|
|
41778
|
+
dependencies: createUpdateDeps,
|
|
41779
|
+
rank: (id) => levelOf.get(id) ?? Number.MAX_SAFE_INTEGER
|
|
41780
|
+
})) this.logger.debug(safeMsg`${after} waits for ${before}: both write an inline policy name on the same IAM principal`);
|
|
41781
|
+
for (const id of createUpdateIds) createUpdateExecutor.add({
|
|
41782
|
+
id,
|
|
41783
|
+
dependencies: createUpdateDeps.get(id),
|
|
41784
|
+
state: "pending",
|
|
41785
|
+
data: changes.get(id)
|
|
41786
|
+
});
|
|
40317
41787
|
const enqueueLostChildren = (executor) => {
|
|
40318
41788
|
const lostIds = noChangeChildrenOfRecreatedParents({
|
|
40319
41789
|
changes,
|
|
@@ -41611,7 +43081,7 @@ function conditionFingerprint(template, name, inputOf) {
|
|
|
41611
43081
|
inputs[parameter] = { token: input.expression };
|
|
41612
43082
|
hasToken = true;
|
|
41613
43083
|
} else {
|
|
41614
|
-
if (
|
|
43084
|
+
if (declaresNoEcho(definition)) return void 0;
|
|
41615
43085
|
if (typeof input.value !== "string") return void 0;
|
|
41616
43086
|
if (carriesDynamicReference(input.value)) return void 0;
|
|
41617
43087
|
if (coerceParameterTypedValue(input.value, type) !== input.value) return void 0;
|
|
@@ -42177,7 +43647,7 @@ async function doDeployWithPrefetch(stackName, template, createOnlyPrefetch) {
|
|
|
42177
43647
|
const persistExportSet = new Set(exportNamesToPersist ?? []);
|
|
42178
43648
|
const exportSetChanged = exportNamesToPersist !== void 0 && (currentEffectiveExports.size !== persistExportSet.size || [...persistExportSet].some((k) => !currentEffectiveExports.has(k)));
|
|
42179
43649
|
if (merge?.kind === "kept" && !outputMapsEqual(persistedOutputs, resolvedOutputs)) this.logger.warn(`Outputs changed but one or more could not be resolved; keeping the previously persisted outputs. ${keptWholeReasonText(merge.reason)} A downstream Fn::ImportValue may fail until the next deploy.`);
|
|
42180
|
-
else if (merge?.kind === "merged" && merge.carriedKeys.length > 0) this.logger.debug(`Kept the previously persisted value of ${merge.carriedKeys.length} output key(s), carried export aliases included, that could not be resolved (no-change path
|
|
43650
|
+
else if (merge?.kind === "merged" && merge.carriedKeys.length > 0) this.logger.debug(`Kept the previously persisted value of ${merge.carriedKeys.length} output key(s), carried export aliases included, that could not be resolved (no-change path)`);
|
|
42181
43651
|
const skippedOutputsChanged = !skippedOutputsEqual(currentState.skippedOutputs, this.skippedOutputs);
|
|
42182
43652
|
const healedAttributesPending = this.hasUnpersistedHeals(currentState.resources);
|
|
42183
43653
|
const conditionVerdictsChanged = !conditionVerdictRecordsEqual(readRecordedConditionVerdicts(currentState), conditionVerdicts);
|
|
@@ -42206,14 +43676,14 @@ async function doDeployWithPrefetch(stackName, template, createOnlyPrefetch) {
|
|
|
42206
43676
|
if (outputsChanged || exportSetChanged) {
|
|
42207
43677
|
persistedOutputs = refreshedState.outputs;
|
|
42208
43678
|
if (outputsChanged) this.logger.info("Persisted Outputs-only change (no resource diff).");
|
|
42209
|
-
else this.logger.debug("Persisted export-set change (no outputs-value diff, no-change path
|
|
43679
|
+
else this.logger.debug("Persisted export-set change (no outputs-value diff, no-change path)");
|
|
42210
43680
|
if (this.exportIndexStore) await this.exportIndexStore.updateForStack(stackName, this.stackRegion, importableOutputs({
|
|
42211
43681
|
...refreshedState,
|
|
42212
43682
|
outputs: this.redactOutputs(refreshedState.outputs)
|
|
42213
43683
|
}));
|
|
42214
43684
|
} else if (observedRefresh) this.logger.debug("Persisted refreshed observedProperties (no-change path)");
|
|
42215
|
-
else if (healedAttributesPending) this.logger.debug("Persisted attributes re-read from AWS for a stale record (no-change path
|
|
42216
|
-
else this.logger.debug("Persisted skipped-outputs record (no outputs-value diff, no-change path
|
|
43685
|
+
else if (healedAttributesPending) this.logger.debug("Persisted attributes re-read from AWS for a stale record (no-change path)");
|
|
43686
|
+
else this.logger.debug("Persisted skipped-outputs record (no outputs-value diff, no-change path)");
|
|
42217
43687
|
} catch (saveError) {
|
|
42218
43688
|
this.logger.warn(`Failed to persist no-change state update: ${saveError instanceof Error ? saveError.message : String(saveError)} — drift baseline / outputs will be re-resolved on next deploy.`);
|
|
42219
43689
|
}
|
|
@@ -42294,7 +43764,8 @@ async function doDeployWithPrefetch(stackName, template, createOnlyPrefetch) {
|
|
|
42294
43764
|
changes: changes.values(),
|
|
42295
43765
|
records: currentState.resources,
|
|
42296
43766
|
template: effectiveTemplate,
|
|
42297
|
-
recreateTargetIds: recreateTargetIdsFor(this.options.recreateTargets, stackName)
|
|
43767
|
+
recreateTargetIds: recreateTargetIdsFor(this.options.recreateTargets, stackName),
|
|
43768
|
+
secretMask: () => this.diffLogMasker(diffResolverContext.recordedSecretValues, effectiveTemplate, parameterValues, currentState.resources)
|
|
42298
43769
|
})) this.generatedNameGuard?.noteApprovalPrompted();
|
|
42299
43770
|
this.generatedNameGuard?.recordPlannedIntents();
|
|
42300
43771
|
this.resolver.resetPhysicalIdFallbackCount();
|
|
@@ -42628,6 +44099,7 @@ var DeployEngine = class {
|
|
|
42628
44099
|
oldDeletedBeforeCreate = /* @__PURE__ */ new Set();
|
|
42629
44100
|
healBaseline = {};
|
|
42630
44101
|
deployChanges = /* @__PURE__ */ new Map();
|
|
44102
|
+
deployStateResources = {};
|
|
42631
44103
|
stackRecordsView = void 0;
|
|
42632
44104
|
inlinePolicyWriters = /* @__PURE__ */ new Map();
|
|
42633
44105
|
attributeHeals = /* @__PURE__ */ new Map();
|
|
@@ -42644,7 +44116,9 @@ var DeployEngine = class {
|
|
|
42644
44116
|
this.exportIndexStore = exportIndexStore;
|
|
42645
44117
|
this.resolver = new IntrinsicFunctionResolver(stackRegion, {
|
|
42646
44118
|
strictGetAtt: options.strictGetAtt ?? false,
|
|
42647
|
-
cfnFallback: options.cfnFallback ?? true
|
|
44119
|
+
cfnFallback: options.cfnFallback ?? true,
|
|
44120
|
+
...options.producerLookup && { producerLookup: options.producerLookup },
|
|
44121
|
+
...options.consumerAccount && { consumerAccount: options.consumerAccount }
|
|
42648
44122
|
});
|
|
42649
44123
|
this.options.concurrency = options.concurrency ?? 10;
|
|
42650
44124
|
this.options.dryRun = options.dryRun ?? false;
|
|
@@ -42692,6 +44166,7 @@ var DeployEngine = class {
|
|
|
42692
44166
|
this.oldDeletedBeforeCreate = /* @__PURE__ */ new Set();
|
|
42693
44167
|
this.healBaseline = {};
|
|
42694
44168
|
this.deployChanges = /* @__PURE__ */ new Map();
|
|
44169
|
+
this.deployStateResources = {};
|
|
42695
44170
|
this.inlinePolicyWriters = /* @__PURE__ */ new Map();
|
|
42696
44171
|
this.attributeHeals = /* @__PURE__ */ new Map();
|
|
42697
44172
|
this.healedAttributes = /* @__PURE__ */ new Map();
|
|
@@ -42761,7 +44236,7 @@ var DeployEngine = class {
|
|
|
42761
44236
|
};
|
|
42762
44237
|
}
|
|
42763
44238
|
inlinePolicyClaimedFor(resourceType, logicalId, stateResources) {
|
|
42764
|
-
if (resourceType !== "AWS::IAM::Policy")
|
|
44239
|
+
if (resourceType !== "AWS::IAM::Policy" && !isInlinePolicyPrincipalType(resourceType) && !isStandaloneInlinePolicyType(resourceType)) return;
|
|
42765
44240
|
const args = {
|
|
42766
44241
|
selfLogicalId: logicalId,
|
|
42767
44242
|
writers: this.inlinePolicyWriters,
|
|
@@ -42908,5 +44383,5 @@ DeployEngine.prototype.replacementNameOrigin = replacementNameOrigin;
|
|
|
42908
44383
|
DeployEngine.prototype.orphanedNameCollisionAdvice = orphanedNameCollisionAdvice;
|
|
42909
44384
|
|
|
42910
44385
|
//#endregion
|
|
42911
|
-
export { getPriorAttempts as $, coerceCfnBoolean as $a, processStackMessages as $c, malformedScrubResourceEntriesRefusalMessage as $i, reserveStackCreateToken as $n, inheritNestedStackParameterAssociations as $o, envCredentialsAreAssumedRole as $r, parseBootstrapMarker as $s, orphanDeleteNeedsIdentity as $t, noEchoExactEchoLeavesOf as A, repairMalformedResourcePropertiesForReadOnly as Aa, resolveApp as Ac, displayLogicalId as Ai, maskEventTextWithBoundBags as An, valueAtCoordinate as Ao, markWaitAbandoned as Ar, hasReadableExportSet as As, isHandledOrphan as At, beginDeleteWait as B, NO_CC_HANDLERS_REASON as Ba, CFN_TEMPLATE_BODY_LIMIT as Bc, malformedLocalResourceEntriesWarning as Bi, ccRoutedFinalSnapshotError as Bn, shareLogOnlyValues as Bo, poisonRenderedSpellingsCollidingIn as Br, isSensitiveAttributeName as Bs, withStackRecords as Bt, recordRetainedForReadoption as C, refuseMalformedResourcesForDestroy as Ca, AssetManifestLoader as Cc, JSON_LISTING_HOLE_VALUE as Ci, collectPublishedOutputNames as Cn, maskedLeafCoordinatesOf as Co, unchangedBehindSecretReference as Cr, crossPrefixDeniedWarning as Cs, revertedNestedRowIds as Ct, withResourceDeadline as D, repairMalformedOrphansForReadOnly as Da, getDefaultStateBucketName as Dc, UNREADABLE_RESOURCES_MAP_ROW as Di, DEFAULT_RESOURCE_WARN_AFTER_MS as Dn, passedNoEchoParametersOf as Do, deleteSkipReason as Dr, acceptedCreateOnlyDropsOf as Ds, withNestedStackContext as Dt, serviceTokenBlockingReason as E, repairMalformedOrphanRecordsForReadOnly as Ea, synthesisStatusMessage as Ec, UNREADABLE_ORPHANS_CONTAINER_ROW as Ei, DEFAULT_RESOURCE_TIMEOUT_MS as En, noEchoOutputsComparison as Eo, deleteLeftInPlace as Er, splitImportedOps as Es, getCurrentNestedStackContext as Et, isProtectionValueActive as F, withheldAccountClause as Fa, resolveStateBucketWithDefaultAndSource as Fc, isReadableResourceEntry as Fi, storedSecretNameNeedles as Fn, literalSplitDelimitersOf as Fo, reDisableInstanceApiTermination as Fr, isReadableBag as Fs, replayFailedOperations as Ft, DELETE_INDEX_WAIT_PROCEED_NOTE as G, isStatefulRecreateTargetForReplace as Ga, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as Gc, malformedOrphansRefusalMessage as Gi, unsupportedFinalSnapshotError as Gn, markSameGenerationBag as Go, assertFlagRoleArn as Gr, loadPublishableAssetManifest as Gs, isReplacementOrphan as Gt, deleteBudgetKey as H, findReplacedReadersOfRecreateTargets as Ha, MIGRATE_TMP_PREFIX as Hc, malformedOrphanRecordsRefusalMessage as Hi, effectiveDeletionPolicy as Hn, errorCauseChain as Ho, producerRegionsFromState as Hr, WorkGraph as Hs, recordAfterRollbackUpdate as Ht, perType as I, findActionableSilentDrops as Ia, resolveUseCdkBootstrapAssets as Ic, malformedDriftResourcePropertiesWarning as Ii, extractDeploymentEventError as In, logOnlyValueCount as Io, reEnableInstanceApiTermination as Ir, orphansAfterRollback as Is, replayRollback as It, waitForIndexesSettled as J, WARM_THROUGHPUT_MEMBERS as Ja, PARTITION_TABLE as Jc, malformedOutputsWarning as Ji, findDestructiveChanges as Jn, DYNAMIC_REFERENCE_TOKEN_SCAN as Jo, isIamRoleArn as Jr, BOOTSTRAP_MARKER_PREFIX as Js, recheckFailedPlan as Jt, INDEX_SETTLE_POLL_INTERVAL_MS as K, isStatefulRecreateTargetSync as Ka, decodeListingKey as Kc, malformedOrphansWarning as Ki, maskingRetryLogger as Kn, redactSecretsForState as Ko, assertSendableRoleArn as Kr, rewriteTemplateAssetReferences as Ks, planFailedOps as Kt, readProtection as L, findSilentDropProperties as La, stateBucketExistenceConfirmed as Lc, malformedExportNamesWarning as Li, ATOMIC_FINAL_SNAPSHOT_TYPES as Ln, printingCorpusOf as Lo, DiffCalculator as Lr, orphansCarriedFrom as Ls, awaitInFlightSibling as Lt, recordedProtectionEvidence as M, unpreviewableOrphanRecords as Ma, resolveCaptureObservedState as Mc, hasReadableOutputs as Mi, secretNamePrintingBag as Mn, carryLogOnlyValues as Mo, ec2InstanceProtectionSite as Mr, hasUnverifiableParameterRefusal as Ms, loadJournaledOrphans as Mt, recordedProtectionNote as N, unreadableOrphanRecords as Na, resolveSkipPrefix as Nc, hasReadableResources as Ni, secretNamedRecordNeedles as Nn, createUnionSecretMasker as No, isTerminationProtectionPropagationError as Nr, importableOutputKeys as Ns, makeForeignHolderScan as Nt, noEchoAttributeNamesOf as O, repairMalformedOutputsForReadOnly as Oa, getLegacyStateBucketName as Oc, accountArgs as Oi, completedReplayEntries as On, readbackPathFor as Oo, withIndeterminateGuard as Or, describeRegionValueKind as Os, deleteJournaledOrphans as Ot, PROTECTION_PROPERTY_BY_TYPE as P, unreadableResourceEntries as Pa, resolveStateBucketWithDefault as Pc, isReadableOrphanRecord as Pi, stateSecretNameNeedles as Pn, hasMaskableValues as Po, observeThenDisableInstanceApiTermination as Pr, importableOutputs as Ps, sameJournaledOrphans as Pt, isTruthyCfnBoolean as Q, toFiniteNumber as Qa, StageLoadError as Qc, malformedResourcesWarning as Qi, ledgerForStack as Qn, identityKeyFor as Qo, callerEnvCredentials as Qr, isCrossRegionRedirect as Qs, seedsDeleteFirstGuard as Qt, DynamoDBGlobalTableProvider as R, unwrittenCreateOnlyReplacement as Ra, warnDeprecatedNoPrefixCliFlag as Rc, malformedExportSourceWarning as Ri, PRE_DELETE_SNAPSHOT_TYPES as Rn, recordLogOnlyParameterValue as Ro, INTRINSIC_KEYS as Rr, shouldRetainResource as Rs, destroyStackRecordsView as Rt, keptForReadoption as S, refuseMalformedResourcesForDeploy as Sa, stripControlChars as Sc, passedParameterClassesOf as Si, collectDeclaredOutputNames as Sn, maskWholeValue as So, slowCcOperationTimeoutMs as Sr, applyCrossPrefixScan as Ss, revertNestedChildFromJournal as St, findServiceTokenRefusals as T, refuseMalformedState as Ta, Synthesizer as Tc, STATE_RESOURCES_MALFORMED as Ti, bindingSkippedOutputs as Tn, noEchoLeavesOf as To, deleteIndeterminateGuards as Tr, isNotImplemented as Ts, collectNestedChildUnaddressed as Tt, resolveDynamoDbDeleteBudgetClock as U, shownType as Ua, findLargeInlineResources as Uc, malformedOrphanRecordsWarning as Ui, isFinalSnapshotError as Un, maskSecretsInError as Uo, regionLessSecretName as Ur, buildAssetRedirectMap as Us, demoteSupersededOrphans as Ut, compensateRemovedDeletionProtection as V, hasNoCloudControlHandlers as Va, CFN_TEMPLATE_URL_LIMIT as Vc, malformedLocalResourcesWarning as Vi, createPreDeleteFinalSnapshot as Vn, createSecretMasker as Vo, classifyReplaySecretRegion as Vr, stringifyValue as Vs, removeProtectionTypeList as Vt, resolveDynamoDbDeleteBudgetMs as W, MULTI_REGION_RECREATE_BLOCKED_TYPES as Wa, uploadCfnTemplate as Wc, malformedOrphanRowsKeptWarning as Wi, refusesFinalSnapshot as Wn, maskSecretsInText as Wo, applyRoleArnIfSet as Wr, createAssetRedirectResolver as Ws, isJournaledOrphan as Wt, S3_AUTO_DELETE_OBJECTS_TAG as X, isWarmThroughputDecrease as Xa, derivePartitionAndUrlSuffix as Xc, malformedResourceEntriesWarning as Xi, RollbackInlinePolicyWriters as Xn, isUncertifiedBaselineMaskPosition as Xo, AWS_CREDENTIAL_ENV_KEYS as Xr, ensureAssetStorage as Xs, replacementNeverSwapped as Xt, ECR_AUTO_DELETE_IMAGES_TAG as Y, coerceWarmThroughput as Ya, canonicalizeRegion as Yc, malformedRenderedContainersWarning as Yi, formatDestructiveChange as Yn, dynamicReferenceTokens as Yo, sendableAssumeRoleCommand as Yr, assertAssetBucketRegion as Ys, recordUnderIdIsNotOwn as Yt, hasCdkAutoDeleteTag as Z, toCfnInteger as Za, AssemblyReader as Zc, malformedResourcePropertiesWarning as Zi, earliestDefined as Zn, pathCrossesDottedKey as Zo, applyCallerIdentityCredentials as Zr, getBootstrapMarkerKey as Zs, resolveReplacementOldType as Zt, makeCanonicalizePropertiesFn as _, refuseMalformedResourceEntriesForOrphan as _a, redactedDockerCause as _c, isStalePlaceholderArnAttribute as _i, carriedExportAliasExposure as _n, TemplateParser as _o, readsNoEchoDeleteValues as _r, isLockInfoExpired as _s, displacedPhysicalIdShown as _t, findNestedTemplateTreeDefect as a, refuseMalformedOrphanRecordsForDestroy as aa, resolveDockerContextDirectory as ac, s3BucketWebsiteUrl as ai, absoluteAssemblyPathEscape as al, redactedDeleteAddressFields as an, replayWarn as ao, readTagList as ar, recordMaskOnlyValuesIn as as, gray as at, lookupEachName as b, refuseMalformedResourcePropertiesForExport as ba, warnFinchArgvExposure as bc, withSharedDrainBudget as bi, isWholeDynamicReferenceValue as bn, maskAtCoordinates as bo, withPrintingSecrets as br, CrossPrefixReadError as bs, nestedChildStackName as bt, renderNestedTemplateTreeDefect as c, refuseMalformedOrphansForOrphan as ca, describeDockerFailure as cc, parameterTypeMayLoseSecretIdentity as ci, renderAssemblyPathEscape as cl, updatePartialReason as cn, requireConfigString as co, tagPlanWarning as cr, recordProvenPublicExpression as cs, yellow as ct, conditionInputsFrom as d, refuseMalformedResourceAttributesForOrphan as da, formatDockerLoginError as dc, carriesDynamicReference as di, resolveBucketRegion as dl, WITHHELD_NAME_DISPLAY as dn, describeTypeWithThrottleRetry as do, CC_BROKEN_REASON as dr, STATE_SOURCED_CROSS_GENERATION_RULES as ds, reachableDivergentParameters as dt, malformedStateRefusalMessage as ea, readBootstrapMarkerBody as ec, isSettledInstanceState as ei, defineOwnKey as el, findNestedStackTypeChanges as en, coerceCfnInteger as eo, withCreateTokenLedger as er, recordNestedStackParameterExpressions as es, markRefusedBeforeApplying as et, parentSuppliedValues as f, refuseMalformedResourceEntries as fa, getDockerCmd as fc, carriesFetchableDynamicReference as fi, displayTextOrWithheld as fn, withRetry as fo, ProviderRegistry as fr, STATE_SOURCED_READBACK_RULES as fs, resourcesNamingDeclaredParameter as ft, mergeNoChangeOutputs as g, refuseMalformedResourceEntriesForImportSave as ga, redactDockerArgvValues as gc, cfnRefValueFromPhysicalId as gi, secretSafeKeyDisplay as gn, DagBuilder as go, TemplateNoEchoReresolver as gr, LockManager as gs, displacedOpClause as gt, keptWholeReasonText as h, refuseMalformedResourceEntriesForImport as ha, partitionSensitiveEnv as hc, AWS_NO_VALUE as hi, secretBearingStateKeyWarning as hn, sameLambdaFunctionAddress as ho, wouldReturnToSdkProvider as hr, isSingleDynamicReferenceToken as hs, NESTED_PENDING_PARENT_REASON as ht, childTemplateLoader as i, refuseMalformedOrphanRecords as ia, buildDockerImage as ic, s3BucketRegionalDomainName as ii, ownValue as il, isRedactedRecordedValue as in, readConfigString as io, planTagDiff as ir, recordMaskOnlyValue as is, cyan as it, noEchoParameterNamesOf as j, repairMalformedResourcesForReadOnly as ja, resolveAutoAssetStorage as jc, hasReadableOrphans as ji, orphanRecordsPrintingBag as jn, liveMatchesUnresolvedTokenFrame as jo, disableInstanceApiTermination as jr, hasReasonlessBaselineRefusal as js, journaledOrphanLines as jt, noEchoComparisonForTemplate as k, repairMalformedResourceEntriesForReadOnly as ka, loadCdkJson as kc, deployRefusesOrphanRowsReason as ki, journaledOrphanPrintingBag as kn, readsNoEchoSource as ko, isWaitAbandonedError as kr, exportNamesCarriedFrom as ks, dropFailedHint as kt, templateIdentity as l, refuseMalformedOutputs as la, dockerSpawnEnvWithSensitive as lc, getAccountInfo as li, resolveAssemblyPath as ll, DeploymentEventsReader as ln, prefetchCreateOnlyPropertyPaths as lo, definedAttributes as lr, wholeStringLeavesOf as ls, computeParameterTaint as lt, isSecretBearingReferenceString as m, refuseMalformedResourceEntriesForDestroy as ma, isMalformedEnvKey as mc, keepsSecretReferenceToken as mi, secretBearing as mn, lambdaFunctionNameForMask as mo, isCustomResource as mr, WHOLE_DYNAMIC_REFERENCE_PATTERN as ms, planOrphanAdoption as mt, IMPLICIT_DELETE_DEPENDENCIES as n, refuseDivergentRecordRegionForDestroy as na, validateContainerRepoName as nc, s3BucketDomainName as ni, hasPlainPrototype$1 as nl, inheritProducerRegions as nn, configIntegerRefusal as no, DBPROXY_TAGS_WHAT as nr, carriesSecretMask as ns, createReadOnlyAttributeHealerFactory as nt, isAbsoluteAssetPath as o, refuseMalformedOrphans as oa, describeDockerCapturedOutput as oc, coerceParameterTypedValue as oi, describeFileReadFailure as ol, redactedDeleteAddressSkip as on, requireConfigArray as oo, refuseMalformedDesiredTags as or, recordNoEchoAttributeValues as os, green as ot, readRecordedConditionVerdicts as p, refuseMalformedResourceEntriesForDeploy as pa, isDockerClientEnvKey as pc, isNamelessDynamicReferenceError as pi, exportAliasCollisionScrubWarning as pn, canonicalLambdaFunctionName as po, ccBrokenReason as pr, TEMPLATE_SOURCED_RULES as ps, makeSiblingClaimReader as pt, deleteTableWithIndexBusyRetry as q, renderStatefulReason as qa, expectedOwnerParam as qc, malformedOutputsRefusalMessage as qi, constructPathOf as qn, scrubResourceRecord as qo, explicitRoleArnOrThrow as qr, AssetModeResolver as qs, planRollback as qt, computeImplicitDeleteEdges as r, refuseMalformedNestedChildOutputs as ra, buildDenyExternalAccessPolicy as rc, s3BucketDualStackDomainName as ri, nullPrototypeRecord as rl, REDACTED_DELETE_ADDRESS_SKIP_REASON as rn, configStringRefusal as ro, DBPROXY_TAG_OPTIONS as rr, provenPublicValue as rs, bold as rt, listNestedTemplateRows as s, refuseMalformedOrphansForDestroy as sa, describeDockerExecFailure as sc, isUnboundTemplateParameter as si, displayAssemblyPath as sl, updatePartialMessage as sn, requireConfigObject as so, tagMapAsList as sr, recordNoEchoParameterFreshValue as ss, red as st, DeployEngine as t, previewableOrphanRecords as ta, validateAssetBucketName as tc, s3BucketArn as ti, hasOwnKey as tl, getCurrentProducerRegions as tn, configBooleanRefusal as to, acquireIdempotencyToken as tr, recoverMaskedOutput as ts, formatResourceLine as tt, conditionFingerprint as u, refuseMalformedOutputsForDestroy as ua, finchSecretArgvRefusal as uc, isImpossibleEmptyStoredAttribute as ui, clearBucketRegionCache as ul, DeploymentEventsStore as un, templateResourceTypes as uo, stringifyIfAssigned as ur, STATE_SOURCED_BASELINE_RULES as us, namesAnyDeclaredParameter as ut, chunks as v, refuseMalformedResourcePropertiesForDestroy as va, runDockerForeground as vc, refStateLookupFromResource as vi, exportNameNoEchoParameters as vn, canonicalCoordinates as vo, isCustomResourceType as vr, S3StateBackend as vs, dropSettledNestedJournals as vt, deferredServiceTokenWarning as w, refuseMalformedResourcesForExport as wa, getDockerImageBySourceHash as wc, STATE_REGION_DIVERGED as wi, isExportAliasCollision as wn, noEchoCoordinatesOf as wo, UNSPECIFIED_SKIP_REASON as wr, isAccessDenied as ws, withNestedRevertRun as wt, withApiLimit as x, refuseMalformedResourcePropertiesForOrphan as xa, escapeRegExp$3 as xc, parameterInputsFor as xi, noEchoParameterValueSeed as xn, maskReadbackAtCoordinates as xo, CloudControlProvider as xr, CrossPrefixScanCache as xs, recordDisplacedSkips as xt, isAccessDeniedError as y, refuseMalformedResourcePropertiesForDrift as ya, runDockerStreaming as yc, parseWebACLArn as yi, exportNameSecretExposure as yn, isMarkedCoordinate as yo, getCurrentResourceSecrets as yr, rebuildClientForBucketRegion as ys, getNestedRevertRun as yt, DYNAMODB_DELETE_MIN_RESOURCE_TIMEOUT_MS as z, withoutSilentDropProperties as za, ssmResolvedValueType as zc, malformedLocalOutputsWarning as zi, buildFinalSnapshotIdentifier as zn, recordLogOnlyValue as zo, IntrinsicFunctionResolver as zr, AssetPublisher as zs, getStackRecords as zt };
|
|
42912
|
-
//# sourceMappingURL=deploy-engine-
|
|
44386
|
+
export { isTruthyCfnBoolean as $, isStatefulRecreateTargetForReplace as $a, CFN_TEMPLATE_URL_LIMIT as $c, malformedOrphansRefusalMessage as $i, ledgerForStack as $n, markSameGenerationBag as $o, assertSendableRoleArn as $r, stringifyValue as $s, seedsDeleteFirstGuard as $t, noEchoComparisonForTemplate as A, refuseMalformedResourcesForDeploy as Aa, runDockerForeground as Ac, declaresNoEcho as Ai, journaledOrphanPrintingBag as An, maskWholeValue as Ao, deleteIndeterminateGuards as Ar, S3StateBackend as As, dropFailedHint as At, DYNAMODB_DELETE_MIN_RESOURCE_TIMEOUT_MS as B, unpreviewableOrphanRecords as Ba, getLegacyStateBucketName as Bc, hasReadableOutputs as Bi, PRE_DELETE_SNAPSHOT_TYPES as Bn, carryLogOnlyValues as Bo, reDisableInstanceApiTermination as Br, describeRegionValueKind as Bs, getStackRecords as Bt, recordRetainedForReadoption as C, refuseMalformedResourceEntriesForImport as Ca, formatDockerLoginError as Cc, cfnRefValueFromPhysicalId as Ci, resolveBucketRegion as Cl, collectDeclaredOutputNames as Cn, sameLambdaFunctionAddress as Co, applyCustomResourceResponseExpiration as Cr, STATE_SOURCED_CROSS_GENERATION_RULES as Cs, revertNestedChildFromJournal as Ct, withResourceDeadline as D, refuseMalformedResourcePropertiesForDrift as Da, partitionSensitiveEnv as Dc, withSharedDrainBudget as Di, DEFAULT_RESOURCE_TIMEOUT_MS as Dn, isMarkedCoordinate as Do, unchangedBehindSecretReference as Dr, isSingleDynamicReferenceToken as Ds, getCurrentNestedStackContext as Dt, serviceTokenBlockingReason as E, refuseMalformedResourcePropertiesForDestroy as Ea, isMalformedEnvKey as Ec, parseWebACLArn as Ei, bindingSkippedOutputs as En, canonicalCoordinates as Eo, slowCcOperationTimeoutMs as Er, WHOLE_DYNAMIC_REFERENCE_PATTERN as Es, collectNestedChildUnaddressed as Et, PROTECTION_PROPERTY_BY_TYPE as F, repairMalformedOrphansForReadOnly as Fa, AssetManifestLoader as Fc, UNREADABLE_RESOURCES_MAP_ROW as Fi, secretNamedRecordNeedles as Fn, passedNoEchoParametersOf as Fo, markWaitAbandoned as Fr, crossPrefixDeniedWarning as Fs, sameJournaledOrphans as Ft, resolveDynamoDbDeleteBudgetMs as G, findSilentDropProperties as Ga, resolveSkipPrefix as Gc, malformedExportNamesWarning as Gi, isFinalSnapshotError as Gn, printingCorpusOf as Go, poisonRenderedSpellingsCollidingIn as Gr, importableOutputKeys as Gs, isJournaledOrphan as Gt, compensateRemovedDeletionProtection as H, unreadableResourceEntries as Ha, resolveApp as Hc, isReadableOrphanRecord as Hi, ccRoutedFinalSnapshotError as Hn, hasMaskableValues as Ho, DiffCalculator as Hr, hasReadableExportSet as Hs, removeProtectionTypeList as Ht, isProtectionValueActive as I, repairMalformedOutputsForReadOnly as Ia, getDockerImageBySourceHash as Ic, accountArgs as Ii, stateSecretNameNeedles as In, readbackPathFor as Io, disableInstanceApiTermination as Ir, isAccessDenied$1 as Is, replayFailedOperations as It, deleteTableWithIndexBusyRetry as J, NO_CC_HANDLERS_REASON as Ja, resolveUseCdkBootstrapAssets as Jc, malformedLocalResourceEntriesWarning as Ji, maskingRetryLogger as Jn, shareLogOnlyValues as Jo, regionLessSecretName as Jr, orphansAfterRollback as Js, planRollback as Jt, DELETE_INDEX_WAIT_PROCEED_NOTE as K, unwrittenCreateOnlyReplacement as Ka, resolveStateBucketWithDefault as Kc, malformedExportSourceWarning as Ki, refusesFinalSnapshot as Kn, recordLogOnlyParameterValue as Ko, classifyReplaySecretRegion as Kr, importableOutputs as Ks, isReplacementOrphan as Kt, perType as L, repairMalformedResourceEntriesForReadOnly as La, Synthesizer as Lc, deployRefusesOrphanRowsReason as Li, storedSecretNameNeedles as Ln, readsNoEchoSource as Lo, ec2InstanceProtectionSite as Lr, isNotImplemented as Ls, replayRollback as Lt, noEchoParameterNamesOf as M, refuseMalformedResourcesForExport as Ma, warnFinchArgvExposure as Mc, STATE_REGION_DIVERGED as Mi, orphanRecordsPrintingBag as Mn, noEchoCoordinatesOf as Mo, deleteSkipReason as Mr, CrossPrefixReadError as Ms, journaledOrphanLines as Mt, recordedProtectionEvidence as N, refuseMalformedState as Na, escapeRegExp$3 as Nc, STATE_RESOURCES_MALFORMED as Ni, secretNamePrintingBag as Nn, noEchoLeavesOf as No, withIndeterminateGuard as Nr, CrossPrefixScanCache as Ns, loadJournaledOrphans as Nt, diffSecurity as O, refuseMalformedResourcePropertiesForExport as Oa, redactDockerArgvValues as Oc, parameterInputsFor as Oi, DEFAULT_RESOURCE_WARN_AFTER_MS as On, maskAtCoordinates as Oo, RollbackInlinePolicyWriters as Or, LockManager as Os, withNestedStackContext as Ot, recordedProtectionNote as P, repairMalformedOrphanRecordsForReadOnly as Pa, stripControlChars as Pc, UNREADABLE_ORPHANS_CONTAINER_ROW as Pi, secretNamePrintingBags as Pn, noEchoOutputsComparison as Po, isWaitAbandonedError as Pr, applyCrossPrefixScan as Ps, makeForeignHolderScan as Pt, hasCdkAutoDeleteTag as Q, MULTI_REGION_RECREATE_BLOCKED_TYPES as Qa, CFN_TEMPLATE_BODY_LIMIT as Qc, malformedOrphanRowsKeptWarning as Qi, earliestDefined as Qn, maskSecretsInText as Qo, assertFlagRoleArn as Qr, isSensitiveAttributeName as Qs, resolveReplacementOldType as Qt, readProtection as R, repairMalformedResourcePropertiesForReadOnly as Ra, synthesisStatusMessage as Rc, displayLogicalId as Ri, extractDeploymentEventError as Rn, valueAtCoordinate as Ro, isTerminationProtectionPropagationError as Rr, splitImportedOps as Rs, awaitInFlightSibling as Rt, keptForReadoption as S, refuseMalformedResourceEntriesForDestroy as Sa, finchSecretArgvRefusal as Sc, AWS_NO_VALUE as Si, clearBucketRegionCache as Sl, noEchoParameterValueSeed as Sn, lambdaFunctionNameForMask as So, CUSTOM_RESOURCE_RESPONSE_LIFECYCLE_RULE_ID as Sr, STATE_SOURCED_BASELINE_RULES as Ss, recordDisplacedSkips as St, findServiceTokenRefusals as T, refuseMalformedResourceEntriesForOrphan as Ta, isDockerClientEnvKey as Tc, refStateLookupFromResource as Ti, isExportAliasCollision as Tn, TemplateParser as To, CloudControlProvider as Tr, TEMPLATE_SOURCED_RULES as Ts, withNestedRevertRun as Tt, deleteBudgetKey as U, withheldAccountClause as Ua, resolveAutoAssetStorage as Uc, isReadableResourceEntry as Ui, createPreDeleteFinalSnapshot as Un, literalSplitDelimitersOf as Uo, INTRINSIC_KEYS as Ur, hasReasonlessBaselineRefusal as Us, recordAfterRollbackUpdate as Ut, beginDeleteWait as V, unreadableOrphanRecords as Va, loadCdkJson as Vc, hasReadableResources as Vi, buildFinalSnapshotIdentifier as Vn, createUnionSecretMasker as Vo, reEnableInstanceApiTermination as Vr, exportNamesCarriedFrom as Vs, withStackRecords as Vt, resolveDynamoDbDeleteBudgetClock as W, findActionableSilentDrops as Wa, resolveCaptureObservedState as Wc, malformedDriftResourcePropertiesWarning as Wi, effectiveDeletionPolicy as Wn, logOnlyValueCount as Wo, IntrinsicFunctionResolver as Wr, hasUnverifiableParameterRefusal as Ws, demoteSupersededOrphans as Wt, ECR_AUTO_DELETE_IMAGES_TAG as X, findReplacedReadersOfRecreateTargets as Xa, warnDeprecatedNoPrefixCliFlag as Xc, malformedOrphanRecordsRefusalMessage as Xi, findDestructiveChanges as Xn, errorCauseChain as Xo, refuseUnreadableCdkCrossAccountRefs as Xr, shouldRetainResource as Xs, recordUnderIdIsNotOwn as Xt, waitForIndexesSettled as Y, hasNoCloudControlHandlers as Ya, stateBucketExistenceConfirmed as Yc, malformedLocalResourcesWarning as Yi, constructPathOf as Yn, createSecretMasker as Yo, producerLookupOver as Yr, orphansCarriedFrom as Ys, recheckFailedPlan as Yt, S3_AUTO_DELETE_OBJECTS_TAG as Z, shownType as Za, ssmResolvedValueType as Zc, malformedOrphanRecordsWarning as Zi, formatDestructiveChange as Zn, maskSecretsInError as Zo, applyRoleArnIfSet as Zr, AssetPublisher as Zs, replacementNeverSwapped as Zt, makeCanonicalizePropertiesFn as _, refuseMalformedOutputs as _a, resolveDockerContextDirectory as _c, isImpossibleEmptyStoredAttribute as _i, absoluteAssemblyPathEscape as _l, secretSafeKeyDisplay as _n, prefetchCreateOnlyPropertyPaths as _o, TemplateNoEchoReresolver as _r, recordMaskOnlyValuesIn as _s, displacedOpClause as _t, findNestedTemplateTreeDefect as a, malformedResourcePropertiesWarning as aa, AssetModeResolver as ac, callerEnvCredentials as ai, expectedOwnerParam as al, isRedactedRecordedValue as an, toCfnInteger as ao, planTagDiff as ar, DYNAMIC_REFERENCE_TOKEN_SCAN as as, cyan as at, lookupEachName as b, refuseMalformedResourceEntries as ba, describeDockerFailure as bc, isNamelessDynamicReferenceError as bi, renderAssemblyPathEscape as bl, exportNameSecretExposure as bn, withRetry as bo, getCurrentResourceSecrets as br, recordProvenPublicExpression as bs, getNestedRevertRun as bt, renderNestedTemplateTreeDefect as c, malformedStateRefusalMessage as ca, ensureAssetStorage as cc, s3BucketArn as ci, derivePartitionAndUrlSuffix as cl, updatePartialMessage as cn, coerceCfnInteger as co, tagMapAsList as cr, pathCrossesDottedKey as cs, red as ct, conditionInputsFrom as d, refuseMalformedNestedChildOutputs as da, parseBootstrapMarker as dc, s3BucketRegionalDomainName as di, processStackMessages as dl, DeploymentEventsStore as dn, configStringRefusal as do, stringifyIfAssigned as dr, recordNestedStackParameterExpressions as ds, namesAnyDeclaredParameter as dt, malformedOrphansWarning as ea, WorkGraph as ec, explicitRoleArnOrThrow as ei, MIGRATE_TMP_PREFIX as el, orphanDeleteNeedsIdentity as en, isStatefulRecreateTargetSync as eo, reserveStackCreateToken as er, redactSecretsForState as es, getPriorAttempts as et, parentSuppliedValues as f, refuseMalformedOrphanRecords as fa, readBootstrapMarkerBody as fc, s3BucketWebsiteUrl as fi, defineOwnKey as fl, WITHHELD_NAME_DISPLAY as fn, readConfigString as fo, CC_BROKEN_REASON as fr, recoverMaskedOutput as fs, reachableDivergentParameters as ft, mergeNoChangeOutputs as g, refuseMalformedOrphansForOrphan as ga, buildDockerImage as gc, getAccountInfo as gi, ownValue as gl, secretBearingStateKeyWarning as gn, requireConfigString as go, wouldReturnToSdkProvider as gr, recordMaskOnlyValue as gs, NESTED_PENDING_PARENT_REASON as gt, keptWholeReasonText as h, refuseMalformedOrphansForDestroy as ha, buildDenyExternalAccessPolicy as hc, parameterTypeMayLoseSecretIdentity as hi, nullPrototypeRecord as hl, secretBearing as hn, requireConfigObject as ho, isCustomResource as hr, provenPublicValue as hs, planOrphanAdoption as ht, childTemplateLoader as i, malformedResourceEntriesWarning as ia, rewriteTemplateAssetReferences as ic, applyCallerIdentityCredentials as ii, decodeListingKey as il, REDACTED_DELETE_ADDRESS_SKIP_REASON as in, isWarmThroughputDecrease as io, DBPROXY_TAG_OPTIONS as ir, recordPhysicalNameCopy as is, bold as it, noEchoExactEchoLeavesOf as j, refuseMalformedResourcesForDestroy as ja, runDockerStreaming as jc, JSON_LISTING_HOLE_VALUE as ji, maskEventTextWithBoundBags as jn, maskedLeafCoordinatesOf as jo, deleteLeftInPlace as jr, rebuildClientForBucketRegion as js, isHandledOrphan as jt, noEchoAttributeNamesOf as k, refuseMalformedResourcePropertiesForOrphan as ka, redactedDockerCause as kc, passedParameterClassesOf as ki, completedReplayEntries as kn, maskReadbackAtCoordinates as ko, UNSPECIFIED_SKIP_REASON as kr, isLockInfoExpired as ks, deleteJournaledOrphans as kt, templateIdentity as l, previewableOrphanRecords as la, getBootstrapMarkerKey as lc, s3BucketDomainName as li, AssemblyReader as ll, updatePartialReason as ln, configBooleanRefusal as lo, tagPlanWarning as lr, identityKeyFor as ls, yellow as lt, isSecretBearingReferenceString as m, refuseMalformedOrphans as ma, validateContainerRepoName as mc, isUnboundTemplateParameter as mi, hasPlainPrototype$1 as ml, exportAliasCollisionScrubWarning as mn, requireConfigArray as mo, ccBrokenReason as mr, carriesSecretMask as ms, makeSiblingClaimReader as mt, IMPLICIT_DELETE_DEPENDENCIES as n, malformedOutputsWarning as na, createAssetRedirectResolver as nc, sendableAssumeRoleCommand as ni, uploadCfnTemplate as nl, getCurrentProducerRegions as nn, WARM_THROUGHPUT_MEMBERS as no, acquireIdempotencyToken as nr, withPhysicalNameCopies as ns, formatResourceLine as nt, isAbsoluteAssetPath as o, malformedResourcesWarning as oa, BOOTSTRAP_MARKER_PREFIX as oc, envCredentialsAreAssumedRole as oi, PARTITION_TABLE as ol, redactedDeleteAddressFields as on, toFiniteNumber as oo, readTagList as or, dynamicReferenceTokens as os, gray as ot, readRecordedConditionVerdicts as p, refuseMalformedOrphanRecordsForDestroy as pa, validateAssetBucketName as pc, coerceParameterTypedValue as pi, hasOwnKey as pl, displayTextOrWithheld as pn, replayWarn as po, ProviderRegistry as pr, splitGetAttStringForm as ps, resourcesNamingDeclaredParameter as pt, INDEX_SETTLE_POLL_INTERVAL_MS as q, withoutSilentDropProperties as qa, resolveStateBucketWithDefaultAndSource as qc, malformedLocalOutputsWarning as qi, unsupportedFinalSnapshotError as qn, recordLogOnlyValue as qo, producerRegionsFromState as qr, isReadableBag as qs, planFailedOps as qt, computeImplicitDeleteEdges as r, malformedRenderedContainersWarning as ra, loadPublishableAssetManifest as rc, AWS_CREDENTIAL_ENV_KEYS as ri, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as rl, inheritProducerRegions as rn, coerceWarmThroughput as ro, DBPROXY_TAGS_WHAT as rr, withoutPhysicalNameCopies as rs, createReadOnlyAttributeHealerFactory as rt, listNestedTemplateRows as s, malformedScrubResourceEntriesRefusalMessage as sa, assertAssetBucketRegion as sc, isSettledInstanceState as si, canonicalizeRegion as sl, redactedDeleteAddressSkip as sn, coerceCfnBoolean as so, refuseMalformedDesiredTags as sr, isUncertifiedBaselineMaskPosition as ss, green as st, DeployEngine as t, malformedOutputsRefusalMessage as ta, buildAssetRedirectMap as tc, isIamRoleArn as ti, findLargeInlineResources as tl, findNestedStackTypeChanges as tn, renderStatefulReason as to, withCreateTokenLedger as tr, scrubResourceRecord as ts, markRefusedBeforeApplying as tt, conditionFingerprint as u, refuseDivergentRecordRegionForDestroy as ua, isCrossRegionRedirect as uc, s3BucketDualStackDomainName as ui, StageLoadError as ul, DeploymentEventsReader as un, configIntegerRefusal as uo, definedAttributes as ur, inheritNestedStackParameterAssociations as us, computeParameterTaint as ut, chunks as v, refuseMalformedOutputsForDestroy as va, describeDockerCapturedOutput as vc, carriesDynamicReference as vi, describeFileReadFailure as vl, carriedExportAliasExposure as vn, templateResourceTypes as vo, readsNoEchoDeleteValues as vr, recordNoEchoAttributeValues as vs, displacedPhysicalIdShown as vt, deferredServiceTokenWarning as w, refuseMalformedResourceEntriesForImportSave as wa, getDockerCmd as wc, isStalePlaceholderArnAttribute as wi, collectPublishedOutputNames as wn, DagBuilder as wo, customResourceResponseExpirationWarning as wr, STATE_SOURCED_READBACK_RULES as ws, revertedNestedRowIds as wt, withApiLimit as x, refuseMalformedResourceEntriesForDeploy as xa, dockerSpawnEnvWithSensitive as xc, keepsSecretReferenceToken as xi, resolveAssemblyPath as xl, isWholeDynamicReferenceValue as xn, canonicalLambdaFunctionName as xo, withPrintingSecrets as xr, wholeStringLeavesOf as xs, nestedChildStackName as xt, isAccessDeniedError as y, refuseMalformedResourceAttributesForOrphan as ya, describeDockerExecFailure as yc, carriesFetchableDynamicReference as yi, displayAssemblyPath as yl, exportNameNoEchoParameters as yn, describeTypeWithThrottleRetry as yo, isCustomResourceType as yr, recordNoEchoParameterFreshValue as ys, dropSettledNestedJournals as yt, DynamoDBGlobalTableProvider as z, repairMalformedResourcesForReadOnly as za, getDefaultStateBucketName as zc, hasReadableOrphans as zi, ATOMIC_FINAL_SNAPSHOT_TYPES as zn, liveMatchesUnresolvedTokenFrame as zo, observeThenDisableInstanceApiTermination as zr, acceptedCreateOnlyDropsOf as zs, destroyStackRecordsView as zt };
|
|
44387
|
+
//# sourceMappingURL=deploy-engine-CJTG3tft.js.map
|