cdk-local 0.162.0 → 0.163.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/README.md +1 -1
- package/dist/cli.js +2 -2
- package/dist/{docker-runner-BkrDB60P.js → docker-runner-DzPTb4Ai.js} +92 -20
- package/dist/{docker-runner-BkrDB60P.js.map → docker-runner-DzPTb4Ai.js.map} +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/internal.d.ts +68 -52
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +4 -4
- package/dist/{local-studio-BVVcAPV8.d.ts → local-studio-BsnTY1vH.d.ts} +54 -4
- package/dist/local-studio-BsnTY1vH.d.ts.map +1 -0
- package/dist/{local-studio-CRKQ1Uhu.js → local-studio-aUkwJL4M.js} +197 -75
- package/dist/local-studio-aUkwJL4M.js.map +1 -0
- package/dist/{workload-identity-ITM3p2Ed.js → workload-identity-D5zkhVcF.js} +708 -16
- package/dist/workload-identity-D5zkhVcF.js.map +1 -0
- package/package.json +1 -1
- package/dist/local-studio-BVVcAPV8.d.ts.map +0 -1
- package/dist/local-studio-CRKQ1Uhu.js.map +0 -1
- package/dist/workload-identity-ITM3p2Ed.js.map +0 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-w6R9maHv.js";
|
|
2
2
|
import { c as getEmbedConfig, t as getLogger } from "./logger-Co5XVwco.js";
|
|
3
3
|
import { r as displayUntrustedValue, u as describeCredentialLoadFailure } from "./assembly-path-R96uhMm1.js";
|
|
4
|
-
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { existsSync, lstatSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { createHash, generateKeyPairSync, randomBytes, sign } from "node:crypto";
|
|
5
6
|
import { Agent, get, request } from "node:http";
|
|
6
7
|
import { Agent as Agent$1, get as get$1, request as request$1 } from "node:https";
|
|
7
8
|
import { rootCertificates } from "node:tls";
|
|
@@ -157,6 +158,9 @@ function setAwsEndpointOverride(o) {
|
|
|
157
158
|
function getAwsEndpointOverride() {
|
|
158
159
|
return endpointOverride;
|
|
159
160
|
}
|
|
161
|
+
function awsEndpointOverrideClientConfig(o) {
|
|
162
|
+
return endpointOverrideClientConfig(o);
|
|
163
|
+
}
|
|
160
164
|
function endpointOverrideClientConfig(o) {
|
|
161
165
|
const httpsAgent = new Agent$1({
|
|
162
166
|
keepAlive: true,
|
|
@@ -346,6 +350,543 @@ async function proxyAwareFetch(url, opts = {}) {
|
|
|
346
350
|
}
|
|
347
351
|
}
|
|
348
352
|
|
|
353
|
+
//#endregion
|
|
354
|
+
//#region src/local/aws-emulator-mixed-mode.ts
|
|
355
|
+
const AWS_EMULATOR_SENTINEL = "aws-emulator";
|
|
356
|
+
const MIXED_MODE_RESIGN_PORT = 47297;
|
|
357
|
+
const ENDPOINT_KEY = /^AWS_ENDPOINT_URL(?:_([A-Z0-9_]+))?$/;
|
|
358
|
+
function isAwsEmulatorSentinel(key, value) {
|
|
359
|
+
return typeof value === "string" && value.trim() === "aws-emulator" && ENDPOINT_KEY.test(key);
|
|
360
|
+
}
|
|
361
|
+
function findAwsEmulatorSentinels(overrides) {
|
|
362
|
+
const found = [];
|
|
363
|
+
if (!overrides || typeof overrides !== "object") return found;
|
|
364
|
+
for (const [section, map] of Object.entries(overrides)) {
|
|
365
|
+
if (!map || typeof map !== "object" || Array.isArray(map)) continue;
|
|
366
|
+
for (const [key, value] of Object.entries(map)) {
|
|
367
|
+
if (!isAwsEmulatorSentinel(key, value)) continue;
|
|
368
|
+
found.push({
|
|
369
|
+
section,
|
|
370
|
+
key,
|
|
371
|
+
service: ENDPOINT_KEY.exec(key)[1] ?? ""
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return found;
|
|
376
|
+
}
|
|
377
|
+
function readAwsEmulatorSentinelsFromFile(filePath) {
|
|
378
|
+
if (!filePath) return [];
|
|
379
|
+
let parsed;
|
|
380
|
+
try {
|
|
381
|
+
parsed = JSON.parse(readFileSync(filePath, "utf8"));
|
|
382
|
+
} catch {
|
|
383
|
+
return [];
|
|
384
|
+
}
|
|
385
|
+
return findAwsEmulatorSentinels(parsed);
|
|
386
|
+
}
|
|
387
|
+
function validateAwsEmulatorSentinels(sentinels, awsEmulatorOn, envVarName = "CDK_LOCAL_AWS_EMULATOR") {
|
|
388
|
+
const first = sentinels[0];
|
|
389
|
+
if (!first) return;
|
|
390
|
+
const where = (s) => `${s.key} under ${displayUntrustedValue(s.section)}`;
|
|
391
|
+
const bare = sentinels.find((s) => s.service === "");
|
|
392
|
+
if (bare) throw new Error(`--env-vars sets ${where(bare)} to "${AWS_EMULATOR_SENTINEL}", but the sentinel routes ONE service (AWS_ENDPOINT_URL_<SERVICE>). To send every service to AWS Emulator, use --aws-emulator without a sentinel.`);
|
|
393
|
+
if (!awsEmulatorOn) throw new Error(`--env-vars sets ${where(first)} to "${AWS_EMULATOR_SENTINEL}", which needs --aws-emulator [url] (or ${envVarName}) to name the AWS Emulator to route it to.`);
|
|
394
|
+
}
|
|
395
|
+
function emulatedServicesOf(sentinels) {
|
|
396
|
+
return [...new Set(sentinels.map((s) => s.service).filter((s) => s !== ""))].sort();
|
|
397
|
+
}
|
|
398
|
+
const SERVICE_NAMESPACE_ALIASES = {
|
|
399
|
+
EVENTBRIDGE: ["EVENTS"],
|
|
400
|
+
SFN: ["STEPFUNCTIONS"],
|
|
401
|
+
CLOUDWATCHLOGS: ["LOGS"],
|
|
402
|
+
COGNITOIDENTITYPROVIDER: ["COGNITO"],
|
|
403
|
+
COGNITOIDENTITY: ["COGNITO"],
|
|
404
|
+
FIREHOSE: ["KINESISFIREHOSE"],
|
|
405
|
+
OPENSEARCH: ["OPENSEARCHSERVICE"],
|
|
406
|
+
ELASTICSEARCHSERVICE: ["ELASTICSEARCH"]
|
|
407
|
+
};
|
|
408
|
+
function resourceTypeMatchesEmulatedService(resourceType, services) {
|
|
409
|
+
const ns = /^AWS::([A-Za-z0-9]+)::/.exec(resourceType)?.[1]?.toUpperCase();
|
|
410
|
+
if (!ns) return false;
|
|
411
|
+
return services.some((s) => {
|
|
412
|
+
const id = s.replace(/_/g, "").toUpperCase();
|
|
413
|
+
return id === ns || (SERVICE_NAMESPACE_ALIASES[id] ?? []).includes(ns);
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
function createMixedModeStateProvider(opts) {
|
|
417
|
+
const { real, emulator, emulatedServices } = opts;
|
|
418
|
+
const swaps = /* @__PURE__ */ new Map();
|
|
419
|
+
const warned = /* @__PURE__ */ new Set();
|
|
420
|
+
const swap = (v) => swaps.get(v) ?? v;
|
|
421
|
+
const provider = {
|
|
422
|
+
label: real.label,
|
|
423
|
+
async load(stackName, synthRegion) {
|
|
424
|
+
const [r, e] = await Promise.all([real.load(stackName, synthRegion), emulator.load(stackName, synthRegion)]);
|
|
425
|
+
if (!e) {
|
|
426
|
+
if (r && !warned.has(stackName)) {
|
|
427
|
+
if (Object.values(r.resources).some((res) => resourceTypeMatchesEmulatedService(res.resourceType, emulatedServices))) {
|
|
428
|
+
warned.add(stackName);
|
|
429
|
+
const why = emulator.getLastLoadError?.();
|
|
430
|
+
getLogger().warn(`${real.label}: stack ${displayUntrustedValue(stackName)} could not be read from AWS Emulator${why ? ` (${why})` : ""}; resources of the emulated services (${emulatedServices.join(", ")}) keep their real AWS names. Override them with --env-vars, or deploy the stack into the emulator.`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return r;
|
|
434
|
+
}
|
|
435
|
+
const emulated = (res) => resourceTypeMatchesEmulatedService(res.resourceType, emulatedServices);
|
|
436
|
+
if (!r) {
|
|
437
|
+
const resources = {};
|
|
438
|
+
for (const [id, res] of Object.entries(e.resources)) if (emulated(res)) resources[id] = res;
|
|
439
|
+
return {
|
|
440
|
+
resources,
|
|
441
|
+
outputs: { ...e.outputs },
|
|
442
|
+
region: e.region
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
const resources = { ...r.resources };
|
|
446
|
+
for (const [id, res] of Object.entries(e.resources)) {
|
|
447
|
+
if (!emulated(res)) continue;
|
|
448
|
+
const realRes = r.resources[id];
|
|
449
|
+
if (realRes?.physicalId && res.physicalId && realRes.physicalId !== res.physicalId) swaps.set(realRes.physicalId, res.physicalId);
|
|
450
|
+
resources[id] = res;
|
|
451
|
+
}
|
|
452
|
+
const outputs = {};
|
|
453
|
+
for (const [k, v] of Object.entries(e.outputs)) outputs[k] = v;
|
|
454
|
+
for (const [k, v] of Object.entries(r.outputs)) outputs[k] = swap(v);
|
|
455
|
+
return {
|
|
456
|
+
resources,
|
|
457
|
+
outputs,
|
|
458
|
+
region: r.region
|
|
459
|
+
};
|
|
460
|
+
},
|
|
461
|
+
buildCrossStackResolver: (region) => real.buildCrossStackResolver(region),
|
|
462
|
+
dispose() {
|
|
463
|
+
try {
|
|
464
|
+
real.dispose();
|
|
465
|
+
} finally {
|
|
466
|
+
emulator.dispose();
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
if (real.resolveDeployedFunctionEnv) {
|
|
471
|
+
const fn = real.resolveDeployedFunctionEnv.bind(real);
|
|
472
|
+
provider.resolveDeployedFunctionEnv = async (id) => {
|
|
473
|
+
const env = await fn(id);
|
|
474
|
+
if (!env) return env;
|
|
475
|
+
const out = {};
|
|
476
|
+
for (const [k, v] of Object.entries(env)) out[k] = swap(v);
|
|
477
|
+
return out;
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
if (real.resolveLambdaExecutionRoleArn) provider.resolveLambdaExecutionRoleArn = real.resolveLambdaExecutionRoleArn.bind(real);
|
|
481
|
+
if (real.resolveAgentCoreRuntimeRoleArn) provider.resolveAgentCoreRuntimeRoleArn = real.resolveAgentCoreRuntimeRoleArn.bind(real);
|
|
482
|
+
if (real.resolveTemplateSsmParameters) provider.resolveTemplateSsmParameters = real.resolveTemplateSsmParameters.bind(real);
|
|
483
|
+
if (real.getLastLoadError) provider.getLastLoadError = real.getLastLoadError.bind(real);
|
|
484
|
+
return provider;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
//#endregion
|
|
488
|
+
//#region src/local/mixed-mode-tls.ts
|
|
489
|
+
function der(tag, content) {
|
|
490
|
+
const n = content.length;
|
|
491
|
+
let len;
|
|
492
|
+
if (n < 128) len = Buffer.from([n]);
|
|
493
|
+
else {
|
|
494
|
+
const bytes = [];
|
|
495
|
+
for (let v = n; v > 0; v = Math.floor(v / 256)) bytes.unshift(v & 255);
|
|
496
|
+
len = Buffer.from([128 | bytes.length, ...bytes]);
|
|
497
|
+
}
|
|
498
|
+
return Buffer.concat([
|
|
499
|
+
Buffer.from([tag]),
|
|
500
|
+
len,
|
|
501
|
+
content
|
|
502
|
+
]);
|
|
503
|
+
}
|
|
504
|
+
const seq = (...parts) => der(48, Buffer.concat(parts));
|
|
505
|
+
const set = (...parts) => der(49, Buffer.concat(parts));
|
|
506
|
+
const octets = (b) => der(4, b);
|
|
507
|
+
const bits = (b, unused = 0) => der(3, Buffer.concat([Buffer.from([unused]), b]));
|
|
508
|
+
const bool = (v) => der(1, Buffer.from([v ? 255 : 0]));
|
|
509
|
+
const utf8 = (s) => der(12, Buffer.from(s, "utf8"));
|
|
510
|
+
const explicit = (n, inner) => der(160 + n, inner);
|
|
511
|
+
function integer(b) {
|
|
512
|
+
let v = b;
|
|
513
|
+
while (v.length > 1 && v[0] === 0 && (v[1] & 128) === 0) v = v.subarray(1);
|
|
514
|
+
if ((v[0] & 128) !== 0) v = Buffer.concat([Buffer.from([0]), v]);
|
|
515
|
+
return der(2, v);
|
|
516
|
+
}
|
|
517
|
+
function oid(dotted) {
|
|
518
|
+
const parts = dotted.split(".").map(Number);
|
|
519
|
+
const out = [40 * parts[0] + parts[1]];
|
|
520
|
+
for (const p of parts.slice(2)) {
|
|
521
|
+
const stack = [p & 127];
|
|
522
|
+
for (let v = Math.floor(p / 128); v > 0; v = Math.floor(v / 128)) stack.unshift(v & 127 | 128);
|
|
523
|
+
out.push(...stack);
|
|
524
|
+
}
|
|
525
|
+
return der(6, Buffer.from(out));
|
|
526
|
+
}
|
|
527
|
+
function utcTime(d) {
|
|
528
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
529
|
+
const s = p(d.getUTCFullYear() % 100) + p(d.getUTCMonth() + 1) + p(d.getUTCDate()) + p(d.getUTCHours()) + p(d.getUTCMinutes()) + p(d.getUTCSeconds()) + "Z";
|
|
530
|
+
return der(23, Buffer.from(s, "ascii"));
|
|
531
|
+
}
|
|
532
|
+
const OID_ECDSA_SHA256 = "1.2.840.10045.4.3.2";
|
|
533
|
+
const OID_CN = "2.5.4.3";
|
|
534
|
+
const OID_SKI = "2.5.29.14";
|
|
535
|
+
const OID_KEY_USAGE = "2.5.29.15";
|
|
536
|
+
const OID_SAN = "2.5.29.17";
|
|
537
|
+
const OID_BASIC_CONSTRAINTS = "2.5.29.19";
|
|
538
|
+
const OID_AKI = "2.5.29.35";
|
|
539
|
+
const OID_EKU = "2.5.29.37";
|
|
540
|
+
const OID_NAME_CONSTRAINTS = "2.5.29.30";
|
|
541
|
+
const OID_SERVER_AUTH = "1.3.6.1.5.5.7.3.1";
|
|
542
|
+
const name = (cn) => seq(set(seq(oid(OID_CN), utf8(cn))));
|
|
543
|
+
function extension(id, critical, value) {
|
|
544
|
+
return seq(oid(id), ...critical ? [bool(true)] : [], octets(value));
|
|
545
|
+
}
|
|
546
|
+
function keyIdentifier(spkiDer) {
|
|
547
|
+
const point = spkiDer.subarray(spkiDer.length - 65);
|
|
548
|
+
return createHash("sha1").update(point).digest();
|
|
549
|
+
}
|
|
550
|
+
function serialNumber() {
|
|
551
|
+
const b = randomBytes(16);
|
|
552
|
+
b[0] = b[0] & 127 | 1;
|
|
553
|
+
return b;
|
|
554
|
+
}
|
|
555
|
+
function issue(spec) {
|
|
556
|
+
const sigAlg = seq(oid(OID_ECDSA_SHA256));
|
|
557
|
+
const spki = spec.publicKey.export({
|
|
558
|
+
type: "spki",
|
|
559
|
+
format: "der"
|
|
560
|
+
});
|
|
561
|
+
const tbs = seq(explicit(0, integer(Buffer.from([2]))), integer(serialNumber()), sigAlg, name(spec.issuer), seq(utcTime(spec.notBefore), utcTime(spec.notAfter)), name(spec.subject), spki, explicit(3, seq(...spec.extensions)));
|
|
562
|
+
const signature = sign("sha256", tbs, spec.signingKey);
|
|
563
|
+
return `-----BEGIN CERTIFICATE-----\n${seq(tbs, sigAlg, bits(signature)).toString("base64").replace(/(.{64})/g, "$1\n").replace(/\n$/, "")}\n-----END CERTIFICATE-----\n`;
|
|
564
|
+
}
|
|
565
|
+
function createMixedModeTlsMaterials(now = /* @__PURE__ */ new Date(), leafDnsNames = ["localhost"]) {
|
|
566
|
+
const notBefore = /* @__PURE__ */ new Date(now.getTime() - 3600 * 1e3);
|
|
567
|
+
const notAfter = new Date(now.getTime() + 720 * 60 * 60 * 1e3);
|
|
568
|
+
const ca = generateKeyPairSync("ec", { namedCurve: "prime256v1" });
|
|
569
|
+
const leaf = generateKeyPairSync("ec", { namedCurve: "prime256v1" });
|
|
570
|
+
const caKeyId = keyIdentifier(ca.publicKey.export({
|
|
571
|
+
type: "spki",
|
|
572
|
+
format: "der"
|
|
573
|
+
}));
|
|
574
|
+
const leafKeyId = keyIdentifier(leaf.publicKey.export({
|
|
575
|
+
type: "spki",
|
|
576
|
+
format: "der"
|
|
577
|
+
}));
|
|
578
|
+
const aki = (id) => seq(der(128, id));
|
|
579
|
+
const caCn = `cdk-local mixed-mode CA ${randomBytes(4).toString("hex")}`;
|
|
580
|
+
const caCertPem = issue({
|
|
581
|
+
subject: caCn,
|
|
582
|
+
issuer: caCn,
|
|
583
|
+
publicKey: ca.publicKey,
|
|
584
|
+
signingKey: ca.privateKey,
|
|
585
|
+
notBefore,
|
|
586
|
+
notAfter,
|
|
587
|
+
extensions: [
|
|
588
|
+
extension(OID_BASIC_CONSTRAINTS, true, seq(bool(true), integer(Buffer.from([0])))),
|
|
589
|
+
extension(OID_KEY_USAGE, true, bits(Buffer.from([6]), 1)),
|
|
590
|
+
extension(OID_SKI, false, octets(caKeyId)),
|
|
591
|
+
extension(OID_AKI, false, aki(caKeyId)),
|
|
592
|
+
extension(OID_NAME_CONSTRAINTS, true, seq(der(160, Buffer.concat([
|
|
593
|
+
seq(der(135, Buffer.from([
|
|
594
|
+
127,
|
|
595
|
+
0,
|
|
596
|
+
0,
|
|
597
|
+
1,
|
|
598
|
+
255,
|
|
599
|
+
255,
|
|
600
|
+
255,
|
|
601
|
+
255
|
|
602
|
+
]))),
|
|
603
|
+
seq(der(135, Buffer.from([
|
|
604
|
+
...new Array(15).fill(0),
|
|
605
|
+
1,
|
|
606
|
+
...new Array(16).fill(255)
|
|
607
|
+
]))),
|
|
608
|
+
seq(der(130, Buffer.from("localhost", "ascii")))
|
|
609
|
+
]))))
|
|
610
|
+
]
|
|
611
|
+
});
|
|
612
|
+
const san = seq(der(135, Buffer.from([
|
|
613
|
+
127,
|
|
614
|
+
0,
|
|
615
|
+
0,
|
|
616
|
+
1
|
|
617
|
+
])), der(135, Buffer.from([
|
|
618
|
+
0,
|
|
619
|
+
0,
|
|
620
|
+
0,
|
|
621
|
+
0,
|
|
622
|
+
0,
|
|
623
|
+
0,
|
|
624
|
+
0,
|
|
625
|
+
0,
|
|
626
|
+
0,
|
|
627
|
+
0,
|
|
628
|
+
0,
|
|
629
|
+
0,
|
|
630
|
+
0,
|
|
631
|
+
0,
|
|
632
|
+
0,
|
|
633
|
+
1
|
|
634
|
+
])), ...leafDnsNames.map((n) => der(130, Buffer.from(n, "ascii"))));
|
|
635
|
+
return {
|
|
636
|
+
caCertPem,
|
|
637
|
+
certPem: issue({
|
|
638
|
+
subject: "127.0.0.1",
|
|
639
|
+
issuer: caCn,
|
|
640
|
+
publicKey: leaf.publicKey,
|
|
641
|
+
signingKey: ca.privateKey,
|
|
642
|
+
notBefore,
|
|
643
|
+
notAfter,
|
|
644
|
+
extensions: [
|
|
645
|
+
extension(OID_BASIC_CONSTRAINTS, true, seq()),
|
|
646
|
+
extension(OID_KEY_USAGE, true, bits(Buffer.from([128]), 7)),
|
|
647
|
+
extension(OID_EKU, false, seq(oid(OID_SERVER_AUTH))),
|
|
648
|
+
extension(OID_SAN, false, san),
|
|
649
|
+
extension(OID_SKI, false, octets(leafKeyId)),
|
|
650
|
+
extension(OID_AKI, false, aki(caKeyId))
|
|
651
|
+
]
|
|
652
|
+
}),
|
|
653
|
+
keyPem: leaf.privateKey.export({
|
|
654
|
+
type: "pkcs8",
|
|
655
|
+
format: "pem"
|
|
656
|
+
}).toString()
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
//#endregion
|
|
661
|
+
//#region src/local/emulator-resign-sidecar.ts
|
|
662
|
+
const MIXED_MODE_RESIGN_BODY_CAP_BYTES = 100 * 1024 * 1024;
|
|
663
|
+
const RESIGN_SIDECAR_CORE = String.raw`
|
|
664
|
+
var crypto=require('crypto');
|
|
665
|
+
function sha256hex(b){return crypto.createHash('sha256').update(b).digest('hex');}
|
|
666
|
+
function hmac(k,s){return crypto.createHmac('sha256',k).update(s,'utf8').digest();}
|
|
667
|
+
function uriEncode(s){return encodeURIComponent(s).replace(/[!'()*]/g,function(c){return '%'+c.charCodeAt(0).toString(16).toUpperCase();});}
|
|
668
|
+
function safeDecode(s){try{return decodeURIComponent(s);}catch(e){return s;}}
|
|
669
|
+
function canonicalPath(rawPath,service){
|
|
670
|
+
var p=rawPath||'/';
|
|
671
|
+
if(service==='s3')return p;
|
|
672
|
+
var segs=[];
|
|
673
|
+
p.split('/').forEach(function(s){if(s===''||s==='.')return;if(s==='..')segs.pop();else segs.push(s);});
|
|
674
|
+
var n=(p.charAt(0)==='/'?'/':'')+segs.join('/')+(segs.length>0&&p.charAt(p.length-1)==='/'?'/':'');
|
|
675
|
+
return uriEncode(n).replace(/%2F/g,'/');
|
|
676
|
+
}
|
|
677
|
+
function splitQuery(q){
|
|
678
|
+
if(!q)return [];
|
|
679
|
+
return q.split('&').filter(function(x){return x!=='';}).map(function(part){
|
|
680
|
+
var i=part.indexOf('=');
|
|
681
|
+
return {raw:part,k:safeDecode(i<0?part:part.slice(0,i)),v:i<0?'':safeDecode(part.slice(i+1))};
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
function canonicalQuery(pairs){
|
|
685
|
+
return pairs.map(function(p){return [uriEncode(p.k),uriEncode(p.v)];})
|
|
686
|
+
.sort(function(a,b){return a[0]<b[0]?-1:a[0]>b[0]?1:a[1]<b[1]?-1:a[1]>b[1]?1:0;})
|
|
687
|
+
.map(function(e){return e[0]+'='+e[1];}).join('&');
|
|
688
|
+
}
|
|
689
|
+
var PRESIGN_PARAMS=new Set(['x-amz-algorithm','x-amz-credential','x-amz-date','x-amz-expires','x-amz-signedheaders','x-amz-signature','x-amz-security-token']);
|
|
690
|
+
function parseCredentialScope(algo,credential){
|
|
691
|
+
var parts=String(credential).split('/');
|
|
692
|
+
if(algo==='AWS4-HMAC-SHA256'&&parts.length>=5)return {region:parts[parts.length-3],service:parts[parts.length-2]};
|
|
693
|
+
if(algo==='AWS4-ECDSA-P256-SHA256'&&parts.length>=4)return {service:parts[parts.length-2]};
|
|
694
|
+
return undefined;
|
|
695
|
+
}
|
|
696
|
+
function parseAuthScope(authorization){
|
|
697
|
+
var m=/^\s*(AWS4-HMAC-SHA256|AWS4-ECDSA-P256-SHA256)\s+(.*)$/.exec(String(authorization));
|
|
698
|
+
if(!m)return undefined;
|
|
699
|
+
var c=/Credential=([^,\s]+)/.exec(m[2]);
|
|
700
|
+
return c?parseCredentialScope(m[1],c[1]):undefined;
|
|
701
|
+
}
|
|
702
|
+
var DROP_HEADERS=new Set(['authorization','host','connection','keep-alive','proxy-authorization','proxy-connection','te','trailer','transfer-encoding','upgrade','expect','content-length','x-amz-date','x-amz-security-token','x-amz-content-sha256','x-amz-decoded-content-length','x-amz-trailer']);
|
|
703
|
+
function upstreamHeaders(incoming){
|
|
704
|
+
var out=Object.create(null);
|
|
705
|
+
Object.keys(incoming).forEach(function(k){var lk=k.toLowerCase();if(DROP_HEADERS.has(lk))return;var v=incoming[k];if(v===undefined)return;out[lk]=Array.isArray(v)?v.join(', '):String(v);});
|
|
706
|
+
return out;
|
|
707
|
+
}
|
|
708
|
+
function isAwsChunked(headers){
|
|
709
|
+
var sha=String(headers['x-amz-content-sha256']||'');
|
|
710
|
+
return sha.indexOf('STREAMING-')===0||/(^|,)\s*aws-chunked\s*(,|$)/i.test(String(headers['content-encoding']||''));
|
|
711
|
+
}
|
|
712
|
+
function stripAwsChunked(ce){
|
|
713
|
+
if(!ce)return undefined;
|
|
714
|
+
var rest=String(ce).split(',').map(function(s){return s.trim();}).filter(function(s){return s!==''&&s.toLowerCase()!=='aws-chunked';});
|
|
715
|
+
return rest.length?rest.join(', '):undefined;
|
|
716
|
+
}
|
|
717
|
+
function decodeAwsChunked(buf){
|
|
718
|
+
var pos=0,parts=[],trailers=Object.create(null);
|
|
719
|
+
for(;;){
|
|
720
|
+
var eol=buf.indexOf('\r\n',pos);
|
|
721
|
+
if(eol<0)throw new Error('aws-chunked body: missing chunk header');
|
|
722
|
+
var size=parseInt(buf.toString('latin1',pos,eol).split(';')[0].trim(),16);
|
|
723
|
+
if(!(size>=0))throw new Error('aws-chunked body: bad chunk size');
|
|
724
|
+
pos=eol+2;
|
|
725
|
+
if(size===0)break;
|
|
726
|
+
if(pos+size+2>buf.length)throw new Error('aws-chunked body: truncated chunk');
|
|
727
|
+
parts.push(buf.subarray(pos,pos+size));
|
|
728
|
+
pos+=size;
|
|
729
|
+
if(buf[pos]!==13||buf[pos+1]!==10)throw new Error('aws-chunked body: missing chunk terminator');
|
|
730
|
+
pos+=2;
|
|
731
|
+
}
|
|
732
|
+
while(pos<buf.length){
|
|
733
|
+
var e=buf.indexOf('\r\n',pos),end=e<0?buf.length:e;
|
|
734
|
+
var line=buf.toString('latin1',pos,end);
|
|
735
|
+
pos=end+2;
|
|
736
|
+
if(line==='')break;
|
|
737
|
+
var c=line.indexOf(':');
|
|
738
|
+
if(c<=0)continue;
|
|
739
|
+
var k=line.slice(0,c).trim().toLowerCase();
|
|
740
|
+
if(k==='x-amz-trailer-signature')continue;
|
|
741
|
+
var v=line.slice(c+1).trim();
|
|
742
|
+
if(!/^[!#$%&'*+.^_|~0-9a-z-]+$/.test(k)||/[^ -~]/.test(v))throw new Error('aws-chunked body: invalid trailer');
|
|
743
|
+
trailers[k]=v;
|
|
744
|
+
}
|
|
745
|
+
return {body:Buffer.concat(parts),trailers:trailers};
|
|
746
|
+
}
|
|
747
|
+
function tooLargeByHeaders(headers,cap){
|
|
748
|
+
var decoded=Number(headers['x-amz-decoded-content-length']);
|
|
749
|
+
if(decoded>cap)return decoded;
|
|
750
|
+
var raw=Number(headers['content-length']);
|
|
751
|
+
if(raw>rawCap(cap))return raw;
|
|
752
|
+
return 0;
|
|
753
|
+
}
|
|
754
|
+
function rawCap(cap){return cap+Math.max(1048576,Math.floor(cap/100));}
|
|
755
|
+
function signRequest(o){
|
|
756
|
+
var amzDate=o.now.toISOString().replace(/[:-]|\.\d{3}/g,''),date=amzDate.slice(0,8),h=o.headers;
|
|
757
|
+
h['x-amz-date']=amzDate;
|
|
758
|
+
h['x-amz-content-sha256']=sha256hex(o.body);
|
|
759
|
+
if(o.creds.sessionToken)h['x-amz-security-token']=o.creds.sessionToken;
|
|
760
|
+
var names=Object.keys(h).filter(function(n){return n==='host'||n==='content-type'||n==='content-md5'||n.indexOf('x-amz-')===0;}).sort();
|
|
761
|
+
var canonHeaders=names.map(function(n){return n+':'+String(h[n]).trim().replace(/\s+/g,' ')+'\n';}).join('');
|
|
762
|
+
var signed=names.join(';');
|
|
763
|
+
var creq=[o.method,canonicalPath(o.path,o.service),canonicalQuery(splitQuery(o.query)),canonHeaders,signed,h['x-amz-content-sha256']].join('\n');
|
|
764
|
+
var scope=date+'/'+o.region+'/'+o.service+'/aws4_request';
|
|
765
|
+
var sts=['AWS4-HMAC-SHA256',amzDate,scope,sha256hex(creq)].join('\n');
|
|
766
|
+
var k=hmac(hmac(hmac(hmac('AWS4'+o.creds.secretAccessKey,date),o.region),o.service),'aws4_request');
|
|
767
|
+
var sig=crypto.createHmac('sha256',k).update(sts,'utf8').digest('hex');
|
|
768
|
+
h['authorization']='AWS4-HMAC-SHA256 Credential='+o.creds.accessKeyId+'/'+scope+', SignedHeaders='+signed+', Signature='+sig;
|
|
769
|
+
return h;
|
|
770
|
+
}
|
|
771
|
+
function prepareResign(req,body,cfg,now){
|
|
772
|
+
var url=String(req.url||'/'),qi=url.indexOf('?');
|
|
773
|
+
var path=qi<0?url:url.slice(0,qi),query=qi<0?'':url.slice(qi+1);
|
|
774
|
+
var incoming=req.headers,scope,signedRequest=false;
|
|
775
|
+
var pairs=splitQuery(query),cred,algo;
|
|
776
|
+
pairs.forEach(function(p){var lk=p.k.toLowerCase();if(lk==='x-amz-credential')cred=p.v;if(lk==='x-amz-algorithm')algo=p.v;});
|
|
777
|
+
// Presign parameters (a real token or signature among them) never go upstream,
|
|
778
|
+
// whether or not the request ALSO carries a header signature.
|
|
779
|
+
query=pairs.filter(function(p){return !PRESIGN_PARAMS.has(p.k.toLowerCase());}).map(function(p){return p.raw;}).join('&');
|
|
780
|
+
if(incoming['authorization']!==undefined){
|
|
781
|
+
signedRequest=true;
|
|
782
|
+
scope=parseAuthScope(incoming['authorization']);
|
|
783
|
+
}else if(cred!==undefined){
|
|
784
|
+
signedRequest=true;
|
|
785
|
+
scope=parseCredentialScope(algo||'AWS4-HMAC-SHA256',cred);
|
|
786
|
+
}
|
|
787
|
+
if(signedRequest&&!scope)return {error:{status:400,code:'IncompleteSignature',message:'cdk-local mixed mode: the request signature has no credential scope the AWS Emulator re-signing sidecar can read.'}};
|
|
788
|
+
var service=scope?scope.service:undefined;
|
|
789
|
+
var headers=upstreamHeaders(incoming);
|
|
790
|
+
if(isAwsChunked(incoming)){
|
|
791
|
+
var decoded;
|
|
792
|
+
try{decoded=decodeAwsChunked(body);}catch(e){return {error:{status:400,code:'IncompleteBody',message:'cdk-local mixed mode: '+e.message+'.'}};}
|
|
793
|
+
body=decoded.body;
|
|
794
|
+
Object.keys(decoded.trailers).forEach(function(k){if(!DROP_HEADERS.has(k))headers[k]=decoded.trailers[k];});
|
|
795
|
+
var ce=stripAwsChunked(headers['content-encoding']);
|
|
796
|
+
if(ce===undefined)delete headers['content-encoding'];else headers['content-encoding']=ce;
|
|
797
|
+
}
|
|
798
|
+
if(body.length>cfg.capBytes)return {error:tooLargeError(body.length,cfg.capBytes,service)};
|
|
799
|
+
headers['host']=cfg.upstreamHost;
|
|
800
|
+
headers['content-length']=String(body.length);
|
|
801
|
+
if(signedRequest){
|
|
802
|
+
var regionSet=incoming['x-amz-region-set'];
|
|
803
|
+
pairs.forEach(function(p){if(p.k.toLowerCase()==='x-amz-region-set'&®ionSet===undefined)regionSet=p.v;});
|
|
804
|
+
var region=scope.region||String(regionSet||'').split(',')[0].trim()||cfg.region||'us-east-1';
|
|
805
|
+
if(region==='*')region=cfg.region||'us-east-1';
|
|
806
|
+
signRequest({method:String(req.method||'GET'),path:path,query:query,headers:headers,body:body,region:region,service:service,creds:cfg.credentials,now:now});
|
|
807
|
+
}
|
|
808
|
+
return {path:query?path+'?'+query:path,headers:headers,body:body,service:service};
|
|
809
|
+
}
|
|
810
|
+
function tooLargeError(size,cap,service){
|
|
811
|
+
return {status:413,code:service==='s3'?'EntityTooLarge':'RequestEntityTooLargeException',message:'cdk-local mixed mode: the request body ('+size+' bytes) is over the '+cap+'-byte cap of the AWS Emulator re-signing sidecar, which holds a body in memory to sign it. Upload a large S3 object with multipart upload (each part under the cap), e.g. boto3 upload_file or @aws-sdk/lib-storage.'};
|
|
812
|
+
}
|
|
813
|
+
function errorBody(err,service){
|
|
814
|
+
if(service==='s3'||service===undefined)return {type:'application/xml',text:'<?xml version="1.0" encoding="UTF-8"?><Error><Code>'+err.code+'</Code><Message>'+err.message.replace(/&/g,'&').replace(/</g,'<')+'</Message></Error>'};
|
|
815
|
+
return {type:'application/x-amz-json-1.0',text:JSON.stringify({__type:err.code,message:err.message})};
|
|
816
|
+
}
|
|
817
|
+
`;
|
|
818
|
+
const RESIGN_SIDECAR_SERVER = String.raw`
|
|
819
|
+
function startResigner(cfgPath){return new Promise(function(done){
|
|
820
|
+
var fs=require('fs'),https=require('https'),http=require('http'),tls=require('tls');
|
|
821
|
+
var cfg;
|
|
822
|
+
try{cfg=JSON.parse(fs.readFileSync(cfgPath,'utf8'));}catch(e){done({error:'CONFIG_'+(e.code||'ERROR')});return;}
|
|
823
|
+
var up=new URL(cfg.upstream),secure=up.protocol==='https:',mod=secure?https:http;
|
|
824
|
+
cfg.upstreamHost=up.host;
|
|
825
|
+
var agentOpts={keepAlive:true};
|
|
826
|
+
if(secure&&cfg.upstreamCaPem)agentOpts.ca=tls.rootCertificates.concat([cfg.upstreamCaPem]);
|
|
827
|
+
var agent=new mod.Agent(agentOpts);
|
|
828
|
+
function send(resp,status,err,service){
|
|
829
|
+
if(resp.headersSent){resp.destroy();return;}
|
|
830
|
+
var b=errorBody(err,service);
|
|
831
|
+
resp.writeHead(status,{'content-type':b.type,'content-length':Buffer.byteLength(b.text),'connection':'close'});
|
|
832
|
+
resp.end(b.text);
|
|
833
|
+
}
|
|
834
|
+
function serviceOf(req){
|
|
835
|
+
var s=parseAuthScope(req.headers['authorization']||'');
|
|
836
|
+
if(!s){var m=/[?&]X-Amz-Credential=([^&]*)/i.exec(String(req.url||''));if(m)s=parseCredentialScope('AWS4-HMAC-SHA256',safeDecode(m[1]));}
|
|
837
|
+
return s?s.service:undefined;
|
|
838
|
+
}
|
|
839
|
+
// Bodies held at once, across concurrent requests: twice the per-request cap.
|
|
840
|
+
// Each held byte is charged twice: building the request body briefly keeps
|
|
841
|
+
// two copies alive (the received chunks while they are joined, then the
|
|
842
|
+
// joined body while an aws-chunked body is decoded), so the charged total
|
|
843
|
+
// bounds the real peak by inflightCap.
|
|
844
|
+
var inflight=0,inflightCap=2*rawCap(cfg.capBytes);
|
|
845
|
+
function handle(req,resp){
|
|
846
|
+
// held counts toward inflight until the response is over; seen is the
|
|
847
|
+
// body size for the 413 message.
|
|
848
|
+
var chunks=[],held=0,seen=0,over=false,busy=false,limit=rawCap(cfg.capBytes);
|
|
849
|
+
var drop=function(){inflight-=2*held;held=0;chunks=[];};
|
|
850
|
+
req.on('data',function(c){seen+=c.length;if(over||busy)return;if(seen>limit){over=true;drop();return;}if(inflight+2*c.length>inflightCap){busy=true;drop();return;}held+=c.length;inflight+=2*c.length;chunks.push(c);});
|
|
851
|
+
req.on('error',function(){drop();resp.destroy();});
|
|
852
|
+
resp.on('close',drop);
|
|
853
|
+
req.on('end',function(){
|
|
854
|
+
if(busy){send(resp,503,{code:'SlowDown',message:'cdk-local mixed mode: the AWS Emulator re-signing sidecar is holding too many request bodies at once; retry.'},serviceOf(req));return;}
|
|
855
|
+
if(over){process.stdout.write('CDKL-RESIGN rejected a streamed request body over the cap without holding it\n');send(resp,413,tooLargeError(seen,cfg.capBytes,serviceOf(req)),serviceOf(req));return;}
|
|
856
|
+
var p;
|
|
857
|
+
var joined=Buffer.concat(chunks);chunks=[];
|
|
858
|
+
try{p=prepareResign(req,joined,cfg,new Date());}catch(e){p={error:{status:400,code:'InvalidRequest',message:'cdk-local mixed mode: the request could not be re-signed.'}};}
|
|
859
|
+
joined=null;
|
|
860
|
+
if(p.error){if(p.error.status===413)process.stdout.write('CDKL-RESIGN rejected a request body over the cap\n');send(resp,p.error.status,p.error,serviceOf(req));return;}
|
|
861
|
+
var r;
|
|
862
|
+
try{r=mod.request({host:up.hostname.replace(/^\[|\]$/g,''),port:up.port||(secure?443:80),method:req.method,path:p.path,headers:p.headers,agent:agent},function(ur){
|
|
863
|
+
var h={};
|
|
864
|
+
Object.keys(ur.headers).forEach(function(k){if(k==='connection'||k==='keep-alive'||k==='transfer-encoding')return;h[k]=ur.headers[k];});
|
|
865
|
+
resp.writeHead(ur.statusCode||502,h);
|
|
866
|
+
ur.pipe(resp);
|
|
867
|
+
ur.on('error',function(){resp.destroy();});
|
|
868
|
+
});}catch(e){send(resp,400,{code:'InvalidRequest',message:'cdk-local mixed mode: the request has a header the AWS Emulator re-signing sidecar cannot forward.'},p.service);return;}
|
|
869
|
+
// A client that hangs up (an aborted GetObject) frees the upstream socket too.
|
|
870
|
+
resp.on('close',function(){if(!resp.writableFinished)r.destroy();});
|
|
871
|
+
r.setTimeout(300000,function(){r.destroy(Object.assign(new Error('upstream timeout'),{code:'ETIMEDOUT'}));});
|
|
872
|
+
r.on('error',function(e){process.stdout.write('CDKL-RESIGN upstream error '+(e.code||'ERROR')+'\n');send(resp,502,{code:'BadGateway',message:'cdk-local mixed mode: the AWS Emulator at '+cfg.upstream+' did not answer ('+(e.code||'ERROR')+').'},p.service);});
|
|
873
|
+
r.end(p.body);
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
var srv=https.createServer({key:cfg.keyPem,cert:cfg.certPem},handle);
|
|
877
|
+
srv.on('checkContinue',function(req,resp){
|
|
878
|
+
var n=tooLargeByHeaders(req.headers,cfg.capBytes);
|
|
879
|
+
if(n>0){process.stdout.write('CDKL-RESIGN rejected a request body over the cap\n');send(resp,413,tooLargeError(n,cfg.capBytes,serviceOf(req)),serviceOf(req));req.resume();return;}
|
|
880
|
+
resp.writeContinue();
|
|
881
|
+
handle(req,resp);
|
|
882
|
+
});
|
|
883
|
+
srv.once('error',function(e){done({error:e.code||'ERROR',port:cfg.port});});
|
|
884
|
+
// Loopback only: it signs with the emulator's keys for whoever connects.
|
|
885
|
+
srv.listen(cfg.port,'127.0.0.1',function(){done({port:cfg.port,address:srv.address().address});});
|
|
886
|
+
});}
|
|
887
|
+
`;
|
|
888
|
+
const RESIGN_SIDECAR_SCRIPT = RESIGN_SIDECAR_CORE + RESIGN_SIDECAR_SERVER;
|
|
889
|
+
|
|
349
890
|
//#endregion
|
|
350
891
|
//#region src/local/workload-aws-wiring.ts
|
|
351
892
|
var workload_aws_wiring_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -355,6 +896,7 @@ var workload_aws_wiring_exports = /* @__PURE__ */ __exportAll({
|
|
|
355
896
|
EMULATOR_SCRUBBED_ENV_KEYS: () => EMULATOR_SCRUBBED_ENV_KEYS,
|
|
356
897
|
HOST_CA_TRUST_ENV_KEYS: () => HOST_CA_TRUST_ENV_KEYS,
|
|
357
898
|
HOST_DEFAULT_CREDENTIALS_MISS_TTL_MS: () => HOST_DEFAULT_CREDENTIALS_MISS_TTL_MS,
|
|
899
|
+
STALE_RESIGN_DIR_AGE_MS: () => STALE_RESIGN_DIR_AGE_MS,
|
|
358
900
|
activateWorkloadAwsWiring: () => activateWorkloadAwsWiring,
|
|
359
901
|
applyWorkloadAwsWiringToEnv: () => applyWorkloadAwsWiringToEnv,
|
|
360
902
|
awsEmulatorEnvName: () => awsEmulatorEnvName,
|
|
@@ -365,6 +907,7 @@ var workload_aws_wiring_exports = /* @__PURE__ */ __exportAll({
|
|
|
365
907
|
getContainerCaBundlePath: () => getContainerCaBundlePath,
|
|
366
908
|
getContainerJavaTruststorePath: () => getContainerJavaTruststorePath,
|
|
367
909
|
javaTruststoreToolOptions: () => javaTruststoreToolOptions,
|
|
910
|
+
loopbackForwarderPlan: () => loopbackForwarderPlan,
|
|
368
911
|
mergeJavaToolOptions: () => mergeJavaToolOptions,
|
|
369
912
|
parseAwsEndpoint: () => parseAwsEndpoint,
|
|
370
913
|
parseIniSections: () => parseIniSections,
|
|
@@ -375,6 +918,7 @@ var workload_aws_wiring_exports = /* @__PURE__ */ __exportAll({
|
|
|
375
918
|
resolveHostDefaultCredentials: () => resolveHostDefaultCredentials,
|
|
376
919
|
resolveWorkloadAwsWiring: () => resolveWorkloadAwsWiring,
|
|
377
920
|
setActiveWorkloadAwsWiring: () => setActiveWorkloadAwsWiring,
|
|
921
|
+
sweepStaleResignDirs: () => sweepStaleResignDirs,
|
|
378
922
|
writeCaBundleFile: () => writeCaBundleFile
|
|
379
923
|
});
|
|
380
924
|
const CA_TRUST_ENV_KEYS = [
|
|
@@ -559,15 +1103,20 @@ function getContainerCaBundlePath() {
|
|
|
559
1103
|
function expandHome(p, homeDir) {
|
|
560
1104
|
return p === "~" ? homeDir : p.startsWith("~/") ? path$1.join(homeDir, p.slice(2)) : p;
|
|
561
1105
|
}
|
|
562
|
-
function writeCaBundleFile(source) {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
pem
|
|
566
|
-
|
|
567
|
-
|
|
1106
|
+
function writeCaBundleFile(source, extraCertsPem = []) {
|
|
1107
|
+
const certs = [];
|
|
1108
|
+
if (source !== void 0) {
|
|
1109
|
+
let pem;
|
|
1110
|
+
try {
|
|
1111
|
+
pem = readFileSync(source, "utf8");
|
|
1112
|
+
} catch (err) {
|
|
1113
|
+
throw new Error(`CA bundle ${displayUntrustedValue(source)} cannot be read (${err.code ?? "error"}).`);
|
|
1114
|
+
}
|
|
1115
|
+
const found = pemCertificates(pem);
|
|
1116
|
+
if (found.length === 0) throw new Error(`CA bundle ${displayUntrustedValue(source)} contains no PEM certificate.`);
|
|
1117
|
+
certs.push(...found);
|
|
568
1118
|
}
|
|
569
|
-
const
|
|
570
|
-
if (!certs) throw new Error(`CA bundle ${displayUntrustedValue(source)} contains no PEM certificate.`);
|
|
1119
|
+
for (const extra of extraCertsPem) certs.push(...pemCertificates(extra));
|
|
571
1120
|
const dir = mkdtempSync(path$1.join(tmpdir(), `${getEmbedConfig().productName}-ca-`));
|
|
572
1121
|
const hostPath = path$1.join(dir, "ca-bundle.pem");
|
|
573
1122
|
writeFileSync(hostPath, `${certs.join("\n")}\n${rootCertificates.join("\n")}\n`, { mode: 420 });
|
|
@@ -579,6 +1128,9 @@ function writeCaBundleFile(source) {
|
|
|
579
1128
|
})
|
|
580
1129
|
};
|
|
581
1130
|
}
|
|
1131
|
+
function pemCertificates(pem) {
|
|
1132
|
+
return pem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g) ?? [];
|
|
1133
|
+
}
|
|
582
1134
|
const DEFAULT_AWS_EMULATOR_URL = "https://127.0.0.1:9297";
|
|
583
1135
|
function probeAwsEmulatorHealth(url, caPath) {
|
|
584
1136
|
return new Promise((resolve) => {
|
|
@@ -607,6 +1159,108 @@ function probeAwsEmulatorHealth(url, caPath) {
|
|
|
607
1159
|
function describeEmulatorUnreachable(ep, reason) {
|
|
608
1160
|
return `--aws-emulator ${ep.url}: no AWS Emulator answers its health check at ${ep.url}/__admin/health (${reason}). Check that it is running on port ${ep.port} (\`aws-emulator -p ${ep.port}\`, or its Docker image published with \`-p 127.0.0.1:${ep.port}:${ep.port}\`), that the URL's port matches, and that it listens on an address this machine reaches (\`-b\`).`;
|
|
609
1161
|
}
|
|
1162
|
+
const STALE_RESIGN_DIR_AGE_MS = 10800 * 1e3;
|
|
1163
|
+
function sweepStaleResignDirs(now = Date.now(), dir = tmpdir()) {
|
|
1164
|
+
const removed = [];
|
|
1165
|
+
const prefix = `${getEmbedConfig().productName}-resign-`;
|
|
1166
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
1167
|
+
let names;
|
|
1168
|
+
try {
|
|
1169
|
+
names = readdirSync(dir);
|
|
1170
|
+
} catch {
|
|
1171
|
+
return removed;
|
|
1172
|
+
}
|
|
1173
|
+
for (const name of names) {
|
|
1174
|
+
if (!name.startsWith(prefix)) continue;
|
|
1175
|
+
const full = path$1.join(dir, name);
|
|
1176
|
+
try {
|
|
1177
|
+
const st = lstatSync(full);
|
|
1178
|
+
if (!st.isDirectory() || uid !== void 0 && st.uid !== uid) continue;
|
|
1179
|
+
if (now - st.mtimeMs < 108e5) continue;
|
|
1180
|
+
const pid = Number(readFileSync(path$1.join(full, "owner.pid"), "utf8").trim());
|
|
1181
|
+
if (!Number.isInteger(pid) || pid <= 0 || processAlive(pid)) continue;
|
|
1182
|
+
rmSync(full, {
|
|
1183
|
+
recursive: true,
|
|
1184
|
+
force: true
|
|
1185
|
+
});
|
|
1186
|
+
removed.push(full);
|
|
1187
|
+
} catch {}
|
|
1188
|
+
}
|
|
1189
|
+
return removed;
|
|
1190
|
+
}
|
|
1191
|
+
function processAlive(pid) {
|
|
1192
|
+
try {
|
|
1193
|
+
process.kill(pid, 0);
|
|
1194
|
+
return true;
|
|
1195
|
+
} catch (err) {
|
|
1196
|
+
return err.code === "EPERM";
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
function resolveMixedModeWiring(args) {
|
|
1200
|
+
const { ep, services, credentials, caSources, disposers } = args;
|
|
1201
|
+
const caPems = [];
|
|
1202
|
+
for (const source of caSources) try {
|
|
1203
|
+
caPems.push(...pemCertificates(readFileSync(source, "utf8")));
|
|
1204
|
+
} catch (err) {
|
|
1205
|
+
throw new Error(`--aws-emulator: the CA bundle ${displayUntrustedValue(source)} cannot be read (${err.code ?? "error"}).`);
|
|
1206
|
+
}
|
|
1207
|
+
const upstreamCaPem = caPems.length > 0 ? caPems.join("\n") : void 0;
|
|
1208
|
+
const tls = createMixedModeTlsMaterials();
|
|
1209
|
+
const bundle = writeCaBundleFile(void 0, [...caPems, tls.caCertPem]);
|
|
1210
|
+
disposers.push(bundle.dispose);
|
|
1211
|
+
sweepStaleResignDirs();
|
|
1212
|
+
const dir = mkdtempSync(path$1.join(tmpdir(), `${getEmbedConfig().productName}-resign-`));
|
|
1213
|
+
disposers.push(() => rmSync(dir, {
|
|
1214
|
+
recursive: true,
|
|
1215
|
+
force: true
|
|
1216
|
+
}));
|
|
1217
|
+
writeFileSync(path$1.join(dir, "owner.pid"), String(process.pid), { mode: 384 });
|
|
1218
|
+
const resignConfigHostPath = path$1.join(dir, "resign.json");
|
|
1219
|
+
const config = {
|
|
1220
|
+
port: MIXED_MODE_RESIGN_PORT,
|
|
1221
|
+
upstream: ep.url,
|
|
1222
|
+
...upstreamCaPem ? { upstreamCaPem } : {},
|
|
1223
|
+
certPem: tls.certPem,
|
|
1224
|
+
keyPem: tls.keyPem,
|
|
1225
|
+
credentials: {
|
|
1226
|
+
accessKeyId: credentials.accessKeyId,
|
|
1227
|
+
secretAccessKey: credentials.secretAccessKey,
|
|
1228
|
+
...credentials.sessionToken ? { sessionToken: credentials.sessionToken } : {}
|
|
1229
|
+
},
|
|
1230
|
+
capBytes: MIXED_MODE_RESIGN_BODY_CAP_BYTES,
|
|
1231
|
+
...args.region ? { region: args.region } : {}
|
|
1232
|
+
};
|
|
1233
|
+
writeFileSync(resignConfigHostPath, JSON.stringify(config), { mode: 384 });
|
|
1234
|
+
const containerPath = getContainerCaBundlePath();
|
|
1235
|
+
const trust = {};
|
|
1236
|
+
for (const k of CA_TRUST_ENV_KEYS) trust[k] = containerPath;
|
|
1237
|
+
return {
|
|
1238
|
+
mode: "forward",
|
|
1239
|
+
mixed: {
|
|
1240
|
+
services,
|
|
1241
|
+
emulatorEndpoint: ep,
|
|
1242
|
+
resignPort: MIXED_MODE_RESIGN_PORT,
|
|
1243
|
+
resignEndpoint: `https://127.0.0.1:${MIXED_MODE_RESIGN_PORT}`,
|
|
1244
|
+
resignConfigHostPath,
|
|
1245
|
+
emulatorClientOverride: {
|
|
1246
|
+
endpoint: ep.url,
|
|
1247
|
+
...upstreamCaPem ? { caPem: upstreamCaPem } : {},
|
|
1248
|
+
credentials: config.credentials
|
|
1249
|
+
}
|
|
1250
|
+
},
|
|
1251
|
+
env: trust,
|
|
1252
|
+
scrubEnvKeys: [],
|
|
1253
|
+
mounts: [{
|
|
1254
|
+
hostPath: bundle.hostPath,
|
|
1255
|
+
containerPath
|
|
1256
|
+
}],
|
|
1257
|
+
loopbackPorts: ep.loopback ? [ep.port, MIXED_MODE_RESIGN_PORT] : [MIXED_MODE_RESIGN_PORT],
|
|
1258
|
+
probeHealth: true,
|
|
1259
|
+
caHostPath: bundle.hostPath,
|
|
1260
|
+
...args.skipPull ? { skipPull: true } : {},
|
|
1261
|
+
dispose: args.dispose
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
610
1264
|
async function resolveWorkloadAwsWiring(opts) {
|
|
611
1265
|
const env = opts.env ?? process.env;
|
|
612
1266
|
const homeDir = opts.homeDir ?? homedir();
|
|
@@ -640,14 +1294,30 @@ async function resolveWorkloadAwsWiring(opts) {
|
|
|
640
1294
|
}
|
|
641
1295
|
const run = readEmulatorRunFiles(ep.port, homeDir);
|
|
642
1296
|
const caSource = opts.caBundle || run.caPath || env["AWS_CA_BUNDLE"];
|
|
643
|
-
const
|
|
1297
|
+
const mixedServices = emulatedServicesOf(opts.awsEmulatorSentinels ?? []);
|
|
1298
|
+
const probeCa = mixedServices.length > 0 ? run.caPath || caSource : caSource;
|
|
1299
|
+
const failure = await (opts.probeHealth ?? probeAwsEmulatorHealth)(ep.url, probeCa ? expandHome(probeCa, homeDir) : void 0);
|
|
644
1300
|
if (failure) throw new Error(describeEmulatorUnreachable(ep, failure));
|
|
645
1301
|
let credentials;
|
|
646
1302
|
if (run.credentials) credentials = run.credentials;
|
|
647
1303
|
else {
|
|
648
1304
|
credentials = AUTH_OFF_PLACEHOLDER_CREDENTIALS;
|
|
649
|
-
getLogger().info(`--aws-emulator: ${path$1.join(run.dir, "emulator_credentials")} not found but the emulator is healthy, so it runs with --auth off; workload containers get placeholder credentials
|
|
1305
|
+
getLogger().info(`--aws-emulator: ${path$1.join(run.dir, "emulator_credentials")} not found but the emulator is healthy, ` + (mixedServices.length > 0 ? "so it runs with --auth off; the re-signing sidecar signs with placeholder credentials." : "so it runs with --auth off; workload containers get placeholder credentials."));
|
|
650
1306
|
}
|
|
1307
|
+
if (mixedServices.length > 0) return resolveMixedModeWiring({
|
|
1308
|
+
ep,
|
|
1309
|
+
services: mixedServices,
|
|
1310
|
+
credentials,
|
|
1311
|
+
caSources: [...new Set([
|
|
1312
|
+
ep.url.startsWith("https:") ? run.caPath : void 0,
|
|
1313
|
+
opts.caBundle,
|
|
1314
|
+
env["AWS_CA_BUNDLE"]
|
|
1315
|
+
].filter((c) => typeof c === "string" && c !== "").map((c) => expandHome(c, homeDir)))],
|
|
1316
|
+
disposers,
|
|
1317
|
+
dispose,
|
|
1318
|
+
skipPull: opts.skipPull,
|
|
1319
|
+
region: env["AWS_REGION"] || env["AWS_DEFAULT_REGION"] || void 0
|
|
1320
|
+
});
|
|
651
1321
|
const ca = caSource && ep.url.startsWith("https:") ? caMount(caSource) : void 0;
|
|
652
1322
|
let java;
|
|
653
1323
|
if (run.truststorePath && ep.url.startsWith("https:")) {
|
|
@@ -736,8 +1406,18 @@ function effectiveLoopbackPorts(env, wiring) {
|
|
|
736
1406
|
if (ep.loopback) used.add(ep.port);
|
|
737
1407
|
} catch {}
|
|
738
1408
|
}
|
|
1409
|
+
const mixed = wiring.mixed;
|
|
1410
|
+
if (mixed && used.has(mixed.resignPort) && mixed.emulatorEndpoint.loopback) used.add(mixed.emulatorEndpoint.port);
|
|
739
1411
|
return wiring.loopbackPorts.filter((p) => used.has(p));
|
|
740
1412
|
}
|
|
1413
|
+
function loopbackForwarderPlan(wiring, ports) {
|
|
1414
|
+
const mixed = wiring.mixed;
|
|
1415
|
+
if (!mixed || !ports.includes(mixed.resignPort)) return { ports: [...ports] };
|
|
1416
|
+
return {
|
|
1417
|
+
ports: ports.filter((p) => p !== mixed.resignPort),
|
|
1418
|
+
resignConfigHostPath: mixed.resignConfigHostPath
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
741
1421
|
function applyWorkloadAwsWiringToEnv(env, wiring, defaultCredentials) {
|
|
742
1422
|
const out = {};
|
|
743
1423
|
for (const [k, v] of Object.entries(env)) Object.defineProperty(out, k, {
|
|
@@ -746,6 +1426,11 @@ function applyWorkloadAwsWiringToEnv(env, wiring, defaultCredentials) {
|
|
|
746
1426
|
configurable: true,
|
|
747
1427
|
writable: true
|
|
748
1428
|
});
|
|
1429
|
+
for (const [k, v] of Object.entries(out)) {
|
|
1430
|
+
if (!isAwsEmulatorSentinel(k, v)) continue;
|
|
1431
|
+
if (!wiring.mixed || k === "AWS_ENDPOINT_URL") throw new Error(`${k}="${AWS_EMULATOR_SENTINEL}" reached a workload container outside mixed mode. The sentinel is read from the --env-vars file, on an AWS_ENDPOINT_URL_<SERVICE> variable, and needs --aws-emulator [url].`);
|
|
1432
|
+
out[k] = wiring.mixed.resignEndpoint;
|
|
1433
|
+
}
|
|
749
1434
|
const setCreds = (c) => {
|
|
750
1435
|
for (const k of CREDENTIAL_KEYS) delete out[k];
|
|
751
1436
|
if (!c) return;
|
|
@@ -824,14 +1509,21 @@ function resolveAwsEmulatorOption(flag, env = process.env) {
|
|
|
824
1509
|
return /^(1|true)$/i.test(raw) ? true : raw;
|
|
825
1510
|
}
|
|
826
1511
|
async function activateWorkloadAwsWiring(options) {
|
|
1512
|
+
const awsEmulator = resolveAwsEmulatorOption(options.awsEmulator);
|
|
1513
|
+
const sentinels = readAwsEmulatorSentinelsFromFile(options.envVars);
|
|
1514
|
+
validateAwsEmulatorSentinels(sentinels, awsEmulator !== void 0, awsEmulatorEnvName());
|
|
827
1515
|
const wiring = await resolveWorkloadAwsWiring({
|
|
828
|
-
awsEmulator
|
|
1516
|
+
awsEmulator,
|
|
829
1517
|
caBundle: options.caBundle,
|
|
830
1518
|
profile: options.profile,
|
|
831
|
-
skipPull: options.pull === false
|
|
1519
|
+
skipPull: options.pull === false,
|
|
1520
|
+
awsEmulatorSentinels: sentinels
|
|
832
1521
|
});
|
|
833
1522
|
setActiveWorkloadAwsWiring(wiring);
|
|
834
|
-
if (wiring.
|
|
1523
|
+
if (wiring.mixed) {
|
|
1524
|
+
setAwsEndpointOverride(void 0);
|
|
1525
|
+
getLogger().info(`Mixed mode: ${wiring.mixed.services.join(", ")} go to AWS Emulator at ${wiring.mixed.emulatorEndpoint.url} (re-signed with its keys); every other AWS call uses your real credentials.`);
|
|
1526
|
+
} else if (wiring.mode === "emulator" && wiring.emulatorEndpoint) {
|
|
835
1527
|
setAwsEndpointOverride({
|
|
836
1528
|
endpoint: wiring.emulatorEndpoint.url,
|
|
837
1529
|
...wiring.caHostPath ? { caPem: readFileSync(wiring.caHostPath, "utf8") } : {},
|
|
@@ -994,5 +1686,5 @@ function warnWorkloadCredentialsStripped() {
|
|
|
994
1686
|
}
|
|
995
1687
|
|
|
996
1688
|
//#endregion
|
|
997
|
-
export {
|
|
998
|
-
//# sourceMappingURL=workload-identity-
|
|
1689
|
+
export { buildProxyClientConfig as $, parseAwsEndpoint as A, MIXED_MODE_RESIGN_BODY_CAP_BYTES as B, effectiveLoopbackPorts as C, javaTruststoreToolOptions as D, getContainerJavaTruststorePath as E, resolveHostDefaultCredentials as F, createMixedModeStateProvider as G, createMixedModeTlsMaterials as H, resolveWorkloadAwsWiring as I, isAwsEmulatorSentinel as J, emulatedServicesOf as K, setActiveWorkloadAwsWiring as L, readEmulatorRunFiles as M, readProfileEndpointSettings as N, loopbackForwarderPlan as O, resolveAwsEmulatorOption as P, awsEndpointOverrideClientConfig as Q, workload_aws_wiring_exports as R, displayEndpointValue as S, getContainerCaBundlePath as T, AWS_EMULATOR_SENTINEL as U, RESIGN_SIDECAR_SCRIPT as V, MIXED_MODE_RESIGN_PORT as W, resourceTypeMatchesEmulatedService as X, readAwsEmulatorSentinelsFromFile as Y, validateAwsEmulatorSentinels as Z, HOST_DEFAULT_CREDENTIALS_MISS_TTL_MS as _, deployAccountStsClientConfig as a, isWildcardHostname as at, awsEmulatorEnvName as b, resetWorkloadIdentity as c, hostFromAuthority as ct, workloadStsClientConfig as d, getAwsEndpointOverride as et, AUTH_OFF_PLACEHOLDER_CREDENTIALS as f, HOST_CA_TRUST_ENV_KEYS as g, EMULATOR_SCRUBBED_ENV_KEYS as h, assumedRoleRecorded as i, isLoopbackHostname as it, probeAwsEmulatorHealth as j, mergeJavaToolOptions as k, warnWorkloadCredentialsStripped as l, DEFAULT_AWS_EMULATOR_URL as m, accountLookupStsClientConfig as n, proxyAwareFetch as nt, readEnvCallerCredentials as o, formatAuthority as ot, CA_TRUST_ENV_KEYS as p, findAwsEmulatorSentinels as q, applyWorkloadEnvCredentials as r, setAwsEndpointOverride as rt, recordAssumedRole as s, formatHostForAuthority as st, SECOND_ASSUME_MESSAGE as t, isProxyEnvConfigured as tt, workloadClientConfig as u, activateWorkloadAwsWiring as v, getActiveWorkloadAwsWiring as w, collectHostEndpointOverrides as x, applyWorkloadAwsWiringToEnv as y, writeCaBundleFile as z };
|
|
1690
|
+
//# sourceMappingURL=workload-identity-D5zkhVcF.js.map
|