@go-to-k/cdkd 0.284.3 → 0.284.4
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-iHWEv4gN.js → asg-provider-C0L3_pHn.js} +2 -2
- package/dist/{asg-provider-iHWEv4gN.js.map → asg-provider-C0L3_pHn.js.map} +1 -1
- package/dist/cli.js +3 -3
- package/dist/cli.js.map +1 -1
- package/dist/{deploy-engine-DJ-CgH80.js → deploy-engine-3Z7P1hKs.js} +365 -39
- package/dist/deploy-engine-3Z7P1hKs.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-DJ-CgH80.js.map +0 -1
|
@@ -1045,7 +1045,7 @@ var aws_clients_exports = /* @__PURE__ */ __exportAll({
|
|
|
1045
1045
|
/**
|
|
1046
1046
|
* AWS clients manager
|
|
1047
1047
|
*/
|
|
1048
|
-
var AwsClients = class {
|
|
1048
|
+
var AwsClients = class AwsClients {
|
|
1049
1049
|
s3Client;
|
|
1050
1050
|
cloudControlClient;
|
|
1051
1051
|
iamClient;
|
|
@@ -1080,6 +1080,87 @@ var AwsClients = class {
|
|
|
1080
1080
|
};
|
|
1081
1081
|
}
|
|
1082
1082
|
/**
|
|
1083
|
+
* The region this instance was EXPLICITLY configured with, or `undefined`.
|
|
1084
|
+
*
|
|
1085
|
+
* Deliberately reads only `config.region` and consults NO environment
|
|
1086
|
+
* variable, which is the opposite of what an earlier revision did and the
|
|
1087
|
+
* difference matters (issue #1957). An env read here is not merely
|
|
1088
|
+
* incomplete, it is UNSTABLE in the one direction that is dangerous: the SDK
|
|
1089
|
+
* memoizes a region-less client's region at its first resolution
|
|
1090
|
+
* (`@smithy/node-config-provider`'s `loadConfig` wraps the provider chain in
|
|
1091
|
+
* `memoize`), while `deploy.ts`'s `switchRegion` keeps mutating
|
|
1092
|
+
* `process.env.AWS_REGION` per stack and restores it in each stack's
|
|
1093
|
+
* `finally`. So an env-derived answer can report region X for a client that
|
|
1094
|
+
* long ago pinned itself to region P — letting a caller conclude "these
|
|
1095
|
+
* clients already point at my region" and use the WRONG ones, which is worse
|
|
1096
|
+
* than not knowing.
|
|
1097
|
+
*
|
|
1098
|
+
* When this returns `undefined` the region is not merely unknown to us, it is
|
|
1099
|
+
* NOT YET DECIDED — and that is the important part. {@link clientOptions}
|
|
1100
|
+
* omits `region` entirely in that case, so each service client resolves and
|
|
1101
|
+
* MEMOIZES its own region independently, at its own first construction, from
|
|
1102
|
+
* an environment `deploy.ts`'s `switchRegion` is actively mutating. The
|
|
1103
|
+
* members of one region-less bag can therefore disagree with each other:
|
|
1104
|
+
* `ssm` can pin `us-west-2` and `secretsManager` pin `us-east-1` a moment
|
|
1105
|
+
* later, because the getters are lazy and each samples a different instant.
|
|
1106
|
+
*
|
|
1107
|
+
* So there is deliberately NO method here that reports "the region of these
|
|
1108
|
+
* clients" for an unconfigured instance. An earlier revision had one — it
|
|
1109
|
+
* asked `this.ssm.config.region()` — and it was unsound for exactly this
|
|
1110
|
+
* reason: it measured ONE member of a bag whose members need not agree, and
|
|
1111
|
+
* the caller then reused the whole bag on the strength of it. A caller that
|
|
1112
|
+
* needs a region it can rely on must build a CONFIGURED bag
|
|
1113
|
+
* ({@link withRegion} always sets `region`, so every member of a derived bag
|
|
1114
|
+
* agrees by construction).
|
|
1115
|
+
*/
|
|
1116
|
+
get configuredRegion() {
|
|
1117
|
+
return this.config.region || void 0;
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* The CREDENTIAL half of this instance's configuration — `profile` plus any
|
|
1121
|
+
* explicitly supplied `credentials` — deliberately WITHOUT `region`.
|
|
1122
|
+
*
|
|
1123
|
+
* This is the half that must survive a region override. Note what it is and
|
|
1124
|
+
* is NOT worth: `--profile` ALSO reaches a freshly constructed client through
|
|
1125
|
+
* the environment, because `src/cli/program.ts` sets `process.env.AWS_PROFILE`
|
|
1126
|
+
* in a `preAction` hook for every command — so carrying `profile` here is
|
|
1127
|
+
* belt-and-braces rather than the thing standing between a user and the wrong
|
|
1128
|
+
* account. What genuinely has no environment path is an explicit
|
|
1129
|
+
* `credentials` object: nothing exports it, so a sibling built without it
|
|
1130
|
+
* falls back to the default chain. The same is true of any library caller
|
|
1131
|
+
* that constructs `AwsClients` directly and therefore never runs the CLI's
|
|
1132
|
+
* `preAction` hook. `--role-arn` needs nothing carried at all —
|
|
1133
|
+
* `applyRoleArnIfSet` exports `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` /
|
|
1134
|
+
* `AWS_SESSION_TOKEN` into the process environment.
|
|
1135
|
+
*
|
|
1136
|
+
* The `credentials` object is CLONED rather than aliased: the returned bag is
|
|
1137
|
+
* handed to every derived sibling, and sharing one mutable object would let a
|
|
1138
|
+
* caller reach through it and rewrite the ambient instance's credentials.
|
|
1139
|
+
*/
|
|
1140
|
+
get credentialConfig() {
|
|
1141
|
+
return {
|
|
1142
|
+
...this.config.profile && { profile: this.config.profile },
|
|
1143
|
+
...this.config.credentials && { credentials: { ...this.config.credentials } }
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Derive a sibling bound to `region`, carrying this instance's credential
|
|
1148
|
+
* configuration (see {@link credentialConfig}) and overriding ONLY the region.
|
|
1149
|
+
*
|
|
1150
|
+
* Used by {@link IntrinsicFunctionResolver} to pin a dynamic-reference lookup
|
|
1151
|
+
* (`{{resolve:secretsmanager:...}}` / `{{resolve:ssm:...}}`) to the stack's own
|
|
1152
|
+
* region instead of whichever region the process-global singleton happens to
|
|
1153
|
+
* hold at that moment (issue #1957). The returned instance owns its own lazily
|
|
1154
|
+
* constructed clients and its own `destroy()`; it is NOT registered as the
|
|
1155
|
+
* global, so nothing else in the process can observe it.
|
|
1156
|
+
*/
|
|
1157
|
+
withRegion(region) {
|
|
1158
|
+
return new AwsClients({
|
|
1159
|
+
...this.credentialConfig,
|
|
1160
|
+
region
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1083
1164
|
* Get S3 client
|
|
1084
1165
|
*
|
|
1085
1166
|
* Note: If region and credentials are not provided, AWS SDK will use:
|
|
@@ -12283,6 +12364,44 @@ const MAX_DYNAMIC_REFERENCE_THROTTLE_RETRIES = 4;
|
|
|
12283
12364
|
* without real waits (mirrors `describeTypeRetryDelays`).
|
|
12284
12365
|
*/
|
|
12285
12366
|
const dynamicReferenceRetryDelays = {};
|
|
12367
|
+
/**
|
|
12368
|
+
* Is `region` safe to build an AWS SDK client from?
|
|
12369
|
+
*
|
|
12370
|
+
* This is a SECURITY gate, not an AWS region registry, and the distinction
|
|
12371
|
+
* decides how strict it is. The SDK turns a region into a hostname by
|
|
12372
|
+
* substitution — `https://ssm.{region}.amazonaws.com` — so a value carrying a
|
|
12373
|
+
* host delimiter escapes the label and re-points the endpoint: the measured
|
|
12374
|
+
* case is `evil.example.com#`, which yields
|
|
12375
|
+
* `https://ssm.evil.example.com/#.amazonaws.com` and sends a SigV4-SIGNED
|
|
12376
|
+
* request (access key id + signature) to an attacker-controlled host.
|
|
12377
|
+
*
|
|
12378
|
+
* The reachable input is `Fn::GetAZs`, whose argument is TEMPLATE-DERIVED and
|
|
12379
|
+
* can arrive through an `Fn::ImportValue` or a parameter — i.e. it is not
|
|
12380
|
+
* necessarily written by whoever runs the deploy. Before issue #1957 that value
|
|
12381
|
+
* only fed the `region-name` FILTER of a `DescribeAvailabilityZones` call and
|
|
12382
|
+
* never built a client, so binding lookups to a region is exactly what made it
|
|
12383
|
+
* reachable; the gate ships with the binding.
|
|
12384
|
+
*
|
|
12385
|
+
* So the predicate is CHARSET-based rather than shape-based: lowercase
|
|
12386
|
+
* alphanumerics and hyphens only, which cannot express `.`, `/`, `:`, `@`, `?`
|
|
12387
|
+
* or `#` and therefore cannot leave the hostname label. It deliberately does
|
|
12388
|
+
* NOT try to enumerate real regions — AWS keeps adding them
|
|
12389
|
+
* (`ap-southeast-7`, `il-central-1`, `mx-central-1`, `eusc-de-east-1`), and a
|
|
12390
|
+
* pattern tight enough to reject `----` would also reject the next one. A
|
|
12391
|
+
* region-shaped-but-nonexistent value is not a security problem: it resolves to
|
|
12392
|
+
* a hostname that does not exist and the SDK fails loudly.
|
|
12393
|
+
*
|
|
12394
|
+
* Note the sibling pattern in `src/cli/commands/state-file-keys.ts` is NOT
|
|
12395
|
+
* reusable here: it requires `^[a-z]{2}(-[a-z]+)+-\d+$`, which rejects
|
|
12396
|
+
* `eusc-de-east-1` (the European Sovereign Cloud partition's four-letter
|
|
12397
|
+
* prefix).
|
|
12398
|
+
*
|
|
12399
|
+
* Callers must {@link canonicalizeRegion} first — `US-EAST-1` is a documented
|
|
12400
|
+
* input and is lowercase-canonical, not invalid.
|
|
12401
|
+
*/
|
|
12402
|
+
function isClientSafeRegion(region) {
|
|
12403
|
+
return /^[a-z0-9][a-z0-9-]{0,30}$/.test(region);
|
|
12404
|
+
}
|
|
12286
12405
|
/** Test seam for {@link FABRICATED_ACCOUNT_INFO_TTL_MS} expiry. */
|
|
12287
12406
|
const accountInfoClock = { now: () => Date.now() };
|
|
12288
12407
|
let fabricatedAccountIdentity = null;
|
|
@@ -12421,6 +12540,63 @@ function stringifyParameterForLog(paramDef, value) {
|
|
|
12421
12540
|
var IntrinsicFunctionResolver = class {
|
|
12422
12541
|
logger = getLogger().child("IntrinsicFunctionResolver");
|
|
12423
12542
|
resolverRegion;
|
|
12543
|
+
/**
|
|
12544
|
+
* The region the CONSTRUCTOR was given, or `undefined` when it was called
|
|
12545
|
+
* without one — unlike {@link resolverRegion}, which substitutes
|
|
12546
|
+
* `AWS_REGION` / `us-east-1` so every consumer has a string to work with.
|
|
12547
|
+
*
|
|
12548
|
+
* The distinction is load-bearing for {@link clientsForRegion} and for
|
|
12549
|
+
* nothing else: re-pointing an AWS lookup away from the ambient clients is
|
|
12550
|
+
* only safe when a caller SAID which region this resolver stands for.
|
|
12551
|
+
*
|
|
12552
|
+
* IN PRODUCTION THIS IS ALWAYS SET, and saying so matters more than the
|
|
12553
|
+
* guard it enables. Every construction site defaults the region BEFORE the
|
|
12554
|
+
* constructor and passes a `string` — `deploy.ts`, `scrub.ts`, `drift.ts`,
|
|
12555
|
+
* `import.ts`, `export.ts`, `diff-recursive.ts`, `rollback-executor.ts` —
|
|
12556
|
+
* so the `undefined` arm is reachable only through the no-argument
|
|
12557
|
+
* constructor, which nothing but tests uses. It is kept because the
|
|
12558
|
+
* parameter is optional and the arm must therefore exist, not because a
|
|
12559
|
+
* shipped path depends on it.
|
|
12560
|
+
*
|
|
12561
|
+
* That has a USER-VISIBLE consequence, deliberately accepted (issue #1957
|
|
12562
|
+
* review). For a REGION-AGNOSTIC stack (no `env.region`) run with neither
|
|
12563
|
+
* `--region` nor `AWS_REGION`, the region those callers compute is the
|
|
12564
|
+
* hard-coded `us-east-1` fallback, so the lookup now goes there — where
|
|
12565
|
+
* before it followed the ambient clients to whatever `~/.aws/config` said.
|
|
12566
|
+
* The new behaviour is the consistent one: `us-east-1` is already the region
|
|
12567
|
+
* cdkd keys that stack's state file, its lock and its export index under, so
|
|
12568
|
+
* the resolved value and the record that stores it now agree. Previously
|
|
12569
|
+
* they did not, which is the same class of defect this issue is about, one
|
|
12570
|
+
* layer up. A stack WITH an explicit `env.region` is unaffected — every
|
|
12571
|
+
* caller prefers it (`scrub.ts` does `stack.region || region`).
|
|
12572
|
+
*/
|
|
12573
|
+
explicitRegion;
|
|
12574
|
+
/**
|
|
12575
|
+
* AWS clients pinned to a region OTHER than the ambient singleton's, built
|
|
12576
|
+
* lazily on first mismatch and keyed by region (issue #1957).
|
|
12577
|
+
*
|
|
12578
|
+
* Lifetime is deliberately the resolver's own, matching {@link cfnClients}
|
|
12579
|
+
* two fields down: both are per-region SDK clients this instance builds for
|
|
12580
|
+
* itself, and neither is destroyed, because `IntrinsicFunctionResolver` has
|
|
12581
|
+
* no teardown hook and every construction site (`DeployEngine`, `scrub`,
|
|
12582
|
+
* `drift`, `import`, `diff-recursive`, `export`, `rollback-executor`) would
|
|
12583
|
+
* have to grow one. The bound on what that costs is small and worth stating:
|
|
12584
|
+
* an entry exists only when a resolver's region DIFFERS from the ambient
|
|
12585
|
+
* one — i.e. only on a genuinely cross-region run — and at most one per
|
|
12586
|
+
* foreign region per resolver, versus the ambient clients which are already
|
|
12587
|
+
* created and destroyed per stack by `deploy.ts`.
|
|
12588
|
+
*
|
|
12589
|
+
* KEYED BY REGION ALONE, which means an entry also pins the CREDENTIAL
|
|
12590
|
+
* configuration of whichever ambient instance was current at the first
|
|
12591
|
+
* mismatch for that region. That is sound today because the credential half
|
|
12592
|
+
* is process-wide rather than per-stack: `--profile` comes from one CLI
|
|
12593
|
+
* option and `--role-arn` lands in `process.env`, so every ambient instance
|
|
12594
|
+
* a run installs carries the same one. Widen the key to include
|
|
12595
|
+
* {@link AwsClients.credentialConfig} the moment that stops being true —
|
|
12596
|
+
* per-stack credentials would otherwise let one stack's lookups run under
|
|
12597
|
+
* another's identity, which is a worse bug than the one this cache serves.
|
|
12598
|
+
*/
|
|
12599
|
+
regionScopedClients = /* @__PURE__ */ new Map();
|
|
12424
12600
|
strictGetAtt;
|
|
12425
12601
|
cfnFallback;
|
|
12426
12602
|
/**
|
|
@@ -12511,24 +12687,17 @@ var IntrinsicFunctionResolver = class {
|
|
|
12511
12687
|
* nested stacks ship or that pass starts recording secrets; a resolver per
|
|
12512
12688
|
* node is the fix then, not a wider key here.
|
|
12513
12689
|
*
|
|
12514
|
-
*
|
|
12515
|
-
*
|
|
12516
|
-
*
|
|
12517
|
-
* (
|
|
12518
|
-
*
|
|
12519
|
-
*
|
|
12520
|
-
*
|
|
12521
|
-
*
|
|
12522
|
-
*
|
|
12523
|
-
*
|
|
12524
|
-
*
|
|
12525
|
-
* stacks in several regions. The split of ownership is therefore: THIS field
|
|
12526
|
-
* closes the cache as a cross-region / cross-stack carrier, while #1957 owns
|
|
12527
|
-
* the wrong-region READ — which is the outcome #1933's title names, so #1933
|
|
12528
|
-
* is not fully resolved until #1957 lands. Pinning the lookup to
|
|
12529
|
-
* {@link resolverRegion} means constructing region-scoped clients, which is a
|
|
12530
|
-
* credentials decision (a bare `new SSMClient({ region })` drops the ambient
|
|
12531
|
-
* profile / assume-role config) and belongs to #1957 rather than here.
|
|
12690
|
+
* The OTHER half of the same outcome — the lookups themselves reading the
|
|
12691
|
+
* process-ambient `getAwsClients()` singleton, whose region is whichever the
|
|
12692
|
+
* process installed last — was issue
|
|
12693
|
+
* [#1957](https://github.com/go-to-k/cdkd/issues/1957) and is now closed by
|
|
12694
|
+
* {@link clientsForRegion}: a resolver whose region differs from the ambient
|
|
12695
|
+
* one builds its own region-pinned clients (carrying the ambient profile /
|
|
12696
|
+
* credentials) instead of reading whatever the singleton currently holds.
|
|
12697
|
+
* The two halves remain SEPARATE mechanisms and both are needed — this field
|
|
12698
|
+
* stops a resolved value from travelling between regions or stacks, while
|
|
12699
|
+
* the scoped clients stop the FIRST resolution from reading the wrong region
|
|
12700
|
+
* (no cache involved, so nothing here could ever have prevented it).
|
|
12532
12701
|
*/
|
|
12533
12702
|
cachedDynamicReferences = /* @__PURE__ */ new Map();
|
|
12534
12703
|
/**
|
|
@@ -12554,6 +12723,7 @@ var IntrinsicFunctionResolver = class {
|
|
|
12554
12723
|
warnedUnrecognizedSsmTypes = /* @__PURE__ */ new Set();
|
|
12555
12724
|
constructor(region, options) {
|
|
12556
12725
|
this.resolverRegion = region || process.env["AWS_REGION"] || "us-east-1";
|
|
12726
|
+
this.explicitRegion = region || void 0;
|
|
12557
12727
|
this.strictGetAtt = options?.strictGetAtt ?? false;
|
|
12558
12728
|
this.cfnFallback = options?.cfnFallback ?? true;
|
|
12559
12729
|
}
|
|
@@ -12566,6 +12736,133 @@ var IntrinsicFunctionResolver = class {
|
|
|
12566
12736
|
this.physicalIdFallbackCount = 0;
|
|
12567
12737
|
}
|
|
12568
12738
|
/**
|
|
12739
|
+
* AWS clients for a REGION-SENSITIVE lookup, pinned to `targetRegion`
|
|
12740
|
+
* (issue [#1957](https://github.com/go-to-k/cdkd/issues/1957)).
|
|
12741
|
+
*
|
|
12742
|
+
* Every lookup in this class used to read `getAwsClients()` — the
|
|
12743
|
+
* PROCESS-GLOBAL singleton, whose region is whichever one the process
|
|
12744
|
+
* installed last. That is not the same thing as the region this resolver
|
|
12745
|
+
* stands for, and the gap is reachable on main:
|
|
12746
|
+
*
|
|
12747
|
+
* - `cdkd deploy` defaults to `--stack-concurrency 4` and re-points the
|
|
12748
|
+
* singleton per stack, so two stacks in different regions race for one
|
|
12749
|
+
* mutable global and stack B's `GetSecretValue` / `GetParameter` can run
|
|
12750
|
+
* against stack A's client. The resolved value is redacted on its way into
|
|
12751
|
+
* state, so nothing downstream records which region answered.
|
|
12752
|
+
* - `cdkd scrub --all` installs the clients ONCE while resolving per-stack
|
|
12753
|
+
* regions, so a region-B `SecureString` whose region-A namesake is a plain
|
|
12754
|
+
* `String` is classified PUBLIC and left in PLAINTEXT in state.json — the
|
|
12755
|
+
* same disclosure class as GHSA-p5qg-v9gv-hc7w, not merely a wrong value.
|
|
12756
|
+
* - `cdkd drift --revert` WRITES the resolved value to a live resource, so
|
|
12757
|
+
* there the wrong region is a wrong write rather than a wrong report.
|
|
12758
|
+
*
|
|
12759
|
+
* Fixing it here rather than at the ~10 `setAwsClients` call sites is what
|
|
12760
|
+
* makes it one mechanism instead of a per-command patch: this class already
|
|
12761
|
+
* knows its own region, and every construction site already passes the
|
|
12762
|
+
* per-stack one.
|
|
12763
|
+
*
|
|
12764
|
+
* REUSING THE AMBIENT CLIENTS REQUIRES PROOF THAT THEY ALREADY POINT AT
|
|
12765
|
+
* `targetRegion`, and the direction of that test is the whole correctness
|
|
12766
|
+
* argument. CloudFormation semantics say a stack's dynamic references resolve
|
|
12767
|
+
* in the STACK's region, and every construction site passes exactly that — so
|
|
12768
|
+
* once a region has been named, sending the lookup there is not an
|
|
12769
|
+
* optimisation to be justified, it is the requirement. Whether the ambient
|
|
12770
|
+
* singleton happens to agree only decides whether an object allocation can be
|
|
12771
|
+
* skipped.
|
|
12772
|
+
*
|
|
12773
|
+
* An earlier revision had this backwards twice over, and both failures are
|
|
12774
|
+
* worth naming because each looks reasonable in isolation.
|
|
12775
|
+
*
|
|
12776
|
+
* It first declined to override whenever the ambient region was UNKNOWN,
|
|
12777
|
+
* reasoning that overriding on an unproven mismatch might re-point a lookup
|
|
12778
|
+
* that works today. That fails OPEN, on the COMMON configuration: `aws
|
|
12779
|
+
* configure` writes the region to `~/.aws/config`, and `cdkd scrub` sets a
|
|
12780
|
+
* client region only when `--region` is passed. The disclosure this issue
|
|
12781
|
+
* exists to close stayed reachable — profile region `us-east-1`, stack B in
|
|
12782
|
+
* `ap-northeast-1`, a name that is `String` in A and `SecureString` in B,
|
|
12783
|
+
* `cdkd scrub --all` with no flags: B's reference answered by A, classified
|
|
12784
|
+
* public, plaintext left in `state.json`.
|
|
12785
|
+
*
|
|
12786
|
+
* It then determined the ambient region by reading `process.env` here, which
|
|
12787
|
+
* is worse than not knowing: the SDK memoizes a region-less client's region
|
|
12788
|
+
* at its first resolution while `deploy.ts`'s `switchRegion` keeps mutating
|
|
12789
|
+
* `AWS_REGION` per stack and restores it in each stack's `finally`, so the
|
|
12790
|
+
* environment could say `baseRegion` for a client long since pinned
|
|
12791
|
+
* elsewhere — and this method would conclude MATCH and hand back clients
|
|
12792
|
+
* pointing somewhere else.
|
|
12793
|
+
*
|
|
12794
|
+
* The fix for THAT was to ask the SDK (`ssm.config.region()`), and it was
|
|
12795
|
+
* still wrong, in a way worth writing down because it looks airtight. An
|
|
12796
|
+
* unconfigured `AwsClients` is not a bag of clients, it is a bag of DEFERRED
|
|
12797
|
+
* client constructions: `clientOptions` omits `region`, the getters are lazy,
|
|
12798
|
+
* and each member therefore samples the mutating environment at its own
|
|
12799
|
+
* instant and memoizes a possibly DIFFERENT region. Asking `ssm` measures one
|
|
12800
|
+
* member and says nothing about `secretsManager`, so the seam could short-
|
|
12801
|
+
* circuit on a us-west-2 `ssm` and then hand out a bag whose `secretsManager`
|
|
12802
|
+
* pins us-east-1 a moment later — issue #1957's Site 1 surviving inside the
|
|
12803
|
+
* arm meant to fix it.
|
|
12804
|
+
*
|
|
12805
|
+
* So the short-circuit is taken ONLY when the ambient's region is
|
|
12806
|
+
* CONFIGURED. That is not a heuristic: a configured bag passes `region` to
|
|
12807
|
+
* every member ({@link AwsClients.clientOptions}), so its members agree by
|
|
12808
|
+
* construction, and {@link AwsClients.withRegion} always sets one, so every
|
|
12809
|
+
* derived bag is internally consistent too. An unconfigured ambient is not
|
|
12810
|
+
* "of unknown region", it is "of not-yet-decided region", and there is
|
|
12811
|
+
* nothing to compare against — so it SCOPES. That is the same "unknown means
|
|
12812
|
+
* SCOPE, not skip" rule as above, applied one level deeper.
|
|
12813
|
+
*
|
|
12814
|
+
* Three arms return the ambient instance, each for a reason that is not
|
|
12815
|
+
* "we could not prove a mismatch":
|
|
12816
|
+
*
|
|
12817
|
+
* 1. No `targetRegion` — no region was ever named (see
|
|
12818
|
+
* {@link explicitRegion}), so there is nothing to bind to.
|
|
12819
|
+
* 2. `targetRegion` is not safe to build a client from (see
|
|
12820
|
+
* {@link isClientSafeRegion}) — which THROWS. An earlier revision warned
|
|
12821
|
+
* and fell back to the ambient clients, reasoning that a malformed region
|
|
12822
|
+
* reaching here is a cdkd bug and failing every lookup would turn it into
|
|
12823
|
+
* an outage. That put this arm on the wrong side of the two-severity
|
|
12824
|
+
* design: falling back to the ambient means READING ANOTHER REGION, which
|
|
12825
|
+
* for `scrub` / `drift` / `import` — whose region is state-derived — is
|
|
12826
|
+
* the disclosure this issue exists to close (a region-B `SecureString`
|
|
12827
|
+
* classified against a region-A `String`). A stopped command is strictly
|
|
12828
|
+
* better than a silent wrong-region read. The `Fn::GetAZs` entry still
|
|
12829
|
+
* validates EARLIER so it can give a message naming the template
|
|
12830
|
+
* construct; this arm is the backstop that guarantees no call site,
|
|
12831
|
+
* present or future, routes unvalidated input into an SDK endpoint.
|
|
12832
|
+
* 3. The installed clients cannot DERIVE a sibling — `withRegion` is absent.
|
|
12833
|
+
* In production that never happens: `getAwsClients()` returns an
|
|
12834
|
+
* `AwsClients`. It is true only of a test double, and it is checked
|
|
12835
|
+
* EXPLICITLY rather than left to emerge, for a reason the review of this
|
|
12836
|
+
* change made concrete. The ~260 suites that stub `getAwsClients()` with a
|
|
12837
|
+
* plain object used to stay on the ambient path as a side effect of the
|
|
12838
|
+
* `undefined`-region guard above — the very guard that made the disclosure
|
|
12839
|
+
* reachable. Removing that guard without putting something deliberate in
|
|
12840
|
+
* its place would have traded a security hole for ~260 `TypeError`s, so
|
|
12841
|
+
* the test-double case is now its own named arm and the security arm no
|
|
12842
|
+
* longer has a testing job to do. Suites that are ABOUT region scoping use
|
|
12843
|
+
* a real `AwsClients` and are unaffected by it.
|
|
12844
|
+
*
|
|
12845
|
+
* Regions are canonicalised on both sides before comparing, because
|
|
12846
|
+
* `--region US-EAST-1` is a documented input and the repo lowercases
|
|
12847
|
+
* elsewhere (`canonicalizeRegion`, issues #1795 / #1850). Without it an
|
|
12848
|
+
* uppercase spelling would build a second client for the same physical
|
|
12849
|
+
* region — benign, but wasteful and confusing in a debug log.
|
|
12850
|
+
*/
|
|
12851
|
+
clientsForRegion(targetRegion) {
|
|
12852
|
+
const ambient = getAwsClients();
|
|
12853
|
+
if (!targetRegion) return ambient;
|
|
12854
|
+
const target = canonicalizeRegion(targetRegion);
|
|
12855
|
+
if (!isClientSafeRegion(target)) throw new Error(`Refusing to build AWS clients for the region '${stripControlChars(target).slice(0, 64)}': it is not a valid AWS region name, and a region is substituted into the AWS service hostname.`);
|
|
12856
|
+
if (typeof ambient.withRegion !== "function") return ambient;
|
|
12857
|
+
const cached = this.regionScopedClients.get(target);
|
|
12858
|
+
if (cached) return cached;
|
|
12859
|
+
if (canonicalizeRegion(ambient.configuredRegion) === target) return ambient;
|
|
12860
|
+
const scoped = ambient.withRegion(target);
|
|
12861
|
+
this.regionScopedClients.set(target, scoped);
|
|
12862
|
+
this.logger.debug(`Using region-scoped AWS clients for ${target}`);
|
|
12863
|
+
return scoped;
|
|
12864
|
+
}
|
|
12865
|
+
/**
|
|
12569
12866
|
* Resolve parameter values from template Parameters section
|
|
12570
12867
|
*
|
|
12571
12868
|
* Merges default values from template with user-provided parameter values.
|
|
@@ -12617,7 +12914,7 @@ var IntrinsicFunctionResolver = class {
|
|
|
12617
12914
|
* Used for parameters with type AWS::SSM::Parameter::Value<...>.
|
|
12618
12915
|
*/
|
|
12619
12916
|
async resolveSSMParameter(parameterName) {
|
|
12620
|
-
return (await
|
|
12917
|
+
return (await this.clientsForRegion(this.explicitRegion).ssm.send(new GetParameterCommand({ Name: parameterName }))).Parameter?.Value ?? "";
|
|
12621
12918
|
}
|
|
12622
12919
|
/**
|
|
12623
12920
|
* Coerce parameter value to the correct type based on parameter definition
|
|
@@ -13190,7 +13487,7 @@ var IntrinsicFunctionResolver = class {
|
|
|
13190
13487
|
const cached = cachedEc2InstanceAttributes[cacheKey];
|
|
13191
13488
|
if (cached !== void 0) return cached;
|
|
13192
13489
|
try {
|
|
13193
|
-
const instance = (await
|
|
13490
|
+
const instance = (await this.clientsForRegion(this.explicitRegion).ec2.send(new DescribeInstancesCommand({ InstanceIds: [physicalId] }))).Reservations?.[0]?.Instances?.[0];
|
|
13194
13491
|
let value;
|
|
13195
13492
|
switch (attributeName) {
|
|
13196
13493
|
case "PrivateIp":
|
|
@@ -13224,7 +13521,7 @@ var IntrinsicFunctionResolver = class {
|
|
|
13224
13521
|
if (resourceType === "AWS::EC2::LaunchTemplate") {
|
|
13225
13522
|
if (attributeName === "LatestVersionNumber" || attributeName === "DefaultVersionNumber") {
|
|
13226
13523
|
try {
|
|
13227
|
-
const lt = (await
|
|
13524
|
+
const lt = (await this.clientsForRegion(this.explicitRegion).ec2.send(new DescribeLaunchTemplatesCommand({ LaunchTemplateIds: [physicalId] }))).LaunchTemplates?.[0];
|
|
13228
13525
|
const value = attributeName === "LatestVersionNumber" ? lt?.LatestVersionNumber : lt?.DefaultVersionNumber;
|
|
13229
13526
|
if (value !== void 0 && value !== null) return String(value);
|
|
13230
13527
|
} catch (err) {
|
|
@@ -13873,7 +14170,9 @@ var IntrinsicFunctionResolver = class {
|
|
|
13873
14170
|
if ("Region" in args && args["Region"] !== void 0 && args["Region"] !== null) {
|
|
13874
14171
|
const resolvedRegion = await this.resolveValue(args["Region"], context);
|
|
13875
14172
|
if (typeof resolvedRegion !== "string" || resolvedRegion === "") throw new Error(`Fn::GetStackOutput: Region must resolve to a non-empty string, got ${typeof resolvedRegion}`);
|
|
13876
|
-
|
|
14173
|
+
const requestedRegion = canonicalizeRegion(resolvedRegion);
|
|
14174
|
+
if (!isClientSafeRegion(requestedRegion)) throw new Error(`Fn::GetStackOutput: '${stripControlChars(resolvedRegion).slice(0, 64)}' is not a valid AWS region name. The region selects both the AWS endpoint and the state-file key, so cdkd will not use it.`);
|
|
14175
|
+
region = requestedRegion;
|
|
13877
14176
|
}
|
|
13878
14177
|
let roleArn;
|
|
13879
14178
|
if ("RoleArn" in args && args["RoleArn"] !== void 0 && args["RoleArn"] !== null) {
|
|
@@ -14056,28 +14355,52 @@ var IntrinsicFunctionResolver = class {
|
|
|
14056
14355
|
async resolveGetAZs(value, context) {
|
|
14057
14356
|
const resolvedValue = await this.resolveValue(value, context);
|
|
14058
14357
|
let region;
|
|
14059
|
-
|
|
14060
|
-
|
|
14358
|
+
/**
|
|
14359
|
+
* Which region's clients answer the `DescribeAvailabilityZones` below.
|
|
14360
|
+
*
|
|
14361
|
+
* `DescribeAvailabilityZones` lists the AZs of the region the CLIENT is
|
|
14362
|
+
* pointed at; the `region-name` filter narrows that listing, it does not
|
|
14363
|
+
* widen it to another region. So a foreign-region client returns an EMPTY
|
|
14364
|
+
* list, which this method then caches and hands back as the resolved value
|
|
14365
|
+
* of `Fn::GetAZs` — silently, since an empty list is not an error here
|
|
14366
|
+
* (issue #1957).
|
|
14367
|
+
*
|
|
14368
|
+
* When the template names a region explicitly, THAT is the region to talk
|
|
14369
|
+
* to. Otherwise fall back to the resolver's own — but only when it was
|
|
14370
|
+
* given explicitly (see {@link explicitRegion}).
|
|
14371
|
+
*/
|
|
14372
|
+
let clientRegion;
|
|
14373
|
+
if (typeof resolvedValue === "string" && resolvedValue !== "") {
|
|
14374
|
+
const requested = canonicalizeRegion(resolvedValue);
|
|
14375
|
+
if (!isClientSafeRegion(requested)) throw new Error(`Fn::GetAZs: '${stripControlChars(resolvedValue).slice(0, 64)}' is not a valid AWS region name. A region is substituted into the AWS service hostname, so cdkd will not build a client from it.`);
|
|
14376
|
+
region = requested;
|
|
14377
|
+
clientRegion = requested;
|
|
14378
|
+
} else {
|
|
14379
|
+
region = (await getAccountInfo(this.resolverRegion)).region;
|
|
14380
|
+
clientRegion = this.explicitRegion;
|
|
14381
|
+
}
|
|
14061
14382
|
const cached = cachedAvailabilityZones[region];
|
|
14062
14383
|
if (cached) {
|
|
14063
14384
|
this.logger.debug(`Resolved Fn::GetAZs from cache: ${region} -> ${JSON.stringify(cached)}`);
|
|
14064
14385
|
return cached;
|
|
14065
14386
|
}
|
|
14066
|
-
const ec2Client =
|
|
14387
|
+
const ec2Client = this.clientsForRegion(clientRegion).ec2;
|
|
14388
|
+
let azNames;
|
|
14067
14389
|
try {
|
|
14068
|
-
|
|
14390
|
+
azNames = ((await ec2Client.send(new DescribeAvailabilityZonesCommand({ Filters: [{
|
|
14069
14391
|
Name: "region-name",
|
|
14070
14392
|
Values: [region]
|
|
14071
14393
|
}, {
|
|
14072
14394
|
Name: "state",
|
|
14073
14395
|
Values: ["available"]
|
|
14074
14396
|
}] }))).AvailabilityZones || []).map((az) => az.ZoneName).filter((name) => name !== void 0).sort();
|
|
14075
|
-
cachedAvailabilityZones[region] = azNames;
|
|
14076
|
-
this.logger.debug(`Resolved Fn::GetAZs: ${region} -> ${JSON.stringify(azNames)}`);
|
|
14077
|
-
return azNames;
|
|
14078
14397
|
} catch (error) {
|
|
14079
14398
|
throw new Error(`Fn::GetAZs: failed to describe availability zones for region '${region}': ${error instanceof Error ? error.message : String(error)}`);
|
|
14080
14399
|
}
|
|
14400
|
+
if (azNames.length === 0) throw new Error(`Fn::GetAZs: no availability zones returned for region '${region}'. Either the region is not enabled on this account (opt-in regions must be enabled before use), or the request was answered by a different region's endpoint.`);
|
|
14401
|
+
cachedAvailabilityZones[region] = azNames;
|
|
14402
|
+
this.logger.debug(`Resolved Fn::GetAZs: ${region} -> ${JSON.stringify(azNames)}`);
|
|
14403
|
+
return azNames;
|
|
14081
14404
|
}
|
|
14082
14405
|
/**
|
|
14083
14406
|
* Resolve pseudo parameters
|
|
@@ -14226,7 +14549,7 @@ var IntrinsicFunctionResolver = class {
|
|
|
14226
14549
|
if (!versionStage) versionStage = "AWSCURRENT";
|
|
14227
14550
|
if (!secretId) throw new Error("Dynamic reference: secretsmanager SECRET_ID is required");
|
|
14228
14551
|
this.logger.debug(`Resolving dynamic reference: secretsmanager:${secretId}:SecretString:${jsonKey}:${versionStage}:${versionId}`);
|
|
14229
|
-
const client =
|
|
14552
|
+
const client = this.clientsForRegion(this.explicitRegion).secretsManager;
|
|
14230
14553
|
const command = new GetSecretValueCommand({
|
|
14231
14554
|
SecretId: secretId,
|
|
14232
14555
|
...versionStage && versionStage !== "" && { VersionStage: versionStage },
|
|
@@ -14354,10 +14677,13 @@ var IntrinsicFunctionResolver = class {
|
|
|
14354
14677
|
* - It says NOTHING about concurrency. The client is captured before the
|
|
14355
14678
|
* first attempt, so a sibling stack's teardown (`stackAwsClients.destroy()`
|
|
14356
14679
|
* in `deploy.ts`) during a backoff surfaces as a raw, non-throttle-shaped
|
|
14357
|
-
* failure on the next attempt
|
|
14358
|
-
*
|
|
14359
|
-
*
|
|
14360
|
-
*
|
|
14680
|
+
* failure on the next attempt, and the retry does not make that safe. Issue
|
|
14681
|
+
* [#1957](https://github.com/go-to-k/cdkd/issues/1957) NARROWED this rather
|
|
14682
|
+
* than removing it: a lookup whose region differs from the ambient one now
|
|
14683
|
+
* runs on {@link clientsForRegion}'s own clients, which no sibling stack can
|
|
14684
|
+
* destroy because nothing else in the process holds a reference to them. A
|
|
14685
|
+
* sibling in the SAME region still shares the ambient instance, so the
|
|
14686
|
+
* window survives exactly where the two stacks agree on the region.
|
|
14361
14687
|
*/
|
|
14362
14688
|
sendWithThrottleRetry(operation, label) {
|
|
14363
14689
|
return withRetry(operation, label, {
|
|
@@ -14386,7 +14712,7 @@ var IntrinsicFunctionResolver = class {
|
|
|
14386
14712
|
const parameterName = parts.slice(1).join(":");
|
|
14387
14713
|
if (!parameterName) throw new Error("Dynamic reference: ssm PARAMETER_NAME is required");
|
|
14388
14714
|
this.logger.debug(`Resolving dynamic reference: ssm:${parameterName}`);
|
|
14389
|
-
const client =
|
|
14715
|
+
const client = this.clientsForRegion(this.explicitRegion).ssm;
|
|
14390
14716
|
const command = new GetParameterCommand({
|
|
14391
14717
|
Name: parameterName,
|
|
14392
14718
|
WithDecryption: decrypt
|
|
@@ -15269,7 +15595,7 @@ var CloudControlProvider = class {
|
|
|
15269
15595
|
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);
|
|
15270
15596
|
if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
|
|
15271
15597
|
this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
|
|
15272
|
-
const { ASGProvider } = await import("./asg-provider-
|
|
15598
|
+
const { ASGProvider } = await import("./asg-provider-C0L3_pHn.js").then((n) => n.n);
|
|
15273
15599
|
return await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
|
|
15274
15600
|
}
|
|
15275
15601
|
const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
|
|
@@ -22814,7 +23140,7 @@ const FLUSH_INTERVAL_MS = 2e3;
|
|
|
22814
23140
|
const FLUSH_EVENT_THRESHOLD = 50;
|
|
22815
23141
|
/** Build-time cdkd version, with a dev fallback for non-built contexts. */
|
|
22816
23142
|
function getCdkdVersion() {
|
|
22817
|
-
return "0.284.
|
|
23143
|
+
return "0.284.4";
|
|
22818
23144
|
}
|
|
22819
23145
|
/**
|
|
22820
23146
|
* Generate a time-sortable unique run id, e.g.
|
|
@@ -25282,4 +25608,4 @@ var DeployEngine = class {
|
|
|
25282
25608
|
|
|
25283
25609
|
//#endregion
|
|
25284
25610
|
export { IntrinsicFunctionResolver as $, StackTerminationProtectionError as $n, buildDenyExternalAccessPolicy as $t, formatResourceLine as A, processStackMessages as An, withRetry as At, isExportAliasCollision as B, DependencyError as Bn, buildAssetRedirectMap as Bt, refusesFinalSnapshot as C, findLargeInlineResources as Cn, s3BucketDualStackDomainName as Ct, MULTI_REGION_RECREATE_BLOCKED_TYPES as D, canonicalizeRegion as Dn, DiffCalculator as Dt, extractDeploymentEventError as E, PARTITION_TABLE as En, applyRoleArnIfSet as Et, red as F, resetAwsClients as Fn, rebuildClientForBucketRegion as Ft, clearOnUpdateRemoval as G, LockError as Gn, stripControlChars as Gt, stateKeySecretExposure as H, LocalInvokeBuildError as Hn, loadPublishableAssetManifest as Ht, yellow as I, setAwsClients as In, shouldRetainResource as It, findSilentDropProperties as J, PartialFailureError as Jn, ensureAssetStorage as Jt, ProviderRegistry as K, MissingCdkCliError as Kn, AssetModeResolver as Kt, collectDeclaredOutputNames as L, AssetError as Ln, AssetPublisher as Lt, cyan as M, resolveBucketRegion as Mn, TemplateParser as Mt, gray as N, AwsClients as Nn, LockManager as Nt, isStatefulRecreateTargetSync as O, derivePartitionAndUrlSuffix as On, INTRINSIC_KEYS as Ot, green as P, getAwsClients as Pn, S3StateBackend as Pt, isTerminationProtectionPropagationError as Q, StackHasActiveImportsError as Qn, validateContainerRepoName as Qt, collectPublishedOutputNames as R, CdkdError as Rn, stringifyValue as Rt, isFinalSnapshotError as S, MIGRATE_TMP_PREFIX as Sn, s3BucketDomainName as St, makeCanonicalizePropertiesFn as T, expectedOwnerParam as Tn, s3BucketWebsiteUrl as Tt, IAMRoleProvider as U, LocalMigrateError as Un, rewriteTemplateAssetReferences as Ut, secretBearingStateKeyWarning as V, DeployCancelledError as Vn, createAssetRedirectResolver as Vt, collectInlinePolicyNamesManagedBySiblings as W, LocalStartServiceError as Wn, escapeRegExp$1 as Wt, slowCcOperationTimeoutMs as X, ResourceTimeoutError as Xn, parseBootstrapMarker as Xt, CloudControlProvider as Y, ProvisioningError as Yn, getBootstrapMarkerKey as Yt, disableInstanceApiTermination as Z, ResourceUpdateNotSupportedError as Zn, validateAssetBucketName as Zt, ATOMIC_FINAL_SNAPSHOT_TYPES as _, resolveUseCdkBootstrapAssets as _n, TEMPLATE_SOURCED_RULES as _t, DeploymentEventsStore as a, AssetManifestLoader as an, withErrorHandling as ar, resolveExplicitPhysicalId as at, ccRoutedFinalSnapshotError as b, CFN_TEMPLATE_BODY_LIMIT as bn, scrubResourceRecord as bt, replayFailedOperations as c, synthesisStatusMessage as cn, isThrottlingError as cr, configBooleanRefusal as ct, updatePartialReason as d, resolveApp as dn, replayWarn as dt, buildDockerImage as en, StateError as er, cfnRefValueFromPhysicalId as et, UNSPECIFIED_SKIP_REASON as f, resolveAutoAssetStorage as fn, requireConfigArray as ft, computeImplicitDeleteEdges as g, resolveStateBucketWithDefaultAndSource as gn, STATE_SOURCED_READBACK_RULES as gt, IMPLICIT_DELETE_DEPENDENCIES as h, resolveStateBucketWithDefault as hn, STATE_SOURCED_CROSS_GENERATION_RULES as ht, DeploymentEventsReader as i, runDockerStreaming as in, normalizeAwsError as ir, normalizeAwsTagsToCfn as it, bold as j, clearBucketRegionCache as jn, DagBuilder as jt, renderStatefulReason as k, AssemblyReader as kn, describeTypeWithThrottleRetry as kt, replayRollback as l, getDefaultStateBucketName as ln, markNonRetryable as lr, configStringRefusal as lt, withResourceDeadline as m, resolveSkipPrefix as mn, requireConfigString as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, getDockerCmd as nn, formatError as nr, refStateLookupFromResource as nt, planFailedOps as o, getDockerImageBySourceHash as on, isMarkedNonRetryable as or, assertRegionMatch as ot, deleteSkipReason as p, resolveCaptureObservedState as pn, requireConfigObject as pt, findActionableSilentDrops as q, NestedStackChildDirectDestroyError as qn, BOOTSTRAP_MARKER_PREFIX as qt, DeployEngine as r, runDockerForeground as rn, isCdkdError as rr, WAFv2WebACLProvider as rt, planRollback as s, Synthesizer as sn, isRetryableTransientError as sr, coerceCfnBoolean as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, formatDockerLoginError as tn, SynthesisError as tr, getAccountInfo as tt, updatePartialMessage as u, getLegacyStateBucketName as un, __exportAll as ur, readConfigString as ut, PRE_DELETE_SNAPSHOT_TYPES as v, stateBucketExistenceConfirmed as vn, maskSecretsInText as vt, unsupportedFinalSnapshotError as w, uploadCfnTemplate as wn, s3BucketRegionalDomainName as wt, createPreDeleteFinalSnapshot as x, CFN_TEMPLATE_URL_LIMIT as xn, s3BucketArn as xt, buildFinalSnapshotIdentifier as y, warnDeprecatedNoPrefixCliFlag as yn, redactSecretsForState as yt, exportAliasCollisionScrubWarning as z, ConfigError as zn, WorkGraph as zt };
|
|
25285
|
-
//# sourceMappingURL=deploy-engine-
|
|
25611
|
+
//# sourceMappingURL=deploy-engine-3Z7P1hKs.js.map
|