@go-to-k/cdkd 0.290.16 → 0.290.18
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-CvkcE8rH.js → asg-provider-Di5-tD3l.js} +3 -3
- package/dist/{asg-provider-CvkcE8rH.js.map → asg-provider-Di5-tD3l.js.map} +1 -1
- package/dist/aws-client-defaults-BoIxyCir.js +574 -0
- package/dist/aws-client-defaults-BoIxyCir.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-B91NMqm1.js → deploy-engine-DuiVEV9B.js} +1750 -133
- package/dist/deploy-engine-DuiVEV9B.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-U03H43s7.js → program-BcdccZA8.js} +388 -568
- package/dist/{program-U03H43s7.js.map → program-BcdccZA8.js.map} +1 -1
- package/dist/{version-1FTpsWNc.js → version-Da2VV388.js} +2 -2
- package/dist/{version-1FTpsWNc.js.map → version-Da2VV388.js.map} +1 -1
- package/package.json +1 -1
- package/dist/aws-client-defaults-D-iYiIJ6.js +0 -218
- package/dist/aws-client-defaults-D-iYiIJ6.js.map +0 -1
- package/dist/deploy-engine-B91NMqm1.js.map +0 -1
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
import { defaultProvider } from "@aws-sdk/credential-provider-node";
|
|
2
|
+
import { NodeHttpHandler } from "@smithy/node-http-handler";
|
|
3
|
+
import { Agent } from "node:http";
|
|
4
|
+
import { Agent as Agent$1 } from "node:https";
|
|
5
|
+
import { Agent as Agent$2 } from "agent-base";
|
|
6
|
+
import { HttpProxyAgent } from "http-proxy-agent";
|
|
7
|
+
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
8
|
+
import { getProxyForUrl } from "proxy-from-env";
|
|
9
|
+
|
|
10
|
+
//#region src/utils/proxy-routing-agent.ts
|
|
11
|
+
/**
|
|
12
|
+
* An `http.Agent` that routes each request through the environment's proxy, or
|
|
13
|
+
* direct, according to `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY`.
|
|
14
|
+
*
|
|
15
|
+
* WHY A ROUTING AGENT AND NOT A PLAIN PROXY AGENT
|
|
16
|
+
*
|
|
17
|
+
* `NodeHttpHandler` picks its agent by PROTOCOL alone — `httpsAgent` for an
|
|
18
|
+
* `https:` request, `httpAgent` otherwise — and never consults the request's
|
|
19
|
+
* host. `https-proxy-agent` does not read `NO_PROXY` either. So a statically
|
|
20
|
+
* chosen proxy agent cannot express "this host is exempt", which is the whole
|
|
21
|
+
* of what `NO_PROXY` means. Deciding per REQUEST is the only place the host is
|
|
22
|
+
* known, and `agent-base` gives us that hook: when `connect()` returns an
|
|
23
|
+
* `http.Agent` rather than a socket, the base class delegates the request to it
|
|
24
|
+
* via `socket.addRequest(req, connectOpts)`.
|
|
25
|
+
*
|
|
26
|
+
* That delegation is also why the INNER agents carry `keepAlive` rather than
|
|
27
|
+
* this one: the sockets pool on whichever agent `connect()` hands back, so a
|
|
28
|
+
* `keepAlive` set only here would never be consulted.
|
|
29
|
+
*
|
|
30
|
+
* WHY THE INNER CACHE IS PER INSTANCE
|
|
31
|
+
*
|
|
32
|
+
* Node's `Agent.prototype.destroy()` walks `[this.freeSockets, this.sockets]`,
|
|
33
|
+
* and the second is the ACTIVE set — it aborts in-flight requests, not just
|
|
34
|
+
* idle sockets. `NodeHttpHandler.destroy()` forwards to `httpAgent` and
|
|
35
|
+
* `httpsAgent` unconditionally, external instances included, and cdkd destroys
|
|
36
|
+
* clients mid-run (`deploy` drops the STS client right after
|
|
37
|
+
* `GetCallerIdentity`). A module-global inner cache would therefore let one
|
|
38
|
+
* client's teardown kill another client's live request. Each `AwsClients`
|
|
39
|
+
* client gets its own routing agent, so the cache is scoped to `this` and
|
|
40
|
+
* {@link destroy} forwards to the inner agents — without that forwarding the
|
|
41
|
+
* tunneled sockets would outlive the client and keep the process alive.
|
|
42
|
+
*
|
|
43
|
+
* The cost of not sharing is duplicate CONNECT + TLS handshakes where two
|
|
44
|
+
* clients talk to the same host. Pooling is per host either way, so sharing
|
|
45
|
+
* could never remove the FIRST handshake to each distinct AWS endpoint, and
|
|
46
|
+
* most runtime traffic goes through the per-service `AwsClients` singleton, so
|
|
47
|
+
* the duplicates are few. This is also what the SDK already does: it builds a
|
|
48
|
+
* `keepAlive` agent per client on the unproxied path.
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* Applied to every inner agent.
|
|
52
|
+
*
|
|
53
|
+
* The SDK sets these itself, but ONLY when it is handed a plain option bag —
|
|
54
|
+
* an external `Agent` instance is passed through untouched
|
|
55
|
+
* (`@smithy/node-http-handler`'s `NodeHttpHandler` constructor). Since a
|
|
56
|
+
* routing agent IS such an instance, omitting them would silently drop
|
|
57
|
+
* connection reuse for every proxied run, and a concurrent deploy would
|
|
58
|
+
* renegotiate TLS per request.
|
|
59
|
+
*/
|
|
60
|
+
const INNER_AGENT_OPTIONS = {
|
|
61
|
+
keepAlive: true,
|
|
62
|
+
maxSockets: 50
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Build the absolute URL `getProxyForUrl` needs from what `connect()` is given.
|
|
66
|
+
*
|
|
67
|
+
* An IPv6 literal is bracketed, or the `host:port` join produces a string no
|
|
68
|
+
* URL parser accepts (`https://::1:443`) and `NO_PROXY` could never match an
|
|
69
|
+
* IPv6 entry. Unreachable with AWS hostnames; handled because the alternative
|
|
70
|
+
* is a silent wrong answer rather than an error.
|
|
71
|
+
*/
|
|
72
|
+
function requestUrl(options) {
|
|
73
|
+
const secure = options.secureEndpoint;
|
|
74
|
+
const rawHost = options.host ?? "localhost";
|
|
75
|
+
const host = rawHost.includes(":") && !rawHost.startsWith("[") ? `[${rawHost}]` : rawHost;
|
|
76
|
+
const port = options.port ?? (secure ? 443 : 80);
|
|
77
|
+
return `${secure ? "https" : "http"}://${host}:${port}`;
|
|
78
|
+
}
|
|
79
|
+
var ProxyRoutingAgent = class extends Agent$2 {
|
|
80
|
+
/**
|
|
81
|
+
* Inner agents by `<scheme>|<proxy url or "direct">`.
|
|
82
|
+
*
|
|
83
|
+
* Keyed on the proxy URL and not merely on "proxied or not" because
|
|
84
|
+
* `NO_PROXY` is not the only thing that varies per host: `HTTP_PROXY` and
|
|
85
|
+
* `HTTPS_PROXY` may name different proxies, and a single agent bound to one
|
|
86
|
+
* of them would quietly send the other scheme's traffic to the wrong place.
|
|
87
|
+
*/
|
|
88
|
+
innerAgents = /* @__PURE__ */ new Map();
|
|
89
|
+
connect(_req, options) {
|
|
90
|
+
const secure = options.secureEndpoint;
|
|
91
|
+
const proxyUrl = getProxyForUrl(requestUrl(options));
|
|
92
|
+
const key = `${secure ? "https" : "http"}|${proxyUrl || "direct"}`;
|
|
93
|
+
const cached = this.innerAgents.get(key);
|
|
94
|
+
if (cached) return cached;
|
|
95
|
+
const agent = createInnerAgent(secure, proxyUrl);
|
|
96
|
+
this.innerAgents.set(key, agent);
|
|
97
|
+
return agent;
|
|
98
|
+
}
|
|
99
|
+
destroy() {
|
|
100
|
+
for (const agent of this.innerAgents.values()) agent.destroy();
|
|
101
|
+
this.innerAgents.clear();
|
|
102
|
+
super.destroy();
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
function createInnerAgent(secure, proxyUrl) {
|
|
106
|
+
if (!proxyUrl) return secure ? new Agent$1(INNER_AGENT_OPTIONS) : new Agent(INNER_AGENT_OPTIONS);
|
|
107
|
+
return secure ? new HttpsProxyAgent(proxyUrl, INNER_AGENT_OPTIONS) : new HttpProxyAgent(proxyUrl, INNER_AGENT_OPTIONS);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
//#endregion
|
|
111
|
+
//#region src/utils/aws-client-defaults.ts
|
|
112
|
+
/**
|
|
113
|
+
* Client config every AWS SDK client in cdkd must be built with.
|
|
114
|
+
*
|
|
115
|
+
* WHY THIS EXISTS
|
|
116
|
+
*
|
|
117
|
+
* The AWS SDK for JavaScript v3 does NOT read `HTTPS_PROXY` / `HTTP_PROXY` the
|
|
118
|
+
* way botocore (the AWS CLI) and Go's `net/http` do. Its own guide says a proxy
|
|
119
|
+
* is supplied "through a third-party HTTP agent" by whoever constructs the
|
|
120
|
+
* client — so on a machine whose only egress is a corporate proxy, an SDK call
|
|
121
|
+
* dials out directly and fails, typically as
|
|
122
|
+
* `CredentialsProviderError: self-signed certificate in certificate chain`
|
|
123
|
+
* because the direct route is what the network intercepts (issue #2388).
|
|
124
|
+
*
|
|
125
|
+
* Node 24's `NODE_USE_ENV_PROXY=1` is not a way out: it rewires the GLOBAL
|
|
126
|
+
* agent, and every SDK client builds its own.
|
|
127
|
+
*
|
|
128
|
+
* WHY THE CREDENTIAL CHAIN IS INJECTED TOO
|
|
129
|
+
*
|
|
130
|
+
* A client's `requestHandler` does not reach every credential hop:
|
|
131
|
+
*
|
|
132
|
+
* - STS (`role_arn` profiles, web identity) reads `requestHandler` off
|
|
133
|
+
* `parentClientConfig`, so it INHERITS ours. Nothing to do.
|
|
134
|
+
* - `@aws-sdk/credential-provider-sso` builds its portal client from
|
|
135
|
+
* `clientConfig` alone, coalescing only `logger`, `region` and
|
|
136
|
+
* `userAgentAppId` from the caller. `@aws-sdk/token-providers` does the same
|
|
137
|
+
* for the SSO-OIDC refresh. So an SSO profile fails at
|
|
138
|
+
* `resolveSSOCredentials` — BEFORE any service call — unless the chain is
|
|
139
|
+
* constructed here with the handler threaded through `clientConfig`.
|
|
140
|
+
* - IMDS (`@smithy/credential-provider-imds`) and ECS container credentials
|
|
141
|
+
* (`@aws-sdk/credential-provider-http`) call `node:http` / build their own
|
|
142
|
+
* handler, so they bypass a proxy on their own and need no special casing.
|
|
143
|
+
*
|
|
144
|
+
* The chain is therefore built per call. `defaultProvider` MEMOIZES resolved
|
|
145
|
+
* credentials inside the chain instance, so one shared instance would hand a
|
|
146
|
+
* client configured for profile A the credentials of profile B. Per call costs
|
|
147
|
+
* nothing relative to today, since each client already builds its own chain.
|
|
148
|
+
*
|
|
149
|
+
* `clientConfig` carries `requestHandler` and NOTHING else — a `region` there
|
|
150
|
+
* would override the SSO portal's own region — and `profile` is passed
|
|
151
|
+
* ALONGSIDE it, because the built-in chain sees a profile through the client
|
|
152
|
+
* config while an injected chain does not.
|
|
153
|
+
*
|
|
154
|
+
* SPREAD THIS FIRST, SITE-SPECIFIC CONFIG SECOND
|
|
155
|
+
*
|
|
156
|
+
* `new S3Client({ ...awsClientDefaults({ profile }), region, ...(creds && { credentials: creds }) })`
|
|
157
|
+
*
|
|
158
|
+
* A site that supplies its own `credentials` must keep them — `config-loader`'s
|
|
159
|
+
* default-bucket probe reuses the STS client's resolved provider on purpose, so
|
|
160
|
+
* that the bucket it probes is checked as the identity the name was derived
|
|
161
|
+
* from. That provider already carries the handler, because the STS client it
|
|
162
|
+
* came from was built through this helper.
|
|
163
|
+
*
|
|
164
|
+
* WHY THE ASSUMED ROLE IS PUBLISHED FROM HERE
|
|
165
|
+
*
|
|
166
|
+
* `--role-arn` / `CDKD_ROLE_ARN` used to reach the SDK through the `AWS_*`
|
|
167
|
+
* environment variables ALONE (`src/utils/role-arn.ts`). That channel is dead
|
|
168
|
+
* whenever a profile is selected: `@aws-sdk/credential-provider-node` skips the
|
|
169
|
+
* env-var provider entirely once `AWS_PROFILE` is set OR a `profile` is passed
|
|
170
|
+
* on the client config, so `cdkd deploy --profile ci --role-arn <r>` assumed the
|
|
171
|
+
* role, logged it as assumed, and then issued every AWS call as the profile's
|
|
172
|
+
* own principal — in the wrong account, silently (issue
|
|
173
|
+
* [#3130](https://github.com/go-to-k/cdkd/issues/3130)).
|
|
174
|
+
*
|
|
175
|
+
* Clearing `AWS_PROFILE` would not have closed it: cdkd ALSO threads `profile`
|
|
176
|
+
* as an explicit client-config key at every construction site, and an explicit
|
|
177
|
+
* key outranks the environment. So the assumed credentials are published HERE
|
|
178
|
+
* instead, as an explicit `credentials` value — which outranks BOTH, because
|
|
179
|
+
* `resolveAwsSdkSigV4Config` uses `config.credentials` when present and never
|
|
180
|
+
* builds the default chain, leaving `profile` to select non-credential settings
|
|
181
|
+
* only (`role-arn.ts`'s header carries why the profile is left in place).
|
|
182
|
+
*
|
|
183
|
+
* This is the one place that reaches every client without a per-site edit:
|
|
184
|
+
* `scripts/check-aws-client-defaults.ts` requires this helper to be spread
|
|
185
|
+
* FIRST at EVERY `new XxxClient(...)` under `src/**`, with an EMPTY allow-list.
|
|
186
|
+
* The same spread order keeps a site's OWN explicit `credentials` winning — the
|
|
187
|
+
* cross-account `Fn::GetStackOutput` read stays scoped to the producer's role.
|
|
188
|
+
*
|
|
189
|
+
* Clients built OUTSIDE `src/**` (cdk-local's own, the CDK app subprocess) see
|
|
190
|
+
* none of this; they read the `AWS_*` triple `applyRoleArnIfSet` also writes,
|
|
191
|
+
* which the SDK honours for them unless a profile is selected.
|
|
192
|
+
*/
|
|
193
|
+
/**
|
|
194
|
+
* The profile `@smithy/shared-ini-file-loader`'s `getProfileName` falls back to
|
|
195
|
+
* when neither a client config nor `AWS_PROFILE` names one.
|
|
196
|
+
*
|
|
197
|
+
* Naming it EXPLICITLY is how {@link callerIdentityForOptOut} builds shape 3's
|
|
198
|
+
* chain: `credential-provider-node`'s first link short-circuits whenever
|
|
199
|
+
* `init.profile ?? process.env.AWS_PROFILE` is truthy, and every OTHER link it
|
|
200
|
+
* feeds (`fromSSO` / `fromIni` / `fromProcess`) resolves the same profile name
|
|
201
|
+
* it would have defaulted to, while `fromTokenFile` and the container / IMDS
|
|
202
|
+
* providers do not read a profile at all. So passing this is EXACTLY "the
|
|
203
|
+
* default chain minus its environment link" — the one thing that has to be
|
|
204
|
+
* subtracted, expressed through the SDK's own documented precedence rather than
|
|
205
|
+
* by re-spelling the chain (which would then go stale on an SDK bump).
|
|
206
|
+
*/
|
|
207
|
+
const SDK_DEFAULT_PROFILE = "default";
|
|
208
|
+
/**
|
|
209
|
+
* The one warning {@link CHAIN_LOGGER} drops, matched on its distinctive line
|
|
210
|
+
* rather than on the whole text: the rest of the message is advisory prose an
|
|
211
|
+
* SDK bump is free to reword, while this line is the warning's identity.
|
|
212
|
+
*/
|
|
213
|
+
const MULTIPLE_CREDENTIAL_SOURCES_WARNING = "Multiple credential sources detected";
|
|
214
|
+
/**
|
|
215
|
+
* Handed to shape 3's injected chain to drop exactly ONE message —
|
|
216
|
+
* `credential-provider-node`'s `Multiple credential sources detected` warning —
|
|
217
|
+
* while every other warning the chain emits still reaches the user.
|
|
218
|
+
*
|
|
219
|
+
* That warning fires on `profile set AND AWS_ACCESS_KEY_ID/SECRET set`, which
|
|
220
|
+
* is exactly shape 3's inputs, because this module NAMES `default` as the
|
|
221
|
+
* profile ({@link SDK_DEFAULT_PROFILE}) to subtract the env link. Its text
|
|
222
|
+
* ("Both AWS_PROFILE and the pair ... are set. This SDK will proceed with the
|
|
223
|
+
* AWS_PROFILE value.") is FALSE here: `AWS_PROFILE` is unset, the profile name
|
|
224
|
+
* is one cdkd passed, and the triple is one cdkd itself wrote. Left alone it
|
|
225
|
+
* goes to `console.warn`, outside cdkd's logger, on every shape-3 client.
|
|
226
|
+
*
|
|
227
|
+
* **Scoped to shape 3, deliberately.** Shape 1 (`AWS_PROFILE` or a `profile`
|
|
228
|
+
* key selected) returns `{}`, so the client builds the chain itself with no
|
|
229
|
+
* logger and the SDK still emits this warning to `console.warn`. That is left
|
|
230
|
+
* alone: there the message is TRUE — a profile really is selected and really
|
|
231
|
+
* does win — so only its closing advice is unhelpful, and silencing it would
|
|
232
|
+
* mean injecting a logger into every client config in the tree.
|
|
233
|
+
*
|
|
234
|
+
* **Why not a wholesale `warn: () => {}`.** Shape 3's own population is who
|
|
235
|
+
* loses real warnings: `@smithy/credential-provider-imds`'s
|
|
236
|
+
* `staticStabilityProvider` warns on `Credential renew failed:` and on
|
|
237
|
+
* extending an expiration, and `@aws-sdk/credential-provider-http`'s `fromHttp`
|
|
238
|
+
* warns when both the relative and full container-credential URIs (or both
|
|
239
|
+
* token spellings) are set. Both of those default to `console` when no logger
|
|
240
|
+
* is passed, so forwarding `warn` to the console restores their baseline
|
|
241
|
+
* exactly. `trace` / `debug` / `info` stay no-ops: `defaultProvider`
|
|
242
|
+
* reaches those through `logger?.` and therefore emits NOTHING without a
|
|
243
|
+
* logger, so forwarding them would newly print a per-link chain trace on every
|
|
244
|
+
* shape-3 run.
|
|
245
|
+
*
|
|
246
|
+
* **`error` is DEFENSIVE, and restores no baseline** — the claim above is true
|
|
247
|
+
* of `warn` alone. No link in the installed chain calls either spelling of it:
|
|
248
|
+
* across `@aws-sdk/credential-provider-*`, `@aws-sdk/token-providers` and
|
|
249
|
+
* `@smithy/credential-provider-imds` the logger methods invoked are
|
|
250
|
+
* `logger?.debug`, `logger?.warn` and `logger.warn`, with zero `logger.error` /
|
|
251
|
+
* `logger?.error` (measured 2026-09-17), and the imds `options?.logger ||
|
|
252
|
+
* console` default is therefore warn-only in practice. The arm is kept so that
|
|
253
|
+
* a link which starts raising one is not silenced by cdkd having supplied a
|
|
254
|
+
* logger. If such a link arrives, re-decide this arm rather than assuming it is
|
|
255
|
+
* still right: a new link will most likely reach the logger through
|
|
256
|
+
* `logger?.error`, which is the same optional-call shape that makes `info`
|
|
257
|
+
* silent without a logger — so forwarding it would print a line the SDK emits
|
|
258
|
+
* for nobody today, and the `info` argument above applies to it unchanged.
|
|
259
|
+
*
|
|
260
|
+
* **A dropped emission is permanent, and that is a bound, not a claim of
|
|
261
|
+
* harmlessness.** `multipleCredentialSourceWarningEmitted` is a module-global
|
|
262
|
+
* one-shot inside `credential-provider-node`, and the SDK sets it whether or
|
|
263
|
+
* not the logger printed anything — so a drop here silences any LATER
|
|
264
|
+
* emission in the process, including one from a chain cdk-local builds. What
|
|
265
|
+
* makes that acceptable is reachability rather than scope: a later legitimate
|
|
266
|
+
* emission needs a profile selected, and a selected profile is shape 1, where
|
|
267
|
+
* nothing is dropped. Only a caller that builds a shape-3 client and then
|
|
268
|
+
* selects a profile in the SAME process could lose one, and no cdkd path does:
|
|
269
|
+
* `buildProgram`'s `preAction` hook (`src/cli/program.ts`) mirrors `--profile`
|
|
270
|
+
* into `process.env.AWS_PROFILE` BEFORE any command action runs, so a
|
|
271
|
+
* `--profile` run is shape 1 at every site from its first client onward.
|
|
272
|
+
*
|
|
273
|
+
* **That hook is the whole argument, which is why it is named.** Only 2 of the
|
|
274
|
+
* 19 opt-out sites pass `profile` to this helper, so "a `--profile` run passes
|
|
275
|
+
* the key to every site" is NOT what makes the claim true — and moving the
|
|
276
|
+
* mirror into the individual commands, or dropping it because the SDK reads the
|
|
277
|
+
* flag anyway, would kill the reachability argument with nothing in THIS file's
|
|
278
|
+
* tests failing. Removing the hook outright does red three cases that assert it
|
|
279
|
+
* directly (`AWS_PROFILE` set before a nested command action runs, and two
|
|
280
|
+
* siblings), so the tree is not silent about the hook itself — it is silent
|
|
281
|
+
* about this argument depending on it.
|
|
282
|
+
*
|
|
283
|
+
* **A BOUND on both forwards** (go-to-k/cdkd#3003, recorded rather than fixed):
|
|
284
|
+
* `console.warn` / `console.error` bypass `ConsoleLogger.formatMessage`, so the
|
|
285
|
+
* extra arguments the chain passes — an SDK error object on
|
|
286
|
+
* `logger.warn("Credential renew failed: ", e)` — skip the C0 / C1 / bidi
|
|
287
|
+
* denylist every cdkd logger call inherits, and skip cdkd's level control too.
|
|
288
|
+
* It is NOT a regression: without a logger those links write to `console`
|
|
289
|
+
* themselves, so the baseline is the same unsanitized call. Routing through
|
|
290
|
+
* `getLogger()` is what the fix would need, and it collides with the import
|
|
291
|
+
* closure this file's own import block describes: this module is reachable from
|
|
292
|
+
* `aws-clients.ts`, so every relative import in it must carry the `.ts`
|
|
293
|
+
* spelling, and `src/utils/logger.ts` reaches `display-safe` /
|
|
294
|
+
* `live-renderer` / `stack-context` through `.js` specifiers — pulling the
|
|
295
|
+
* whole tree into the closure `vp run audit:coverage:check` resolves the way
|
|
296
|
+
* `node` does. The same constraint is why the whitespace-proxy refusal below
|
|
297
|
+
* throws a plain `Error` rather than a `CdkdError`.
|
|
298
|
+
*
|
|
299
|
+
* `constructor.name` must not be `NoOpLogger` or `credential-provider-node`
|
|
300
|
+
* routes the warning to `console.warn` instead of here — a plain object literal
|
|
301
|
+
* is what satisfies that.
|
|
302
|
+
*
|
|
303
|
+
* EXPORTED as a test seam only, like {@link resetAwsClientDefaults}. No
|
|
304
|
+
* `src/**` module reads it THROUGH this export — the one reader is
|
|
305
|
+
* `callerIdentityForOptOut` below, which hands it straight to `defaultProvider`.
|
|
306
|
+
* `defaultProvider` closes over it, so `error` —
|
|
307
|
+
* the arm no installed link calls — is reachable from a test by NO other route.
|
|
308
|
+
* Do not mutate it: every shape-3 chain in the process shares this one object.
|
|
309
|
+
*/
|
|
310
|
+
const CHAIN_LOGGER = {
|
|
311
|
+
trace: (..._args) => {},
|
|
312
|
+
debug: (..._args) => {},
|
|
313
|
+
info: (..._args) => {},
|
|
314
|
+
warn: (...args) => {
|
|
315
|
+
if (args.some((arg) => typeof arg === "string" && arg.includes(MULTIPLE_CREDENTIAL_SOURCES_WARNING))) return;
|
|
316
|
+
console.warn(...args);
|
|
317
|
+
},
|
|
318
|
+
error: (...args) => {
|
|
319
|
+
console.error(...args);
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
/**
|
|
323
|
+
* The proxy variables, in the order `proxy-from-env` itself reads them.
|
|
324
|
+
*
|
|
325
|
+
* Only their PRESENCE is decided here. Which one applies to a given request —
|
|
326
|
+
* and whether `NO_PROXY` exempts it — is `getProxyForUrl`'s job, per request,
|
|
327
|
+
* inside {@link ProxyRoutingAgent}. Deciding it here instead would collapse
|
|
328
|
+
* `HTTP_PROXY` and `HTTPS_PROXY` into one answer and send `http://` traffic to
|
|
329
|
+
* an HTTPS proxy.
|
|
330
|
+
*/
|
|
331
|
+
const PROXY_ENV_VARS = [
|
|
332
|
+
"HTTPS_PROXY",
|
|
333
|
+
"https_proxy",
|
|
334
|
+
"HTTP_PROXY",
|
|
335
|
+
"http_proxy",
|
|
336
|
+
"ALL_PROXY",
|
|
337
|
+
"all_proxy"
|
|
338
|
+
];
|
|
339
|
+
let proxyConfigured;
|
|
340
|
+
/**
|
|
341
|
+
* The credentials `--role-arn` assumed for THIS process, or `undefined` when no
|
|
342
|
+
* role was assumed. Set once, at the command's entry, by `applyRoleArnIfSet`.
|
|
343
|
+
*
|
|
344
|
+
* Process-global for the same reason the `AWS_*` env vars it replaces were:
|
|
345
|
+
* `--role-arn` is a process-wide decision taken before any stack work starts,
|
|
346
|
+
* so there is nothing per-stack or per-concurrent-task to key it on. It is
|
|
347
|
+
* monotonic — set once and never unset in production — so a client built before
|
|
348
|
+
* it is set and one built after cannot disagree about which identity the user
|
|
349
|
+
* chose; only about whether the role had been assumed yet, and every command
|
|
350
|
+
* assumes it before constructing any other client. That is ENFORCED, not
|
|
351
|
+
* merely intended: {@link setAssumedRoleCredentials} refuses a second publish.
|
|
352
|
+
*/
|
|
353
|
+
let assumedRoleCredentials;
|
|
354
|
+
/**
|
|
355
|
+
* The `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN` triple
|
|
356
|
+
* the process was started with, captured by `applyRoleArnIfSet` IMMEDIATELY
|
|
357
|
+
* BEFORE it overwrites those variables with the assumed role's.
|
|
358
|
+
*
|
|
359
|
+
* `undefined` means one of two different things, and the caller must not
|
|
360
|
+
* conflate them: no role has been assumed at all (ask
|
|
361
|
+
* {@link getAssumedRoleCredentials}), or a role was assumed and the caller had
|
|
362
|
+
* NO static credentials in its environment — the SSO / IMDS / container-role
|
|
363
|
+
* chains, where the triple was absent before the assume and there is nothing to
|
|
364
|
+
* restore. `src/utils/caller-credentials.ts` is the one place that reads the two
|
|
365
|
+
* together and turns them into an answer.
|
|
366
|
+
*
|
|
367
|
+
* Process-global for the same reason {@link assumedRoleCredentials} is: this is
|
|
368
|
+
* the state of the environment at a single process-wide decision point.
|
|
369
|
+
*/
|
|
370
|
+
let preAssumeEnvCredentials;
|
|
371
|
+
/**
|
|
372
|
+
* The message BOTH ends of the single-publish invariant refuse with — this
|
|
373
|
+
* module's {@link setAssumedRoleCredentials} and `role-arn.ts`'s
|
|
374
|
+
* `applyRoleArnIfSet`, which trips first so that nothing is corrupted before
|
|
375
|
+
* the refusal (see that function's comment).
|
|
376
|
+
*
|
|
377
|
+
* Shared rather than spelled twice: the two were byte-identical copies, so
|
|
378
|
+
* rewording one would silently leave a user facing two different explanations
|
|
379
|
+
* of one invariant depending on which end caught them — and the test that
|
|
380
|
+
* matches the text would keep passing against the stale copy.
|
|
381
|
+
*/
|
|
382
|
+
const SECOND_ROLE_PUBLISH_REFUSAL = "An assumed role has already been published for this process. `--role-arn` is a single process-wide decision taken once at a command entry; publishing a second role would leave clients built before and after it running as different identities.";
|
|
383
|
+
/**
|
|
384
|
+
* Publish the credentials `--role-arn` assumed, so every later SDK client
|
|
385
|
+
* built through {@link awsClientDefaults} runs as the role.
|
|
386
|
+
*
|
|
387
|
+
* Called by `applyRoleArnIfSet` AFTER the `sts:AssumeRole` hop completes — the
|
|
388
|
+
* base credentials (a profile, the env, an instance role) must still be the
|
|
389
|
+
* ones answering that hop.
|
|
390
|
+
*
|
|
391
|
+
* **A SECOND publish is REFUSED.** {@link assumedRoleCredentials}'s doc calls
|
|
392
|
+
* the value monotonic, and every consumer relies on it: a client built before a
|
|
393
|
+
* second publish and one built after would run as DIFFERENT identities inside
|
|
394
|
+
* one process, with nothing naming which. `applyRoleArnIfSet` is meant to run
|
|
395
|
+
* once at a command's entry, so a second call is a wiring defect — and the
|
|
396
|
+
* failure it produces without this refusal is silent and account-scoped, which
|
|
397
|
+
* is the class issue [#3130](https://github.com/go-to-k/cdkd/issues/3130)
|
|
398
|
+
* exists to close. `resetAwsClientDefaults()` is the only way back, and it is a
|
|
399
|
+
* TEST seam. A plain `Error` rather than `CdkdError` for this module's usual
|
|
400
|
+
* reason: importing the error module would add a relative `.js` import to the
|
|
401
|
+
* closure `scripts/audit-provider-coverage.ts` resolves literally.
|
|
402
|
+
*
|
|
403
|
+
* The bag is COPIED rather than stored by reference. The same object is handed
|
|
404
|
+
* to every client `awsClientDefaults` serves, so aliasing the caller's would
|
|
405
|
+
* let whoever still holds it rewrite the identity of clients already built —
|
|
406
|
+
* the reason `AwsClients.credentialConfig` clones for its own siblings.
|
|
407
|
+
*/
|
|
408
|
+
function setAssumedRoleCredentials(credentials) {
|
|
409
|
+
if (assumedRoleCredentials !== void 0) throw new Error(SECOND_ROLE_PUBLISH_REFUSAL);
|
|
410
|
+
assumedRoleCredentials = {
|
|
411
|
+
accessKeyId: credentials.accessKeyId,
|
|
412
|
+
secretAccessKey: credentials.secretAccessKey,
|
|
413
|
+
sessionToken: credentials.sessionToken,
|
|
414
|
+
...credentials.expiration && { expiration: credentials.expiration }
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Record the caller's own pre-assume environment credentials, or `undefined`
|
|
419
|
+
* when the environment carried none.
|
|
420
|
+
*
|
|
421
|
+
* Called by `applyRoleArnIfSet` BEFORE it overwrites the `AWS_*` triple —
|
|
422
|
+
* afterwards the value is unrecoverable, and it is the only identity the
|
|
423
|
+
* `cdkd local` surface may hand to an emulated workload.
|
|
424
|
+
*
|
|
425
|
+
* COPIED rather than stored by reference, for {@link setAssumedRoleCredentials}'s
|
|
426
|
+
* reason and with more reach than it had before: since shape 2 of the
|
|
427
|
+
* `ignoreAssumedRole` opt-out reads this snapshot, it is the source of the
|
|
428
|
+
* identity EVERY opted-out client runs as, and {@link getPreAssumeEnvCredentials}
|
|
429
|
+
* hands it out. Aliasing the caller's object would let whoever still holds it
|
|
430
|
+
* rewrite that identity after the fact.
|
|
431
|
+
*/
|
|
432
|
+
function setPreAssumeEnvCredentials(credentials) {
|
|
433
|
+
preAssumeEnvCredentials = credentials ? {
|
|
434
|
+
accessKeyId: credentials.accessKeyId,
|
|
435
|
+
secretAccessKey: credentials.secretAccessKey,
|
|
436
|
+
...credentials.sessionToken !== void 0 && { sessionToken: credentials.sessionToken }
|
|
437
|
+
} : void 0;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* The credentials recorded by {@link setPreAssumeEnvCredentials}, if any.
|
|
441
|
+
*
|
|
442
|
+
* Read by `src/utils/caller-credentials.ts` — the local surface's env channel —
|
|
443
|
+
* and by tests. Read it THROUGH that module rather than directly: `undefined`
|
|
444
|
+
* here is ambiguous on its own (see {@link preAssumeEnvCredentials}).
|
|
445
|
+
*
|
|
446
|
+
* The returned bag is this module's OWN copy, not the caller's — do not mutate
|
|
447
|
+
* it; shape 2 of the opt-out resolves every opted-out client's identity from
|
|
448
|
+
* the same object.
|
|
449
|
+
*/
|
|
450
|
+
function getPreAssumeEnvCredentials() {
|
|
451
|
+
return preAssumeEnvCredentials;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* The credentials published by {@link setAssumedRoleCredentials}, if any.
|
|
455
|
+
*
|
|
456
|
+
* ONE production consumer, and it asks a yes/no question rather than using the
|
|
457
|
+
* value: `applyRoleArnIfSet` reads it to refuse a second `--role-arn` BEFORE
|
|
458
|
+
* its `AssumeRole` hop, so that nothing is corrupted first. No client ever
|
|
459
|
+
* reads it — every one of those receives the credentials through
|
|
460
|
+
* {@link awsClientDefaults} — so this is not a way to "get the role" and must
|
|
461
|
+
* not become one; a site that wants the role's identity simply omits
|
|
462
|
+
* `ignoreAssumedRole`.
|
|
463
|
+
*/
|
|
464
|
+
function getAssumedRoleCredentials() {
|
|
465
|
+
return assumedRoleCredentials;
|
|
466
|
+
}
|
|
467
|
+
function isProxyConfigured() {
|
|
468
|
+
if (proxyConfigured === void 0) {
|
|
469
|
+
let configured = false;
|
|
470
|
+
for (const name of PROXY_ENV_VARS) {
|
|
471
|
+
const value = process.env[name];
|
|
472
|
+
if (value === void 0 || value === "") continue;
|
|
473
|
+
if (value.trim() === "") throw new Error(`${name} is set to whitespace only. Set it to a proxy URL (e.g. http://proxy.example:8080) or unset it.`);
|
|
474
|
+
configured = true;
|
|
475
|
+
}
|
|
476
|
+
proxyConfigured = configured;
|
|
477
|
+
}
|
|
478
|
+
return proxyConfigured;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* The identity a client that passed `ignoreAssumedRole` must run as, or
|
|
482
|
+
* `undefined` when the SDK's own chain already resolves the caller and nothing
|
|
483
|
+
* needs injecting.
|
|
484
|
+
*
|
|
485
|
+
* Only called once a role HAS been published — see
|
|
486
|
+
* {@link AwsClientDefaultsOptions.ignoreAssumedRole} for the three shapes and
|
|
487
|
+
* why shape 3 cannot be left to the default chain.
|
|
488
|
+
*
|
|
489
|
+
* `process.env` is READ here and never written. Clearing the triple around the
|
|
490
|
+
* client construction would be the obvious alternative and is wrong: cdkd
|
|
491
|
+
* builds clients concurrently (`--concurrency`, parallel stacks), so a window
|
|
492
|
+
* in which the triple is absent is a window another task resolves through.
|
|
493
|
+
*
|
|
494
|
+
* TWO BOUNDS, RECORDED RATHER THAN CLOSED
|
|
495
|
+
*
|
|
496
|
+
* 1. **A `[default]` profile that sources the environment defeats shape 3.**
|
|
497
|
+
* Subtracting the chain's own env link does not subtract `fromEnv` reached
|
|
498
|
+
* THROUGH another link, and `credential_source = Environment` in the
|
|
499
|
+
* `[default]` profile is exactly that — so the opt-out resolves the ROLE's
|
|
500
|
+
* triple again. Its sibling is `credential_process`: the ini format has no
|
|
501
|
+
* environment interpolation, so a templated `aws_access_key_id` is not a
|
|
502
|
+
* vector, but a helper process that reads `AWS_ACCESS_KEY_ID` out of the
|
|
503
|
+
* environment it inherits and echoes it back defeats the subtraction
|
|
504
|
+
* identically. It is a narrow case and it is not a regression: before this
|
|
505
|
+
* shape existed the same configuration simply resolved the role too. Closing
|
|
506
|
+
* it would mean neutering `process.env` around the resolution, which the
|
|
507
|
+
* paragraph above rules out on concurrency grounds.
|
|
508
|
+
* 2. **Shapes 2 and 3 inject an explicit `credentials`, which outranks a
|
|
509
|
+
* `profile` key a site sets AFTER the spread.** Shape 1 exists to hand the
|
|
510
|
+
* profile back to the SDK, but it only sees a profile passed through
|
|
511
|
+
* {@link AwsClientDefaultsOptions.profile} or `AWS_PROFILE` — a site that
|
|
512
|
+
* passes neither and then writes `profile: x` into its own client literal
|
|
513
|
+
* gets shape 2/3's credentials instead, silently. No site does that today
|
|
514
|
+
* (all 19 opt-out sites audited at PR #3223), and no fence catches it: the
|
|
515
|
+
* local-surface fence asks whether a site DECLARED an identity, not which
|
|
516
|
+
* keys its literal carries. Pass the profile to this helper, not only to the
|
|
517
|
+
* client.
|
|
518
|
+
*/
|
|
519
|
+
function callerIdentityForOptOut(options, requestHandler) {
|
|
520
|
+
const profile = options.profile !== void 0 && options.profile !== "" ? options.profile : process.env["AWS_PROFILE"];
|
|
521
|
+
if (profile !== void 0 && profile !== "") return void 0;
|
|
522
|
+
const caller = preAssumeEnvCredentials;
|
|
523
|
+
if (caller) return {
|
|
524
|
+
accessKeyId: caller.accessKeyId,
|
|
525
|
+
secretAccessKey: caller.secretAccessKey,
|
|
526
|
+
...caller.sessionToken && { sessionToken: caller.sessionToken }
|
|
527
|
+
};
|
|
528
|
+
return defaultProvider({
|
|
529
|
+
profile: SDK_DEFAULT_PROFILE,
|
|
530
|
+
logger: CHAIN_LOGGER,
|
|
531
|
+
...requestHandler && { clientConfig: { requestHandler } }
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Returns `{}` when no proxy is configured, no role has been assumed, and no
|
|
536
|
+
* opt-out is in play — which keeps that path byte-identical to what the SDK
|
|
537
|
+
* builds on its own and makes this a no-op for every existing user.
|
|
538
|
+
*/
|
|
539
|
+
function awsClientDefaults(options = {}) {
|
|
540
|
+
const proxied = isProxyConfigured();
|
|
541
|
+
const optedOut = options.ignoreAssumedRole === true;
|
|
542
|
+
const assumed = optedOut ? void 0 : assumedRoleCredentials;
|
|
543
|
+
const optedOutOfPublishedRole = optedOut && assumedRoleCredentials !== void 0;
|
|
544
|
+
if (!proxied) {
|
|
545
|
+
if (assumed) return { credentials: assumed };
|
|
546
|
+
const caller = optedOutOfPublishedRole ? callerIdentityForOptOut(options, void 0) : void 0;
|
|
547
|
+
return caller ? { credentials: caller } : {};
|
|
548
|
+
}
|
|
549
|
+
const agent = new ProxyRoutingAgent();
|
|
550
|
+
const requestHandler = new NodeHttpHandler({
|
|
551
|
+
httpAgent: agent,
|
|
552
|
+
httpsAgent: agent
|
|
553
|
+
});
|
|
554
|
+
if (assumed) return {
|
|
555
|
+
requestHandler,
|
|
556
|
+
credentials: assumed
|
|
557
|
+
};
|
|
558
|
+
const caller = optedOutOfPublishedRole ? callerIdentityForOptOut(options, requestHandler) : void 0;
|
|
559
|
+
if (caller) return {
|
|
560
|
+
requestHandler,
|
|
561
|
+
credentials: caller
|
|
562
|
+
};
|
|
563
|
+
return {
|
|
564
|
+
requestHandler,
|
|
565
|
+
credentials: defaultProvider({
|
|
566
|
+
...options.profile !== void 0 && options.profile !== "" && { profile: options.profile },
|
|
567
|
+
clientConfig: { requestHandler }
|
|
568
|
+
})
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
//#endregion
|
|
573
|
+
export { setAssumedRoleCredentials as a, getPreAssumeEnvCredentials as i, awsClientDefaults as n, setPreAssumeEnvCredentials as o, getAssumedRoleCredentials as r, SECOND_ROLE_PUBLISH_REFUSAL as t };
|
|
574
|
+
//# sourceMappingURL=aws-client-defaults-BoIxyCir.js.map
|