cdk-local 0.58.0 → 0.60.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 +2 -2
- package/dist/cli.js +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{local-list-B-5gO8ht.js → local-list-CZEZ5-JP.js} +505 -46
- package/dist/local-list-CZEZ5-JP.js.map +1 -0
- package/package.json +1 -1
- package/dist/local-list-B-5gO8ht.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as runDockerStreaming, c as getEmbedConfig, r as getDockerCmd, s as getLogger, u as setEmbedConfig } from "./docker-cmd-voNPrcRh.js";
|
|
2
|
-
import { $n as applyRoleArnIfSet, $t as derivePartitionAndUrlSuffix, At as waitForRieReady, Bn as resolveAgentCoreTarget, Bt as execEnvForSecrets, Cn as resolveMultiTarget, Dt as AssetManifestLoader, En as listTargets, Et as singleFlight, Ft as buildDockerImage, Gn as matchStacks, Gt as streamLogs, Hn as derivePseudoParametersFromRegion, Ht as pullImage, In as AGENTCORE_MCP_PROTOCOL, It as DockerRunnerError, Jn as resolveCdkPathToLogicalIds, Jt as resolveRuntimeImage, Kn as buildCdkPathIndex, Kt as resolveRuntimeCodeMountPath, Lt as SENSITIVE_ENV_KEYS, Mt as buildContainerImage, Nn as AGENTCORE_A2A_PROTOCOL, Nt as parseEcrUri, Ot as getDockerImageBySourceHash, Pn as AGENTCORE_AGUI_PROTOCOL, Pt as pullEcrImage, Q as verifyJwtViaDiscovery, Qn as withErrorHandling, Qt as checkVolumeHostPath, Rt as appendEnvFlags, Sn as Synthesizer, Tn as countTargets, Tt as writeProfileCredentialsFile, Ut as removeContainer, Vn as resolveLambdaTarget, Vt as pickFreePort, Wt as runDetached, Xn as LocalInvokeBuildError, Xt as TASK_ROLE_ACCOUNT_PLACEHOLDER, Y as createJwksCache, Yn as CdkLocalError, Yt as EcsTaskResolutionError, Zn as LocalStartServiceError, Zt as applyCrossStackResolverToTask, _t as invokeAgentCore, an as substituteAgainstStateAsync, ar as warnIfDeprecatedRegion, at as invokeAgentCoreWs, bn as resolveApp, c as resolveProfileCredentials$1, cn as resolveEnvVars, ct as a2aInvokeOnce, dn as createLocalStateProvider, en as detectEcsImageResolutionNeeds, er as appOptions, ft as mcpInvokeOnce, ht as signAgentCoreInvocation, i as getPublishedHostPort, ir as parseContextOptions, jt as architectureToPlatform, kt as invokeRie, ln as materializeLayerFromArn, lt as MCP_CONTAINER_PORT, mn as resolveCfnFallbackRegion, n as CloudMapRegistry, nn as resolveEcsTaskTarget, nr as contextOptions, ot as A2A_CONTAINER_PORT, pn as rejectExplicitCfnStackWithMultipleStacks, qn as readCdkPathOrUndefined, qt as resolveRuntimeFileExtension, r as getContainerNetworkIp, rn as applyDeployedEnvFallback, rr as deprecatedRegionOption, sn as substituteEnvVarsFromStateAsync, st as A2A_PATH, t as buildCloudMapIndex, tn as parseEcsTarget, tr as commonOptions, ut as MCP_PATH, vt as waitForAgentCorePing, wn as resolveSingleTarget, xt as buildAgentCoreCodeImage, yt as downloadAndExtractS3Bundle, zn as pickAgentCoreCandidateStack, zt as ensureDockerAvailable } from "./cloud-map-resolver-DjKCYyIq.js";
|
|
2
|
+
import { $n as applyRoleArnIfSet, $t as derivePartitionAndUrlSuffix, At as waitForRieReady, Bn as resolveAgentCoreTarget, Bt as execEnvForSecrets, Cn as resolveMultiTarget, Dt as AssetManifestLoader, En as listTargets, Et as singleFlight, Ft as buildDockerImage, Gn as matchStacks, Gt as streamLogs, Hn as derivePseudoParametersFromRegion, Ht as pullImage, In as AGENTCORE_MCP_PROTOCOL, It as DockerRunnerError, Jn as resolveCdkPathToLogicalIds, Jt as resolveRuntimeImage, Kn as buildCdkPathIndex, Kt as resolveRuntimeCodeMountPath, Lt as SENSITIVE_ENV_KEYS, Mt as buildContainerImage, Nn as AGENTCORE_A2A_PROTOCOL, Nt as parseEcrUri, Ot as getDockerImageBySourceHash, Pn as AGENTCORE_AGUI_PROTOCOL, Pt as pullEcrImage, Q as verifyJwtViaDiscovery, Qn as withErrorHandling, Qt as checkVolumeHostPath, Rt as appendEnvFlags, Sn as Synthesizer, Tn as countTargets, Tt as writeProfileCredentialsFile, Ut as removeContainer, Vn as resolveLambdaTarget, Vt as pickFreePort, Wt as runDetached, Xn as LocalInvokeBuildError, Xt as TASK_ROLE_ACCOUNT_PLACEHOLDER, Y as createJwksCache, Yn as CdkLocalError, Yt as EcsTaskResolutionError, Z as verifyJwtAuthorizer, Zn as LocalStartServiceError, Zt as applyCrossStackResolverToTask, _t as invokeAgentCore, an as substituteAgainstStateAsync, ar as warnIfDeprecatedRegion, at as invokeAgentCoreWs, bn as resolveApp, c as resolveProfileCredentials$1, cn as resolveEnvVars, ct as a2aInvokeOnce, dn as createLocalStateProvider, en as detectEcsImageResolutionNeeds, er as appOptions, ft as mcpInvokeOnce, ht as signAgentCoreInvocation, i as getPublishedHostPort, ir as parseContextOptions, jt as architectureToPlatform, kt as invokeRie, ln as materializeLayerFromArn, lt as MCP_CONTAINER_PORT, mn as resolveCfnFallbackRegion, n as CloudMapRegistry, nn as resolveEcsTaskTarget, nr as contextOptions, ot as A2A_CONTAINER_PORT, pn as rejectExplicitCfnStackWithMultipleStacks, qn as readCdkPathOrUndefined, qt as resolveRuntimeFileExtension, r as getContainerNetworkIp, rn as applyDeployedEnvFallback, rr as deprecatedRegionOption, sn as substituteEnvVarsFromStateAsync, st as A2A_PATH, t as buildCloudMapIndex, tn as parseEcsTarget, tr as commonOptions, ut as MCP_PATH, vt as waitForAgentCorePing, wn as resolveSingleTarget, xt as buildAgentCoreCodeImage, yt as downloadAndExtractS3Bundle, zn as pickAgentCoreCandidateStack, zt as ensureDockerAvailable } from "./cloud-map-resolver-DjKCYyIq.js";
|
|
3
3
|
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { homedir, tmpdir } from "node:os";
|
|
5
5
|
import * as path from "node:path";
|
|
@@ -7,10 +7,11 @@ import { dirname, join } from "node:path";
|
|
|
7
7
|
import { Command, Option } from "commander";
|
|
8
8
|
import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
|
|
9
9
|
import { execFile, spawn } from "node:child_process";
|
|
10
|
+
import { connect } from "node:net";
|
|
10
11
|
import { promisify } from "node:util";
|
|
11
12
|
import { X509Certificate, randomBytes, randomUUID } from "node:crypto";
|
|
12
|
-
import { createServer, request } from "node:http";
|
|
13
|
-
import { createServer as createServer$
|
|
13
|
+
import { createServer as createServer$1, request } from "node:http";
|
|
14
|
+
import { createServer as createServer$2 } from "node:https";
|
|
14
15
|
import graphlib from "graphlib";
|
|
15
16
|
import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
|
|
16
17
|
|
|
@@ -3783,12 +3784,18 @@ async function startFrontDoorServer(opts) {
|
|
|
3783
3784
|
if (!res.headersSent) writeError(res, 502, "Bad Gateway");
|
|
3784
3785
|
});
|
|
3785
3786
|
};
|
|
3786
|
-
const server = opts.tls ? createServer$
|
|
3787
|
+
const server = opts.tls ? createServer$2({
|
|
3787
3788
|
cert: opts.tls.certPem,
|
|
3788
3789
|
key: opts.tls.keyPem
|
|
3789
|
-
}, requestHandler) : createServer(requestHandler);
|
|
3790
|
+
}, requestHandler) : createServer$1(requestHandler);
|
|
3790
3791
|
const scheme = opts.tls ? "https" : "http";
|
|
3791
3792
|
server.on("connection", (socket) => socket.setNoDelay(true));
|
|
3793
|
+
server.on("upgrade", (req, clientSocket, head) => {
|
|
3794
|
+
handleUpgrade(req, clientSocket, head, opts).catch((err) => {
|
|
3795
|
+
logger.debug(`front-door upgrade error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3796
|
+
if (!clientSocket.destroyed) clientSocket.destroy();
|
|
3797
|
+
});
|
|
3798
|
+
});
|
|
3792
3799
|
const boundPort = await new Promise((resolve, reject) => {
|
|
3793
3800
|
server.once("error", reject);
|
|
3794
3801
|
server.listen(opts.port, host, () => {
|
|
@@ -3843,25 +3850,67 @@ function handleProxyRequest(req, res, opts) {
|
|
|
3843
3850
|
...req.socket.remoteAddress !== void 0 && { sourceIp: req.socket.remoteAddress }
|
|
3844
3851
|
});
|
|
3845
3852
|
if (!action) return reply404(req, res, opts);
|
|
3846
|
-
if (action.
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3853
|
+
if (action.auth) {
|
|
3854
|
+
const auth = action.auth;
|
|
3855
|
+
return auth.check(req.headers).then((result) => {
|
|
3856
|
+
if (!result.allow) {
|
|
3857
|
+
req.resume();
|
|
3858
|
+
writeUnauthorized(res, auth.realm, result.reason);
|
|
3859
|
+
return;
|
|
3860
|
+
}
|
|
3861
|
+
return serveAction(req, res, action, opts);
|
|
3862
|
+
}).catch((err) => {
|
|
3863
|
+
getLogger().child("front-door").debug(`auth gate error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3864
|
+
req.resume();
|
|
3865
|
+
writeUnauthorized(res, auth.realm, "auth check failed");
|
|
3866
|
+
});
|
|
3856
3867
|
}
|
|
3857
|
-
|
|
3858
|
-
return handlePoolRequest(req, res, picked.pool, opts);
|
|
3868
|
+
return serveAction(req, res, action, opts);
|
|
3859
3869
|
}
|
|
3860
3870
|
const target = resolveDispatchTarget(opts, url);
|
|
3861
3871
|
if (!target) return reply404(req, res, opts);
|
|
3862
3872
|
if (target.kind === "lambda") return handleLambdaRequest(req, res, target.lambda, opts);
|
|
3863
3873
|
return handlePoolRequest(req, res, target.pool, opts);
|
|
3864
3874
|
}
|
|
3875
|
+
/**
|
|
3876
|
+
* Dispatch a resolved {@link RouteAction} — the path the route resolver
|
|
3877
|
+
* returned (after any auth gate). Splits forward (ECS pool or Lambda invoker)
|
|
3878
|
+
* from redirect / fixed-response, mirroring what the inline logic used to do.
|
|
3879
|
+
*/
|
|
3880
|
+
function serveAction(req, res, action, opts) {
|
|
3881
|
+
if (action.kind === "redirect" || action.kind === "fixed-response") {
|
|
3882
|
+
req.resume();
|
|
3883
|
+
if (action.kind === "redirect") writeRedirect(res, action, req, opts.listenerPort, opts.tls ? "https" : "http");
|
|
3884
|
+
else writeFixedResponse(res, action);
|
|
3885
|
+
return Promise.resolve();
|
|
3886
|
+
}
|
|
3887
|
+
const picked = pickWeightedTarget(action.pools);
|
|
3888
|
+
if (!picked) {
|
|
3889
|
+
writeError(res, 502, `No forward target selected behind ${opts.label} (every weighted target has weight 0).`);
|
|
3890
|
+
return Promise.resolve();
|
|
3891
|
+
}
|
|
3892
|
+
if ("lambda" in picked) return handleLambdaRequest(req, res, picked.lambda, opts);
|
|
3893
|
+
return handlePoolRequest(req, res, picked.pool, opts);
|
|
3894
|
+
}
|
|
3895
|
+
/**
|
|
3896
|
+
* Write a 401 with `WWW-Authenticate: Bearer realm="..."`. ALB itself would
|
|
3897
|
+
* redirect to the IdP's authorize endpoint; for local dev parity we deny
|
|
3898
|
+
* loudly so the user notices and either supplies `--bearer-token` / passes a
|
|
3899
|
+
* fresh Authorization header / disables the guard with `--no-verify-auth`.
|
|
3900
|
+
*/
|
|
3901
|
+
function writeUnauthorized(res, realm, reason) {
|
|
3902
|
+
const body = reason && reason !== "" ? reason : "Unauthorized";
|
|
3903
|
+
res.writeHead(401, {
|
|
3904
|
+
"content-type": "text/plain; charset=utf-8",
|
|
3905
|
+
"content-length": String(Buffer.byteLength(`${body}\n`)),
|
|
3906
|
+
"www-authenticate": `Bearer realm="${escapeRealmQuotes(realm)}"`
|
|
3907
|
+
});
|
|
3908
|
+
res.end(`${body}\n`);
|
|
3909
|
+
}
|
|
3910
|
+
/** Escape `"` in the realm so the `WWW-Authenticate` header parses cleanly. */
|
|
3911
|
+
function escapeRealmQuotes(realm) {
|
|
3912
|
+
return realm.replace(/"/g, "\\\"");
|
|
3913
|
+
}
|
|
3865
3914
|
/** Reply 404 — an ALB listener with no matching rule and no default action. */
|
|
3866
3915
|
function reply404(req, res, opts) {
|
|
3867
3916
|
writeError(res, 404, `No listener rule matched '${req.url ?? "/"}' on ${opts.label}, and the listener has no default action forwarding to a local target.`);
|
|
@@ -4116,6 +4165,226 @@ function writeError(res, statusCode, message) {
|
|
|
4116
4165
|
res.writeHead(statusCode, { "content-type": "text/plain; charset=utf-8" });
|
|
4117
4166
|
res.end(`${message}\n`);
|
|
4118
4167
|
}
|
|
4168
|
+
/**
|
|
4169
|
+
* Handle an HTTP `Upgrade` request (WebSocket). Goes through the same
|
|
4170
|
+
* `route()` callback so listener-rule matching + auth gates apply
|
|
4171
|
+
* identically. ECS forward targets get a raw TCP bridge to the picked
|
|
4172
|
+
* replica; Lambda forward targets answer 502 (Lambda TGs do not support
|
|
4173
|
+
* WS); redirect / fixed-response actions synthesize a regular HTTP/1.1
|
|
4174
|
+
* response over the upgrade socket and close. Errors at every stage
|
|
4175
|
+
* destroy the client socket cleanly.
|
|
4176
|
+
*/
|
|
4177
|
+
function handleUpgrade(req, clientSocket, head, opts) {
|
|
4178
|
+
clientSocket.setNoDelay?.(true);
|
|
4179
|
+
if (!opts.route) {
|
|
4180
|
+
writeRawHttpError(clientSocket, 404, "Not Found");
|
|
4181
|
+
return Promise.resolve();
|
|
4182
|
+
}
|
|
4183
|
+
const action = opts.route({
|
|
4184
|
+
path: req.url ?? "/",
|
|
4185
|
+
...hostHeader(req),
|
|
4186
|
+
headers: req.headers,
|
|
4187
|
+
...req.method !== void 0 && { method: req.method },
|
|
4188
|
+
...req.socket.remoteAddress !== void 0 && { sourceIp: req.socket.remoteAddress }
|
|
4189
|
+
});
|
|
4190
|
+
if (!action) {
|
|
4191
|
+
writeRawHttpError(clientSocket, 404, "No listener rule matched the upgrade request.");
|
|
4192
|
+
return Promise.resolve();
|
|
4193
|
+
}
|
|
4194
|
+
const proceed = () => {
|
|
4195
|
+
if (action.kind === "redirect") {
|
|
4196
|
+
writeRawHttpRedirect(clientSocket, action, req, opts.listenerPort, opts.tls ? "https" : "http");
|
|
4197
|
+
return Promise.resolve();
|
|
4198
|
+
}
|
|
4199
|
+
if (action.kind === "fixed-response") {
|
|
4200
|
+
writeRawHttpFixedResponse(clientSocket, action);
|
|
4201
|
+
return Promise.resolve();
|
|
4202
|
+
}
|
|
4203
|
+
const picked = pickWeightedTarget(action.pools);
|
|
4204
|
+
if (!picked) {
|
|
4205
|
+
writeRawHttpError(clientSocket, 502, `No forward target selected behind ${opts.label} (every weighted target has weight 0).`);
|
|
4206
|
+
return Promise.resolve();
|
|
4207
|
+
}
|
|
4208
|
+
if ("lambda" in picked) {
|
|
4209
|
+
writeRawHttpError(clientSocket, 502, `WebSocket upgrade is not supported for Lambda target groups (${picked.lambda.label}).`);
|
|
4210
|
+
return Promise.resolve();
|
|
4211
|
+
}
|
|
4212
|
+
return bridgeWebSocket(req, clientSocket, head, picked.pool, opts);
|
|
4213
|
+
};
|
|
4214
|
+
if (action.auth) {
|
|
4215
|
+
const auth = action.auth;
|
|
4216
|
+
return auth.check(req.headers).then((result) => {
|
|
4217
|
+
if (!result.allow) {
|
|
4218
|
+
writeRawHttpUnauthorized(clientSocket, auth.realm, result.reason);
|
|
4219
|
+
return;
|
|
4220
|
+
}
|
|
4221
|
+
return proceed();
|
|
4222
|
+
}).catch((err) => {
|
|
4223
|
+
getLogger().child("front-door").debug(`upgrade auth gate error: ${err instanceof Error ? err.message : String(err)}`);
|
|
4224
|
+
writeRawHttpUnauthorized(clientSocket, auth.realm, "auth check failed");
|
|
4225
|
+
});
|
|
4226
|
+
}
|
|
4227
|
+
return proceed();
|
|
4228
|
+
}
|
|
4229
|
+
/**
|
|
4230
|
+
* Bridge a WebSocket upgrade onto an ECS replica. Opens a raw TCP socket
|
|
4231
|
+
* to the picked endpoint, replays the upgrade request (with `X-Forwarded-*`
|
|
4232
|
+
* stamped on), forwards any pre-read `head` bytes, then pipes the two
|
|
4233
|
+
* sockets in both directions. `Upgrade` / `Connection: Upgrade` / the
|
|
4234
|
+
* `Sec-WebSocket-*` headers are forwarded verbatim — RFC 7230 marks
|
|
4235
|
+
* `Upgrade` as hop-by-hop, but for the upgrade handshake itself the proxy
|
|
4236
|
+
* MUST preserve them (nginx / haproxy / ALB all do).
|
|
4237
|
+
*/
|
|
4238
|
+
function bridgeWebSocket(req, clientSocket, head, pool, opts) {
|
|
4239
|
+
const logger = getLogger().child("front-door");
|
|
4240
|
+
return new Promise((resolve) => {
|
|
4241
|
+
const endpoint = pool.next();
|
|
4242
|
+
if (!endpoint) {
|
|
4243
|
+
writeRawHttpError(clientSocket, 503, `No running replicas behind ${opts.label} for the matched target.`);
|
|
4244
|
+
resolve();
|
|
4245
|
+
return;
|
|
4246
|
+
}
|
|
4247
|
+
const upstream = connect({
|
|
4248
|
+
host: endpoint.host,
|
|
4249
|
+
port: endpoint.port
|
|
4250
|
+
});
|
|
4251
|
+
upstream.setNoDelay(true);
|
|
4252
|
+
let settled = false;
|
|
4253
|
+
const done = () => {
|
|
4254
|
+
if (settled) return;
|
|
4255
|
+
settled = true;
|
|
4256
|
+
resolve();
|
|
4257
|
+
};
|
|
4258
|
+
let upstreamConnected = false;
|
|
4259
|
+
upstream.on("connect", () => {
|
|
4260
|
+
upstreamConnected = true;
|
|
4261
|
+
const headers = { ...req.headers };
|
|
4262
|
+
appendForwardedHeaders(headers, req, opts.listenerPort, opts.tls ? "https" : "http");
|
|
4263
|
+
const lines = [`${req.method ?? "GET"} ${req.url ?? "/"} HTTP/${req.httpVersion}`];
|
|
4264
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
4265
|
+
if (value === void 0) continue;
|
|
4266
|
+
if (Array.isArray(value)) for (const v of value) lines.push(`${name}: ${v}`);
|
|
4267
|
+
else lines.push(`${name}: ${value}`);
|
|
4268
|
+
}
|
|
4269
|
+
upstream.write(`${lines.join("\r\n")}\r\n\r\n`);
|
|
4270
|
+
if (head.length > 0) upstream.write(head);
|
|
4271
|
+
clientSocket.pipe(upstream);
|
|
4272
|
+
upstream.pipe(clientSocket);
|
|
4273
|
+
});
|
|
4274
|
+
upstream.on("error", (err) => {
|
|
4275
|
+
logger.debug(`WS upstream error (${endpoint.host}:${endpoint.port}): ${err instanceof Error ? err.message : String(err)}`);
|
|
4276
|
+
if (!clientSocket.destroyed) {
|
|
4277
|
+
if (!upstreamConnected) try {
|
|
4278
|
+
writeRawHttpError(clientSocket, 502, `Failed to reach replica ${endpoint.host}:${endpoint.port} behind ${opts.label}.`);
|
|
4279
|
+
} catch {}
|
|
4280
|
+
clientSocket.destroy();
|
|
4281
|
+
}
|
|
4282
|
+
done();
|
|
4283
|
+
});
|
|
4284
|
+
upstream.on("close", () => {
|
|
4285
|
+
if (!clientSocket.destroyed) clientSocket.destroy();
|
|
4286
|
+
done();
|
|
4287
|
+
});
|
|
4288
|
+
clientSocket.on("error", () => {
|
|
4289
|
+
if (!upstream.destroyed) upstream.destroy();
|
|
4290
|
+
done();
|
|
4291
|
+
});
|
|
4292
|
+
clientSocket.on("close", () => {
|
|
4293
|
+
if (!upstream.destroyed) upstream.destroy();
|
|
4294
|
+
done();
|
|
4295
|
+
});
|
|
4296
|
+
});
|
|
4297
|
+
}
|
|
4298
|
+
/**
|
|
4299
|
+
* Write a minimal HTTP/1.1 error response over a raw upgrade socket and
|
|
4300
|
+
* close it. Used for the pre-101-Switching-Protocols failure paths
|
|
4301
|
+
* (no rule match, no replica, Lambda target, etc.).
|
|
4302
|
+
*/
|
|
4303
|
+
function writeRawHttpError(socket, statusCode, message) {
|
|
4304
|
+
if (socket.destroyed) return;
|
|
4305
|
+
const body = `${message}\n`;
|
|
4306
|
+
const lines = [
|
|
4307
|
+
`HTTP/1.1 ${statusCode} ${STATUS_TEXT[statusCode] ?? "Error"}`,
|
|
4308
|
+
"content-type: text/plain; charset=utf-8",
|
|
4309
|
+
`content-length: ${Buffer.byteLength(body)}`,
|
|
4310
|
+
"connection: close",
|
|
4311
|
+
"",
|
|
4312
|
+
""
|
|
4313
|
+
];
|
|
4314
|
+
try {
|
|
4315
|
+
socket.write(lines.join("\r\n") + body);
|
|
4316
|
+
} catch {}
|
|
4317
|
+
socket.end();
|
|
4318
|
+
}
|
|
4319
|
+
/** Write a 401 over a raw upgrade socket with the WS-aware `WWW-Authenticate` header. */
|
|
4320
|
+
function writeRawHttpUnauthorized(socket, realm, reason) {
|
|
4321
|
+
if (socket.destroyed) return;
|
|
4322
|
+
const body = `${reason && reason !== "" ? reason : "Unauthorized"}\n`;
|
|
4323
|
+
const lines = [
|
|
4324
|
+
"HTTP/1.1 401 Unauthorized",
|
|
4325
|
+
"content-type: text/plain; charset=utf-8",
|
|
4326
|
+
`content-length: ${Buffer.byteLength(body)}`,
|
|
4327
|
+
`www-authenticate: Bearer realm="${escapeRealmQuotes(realm)}"`,
|
|
4328
|
+
"connection: close",
|
|
4329
|
+
"",
|
|
4330
|
+
""
|
|
4331
|
+
];
|
|
4332
|
+
try {
|
|
4333
|
+
socket.write(lines.join("\r\n") + body);
|
|
4334
|
+
} catch {}
|
|
4335
|
+
socket.end();
|
|
4336
|
+
}
|
|
4337
|
+
/** Write an ALB-style 301 / 302 redirect over a raw upgrade socket. */
|
|
4338
|
+
function writeRawHttpRedirect(socket, action, req, listenerPort, scheme) {
|
|
4339
|
+
if (socket.destroyed) return;
|
|
4340
|
+
const location = buildRedirectLocation(action, req, listenerPort, scheme);
|
|
4341
|
+
const statusText = action.statusCode === 301 ? "Moved Permanently" : "Found";
|
|
4342
|
+
const lines = [
|
|
4343
|
+
`HTTP/1.1 ${action.statusCode} ${statusText}`,
|
|
4344
|
+
`location: ${location}`,
|
|
4345
|
+
"content-type: text/plain; charset=utf-8",
|
|
4346
|
+
"content-length: 0",
|
|
4347
|
+
"connection: close",
|
|
4348
|
+
"",
|
|
4349
|
+
""
|
|
4350
|
+
];
|
|
4351
|
+
try {
|
|
4352
|
+
socket.write(lines.join("\r\n"));
|
|
4353
|
+
} catch {}
|
|
4354
|
+
socket.end();
|
|
4355
|
+
}
|
|
4356
|
+
/** Write an ALB-style fixed-response over a raw upgrade socket. */
|
|
4357
|
+
function writeRawHttpFixedResponse(socket, action) {
|
|
4358
|
+
if (socket.destroyed) return;
|
|
4359
|
+
const body = action.messageBody ?? "";
|
|
4360
|
+
const statusText = STATUS_TEXT[action.statusCode] ?? "";
|
|
4361
|
+
const contentType = sanitizeRawHeaderValue(action.contentType ?? "text/plain; charset=utf-8");
|
|
4362
|
+
const lines = [
|
|
4363
|
+
`HTTP/1.1 ${action.statusCode} ${statusText}`,
|
|
4364
|
+
`content-type: ${contentType}`,
|
|
4365
|
+
`content-length: ${Buffer.byteLength(body)}`,
|
|
4366
|
+
"connection: close",
|
|
4367
|
+
"",
|
|
4368
|
+
""
|
|
4369
|
+
];
|
|
4370
|
+
try {
|
|
4371
|
+
socket.write(lines.join("\r\n") + body);
|
|
4372
|
+
} catch {}
|
|
4373
|
+
socket.end();
|
|
4374
|
+
}
|
|
4375
|
+
/** Strip CR / LF / NUL from a raw HTTP header value to prevent header injection. */
|
|
4376
|
+
function sanitizeRawHeaderValue(value) {
|
|
4377
|
+
return value.replace(/[\r\n]/g, " ");
|
|
4378
|
+
}
|
|
4379
|
+
/** Minimal HTTP/1.1 status text map for the codes the raw writers emit. */
|
|
4380
|
+
const STATUS_TEXT = {
|
|
4381
|
+
301: "Moved Permanently",
|
|
4382
|
+
302: "Found",
|
|
4383
|
+
401: "Unauthorized",
|
|
4384
|
+
404: "Not Found",
|
|
4385
|
+
502: "Bad Gateway",
|
|
4386
|
+
503: "Service Unavailable"
|
|
4387
|
+
};
|
|
4119
4388
|
|
|
4120
4389
|
//#endregion
|
|
4121
4390
|
//#region src/local/alb-path-matcher.ts
|
|
@@ -4730,6 +4999,98 @@ function createFrontDoorLambdaRunner(lambda, opts) {
|
|
|
4730
4999
|
};
|
|
4731
5000
|
}
|
|
4732
5001
|
|
|
5002
|
+
//#endregion
|
|
5003
|
+
//#region src/local/front-door-auth.ts
|
|
5004
|
+
/**
|
|
5005
|
+
* Build the front-door's auth-check callback for an
|
|
5006
|
+
* `authenticate-cognito` / `authenticate-oidc` guard.
|
|
5007
|
+
*
|
|
5008
|
+
* Local-dev parity model: the cloud-side ALB authenticates the user via a
|
|
5009
|
+
* full OAuth roundtrip (redirect to the IdP's authorize endpoint, callback,
|
|
5010
|
+
* AWSELBAuthSessionCookie issuance). The local front-door does NOT reproduce
|
|
5011
|
+
* the roundtrip — it accepts EITHER a Bearer JWT (verified against the
|
|
5012
|
+
* Cognito JWKS / OIDC discovery URL just like API Gateway's JWT authorizer)
|
|
5013
|
+
* OR an `AWSELBAuthSessionCookie-*` cookie pass-through (the user is acting
|
|
5014
|
+
* as if already signed in via the deployed ALB — `--bearer-token` makes the
|
|
5015
|
+
* Bearer-JWT path the headline path; the cookie pass-through is convenience
|
|
5016
|
+
* for hitting the local front-door from a browser session that already
|
|
5017
|
+
* authenticated through the deployed ALB).
|
|
5018
|
+
*
|
|
5019
|
+
* `--no-verify-auth` (the `noVerifyAuth` flag) short-circuits everything to
|
|
5020
|
+
* `allow: true` — explicitly off-switching the guard for local dev where
|
|
5021
|
+
* you do not want to mint a Bearer token at all.
|
|
5022
|
+
*
|
|
5023
|
+
* `--bearer-token <jwt>` (the `bearerToken` arg) makes the supplied token
|
|
5024
|
+
* the default `Authorization` value when the inbound request has none.
|
|
5025
|
+
*/
|
|
5026
|
+
function buildAuthCheck(guard, jwksCache, opts = {}) {
|
|
5027
|
+
const realm = guard.label;
|
|
5028
|
+
if (opts.noVerifyAuth === true) return {
|
|
5029
|
+
realm,
|
|
5030
|
+
check: async () => ({ allow: true })
|
|
5031
|
+
};
|
|
5032
|
+
const warned = opts.warned ?? /* @__PURE__ */ new Set();
|
|
5033
|
+
const sessionCookiePrefix = guard.sessionCookieName;
|
|
5034
|
+
const injectedBearer = opts.bearerToken;
|
|
5035
|
+
return {
|
|
5036
|
+
realm,
|
|
5037
|
+
check: async (headers) => {
|
|
5038
|
+
const cookieHeader = headerValue(headers["cookie"]);
|
|
5039
|
+
if (cookieHeader && cookieHasSessionPrefix(cookieHeader, sessionCookiePrefix)) return { allow: true };
|
|
5040
|
+
let authorization = headerValue(headers["authorization"]);
|
|
5041
|
+
if ((!authorization || authorization === "") && injectedBearer !== void 0) authorization = `Bearer ${injectedBearer}`;
|
|
5042
|
+
if (!authorization || authorization === "") return {
|
|
5043
|
+
allow: false,
|
|
5044
|
+
reason: "No Bearer token presented. Supply Authorization: Bearer <jwt> or pass --bearer-token <jwt>."
|
|
5045
|
+
};
|
|
5046
|
+
if (!authorization.toLowerCase().startsWith("bearer ")) return {
|
|
5047
|
+
allow: false,
|
|
5048
|
+
reason: "Authorization scheme is not Bearer; the ALB authenticate-* guard only accepts Bearer JWTs."
|
|
5049
|
+
};
|
|
5050
|
+
try {
|
|
5051
|
+
if ((await verifyJwtAuthorizer({
|
|
5052
|
+
kind: "jwt",
|
|
5053
|
+
logicalId: guard.label,
|
|
5054
|
+
declaredAt: "start-alb authenticate-* action",
|
|
5055
|
+
issuer: guard.issuer,
|
|
5056
|
+
audience: [guard.audience],
|
|
5057
|
+
...guard.region !== void 0 && { region: guard.region },
|
|
5058
|
+
...guard.userPoolId !== void 0 && { userPoolId: guard.userPoolId }
|
|
5059
|
+
}, authorization, jwksCache, { warned })).allow) return { allow: true };
|
|
5060
|
+
return {
|
|
5061
|
+
allow: false,
|
|
5062
|
+
reason: "Bearer token rejected (signature / iss / aud / exp check failed)."
|
|
5063
|
+
};
|
|
5064
|
+
} catch (err) {
|
|
5065
|
+
getLogger().child("front-door-auth").debug(`auth check threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
5066
|
+
return {
|
|
5067
|
+
allow: false,
|
|
5068
|
+
reason: "Auth check failed."
|
|
5069
|
+
};
|
|
5070
|
+
}
|
|
5071
|
+
}
|
|
5072
|
+
};
|
|
5073
|
+
}
|
|
5074
|
+
function headerValue(raw) {
|
|
5075
|
+
if (raw === void 0) return void 0;
|
|
5076
|
+
if (Array.isArray(raw)) return raw[0];
|
|
5077
|
+
return raw;
|
|
5078
|
+
}
|
|
5079
|
+
/**
|
|
5080
|
+
* True iff the `Cookie` header contains a cookie whose name starts with
|
|
5081
|
+
* `<sessionCookiePrefix>` (ALB suffixes the cookie with `-0` / `-1` / ...
|
|
5082
|
+
* when the auth session payload exceeds the per-cookie size limit, so we
|
|
5083
|
+
* match the prefix, not an exact name).
|
|
5084
|
+
*/
|
|
5085
|
+
function cookieHasSessionPrefix(cookieHeader, sessionCookiePrefix) {
|
|
5086
|
+
for (const pair of cookieHeader.split(";")) {
|
|
5087
|
+
const eq = pair.indexOf("=");
|
|
5088
|
+
const name = (eq === -1 ? pair : pair.slice(0, eq)).trim();
|
|
5089
|
+
if (name === sessionCookiePrefix || name.startsWith(`${sessionCookiePrefix}-`)) return true;
|
|
5090
|
+
}
|
|
5091
|
+
return false;
|
|
5092
|
+
}
|
|
5093
|
+
|
|
4733
5094
|
//#endregion
|
|
4734
5095
|
//#region src/cli/commands/ecs-service-emulator.ts
|
|
4735
5096
|
/**
|
|
@@ -5038,9 +5399,20 @@ async function buildFrontDoor(plan, options, logger) {
|
|
|
5038
5399
|
certPath: options.tlsCert,
|
|
5039
5400
|
keyPath: options.tlsKey
|
|
5040
5401
|
}) : void 0;
|
|
5402
|
+
const jwksCache = createJwksCache();
|
|
5403
|
+
const sharedWarned = /* @__PURE__ */ new Set();
|
|
5404
|
+
const authForGuard = (guard) => buildAuthCheck(guard, jwksCache, {
|
|
5405
|
+
...options.verifyAuth === false && { noVerifyAuth: true },
|
|
5406
|
+
...options.bearerToken !== void 0 && { bearerToken: options.bearerToken },
|
|
5407
|
+
warned: sharedWarned
|
|
5408
|
+
});
|
|
5409
|
+
const attachAuth = (action, guard) => guard ? {
|
|
5410
|
+
...action,
|
|
5411
|
+
auth: authForGuard(guard)
|
|
5412
|
+
} : action;
|
|
5041
5413
|
try {
|
|
5042
5414
|
for (const listener of plan.listeners) {
|
|
5043
|
-
const defaultRoute = listener.defaultAction ? toRouteAction(listener.defaultAction) : void 0;
|
|
5415
|
+
const defaultRoute = listener.defaultAction ? attachAuth(toRouteAction(listener.defaultAction), listener.defaultAuthGuard) : void 0;
|
|
5044
5416
|
const ruleRoutes = listener.rules.map((r) => ({
|
|
5045
5417
|
priority: r.priority,
|
|
5046
5418
|
pathPatterns: r.pathPatterns,
|
|
@@ -5049,7 +5421,7 @@ async function buildFrontDoor(plan, options, logger) {
|
|
|
5049
5421
|
httpRequestMethods: r.httpRequestMethods,
|
|
5050
5422
|
queryStringConditions: r.queryStringConditions,
|
|
5051
5423
|
sourceIpCidrs: r.sourceIpCidrs,
|
|
5052
|
-
target: toRouteAction(r.action)
|
|
5424
|
+
target: attachAuth(toRouteAction(r.action), r.authGuard)
|
|
5053
5425
|
}));
|
|
5054
5426
|
const route = (req) => matchAlbPathRule(req, ruleRoutes) ?? defaultRoute;
|
|
5055
5427
|
const tls = listener.protocol === "HTTPS" ? tlsMaterials : void 0;
|
|
@@ -5367,9 +5739,19 @@ function createLocalStartServiceCommand(opts = {}) {
|
|
|
5367
5739
|
* mix ECS and Lambda targets. HTTPS listeners are served — local TLS
|
|
5368
5740
|
* termination uses a user-supplied or auto-generated self-signed cert/key
|
|
5369
5741
|
* pair (the deployed `Listener.Certificates[]` ACM ARNs are not fetched,
|
|
5370
|
-
* because ACM private keys are not retrievable by design).
|
|
5371
|
-
*
|
|
5372
|
-
* `authenticate-oidc` actions
|
|
5742
|
+
* because ACM private keys are not retrievable by design).
|
|
5743
|
+
*
|
|
5744
|
+
* `authenticate-cognito` / `authenticate-oidc` actions ARE served — they are
|
|
5745
|
+
* lifted from the `Actions[]` array into an `authGuard` attached to the
|
|
5746
|
+
* terminal action they wrap. The front-door enforces a Bearer-JWT check
|
|
5747
|
+
* locally (signature + `iss` + `exp` + `aud`) using the existing JWKS-cached
|
|
5748
|
+
* verifier; missing / invalid token -> 401 with `WWW-Authenticate: Bearer`.
|
|
5749
|
+
* Out of scope: the full OAuth roundtrip (authorize-endpoint redirect +
|
|
5750
|
+
* callback). The local-dev parity is "I have a JWT, let me through" — the
|
|
5751
|
+
* `--bearer-token <jwt>` flag is the convenience escape hatch; the
|
|
5752
|
+
* `--no-verify-auth` flag disables the guard entirely.
|
|
5753
|
+
*
|
|
5754
|
+
* Skipped with a warning: TLS listeners (NLB-style, not ALB).
|
|
5373
5755
|
*/
|
|
5374
5756
|
const ALB_TYPE = "AWS::ElasticLoadBalancingV2::LoadBalancer";
|
|
5375
5757
|
const LISTENER_TYPE = "AWS::ElasticLoadBalancingV2::Listener";
|
|
@@ -5401,7 +5783,7 @@ function resolveAlbFrontDoor(stack, albLogicalId) {
|
|
|
5401
5783
|
continue;
|
|
5402
5784
|
}
|
|
5403
5785
|
if (protocol === "HTTPS" && Array.isArray(props["Certificates"]) && props["Certificates"].length > 0) warnings.push(`Listener '${listenerLogicalId}' on port ${port} declares ACM Certificates which are not retrievable locally. The front-door terminates TLS with --tls-cert/--tls-key or an auto-generated self-signed cert.`);
|
|
5404
|
-
const
|
|
5786
|
+
const defaultResolved = resolveAction(props["DefaultActions"], resources, tgToService, stackName, `Listener '${listenerLogicalId}' (port ${port}) default action`, warnings);
|
|
5405
5787
|
const rules = [];
|
|
5406
5788
|
for (const { ruleLogicalId, ruleProps } of rulesByListener.get(listenerLogicalId) ?? []) {
|
|
5407
5789
|
const priority = parsePriority(ruleProps["Priority"]);
|
|
@@ -5410,8 +5792,8 @@ function resolveAlbFrontDoor(stack, albLogicalId) {
|
|
|
5410
5792
|
if (!parsed) continue;
|
|
5411
5793
|
const { pathPatterns, hostPatterns, httpHeaderConditions, httpRequestMethods, queryStringConditions, sourceIpCidrs } = parsed;
|
|
5412
5794
|
if (!(pathPatterns.length > 0 || hostPatterns.length > 0 || httpHeaderConditions.length > 0 || httpRequestMethods.length > 0 || queryStringConditions.length > 0 || sourceIpCidrs.length > 0)) continue;
|
|
5413
|
-
const
|
|
5414
|
-
if (!
|
|
5795
|
+
const ruleResolved = resolveAction(ruleProps["Actions"], resources, tgToService, stackName, `${ruleLabel} action`, warnings);
|
|
5796
|
+
if (!ruleResolved) continue;
|
|
5415
5797
|
rules.push({
|
|
5416
5798
|
priority,
|
|
5417
5799
|
pathPatterns,
|
|
@@ -5420,15 +5802,17 @@ function resolveAlbFrontDoor(stack, albLogicalId) {
|
|
|
5420
5802
|
httpRequestMethods,
|
|
5421
5803
|
queryStringConditions,
|
|
5422
5804
|
sourceIpCidrs,
|
|
5423
|
-
action
|
|
5805
|
+
action: ruleResolved.action,
|
|
5806
|
+
...ruleResolved.authGuard ? { authGuard: ruleResolved.authGuard } : {}
|
|
5424
5807
|
});
|
|
5425
5808
|
}
|
|
5426
|
-
if (!
|
|
5809
|
+
if (!defaultResolved && rules.length === 0) continue;
|
|
5427
5810
|
listeners.push({
|
|
5428
5811
|
listenerPort: port,
|
|
5429
5812
|
listenerProtocol: protocol,
|
|
5430
5813
|
listenerLogicalId,
|
|
5431
|
-
...
|
|
5814
|
+
...defaultResolved ? { defaultAction: defaultResolved.action } : {},
|
|
5815
|
+
...defaultResolved?.authGuard ? { defaultAuthGuard: defaultResolved.authGuard } : {},
|
|
5432
5816
|
rules
|
|
5433
5817
|
});
|
|
5434
5818
|
}
|
|
@@ -5444,15 +5828,16 @@ function isApplicationLoadBalancer(resource) {
|
|
|
5444
5828
|
return type === void 0 || type === "application";
|
|
5445
5829
|
}
|
|
5446
5830
|
/**
|
|
5447
|
-
* Resolve a listener / rule `Actions` (or `DefaultActions`) array
|
|
5448
|
-
*
|
|
5449
|
-
*
|
|
5450
|
-
*
|
|
5451
|
-
*
|
|
5452
|
-
*
|
|
5831
|
+
* Resolve a listener / rule `Actions` (or `DefaultActions`) array. ALB allows
|
|
5832
|
+
* any number of `authenticate-cognito` / `authenticate-oidc` entries followed
|
|
5833
|
+
* by exactly one terminal action (`forward` / `redirect` / `fixed-response`).
|
|
5834
|
+
* The first parseable authenticate-* (last wins if multiple) becomes the
|
|
5835
|
+
* returned `authGuard`; the terminal action becomes `action`. Returns
|
|
5836
|
+
* `undefined` when no terminal action is resolvable.
|
|
5453
5837
|
*/
|
|
5454
5838
|
function resolveAction(actions, resources, tgToService, stackName, label, warnings) {
|
|
5455
5839
|
if (!Array.isArray(actions)) return void 0;
|
|
5840
|
+
let authGuard;
|
|
5456
5841
|
let sawAuthenticate = false;
|
|
5457
5842
|
for (const action of actions) {
|
|
5458
5843
|
if (!action || typeof action !== "object") continue;
|
|
@@ -5460,18 +5845,21 @@ function resolveAction(actions, resources, tgToService, stackName, label, warnin
|
|
|
5460
5845
|
const type = a["Type"];
|
|
5461
5846
|
if (type === "authenticate-cognito" || type === "authenticate-oidc") {
|
|
5462
5847
|
sawAuthenticate = true;
|
|
5848
|
+
const parsed = parseAuthenticateAction(a, type, label, warnings);
|
|
5849
|
+
if (parsed) authGuard = parsed;
|
|
5463
5850
|
continue;
|
|
5464
5851
|
}
|
|
5465
|
-
|
|
5466
|
-
if (type === "
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5852
|
+
let terminal;
|
|
5853
|
+
if (type === "forward") terminal = resolveForwardAction(a, resources, tgToService, stackName, label, warnings);
|
|
5854
|
+
else if (type === "redirect") terminal = resolveRedirectAction(a, label, warnings);
|
|
5855
|
+
else if (type === "fixed-response") terminal = resolveFixedResponseAction(a);
|
|
5856
|
+
else if (typeof type === "string") warnings.push(`${label} uses an unsupported action type '${type}'. The local ALB front-door supports forward / redirect / fixed-response actions only. Skipping it.`);
|
|
5857
|
+
if (terminal) return authGuard ? {
|
|
5858
|
+
action: terminal,
|
|
5859
|
+
authGuard
|
|
5860
|
+
} : { action: terminal };
|
|
5473
5861
|
}
|
|
5474
|
-
if (sawAuthenticate) warnings.push(`${label} is an authenticate-* action with no local-servable terminal action
|
|
5862
|
+
if (sawAuthenticate) warnings.push(`${label} is an authenticate-* action with no local-servable terminal action; skipping it.`);
|
|
5475
5863
|
}
|
|
5476
5864
|
/**
|
|
5477
5865
|
* Resolve a `forward` action into one or more weighted targets. Each target
|
|
@@ -5877,6 +6265,75 @@ function parsePriority(raw) {
|
|
|
5877
6265
|
if (typeof raw === "string" && /^\d+$/.test(raw)) return parseInt(raw, 10);
|
|
5878
6266
|
return Number.MAX_SAFE_INTEGER;
|
|
5879
6267
|
}
|
|
6268
|
+
/** ALB's default session cookie name prefix (suffixed `-0` / `-1` / ... by ALB). */
|
|
6269
|
+
const DEFAULT_ALB_SESSION_COOKIE = "AWSELBAuthSessionCookie";
|
|
6270
|
+
/**
|
|
6271
|
+
* Cognito User Pool ARN shape:
|
|
6272
|
+
* `arn:aws:cognito-idp:<region>:<account>:userpool/<pool-id>`
|
|
6273
|
+
* The pool id itself contains a `_`, but never a `/`, so anchoring on the
|
|
6274
|
+
* `userpool/` segment is robust.
|
|
6275
|
+
*/
|
|
6276
|
+
const COGNITO_USERPOOL_ARN = /^arn:[^:]+:cognito-idp:([^:]+):[^:]+:userpool\/([^/]+)$/;
|
|
6277
|
+
/**
|
|
6278
|
+
* Parse an `authenticate-cognito` / `authenticate-oidc` ALB action into a
|
|
6279
|
+
* resolvable {@link FrontDoorAuthGuard}, or `undefined` when the config is
|
|
6280
|
+
* unresolvable (a Ref / intrinsic in a required field, a malformed
|
|
6281
|
+
* UserPoolArn, etc.) — a warning is emitted in that case so the user knows
|
|
6282
|
+
* the guard was dropped (the terminal action will still serve unguarded).
|
|
6283
|
+
*/
|
|
6284
|
+
function parseAuthenticateAction(action, type, label, warnings) {
|
|
6285
|
+
if (type === "authenticate-cognito") {
|
|
6286
|
+
const cfg = action["AuthenticateCognitoConfig"];
|
|
6287
|
+
if (!cfg || typeof cfg !== "object") {
|
|
6288
|
+
warnings.push(`${label}: authenticate-cognito missing AuthenticateCognitoConfig; skipping guard.`);
|
|
6289
|
+
return;
|
|
6290
|
+
}
|
|
6291
|
+
const c = cfg;
|
|
6292
|
+
const userPoolArn = c["UserPoolArn"];
|
|
6293
|
+
const userPoolClientId = c["UserPoolClientId"];
|
|
6294
|
+
if (typeof userPoolArn !== "string" || typeof userPoolClientId !== "string") {
|
|
6295
|
+
warnings.push(`${label}: authenticate-cognito UserPoolArn / UserPoolClientId must be literal strings (Ref / intrinsics cannot be resolved by the local front-door); skipping guard.`);
|
|
6296
|
+
return;
|
|
6297
|
+
}
|
|
6298
|
+
const match = COGNITO_USERPOOL_ARN.exec(userPoolArn);
|
|
6299
|
+
if (!match) {
|
|
6300
|
+
warnings.push(`${label}: authenticate-cognito UserPoolArn '${userPoolArn}' is not in the expected arn:...:cognito-idp:<region>:<account>:userpool/<pool-id> shape; skipping guard.`);
|
|
6301
|
+
return;
|
|
6302
|
+
}
|
|
6303
|
+
const region = match[1];
|
|
6304
|
+
const userPoolId = match[2];
|
|
6305
|
+
const sessionCookieName = typeof c["SessionCookieName"] === "string" && c["SessionCookieName"] !== "" ? c["SessionCookieName"] : DEFAULT_ALB_SESSION_COOKIE;
|
|
6306
|
+
return {
|
|
6307
|
+
kind: "authenticate-cognito",
|
|
6308
|
+
issuer: `https://cognito-idp.${region}.amazonaws.com/${userPoolId}`,
|
|
6309
|
+
audience: userPoolClientId,
|
|
6310
|
+
region,
|
|
6311
|
+
userPoolId,
|
|
6312
|
+
sessionCookieName,
|
|
6313
|
+
label: `authenticate-cognito (UserPool=${userPoolId})`
|
|
6314
|
+
};
|
|
6315
|
+
}
|
|
6316
|
+
const cfg = action["AuthenticateOidcConfig"];
|
|
6317
|
+
if (!cfg || typeof cfg !== "object") {
|
|
6318
|
+
warnings.push(`${label}: authenticate-oidc missing AuthenticateOidcConfig; skipping guard.`);
|
|
6319
|
+
return;
|
|
6320
|
+
}
|
|
6321
|
+
const c = cfg;
|
|
6322
|
+
const issuer = c["Issuer"];
|
|
6323
|
+
const clientId = c["ClientId"];
|
|
6324
|
+
if (typeof issuer !== "string" || typeof clientId !== "string") {
|
|
6325
|
+
warnings.push(`${label}: authenticate-oidc Issuer / ClientId must be literal strings (Ref / intrinsics cannot be resolved by the local front-door); skipping guard.`);
|
|
6326
|
+
return;
|
|
6327
|
+
}
|
|
6328
|
+
const sessionCookieName = typeof c["SessionCookieName"] === "string" && c["SessionCookieName"] !== "" ? c["SessionCookieName"] : DEFAULT_ALB_SESSION_COOKIE;
|
|
6329
|
+
return {
|
|
6330
|
+
kind: "authenticate-oidc",
|
|
6331
|
+
issuer: issuer.replace(/\/+$/, ""),
|
|
6332
|
+
audience: clientId,
|
|
6333
|
+
sessionCookieName,
|
|
6334
|
+
label: `authenticate-oidc (Issuer=${issuer})`
|
|
6335
|
+
};
|
|
6336
|
+
}
|
|
5880
6337
|
|
|
5881
6338
|
//#endregion
|
|
5882
6339
|
//#region src/cli/commands/local-start-alb.ts
|
|
@@ -6018,6 +6475,7 @@ function albStrategy(options) {
|
|
|
6018
6475
|
hostPort,
|
|
6019
6476
|
protocol: listener.listenerProtocol,
|
|
6020
6477
|
...listener.defaultAction ? { defaultAction: qualify(listener.defaultAction) } : {},
|
|
6478
|
+
...listener.defaultAuthGuard ? { defaultAuthGuard: listener.defaultAuthGuard } : {},
|
|
6021
6479
|
rules: listener.rules.map((r) => ({
|
|
6022
6480
|
priority: r.priority,
|
|
6023
6481
|
pathPatterns: r.pathPatterns,
|
|
@@ -6026,7 +6484,8 @@ function albStrategy(options) {
|
|
|
6026
6484
|
httpRequestMethods: r.httpRequestMethods,
|
|
6027
6485
|
queryStringConditions: r.queryStringConditions,
|
|
6028
6486
|
sourceIpCidrs: r.sourceIpCidrs,
|
|
6029
|
-
action: qualify(r.action)
|
|
6487
|
+
action: qualify(r.action),
|
|
6488
|
+
...r.authGuard ? { authGuard: r.authGuard } : {}
|
|
6030
6489
|
}))
|
|
6031
6490
|
});
|
|
6032
6491
|
}
|
|
@@ -6055,7 +6514,7 @@ function albStrategy(options) {
|
|
|
6055
6514
|
*/
|
|
6056
6515
|
function createLocalStartAlbCommand(opts = {}) {
|
|
6057
6516
|
setEmbedConfig(opts.embedConfig);
|
|
6058
|
-
return addCommonEcsServiceOptions(new Command("start-alb").description("Run an Application Load Balancer locally: name the ALB, and cdk-local boots the ECS service(s) behind its listeners and stands up a local front-door on each listener port that round-robins across the running replicas and routes its listener rules across the backing services — a stable host endpoint, like behind a real load balancer. The symmetric ALB counterpart of `start-api`. Each <target> accepts a CDK display path (MyStack/MyAlb) or stack-qualified logical ID; single-stack apps may omit the stack prefix. Supports HTTP and HTTPS listeners (TLS terminated locally with --tls-cert/--tls-key or an auto-generated self-signed cert); all six ALB rule-condition fields (path-pattern / host-header / http-header / http-request-method / query-string / source-ip); forward (single and weighted), redirect, and fixed-response actions; and ECS or Lambda targets (a Lambda target group is invoked locally via the Lambda RIE). authenticate-cognito / authenticate-oidc actions
|
|
6517
|
+
return addCommonEcsServiceOptions(new Command("start-alb").description("Run an Application Load Balancer locally: name the ALB, and cdk-local boots the ECS service(s) behind its listeners and stands up a local front-door on each listener port that round-robins across the running replicas and routes its listener rules across the backing services — a stable host endpoint, like behind a real load balancer. The symmetric ALB counterpart of `start-api`. Each <target> accepts a CDK display path (MyStack/MyAlb) or stack-qualified logical ID; single-stack apps may omit the stack prefix. Supports HTTP and HTTPS listeners (TLS terminated locally with --tls-cert/--tls-key or an auto-generated self-signed cert); all six ALB rule-condition fields (path-pattern / host-header / http-header / http-request-method / query-string / source-ip); forward (single and weighted), redirect, and fixed-response actions; and ECS or Lambda targets (a Lambda target group is invoked locally via the Lambda RIE). authenticate-cognito / authenticate-oidc actions enforce a local Bearer-JWT check (or AWSELBAuthSessionCookie pass-through) against the same JWKS / OIDC discovery URL the deployed ALB would; use --bearer-token <jwt> to inject a default token or --no-verify-auth to disable the guard. Omit <targets> in an interactive terminal to multi-select the load balancers from a list.").argument("[targets...]", "One or more CDK display paths or stack-qualified logical IDs of the AWS::ElasticLoadBalancingV2::LoadBalancer resources to run (omit to multi-select interactively in a TTY)").addOption(new Option("--lb-port <listenerPort=hostPort...>", "Bind the local front-door on a specific host port (e.g. 80=8080); repeatable. Default: host port == ALB listener port. Use this on macOS to remap a privileged listener port (< 1024) to a non-privileged host port.")).addOption(new Option("--tls-cert <path>", "PEM-encoded server certificate for HTTPS front-door listeners. Must be set together with --tls-key. Omit both flags to auto-generate a self-signed cert (cached under $XDG_CACHE_HOME/cdk-local/alb-https/, default ~/.cache/cdk-local/alb-https/); requires openssl on PATH. The deployed Listener Certificates[] are NOT fetched (ACM private keys are not retrievable by design). The auto-generated cert lists DNS:localhost,IP:127.0.0.1 as SubjectAltName, so a client validating a non-loopback --container-host will fail the SAN check — pass --tls-cert / --tls-key with a SAN covering that host instead.")).addOption(new Option("--tls-key <path>", "PEM-encoded server private key matching --tls-cert. Must be set together with --tls-cert.")).addOption(new Option("--no-verify-auth", "Disable local enforcement of authenticate-cognito / authenticate-oidc actions. Every request is served as if the auth check passed. Useful for local dev where you do not want to mint a Bearer token at all.")).addOption(new Option("--bearer-token <jwt>", "Default Bearer JWT injected as Authorization: Bearer <jwt> when the inbound request has none. Verified against the same JWKS / OIDC discovery URL the deployed ALB would (signature + iss + aud + exp). Local-dev convenience; cookie pass-through (AWSELBAuthSessionCookie-*) also works.")).action(withErrorHandling(async (targets, options) => {
|
|
6059
6518
|
await runEcsServiceEmulator(targets, options, albStrategy(options), opts.extraStateProviders);
|
|
6060
6519
|
})));
|
|
6061
6520
|
}
|
|
@@ -6132,4 +6591,4 @@ function createLocalListCommand(opts = {}) {
|
|
|
6132
6591
|
|
|
6133
6592
|
//#endregion
|
|
6134
6593
|
export { createLocalRunTaskCommand as a, createLocalStartServiceCommand as i, formatTargetListing as n, createLocalInvokeAgentCoreCommand as o, createLocalStartAlbCommand as r, createLocalInvokeCommand as s, createLocalListCommand as t };
|
|
6135
|
-
//# sourceMappingURL=local-list-
|
|
6594
|
+
//# sourceMappingURL=local-list-CZEZ5-JP.js.map
|