@go-to-k/cdkd 0.72.0 → 0.74.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 +35 -5
- package/dist/cli.js +1639 -262
- package/dist/cli.js.map +4 -4
- package/dist/go-to-k-cdkd-0.74.0.tgz +0 -0
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.72.0.tgz +0 -0
package/dist/cli.js
CHANGED
|
@@ -70034,7 +70034,7 @@ async function captureObservedForImportedResources(stackState, providerRegistry,
|
|
|
70034
70034
|
}
|
|
70035
70035
|
|
|
70036
70036
|
// src/cli/commands/local-invoke.ts
|
|
70037
|
-
import { mkdtempSync as mkdtempSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync7, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
|
|
70037
|
+
import { cpSync as cpSync2, mkdtempSync as mkdtempSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync7, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
|
|
70038
70038
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
70039
70039
|
import { dirname as dirname3 } from "node:path";
|
|
70040
70040
|
import * as path2 from "node:path";
|
|
@@ -70169,6 +70169,7 @@ function extractLambdaProperties(stack, logicalId, resource) {
|
|
|
70169
70169
|
if (!inlineCode) {
|
|
70170
70170
|
codePath = resolveAssetCodePath(stack, logicalId, resource);
|
|
70171
70171
|
}
|
|
70172
|
+
const layers = resolveLambdaLayers(stack, logicalId, props);
|
|
70172
70173
|
return {
|
|
70173
70174
|
kind: "zip",
|
|
70174
70175
|
stack,
|
|
@@ -70179,6 +70180,7 @@ function extractLambdaProperties(stack, logicalId, resource) {
|
|
|
70179
70180
|
memoryMb,
|
|
70180
70181
|
timeoutSec,
|
|
70181
70182
|
codePath,
|
|
70183
|
+
layers,
|
|
70182
70184
|
...inlineCode !== void 0 && { inlineCode }
|
|
70183
70185
|
};
|
|
70184
70186
|
}
|
|
@@ -70235,7 +70237,8 @@ function extractImageLambdaProperties(args) {
|
|
|
70235
70237
|
timeoutSec,
|
|
70236
70238
|
imageUri,
|
|
70237
70239
|
imageConfig,
|
|
70238
|
-
architecture
|
|
70240
|
+
architecture,
|
|
70241
|
+
layers: []
|
|
70239
70242
|
};
|
|
70240
70243
|
}
|
|
70241
70244
|
function resolveAssetCodePath(stack, logicalId, resource) {
|
|
@@ -70255,6 +70258,72 @@ function resolveAssetCodePath(stack, logicalId, resource) {
|
|
|
70255
70258
|
}
|
|
70256
70259
|
return abs;
|
|
70257
70260
|
}
|
|
70261
|
+
function resolveLambdaLayers(stack, logicalId, props) {
|
|
70262
|
+
const layers = props["Layers"];
|
|
70263
|
+
if (layers === void 0)
|
|
70264
|
+
return [];
|
|
70265
|
+
if (!Array.isArray(layers)) {
|
|
70266
|
+
throw new LocalInvokeResolutionError(
|
|
70267
|
+
`Lambda '${logicalId}' has a non-array Layers property. Expected an array of LayerVersion references.`
|
|
70268
|
+
);
|
|
70269
|
+
}
|
|
70270
|
+
if (layers.length === 0)
|
|
70271
|
+
return [];
|
|
70272
|
+
const resources = stack.template.Resources ?? {};
|
|
70273
|
+
const out = [];
|
|
70274
|
+
for (let i = 0; i < layers.length; i++) {
|
|
70275
|
+
const entry = layers[i];
|
|
70276
|
+
const layerLogicalId = pickLayerLogicalId(entry);
|
|
70277
|
+
if (!layerLogicalId) {
|
|
70278
|
+
throw new LocalInvokeResolutionError(
|
|
70279
|
+
`Lambda '${logicalId}' has a Layers entry [${i}] cdkd cannot resolve locally: ${describeLayerEntry(entry)}. Only same-stack Ref / Fn::GetAtt to an AWS::Lambda::LayerVersion are supported in v1; cross-account / cross-region / pre-existing-ARN layers are deferred to a follow-up PR.`
|
|
70280
|
+
);
|
|
70281
|
+
}
|
|
70282
|
+
const layerResource = resources[layerLogicalId];
|
|
70283
|
+
if (!layerResource) {
|
|
70284
|
+
throw new LocalInvokeResolutionError(
|
|
70285
|
+
`Lambda '${logicalId}' Layers entry [${i}] references '${layerLogicalId}', but no resource with that logical ID exists in stack '${stack.stackName}'.`
|
|
70286
|
+
);
|
|
70287
|
+
}
|
|
70288
|
+
if (layerResource.Type !== "AWS::Lambda::LayerVersion") {
|
|
70289
|
+
throw new LocalInvokeResolutionError(
|
|
70290
|
+
`Lambda '${logicalId}' Layers entry [${i}] references '${layerLogicalId}' (${layerResource.Type}), which is not an AWS::Lambda::LayerVersion.`
|
|
70291
|
+
);
|
|
70292
|
+
}
|
|
70293
|
+
const assetPath = resolveAssetCodePath(stack, layerLogicalId, layerResource);
|
|
70294
|
+
out.push({ logicalId: layerLogicalId, assetPath });
|
|
70295
|
+
}
|
|
70296
|
+
return out;
|
|
70297
|
+
}
|
|
70298
|
+
function pickLayerLogicalId(entry) {
|
|
70299
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry))
|
|
70300
|
+
return void 0;
|
|
70301
|
+
const obj = entry;
|
|
70302
|
+
if (typeof obj["Ref"] === "string")
|
|
70303
|
+
return obj["Ref"];
|
|
70304
|
+
if ("Fn::GetAtt" in obj) {
|
|
70305
|
+
const arg = obj["Fn::GetAtt"];
|
|
70306
|
+
if (Array.isArray(arg) && typeof arg[0] === "string")
|
|
70307
|
+
return arg[0];
|
|
70308
|
+
if (typeof arg === "string")
|
|
70309
|
+
return arg.split(".")[0];
|
|
70310
|
+
}
|
|
70311
|
+
return void 0;
|
|
70312
|
+
}
|
|
70313
|
+
function describeLayerEntry(entry) {
|
|
70314
|
+
if (typeof entry === "string")
|
|
70315
|
+
return `literal ARN '${entry}'`;
|
|
70316
|
+
if (entry === null)
|
|
70317
|
+
return "null";
|
|
70318
|
+
if (typeof entry !== "object")
|
|
70319
|
+
return String(entry);
|
|
70320
|
+
try {
|
|
70321
|
+
const json = JSON.stringify(entry);
|
|
70322
|
+
return json.length > 120 ? json.substring(0, 117) + "..." : json;
|
|
70323
|
+
} catch {
|
|
70324
|
+
return Object.prototype.toString.call(entry);
|
|
70325
|
+
}
|
|
70326
|
+
}
|
|
70258
70327
|
function notFoundError(target, stack, resources) {
|
|
70259
70328
|
const lambdas = [];
|
|
70260
70329
|
for (const [logicalId, resource] of Object.entries(resources)) {
|
|
@@ -70578,6 +70647,12 @@ async function runDetached(opts) {
|
|
|
70578
70647
|
const ro = mount.readOnly ? ":ro" : "";
|
|
70579
70648
|
args.push("-v", `${mount.hostPath}:${mount.containerPath}${ro}`);
|
|
70580
70649
|
}
|
|
70650
|
+
if (opts.extraMounts) {
|
|
70651
|
+
for (const mount of opts.extraMounts) {
|
|
70652
|
+
const ro = mount.readOnly ? ":ro" : "";
|
|
70653
|
+
args.push("-v", `${mount.hostPath}:${mount.containerPath}${ro}`);
|
|
70654
|
+
}
|
|
70655
|
+
}
|
|
70581
70656
|
for (const [k, v] of Object.entries(opts.env)) {
|
|
70582
70657
|
args.push("-e", `${k}=${v}`);
|
|
70583
70658
|
}
|
|
@@ -71089,7 +71164,7 @@ function extractHashFromImageUri(imageUri) {
|
|
|
71089
71164
|
init_aws_clients();
|
|
71090
71165
|
|
|
71091
71166
|
// src/cli/commands/local-start-api.ts
|
|
71092
|
-
import { mkdirSync as mkdirSync2, mkdtempSync as mkdtempSync2, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
71167
|
+
import { cpSync, mkdirSync as mkdirSync2, mkdtempSync as mkdtempSync2, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
71093
71168
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
71094
71169
|
import * as path from "node:path";
|
|
71095
71170
|
import { Command as Command14, Option as Option7 } from "commander";
|
|
@@ -71480,9 +71555,11 @@ function createContainerPool(specs, options) {
|
|
|
71480
71555
|
logger.debug(
|
|
71481
71556
|
`Starting container ${name} for ${spec.lambda.logicalId} on ${spec.containerHost}:${hostPort}`
|
|
71482
71557
|
);
|
|
71558
|
+
const optMount = spec.optDir ? [{ hostPath: spec.optDir, containerPath: "/opt", readOnly: true }] : [];
|
|
71483
71559
|
const containerId = await runDetached({
|
|
71484
71560
|
image,
|
|
71485
71561
|
mounts: [{ hostPath: spec.codeDir, containerPath: "/var/task", readOnly: true }],
|
|
71562
|
+
extraMounts: optMount,
|
|
71486
71563
|
env: spec.env,
|
|
71487
71564
|
cmd: [spec.lambda.handler],
|
|
71488
71565
|
hostPort,
|
|
@@ -71778,6 +71855,48 @@ function buildRestV1Event(req, ctx, opts = {}) {
|
|
|
71778
71855
|
};
|
|
71779
71856
|
return event;
|
|
71780
71857
|
}
|
|
71858
|
+
function applyAuthorizerOverlay(event, overlay) {
|
|
71859
|
+
const requestContext = event["requestContext"] ?? {};
|
|
71860
|
+
let authorizer;
|
|
71861
|
+
switch (overlay.kind) {
|
|
71862
|
+
case "lambda-rest-v1": {
|
|
71863
|
+
authorizer = {
|
|
71864
|
+
...overlay.principalId !== void 0 && { principalId: overlay.principalId },
|
|
71865
|
+
...overlay.context ?? {}
|
|
71866
|
+
};
|
|
71867
|
+
break;
|
|
71868
|
+
}
|
|
71869
|
+
case "lambda-http-v2": {
|
|
71870
|
+
authorizer = {
|
|
71871
|
+
lambda: {
|
|
71872
|
+
...overlay.principalId !== void 0 && { principalId: overlay.principalId },
|
|
71873
|
+
...overlay.context ?? {}
|
|
71874
|
+
}
|
|
71875
|
+
};
|
|
71876
|
+
break;
|
|
71877
|
+
}
|
|
71878
|
+
case "cognito-rest-v1": {
|
|
71879
|
+
authorizer = { claims: { ...overlay.claims } };
|
|
71880
|
+
break;
|
|
71881
|
+
}
|
|
71882
|
+
case "jwt-http-v2": {
|
|
71883
|
+
authorizer = {
|
|
71884
|
+
jwt: {
|
|
71885
|
+
claims: { ...overlay.claims },
|
|
71886
|
+
scopes: overlay.scopes ?? []
|
|
71887
|
+
}
|
|
71888
|
+
};
|
|
71889
|
+
break;
|
|
71890
|
+
}
|
|
71891
|
+
}
|
|
71892
|
+
return {
|
|
71893
|
+
...event,
|
|
71894
|
+
requestContext: {
|
|
71895
|
+
...requestContext,
|
|
71896
|
+
authorizer
|
|
71897
|
+
}
|
|
71898
|
+
};
|
|
71899
|
+
}
|
|
71781
71900
|
function splitRawUrl(rawUrl) {
|
|
71782
71901
|
const q = rawUrl.indexOf("?");
|
|
71783
71902
|
if (q === -1)
|
|
@@ -72144,154 +72263,1257 @@ function isPlaceholder(segment) {
|
|
|
72144
72263
|
return /^\{[^/{}+]+\}$/.test(segment);
|
|
72145
72264
|
}
|
|
72146
72265
|
|
|
72147
|
-
// src/local/
|
|
72148
|
-
|
|
72149
|
-
const
|
|
72150
|
-
|
|
72151
|
-
|
|
72152
|
-
|
|
72153
|
-
|
|
72266
|
+
// src/local/authorizer-resolver.ts
|
|
72267
|
+
function resolveRestV1Authorizer(authorizerLogicalId, template, stackName, declaredAt) {
|
|
72268
|
+
const authResource = template.Resources?.[authorizerLogicalId];
|
|
72269
|
+
if (!authResource || authResource.Type !== "AWS::ApiGateway::Authorizer") {
|
|
72270
|
+
throw new RouteDiscoveryError(
|
|
72271
|
+
`${declaredAt}: AuthorizerId '${authorizerLogicalId}' does not point at an AWS::ApiGateway::Authorizer in stack '${stackName}'.`
|
|
72272
|
+
);
|
|
72273
|
+
}
|
|
72274
|
+
const props = authResource.Properties ?? {};
|
|
72275
|
+
const type = props["Type"];
|
|
72276
|
+
if (type === "TOKEN") {
|
|
72277
|
+
const lambdaLogicalId = resolveLambdaArn(
|
|
72278
|
+
props["AuthorizerUri"],
|
|
72279
|
+
`${stackName}/${authorizerLogicalId}.AuthorizerUri`
|
|
72280
|
+
);
|
|
72281
|
+
const identitySource = typeof props["IdentitySource"] === "string" ? props["IdentitySource"] : "method.request.header.Authorization";
|
|
72282
|
+
const tokenHeader = parseRestV1HeaderSelector(identitySource, stackName, authorizerLogicalId);
|
|
72283
|
+
const ttl = parseTtl(props["AuthorizerResultTtlInSeconds"], 300, 3600);
|
|
72284
|
+
return {
|
|
72285
|
+
kind: "lambda-token",
|
|
72286
|
+
logicalId: authorizerLogicalId,
|
|
72287
|
+
lambdaLogicalId,
|
|
72288
|
+
tokenHeader,
|
|
72289
|
+
resultTtlSeconds: ttl,
|
|
72290
|
+
declaredAt
|
|
72291
|
+
};
|
|
72292
|
+
}
|
|
72293
|
+
if (type === "REQUEST") {
|
|
72294
|
+
const lambdaLogicalId = resolveLambdaArn(
|
|
72295
|
+
props["AuthorizerUri"],
|
|
72296
|
+
`${stackName}/${authorizerLogicalId}.AuthorizerUri`
|
|
72297
|
+
);
|
|
72298
|
+
const identitySources = parseRestV1IdentitySources(
|
|
72299
|
+
typeof props["IdentitySource"] === "string" ? props["IdentitySource"] : ""
|
|
72300
|
+
);
|
|
72301
|
+
const ttl = parseTtl(props["AuthorizerResultTtlInSeconds"], 300, 3600);
|
|
72302
|
+
return {
|
|
72303
|
+
kind: "lambda-request",
|
|
72304
|
+
logicalId: authorizerLogicalId,
|
|
72305
|
+
lambdaLogicalId,
|
|
72306
|
+
identitySources,
|
|
72307
|
+
resultTtlSeconds: ttl,
|
|
72308
|
+
apiVersion: "v1",
|
|
72309
|
+
declaredAt
|
|
72310
|
+
};
|
|
72311
|
+
}
|
|
72312
|
+
if (type === "COGNITO_USER_POOLS") {
|
|
72313
|
+
const arns = props["ProviderARNs"];
|
|
72314
|
+
if (!Array.isArray(arns) || arns.length === 0) {
|
|
72315
|
+
throw new RouteDiscoveryError(
|
|
72316
|
+
`${stackName}/${authorizerLogicalId}: COGNITO_USER_POOLS authorizer is missing ProviderARNs.`
|
|
72154
72317
|
);
|
|
72155
|
-
if (!res.headersSent) {
|
|
72156
|
-
writeError(res, 502);
|
|
72157
|
-
}
|
|
72158
|
-
});
|
|
72159
|
-
});
|
|
72160
|
-
server.on("connection", (socket) => {
|
|
72161
|
-
socket.setNoDelay(true);
|
|
72162
|
-
});
|
|
72163
|
-
const { actualPort, actualHost } = await new Promise(
|
|
72164
|
-
(resolveListen, rejectListen) => {
|
|
72165
|
-
server.once("error", rejectListen);
|
|
72166
|
-
server.listen(opts.port, opts.host, () => {
|
|
72167
|
-
const addr = server.address();
|
|
72168
|
-
if (addr === null || typeof addr === "string") {
|
|
72169
|
-
rejectListen(new Error("Could not determine listening address"));
|
|
72170
|
-
return;
|
|
72171
|
-
}
|
|
72172
|
-
resolveListen({ actualPort: addr.port, actualHost: opts.host });
|
|
72173
|
-
});
|
|
72174
72318
|
}
|
|
72319
|
+
const arn = pickStringFromArn(arns[0], `${stackName}/${authorizerLogicalId}.ProviderARNs[0]`);
|
|
72320
|
+
const parsed = parseCognitoUserPoolArn(arn, `${stackName}/${authorizerLogicalId}`);
|
|
72321
|
+
return {
|
|
72322
|
+
kind: "cognito",
|
|
72323
|
+
logicalId: authorizerLogicalId,
|
|
72324
|
+
userPoolArn: arn,
|
|
72325
|
+
region: parsed.region,
|
|
72326
|
+
userPoolId: parsed.userPoolId,
|
|
72327
|
+
declaredAt
|
|
72328
|
+
};
|
|
72329
|
+
}
|
|
72330
|
+
throw new RouteDiscoveryError(
|
|
72331
|
+
`${stackName}/${authorizerLogicalId}: AWS::ApiGateway::Authorizer.Type '${String(type)}' is not supported by cdkd local start-api (only TOKEN / REQUEST / COGNITO_USER_POOLS \u2014 IAM / mTLS authorizers are deferred to a follow-up PR).`
|
|
72175
72332
|
);
|
|
72176
|
-
let closed = false;
|
|
72177
|
-
return {
|
|
72178
|
-
port: actualPort,
|
|
72179
|
-
host: actualHost,
|
|
72180
|
-
server,
|
|
72181
|
-
close: async () => {
|
|
72182
|
-
if (closed)
|
|
72183
|
-
return;
|
|
72184
|
-
closed = true;
|
|
72185
|
-
await new Promise((resolveClose) => {
|
|
72186
|
-
server.close(() => resolveClose());
|
|
72187
|
-
server.closeAllConnections?.();
|
|
72188
|
-
});
|
|
72189
|
-
}
|
|
72190
|
-
};
|
|
72191
72333
|
}
|
|
72192
|
-
|
|
72193
|
-
const
|
|
72194
|
-
|
|
72195
|
-
|
|
72196
|
-
|
|
72197
|
-
const requestPath = rawUrl.split("?")[0] ?? "/";
|
|
72198
|
-
const match = matchRoute(method, requestPath, opts.routes);
|
|
72199
|
-
if (!match) {
|
|
72200
|
-
writeError(res, 404, '{"message":"Not Found"}');
|
|
72201
|
-
return;
|
|
72202
|
-
}
|
|
72203
|
-
const snapshot = {
|
|
72204
|
-
method,
|
|
72205
|
-
rawUrl,
|
|
72206
|
-
headers: collectHeaders(req),
|
|
72207
|
-
body: bodyBuf,
|
|
72208
|
-
...req.socket.remoteAddress !== void 0 && { sourceIp: req.socket.remoteAddress }
|
|
72209
|
-
};
|
|
72210
|
-
const matchCtx = {
|
|
72211
|
-
route: match.route,
|
|
72212
|
-
pathParameters: match.pathParameters,
|
|
72213
|
-
matchedPath: requestPath
|
|
72214
|
-
};
|
|
72215
|
-
const event = match.route.apiVersion === "v1" ? buildRestV1Event(snapshot, matchCtx) : buildHttpApiV2Event(snapshot, matchCtx);
|
|
72216
|
-
let handle;
|
|
72217
|
-
try {
|
|
72218
|
-
handle = await opts.pool.acquire(match.route.lambdaLogicalId);
|
|
72219
|
-
} catch (err) {
|
|
72220
|
-
logger.error(
|
|
72221
|
-
`Failed to acquire container for ${match.route.lambdaLogicalId}: ${err instanceof Error ? err.message : String(err)}`
|
|
72334
|
+
function resolveHttpApiAuthorizer(authorizerLogicalId, routeAuthorizationScopes, template, stackName, declaredAt) {
|
|
72335
|
+
const authResource = template.Resources?.[authorizerLogicalId];
|
|
72336
|
+
if (!authResource || authResource.Type !== "AWS::ApiGatewayV2::Authorizer") {
|
|
72337
|
+
throw new RouteDiscoveryError(
|
|
72338
|
+
`${declaredAt}: AuthorizerId '${authorizerLogicalId}' does not point at an AWS::ApiGatewayV2::Authorizer in stack '${stackName}'.`
|
|
72222
72339
|
);
|
|
72223
|
-
writeError(res, 502);
|
|
72224
|
-
return;
|
|
72225
72340
|
}
|
|
72226
|
-
|
|
72227
|
-
|
|
72228
|
-
|
|
72229
|
-
|
|
72230
|
-
|
|
72231
|
-
|
|
72341
|
+
const props = authResource.Properties ?? {};
|
|
72342
|
+
const authType = props["AuthorizerType"];
|
|
72343
|
+
if (authType === "REQUEST") {
|
|
72344
|
+
const lambdaLogicalId = resolveLambdaArn(
|
|
72345
|
+
props["AuthorizerUri"],
|
|
72346
|
+
`${stackName}/${authorizerLogicalId}.AuthorizerUri`
|
|
72232
72347
|
);
|
|
72233
|
-
const
|
|
72234
|
-
|
|
72235
|
-
|
|
72236
|
-
|
|
72348
|
+
const identitySources = parseHttpV2IdentitySources(props["IdentitySource"]);
|
|
72349
|
+
const ttl = parseTtl(props["AuthorizerResultTtlInSeconds"], 0, 3600);
|
|
72350
|
+
return {
|
|
72351
|
+
kind: "lambda-request",
|
|
72352
|
+
logicalId: authorizerLogicalId,
|
|
72353
|
+
lambdaLogicalId,
|
|
72354
|
+
identitySources,
|
|
72355
|
+
resultTtlSeconds: ttl,
|
|
72356
|
+
apiVersion: "v2",
|
|
72357
|
+
declaredAt
|
|
72358
|
+
};
|
|
72359
|
+
}
|
|
72360
|
+
if (authType === "JWT") {
|
|
72361
|
+
const jwt = props["JwtConfiguration"];
|
|
72362
|
+
if (!jwt || typeof jwt !== "object") {
|
|
72363
|
+
throw new RouteDiscoveryError(
|
|
72364
|
+
`${stackName}/${authorizerLogicalId}: AWS::ApiGatewayV2::Authorizer.JwtConfiguration is required for AuthorizerType=JWT.`
|
|
72365
|
+
);
|
|
72237
72366
|
}
|
|
72238
|
-
|
|
72239
|
-
|
|
72367
|
+
const obj = jwt;
|
|
72368
|
+
const issuer = obj["Issuer"];
|
|
72369
|
+
if (typeof issuer !== "string" || issuer.length === 0) {
|
|
72370
|
+
throw new RouteDiscoveryError(
|
|
72371
|
+
`${stackName}/${authorizerLogicalId}: JwtConfiguration.Issuer must be a string.`
|
|
72372
|
+
);
|
|
72240
72373
|
}
|
|
72241
|
-
|
|
72242
|
-
|
|
72243
|
-
|
|
72244
|
-
|
|
72245
|
-
|
|
72246
|
-
|
|
72247
|
-
|
|
72248
|
-
|
|
72249
|
-
|
|
72374
|
+
const audienceRaw = obj["Audience"];
|
|
72375
|
+
const audience = Array.isArray(audienceRaw) ? audienceRaw.filter((s) => typeof s === "string") : [];
|
|
72376
|
+
const cognito = parseCognitoIssuer(issuer);
|
|
72377
|
+
return {
|
|
72378
|
+
kind: "jwt",
|
|
72379
|
+
logicalId: authorizerLogicalId,
|
|
72380
|
+
issuer,
|
|
72381
|
+
audience,
|
|
72382
|
+
...cognito && { region: cognito.region, userPoolId: cognito.userPoolId },
|
|
72383
|
+
declaredAt
|
|
72384
|
+
};
|
|
72385
|
+
}
|
|
72386
|
+
throw new RouteDiscoveryError(
|
|
72387
|
+
`${stackName}/${authorizerLogicalId}: AWS::ApiGatewayV2::Authorizer.AuthorizerType '${String(authType)}' is not supported by cdkd local start-api (only REQUEST / JWT).`
|
|
72388
|
+
);
|
|
72389
|
+
}
|
|
72390
|
+
function resolveLambdaArn(value, location) {
|
|
72391
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
72392
|
+
const obj = value;
|
|
72393
|
+
if ("Ref" in obj && typeof obj["Ref"] === "string")
|
|
72394
|
+
return obj["Ref"];
|
|
72395
|
+
if ("Fn::GetAtt" in obj) {
|
|
72396
|
+
const arg = obj["Fn::GetAtt"];
|
|
72397
|
+
if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && arg[1] === "Arn") {
|
|
72398
|
+
return arg[0];
|
|
72399
|
+
}
|
|
72400
|
+
}
|
|
72401
|
+
if ("Fn::Join" in obj) {
|
|
72402
|
+
const join9 = obj["Fn::Join"];
|
|
72403
|
+
if (Array.isArray(join9) && join9.length === 2 && Array.isArray(join9[1])) {
|
|
72404
|
+
const parts = join9[1];
|
|
72405
|
+
const literal = parts.filter((p) => typeof p === "string").join("");
|
|
72406
|
+
if (literal.includes(":lambda:path/2015-03-31/functions/")) {
|
|
72407
|
+
for (const p of parts) {
|
|
72408
|
+
if (p && typeof p === "object" && !Array.isArray(p)) {
|
|
72409
|
+
const inner = p;
|
|
72410
|
+
const arg = inner["Fn::GetAtt"];
|
|
72411
|
+
if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && arg[1] === "Arn") {
|
|
72412
|
+
return arg[0];
|
|
72413
|
+
}
|
|
72414
|
+
}
|
|
72415
|
+
}
|
|
72416
|
+
}
|
|
72417
|
+
}
|
|
72250
72418
|
}
|
|
72251
|
-
} finally {
|
|
72252
|
-
opts.pool.release(handle);
|
|
72253
72419
|
}
|
|
72420
|
+
throw new RouteDiscoveryError(
|
|
72421
|
+
`${location}: only { Ref }, { Fn::GetAtt: [..., 'Arn'] }, or the REST v1 invoke-ARN Fn::Join wrapper are supported (got ${shortJson2(value)}).`
|
|
72422
|
+
);
|
|
72254
72423
|
}
|
|
72255
|
-
function
|
|
72256
|
-
|
|
72257
|
-
|
|
72258
|
-
|
|
72259
|
-
|
|
72260
|
-
|
|
72261
|
-
|
|
72262
|
-
|
|
72263
|
-
});
|
|
72424
|
+
function parseRestV1HeaderSelector(identitySource, stackName, authorizerLogicalId) {
|
|
72425
|
+
const m = /^method\.request\.header\.([A-Za-z0-9-_]+)$/.exec(identitySource.trim());
|
|
72426
|
+
if (!m) {
|
|
72427
|
+
throw new RouteDiscoveryError(
|
|
72428
|
+
`${stackName}/${authorizerLogicalId}: TOKEN authorizer IdentitySource '${identitySource}' must be 'method.request.header.<HeaderName>'.`
|
|
72429
|
+
);
|
|
72430
|
+
}
|
|
72431
|
+
return m[1].toLowerCase();
|
|
72264
72432
|
}
|
|
72265
|
-
function
|
|
72266
|
-
const out =
|
|
72267
|
-
for (const
|
|
72268
|
-
|
|
72269
|
-
|
|
72270
|
-
|
|
72271
|
-
|
|
72433
|
+
function parseRestV1IdentitySources(raw) {
|
|
72434
|
+
const out = [];
|
|
72435
|
+
for (const tokenRaw of raw.split(",")) {
|
|
72436
|
+
const token = tokenRaw.trim();
|
|
72437
|
+
if (token.length === 0)
|
|
72438
|
+
continue;
|
|
72439
|
+
const headerMatch = /^method\.request\.header\.([A-Za-z0-9-_]+)$/.exec(token);
|
|
72440
|
+
if (headerMatch) {
|
|
72441
|
+
out.push({ kind: "header", name: headerMatch[1].toLowerCase() });
|
|
72442
|
+
continue;
|
|
72443
|
+
}
|
|
72444
|
+
const queryMatch = /^method\.request\.querystring\.([A-Za-z0-9-_]+)$/.exec(token);
|
|
72445
|
+
if (queryMatch) {
|
|
72446
|
+
out.push({ kind: "query", name: queryMatch[1] });
|
|
72447
|
+
continue;
|
|
72448
|
+
}
|
|
72449
|
+
const contextMatch = /^context\.([A-Za-z0-9._-]+)$/.exec(token);
|
|
72450
|
+
if (contextMatch) {
|
|
72451
|
+
out.push({ kind: "context", name: contextMatch[1] });
|
|
72452
|
+
continue;
|
|
72453
|
+
}
|
|
72454
|
+
const stageMatch = /^stageVariables\.([A-Za-z0-9._-]+)$/.exec(token);
|
|
72455
|
+
if (stageMatch) {
|
|
72456
|
+
out.push({ kind: "stage-variable", name: stageMatch[1] });
|
|
72457
|
+
continue;
|
|
72272
72458
|
}
|
|
72459
|
+
out.push({ kind: "header", name: token.toLowerCase() });
|
|
72273
72460
|
}
|
|
72274
72461
|
return out;
|
|
72275
72462
|
}
|
|
72276
|
-
function
|
|
72277
|
-
|
|
72278
|
-
|
|
72279
|
-
|
|
72280
|
-
|
|
72463
|
+
function parseHttpV2IdentitySources(raw) {
|
|
72464
|
+
if (!Array.isArray(raw))
|
|
72465
|
+
return [];
|
|
72466
|
+
const out = [];
|
|
72467
|
+
for (const entry of raw) {
|
|
72468
|
+
if (typeof entry !== "string")
|
|
72469
|
+
continue;
|
|
72470
|
+
const headerMatch = /^\$request\.header\.([A-Za-z0-9-_]+)$/.exec(entry);
|
|
72471
|
+
if (headerMatch) {
|
|
72472
|
+
out.push({ kind: "header", name: headerMatch[1].toLowerCase() });
|
|
72473
|
+
continue;
|
|
72474
|
+
}
|
|
72475
|
+
const queryMatch = /^\$request\.querystring\.([A-Za-z0-9-_]+)$/.exec(entry);
|
|
72476
|
+
if (queryMatch) {
|
|
72477
|
+
out.push({ kind: "query", name: queryMatch[1] });
|
|
72478
|
+
continue;
|
|
72479
|
+
}
|
|
72480
|
+
out.push({ kind: "header", name: entry.toLowerCase() });
|
|
72481
|
+
}
|
|
72482
|
+
return out;
|
|
72281
72483
|
}
|
|
72282
|
-
|
|
72283
|
-
|
|
72284
|
-
|
|
72285
|
-
|
|
72286
|
-
|
|
72287
|
-
|
|
72484
|
+
function parseCognitoUserPoolArn(arn, location) {
|
|
72485
|
+
const m = /^arn:aws[a-z0-9-]*:cognito-idp:([a-z0-9-]+):[0-9]+:userpool\/(.+)$/.exec(arn);
|
|
72486
|
+
if (!m) {
|
|
72487
|
+
throw new RouteDiscoveryError(
|
|
72488
|
+
`${location}: malformed Cognito User Pool ARN '${arn}'. Expected 'arn:aws:cognito-idp:<region>:<account>:userpool/<id>'.`
|
|
72489
|
+
);
|
|
72288
72490
|
}
|
|
72289
|
-
|
|
72290
|
-
|
|
72291
|
-
|
|
72292
|
-
const
|
|
72293
|
-
if (!
|
|
72294
|
-
|
|
72491
|
+
return { region: m[1], userPoolId: m[2] };
|
|
72492
|
+
}
|
|
72493
|
+
function parseCognitoIssuer(issuer) {
|
|
72494
|
+
const m = /^https:\/\/cognito-idp\.([a-z0-9-]+)\.amazonaws\.com\/([^/]+)\/?$/.exec(issuer);
|
|
72495
|
+
if (!m)
|
|
72496
|
+
return void 0;
|
|
72497
|
+
return { region: m[1], userPoolId: m[2] };
|
|
72498
|
+
}
|
|
72499
|
+
function pickStringFromArn(value, location) {
|
|
72500
|
+
if (typeof value === "string")
|
|
72501
|
+
return value;
|
|
72502
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
72503
|
+
const obj = value;
|
|
72504
|
+
if ("Fn::GetAtt" in obj) {
|
|
72505
|
+
const arg = obj["Fn::GetAtt"];
|
|
72506
|
+
if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && arg[1] === "Arn") {
|
|
72507
|
+
throw new RouteDiscoveryError(
|
|
72508
|
+
`${location}: ProviderARNs[0] uses Fn::GetAtt against logical ID '${arg[0]}'. cdkd local start-api needs the literal ARN string to derive the JWKS URL \u2014 set the user pool ARN explicitly via 'authorizer.providerArns' on the CDK construct, or upgrade to JWT (HTTP v2) which encodes the pool in the Issuer URL.`
|
|
72509
|
+
);
|
|
72510
|
+
}
|
|
72511
|
+
}
|
|
72512
|
+
}
|
|
72513
|
+
throw new RouteDiscoveryError(
|
|
72514
|
+
`${location}: ProviderARNs[0] must be a literal string (got ${shortJson2(value)}).`
|
|
72515
|
+
);
|
|
72516
|
+
}
|
|
72517
|
+
function parseTtl(raw, fallback, max) {
|
|
72518
|
+
if (typeof raw !== "number" || !Number.isFinite(raw) || raw < 0)
|
|
72519
|
+
return fallback;
|
|
72520
|
+
return Math.min(Math.trunc(raw), max);
|
|
72521
|
+
}
|
|
72522
|
+
function buildIdentityHash(parts) {
|
|
72523
|
+
return parts.map((p) => p ?? "").join("\0");
|
|
72524
|
+
}
|
|
72525
|
+
function attachAuthorizers(stacks, routes) {
|
|
72526
|
+
const stackByRoute = /* @__PURE__ */ new Map();
|
|
72527
|
+
for (const stack of stacks) {
|
|
72528
|
+
const prefix = `${stack.stackName}/`;
|
|
72529
|
+
for (const route of routes) {
|
|
72530
|
+
if (route.declaredAt.startsWith(prefix))
|
|
72531
|
+
stackByRoute.set(route.declaredAt, stack);
|
|
72532
|
+
}
|
|
72533
|
+
}
|
|
72534
|
+
const out = [];
|
|
72535
|
+
const errors = [];
|
|
72536
|
+
for (const route of routes) {
|
|
72537
|
+
const stack = stackByRoute.get(route.declaredAt);
|
|
72538
|
+
if (!stack) {
|
|
72539
|
+
out.push({ route });
|
|
72540
|
+
continue;
|
|
72541
|
+
}
|
|
72542
|
+
try {
|
|
72543
|
+
const authorizer = detectAuthorizer(route, stack);
|
|
72544
|
+
out.push({ route, ...authorizer && { authorizer } });
|
|
72545
|
+
} catch (err) {
|
|
72546
|
+
errors.push(err instanceof Error ? err.message : String(err));
|
|
72547
|
+
}
|
|
72548
|
+
}
|
|
72549
|
+
if (errors.length > 0) {
|
|
72550
|
+
throw new RouteDiscoveryError(
|
|
72551
|
+
`cdkd local start-api: ${errors.length} authorizer error(s):
|
|
72552
|
+
` + errors.map((e) => ` - ${e}`).join("\n")
|
|
72553
|
+
);
|
|
72554
|
+
}
|
|
72555
|
+
return out;
|
|
72556
|
+
}
|
|
72557
|
+
function detectAuthorizer(route, stack) {
|
|
72558
|
+
const slash = route.declaredAt.indexOf("/");
|
|
72559
|
+
if (slash < 0)
|
|
72560
|
+
return void 0;
|
|
72561
|
+
const logicalId = route.declaredAt.slice(slash + 1);
|
|
72562
|
+
const resource = stack.template.Resources?.[logicalId];
|
|
72563
|
+
if (!resource)
|
|
72564
|
+
return void 0;
|
|
72565
|
+
if (resource.Type === "AWS::ApiGateway::Method") {
|
|
72566
|
+
return detectRestV1Authorizer(resource, logicalId, stack);
|
|
72567
|
+
}
|
|
72568
|
+
if (resource.Type === "AWS::ApiGatewayV2::Route") {
|
|
72569
|
+
return detectHttpApiAuthorizer(resource, logicalId, stack);
|
|
72570
|
+
}
|
|
72571
|
+
return void 0;
|
|
72572
|
+
}
|
|
72573
|
+
function detectRestV1Authorizer(methodResource, methodLogicalId, stack) {
|
|
72574
|
+
const props = methodResource.Properties ?? {};
|
|
72575
|
+
const authType = props["AuthorizationType"];
|
|
72576
|
+
if (authType === void 0 || authType === "NONE")
|
|
72577
|
+
return void 0;
|
|
72578
|
+
const authorizerId = props["AuthorizerId"];
|
|
72579
|
+
const refLogicalId = pickRefLogicalId2(authorizerId);
|
|
72580
|
+
if (authType === "AWS_IAM") {
|
|
72581
|
+
throw new RouteDiscoveryError(
|
|
72582
|
+
`${stack.stackName}/${methodLogicalId}: REST v1 AWS_IAM authorization is not supported by cdkd local start-api (deferred follow-up PR).`
|
|
72583
|
+
);
|
|
72584
|
+
}
|
|
72585
|
+
if (!refLogicalId) {
|
|
72586
|
+
throw new RouteDiscoveryError(
|
|
72587
|
+
`${stack.stackName}/${methodLogicalId}: AuthorizationType='${String(authType)}' but AuthorizerId is missing or not a {Ref:...}.`
|
|
72588
|
+
);
|
|
72589
|
+
}
|
|
72590
|
+
return resolveRestV1Authorizer(
|
|
72591
|
+
refLogicalId,
|
|
72592
|
+
stack.template,
|
|
72593
|
+
stack.stackName,
|
|
72594
|
+
`${stack.stackName}/${methodLogicalId}`
|
|
72595
|
+
);
|
|
72596
|
+
}
|
|
72597
|
+
function detectHttpApiAuthorizer(routeResource, routeLogicalId, stack) {
|
|
72598
|
+
const props = routeResource.Properties ?? {};
|
|
72599
|
+
const authType = props["AuthorizationType"];
|
|
72600
|
+
if (authType === void 0 || authType === "NONE")
|
|
72601
|
+
return void 0;
|
|
72602
|
+
const authorizerId = props["AuthorizerId"];
|
|
72603
|
+
const refLogicalId = pickRefLogicalId2(authorizerId);
|
|
72604
|
+
if (!refLogicalId) {
|
|
72605
|
+
throw new RouteDiscoveryError(
|
|
72606
|
+
`${stack.stackName}/${routeLogicalId}: AuthorizationType='${String(authType)}' but AuthorizerId is missing or not a {Ref:...}.`
|
|
72607
|
+
);
|
|
72608
|
+
}
|
|
72609
|
+
const scopesRaw = props["AuthorizationScopes"];
|
|
72610
|
+
const scopes = Array.isArray(scopesRaw) ? scopesRaw.filter((s) => typeof s === "string") : void 0;
|
|
72611
|
+
return resolveHttpApiAuthorizer(
|
|
72612
|
+
refLogicalId,
|
|
72613
|
+
scopes,
|
|
72614
|
+
stack.template,
|
|
72615
|
+
stack.stackName,
|
|
72616
|
+
`${stack.stackName}/${routeLogicalId}`
|
|
72617
|
+
);
|
|
72618
|
+
}
|
|
72619
|
+
function pickRefLogicalId2(value) {
|
|
72620
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
72621
|
+
const ref = value["Ref"];
|
|
72622
|
+
if (typeof ref === "string")
|
|
72623
|
+
return ref;
|
|
72624
|
+
}
|
|
72625
|
+
return null;
|
|
72626
|
+
}
|
|
72627
|
+
function shortJson2(value) {
|
|
72628
|
+
try {
|
|
72629
|
+
const s = JSON.stringify(value);
|
|
72630
|
+
return s.length > 200 ? `${s.slice(0, 200)}\u2026` : s;
|
|
72631
|
+
} catch {
|
|
72632
|
+
return String(value);
|
|
72633
|
+
}
|
|
72634
|
+
}
|
|
72635
|
+
|
|
72636
|
+
// src/local/lambda-authorizer.ts
|
|
72637
|
+
function buildMethodArn(opts) {
|
|
72638
|
+
const region = opts.region ?? "local";
|
|
72639
|
+
const trimmedPath = opts.path.replace(/^\//, "");
|
|
72640
|
+
return `arn:aws:execute-api:${region}:${opts.accountId}:${opts.apiId}/${opts.stage}/${opts.method.toUpperCase()}/${trimmedPath}`;
|
|
72641
|
+
}
|
|
72642
|
+
async function invokeTokenAuthorizer(authorizer, request, ctx) {
|
|
72643
|
+
const token = request.headers[authorizer.tokenHeader];
|
|
72644
|
+
if (!token || token.length === 0) {
|
|
72645
|
+
return { allow: false, identityHash: void 0 };
|
|
72646
|
+
}
|
|
72647
|
+
const event = {
|
|
72648
|
+
type: "TOKEN",
|
|
72649
|
+
authorizationToken: token,
|
|
72650
|
+
methodArn: ctx.methodArn
|
|
72651
|
+
};
|
|
72652
|
+
const identityHash = buildIdentityHash([token]);
|
|
72653
|
+
const result = await invokeAuthorizerLambda(authorizer.lambdaLogicalId, event, ctx);
|
|
72654
|
+
return parseLambdaAuthorizerResponse(result, ctx.methodArn, identityHash);
|
|
72655
|
+
}
|
|
72656
|
+
async function invokeRequestAuthorizer(authorizer, request, ctx) {
|
|
72657
|
+
const { identityHash, missing } = computeRequestIdentityHash(authorizer, request);
|
|
72658
|
+
if (missing) {
|
|
72659
|
+
return { allow: false, identityHash: void 0 };
|
|
72660
|
+
}
|
|
72661
|
+
const event = authorizer.apiVersion === "v1" ? buildRequestEventV1(authorizer, request, ctx) : buildRequestEventV2(authorizer, request, ctx);
|
|
72662
|
+
const result = await invokeAuthorizerLambda(authorizer.lambdaLogicalId, event, ctx);
|
|
72663
|
+
if (authorizer.apiVersion === "v2") {
|
|
72664
|
+
return parseHttpV2RequestResponse(result, ctx.methodArn, identityHash);
|
|
72665
|
+
}
|
|
72666
|
+
return parseLambdaAuthorizerResponse(result, ctx.methodArn, identityHash);
|
|
72667
|
+
}
|
|
72668
|
+
function extractIdentityValue(sel, request) {
|
|
72669
|
+
switch (sel.kind) {
|
|
72670
|
+
case "header":
|
|
72671
|
+
return request.headers[sel.name];
|
|
72672
|
+
case "query":
|
|
72673
|
+
return request.queryStringParameters[sel.name];
|
|
72674
|
+
case "context":
|
|
72675
|
+
return void 0;
|
|
72676
|
+
case "stage-variable":
|
|
72677
|
+
return void 0;
|
|
72678
|
+
}
|
|
72679
|
+
}
|
|
72680
|
+
function computeRequestIdentityHash(authorizer, request) {
|
|
72681
|
+
const identityValues = authorizer.identitySources.map(
|
|
72682
|
+
(sel) => extractIdentityValue(sel, request)
|
|
72683
|
+
);
|
|
72684
|
+
const missing = authorizer.apiVersion === "v1" && authorizer.identitySources.length > 0 && identityValues.every((v) => v === void 0 || v === "");
|
|
72685
|
+
return { identityHash: buildIdentityHash(identityValues), missing };
|
|
72686
|
+
}
|
|
72687
|
+
function buildRequestEventV1(authorizer, request, ctx) {
|
|
72688
|
+
return {
|
|
72689
|
+
type: "REQUEST",
|
|
72690
|
+
methodArn: ctx.methodArn,
|
|
72691
|
+
resource: request.matchedPath,
|
|
72692
|
+
path: request.matchedPath,
|
|
72693
|
+
httpMethod: request.method,
|
|
72694
|
+
headers: request.headers,
|
|
72695
|
+
multiValueHeaders: Object.fromEntries(
|
|
72696
|
+
Object.entries(request.headers).map(([k, v]) => [k, v.split(",")])
|
|
72697
|
+
),
|
|
72698
|
+
queryStringParameters: request.queryStringParameters,
|
|
72699
|
+
multiValueQueryStringParameters: Object.fromEntries(
|
|
72700
|
+
Object.entries(request.queryStringParameters).map(([k, v]) => [k, [v]])
|
|
72701
|
+
),
|
|
72702
|
+
pathParameters: request.pathParameters,
|
|
72703
|
+
stageVariables: null,
|
|
72704
|
+
requestContext: {
|
|
72705
|
+
accountId: ctx.mockAccountId,
|
|
72706
|
+
apiId: ctx.mockApiId,
|
|
72707
|
+
httpMethod: request.method,
|
|
72708
|
+
identity: { sourceIp: request.sourceIp },
|
|
72709
|
+
path: `/${request.stage}${request.matchedPath}`,
|
|
72710
|
+
stage: request.stage
|
|
72711
|
+
},
|
|
72712
|
+
authorizationToken: request.headers[authorizer.identitySources[0]?.name ?? "authorization"]
|
|
72713
|
+
};
|
|
72714
|
+
}
|
|
72715
|
+
function buildRequestEventV2(_authorizer, request, ctx) {
|
|
72716
|
+
return {
|
|
72717
|
+
version: "2.0",
|
|
72718
|
+
type: "REQUEST",
|
|
72719
|
+
routeArn: ctx.methodArn,
|
|
72720
|
+
identitySource: [],
|
|
72721
|
+
// Honored by AWS but not interpreted by user code.
|
|
72722
|
+
routeKey: `${request.method} ${request.matchedPath}`,
|
|
72723
|
+
rawPath: request.matchedPath,
|
|
72724
|
+
rawQueryString: "",
|
|
72725
|
+
headers: request.headers,
|
|
72726
|
+
queryStringParameters: request.queryStringParameters,
|
|
72727
|
+
pathParameters: request.pathParameters,
|
|
72728
|
+
stageVariables: null,
|
|
72729
|
+
requestContext: {
|
|
72730
|
+
accountId: ctx.mockAccountId,
|
|
72731
|
+
apiId: ctx.mockApiId,
|
|
72732
|
+
domainName: "localhost",
|
|
72733
|
+
domainPrefix: "local",
|
|
72734
|
+
http: {
|
|
72735
|
+
method: request.method,
|
|
72736
|
+
path: request.matchedPath,
|
|
72737
|
+
protocol: "HTTP/1.1",
|
|
72738
|
+
sourceIp: request.sourceIp,
|
|
72739
|
+
userAgent: request.headers["user-agent"] ?? ""
|
|
72740
|
+
},
|
|
72741
|
+
requestId: "local-authorizer",
|
|
72742
|
+
routeKey: `${request.method} ${request.matchedPath}`,
|
|
72743
|
+
stage: request.stage,
|
|
72744
|
+
time: "",
|
|
72745
|
+
timeEpoch: 0
|
|
72746
|
+
}
|
|
72747
|
+
};
|
|
72748
|
+
}
|
|
72749
|
+
async function invokeAuthorizerLambda(lambdaLogicalId, event, ctx) {
|
|
72750
|
+
const handle = await ctx.pool.acquire(lambdaLogicalId);
|
|
72751
|
+
try {
|
|
72752
|
+
const result = await invokeRie(handle.containerHost, handle.hostPort, event, ctx.rieTimeoutMs);
|
|
72753
|
+
return result.payload;
|
|
72754
|
+
} finally {
|
|
72755
|
+
ctx.pool.release(handle);
|
|
72756
|
+
}
|
|
72757
|
+
}
|
|
72758
|
+
function parseLambdaAuthorizerResponse(payload, methodArn, identityHash) {
|
|
72759
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
72760
|
+
return { allow: false, identityHash };
|
|
72761
|
+
}
|
|
72762
|
+
const obj = payload;
|
|
72763
|
+
const principalId = typeof obj["principalId"] === "string" ? obj["principalId"] : void 0;
|
|
72764
|
+
const context = obj["context"] && typeof obj["context"] === "object" && !Array.isArray(obj["context"]) ? obj["context"] : void 0;
|
|
72765
|
+
const policy = obj["policyDocument"];
|
|
72766
|
+
if (!policy || typeof policy !== "object") {
|
|
72767
|
+
return {
|
|
72768
|
+
allow: false,
|
|
72769
|
+
identityHash,
|
|
72770
|
+
...principalId !== void 0 && { principalId },
|
|
72771
|
+
...context && { context }
|
|
72772
|
+
};
|
|
72773
|
+
}
|
|
72774
|
+
const stmts = policy["Statement"];
|
|
72775
|
+
if (!Array.isArray(stmts)) {
|
|
72776
|
+
return {
|
|
72777
|
+
allow: false,
|
|
72778
|
+
identityHash,
|
|
72779
|
+
...principalId !== void 0 && { principalId },
|
|
72780
|
+
...context && { context },
|
|
72781
|
+
policy
|
|
72782
|
+
};
|
|
72783
|
+
}
|
|
72784
|
+
const allow = stmts.some((stmt) => {
|
|
72785
|
+
if (!stmt || typeof stmt !== "object" || Array.isArray(stmt))
|
|
72786
|
+
return false;
|
|
72787
|
+
const s = stmt;
|
|
72788
|
+
if (s["Effect"] !== "Allow")
|
|
72789
|
+
return false;
|
|
72790
|
+
const resources = Array.isArray(s["Resource"]) ? s["Resource"] : [s["Resource"]];
|
|
72791
|
+
return resources.some((r) => typeof r === "string" && resourceMatches(r, methodArn));
|
|
72792
|
+
});
|
|
72793
|
+
return {
|
|
72794
|
+
allow,
|
|
72795
|
+
identityHash,
|
|
72796
|
+
...principalId !== void 0 && { principalId },
|
|
72797
|
+
...context && { context },
|
|
72798
|
+
policy
|
|
72799
|
+
};
|
|
72800
|
+
}
|
|
72801
|
+
function parseHttpV2RequestResponse(payload, methodArn, identityHash) {
|
|
72802
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
72803
|
+
const obj = payload;
|
|
72804
|
+
if (typeof obj["isAuthorized"] === "boolean") {
|
|
72805
|
+
const context = obj["context"] && typeof obj["context"] === "object" && !Array.isArray(obj["context"]) ? obj["context"] : void 0;
|
|
72806
|
+
return {
|
|
72807
|
+
allow: obj["isAuthorized"],
|
|
72808
|
+
identityHash,
|
|
72809
|
+
...context && { context }
|
|
72810
|
+
};
|
|
72811
|
+
}
|
|
72812
|
+
}
|
|
72813
|
+
return parseLambdaAuthorizerResponse(payload, methodArn, identityHash);
|
|
72814
|
+
}
|
|
72815
|
+
function resourceMatches(pattern, methodArn) {
|
|
72816
|
+
if (pattern === methodArn)
|
|
72817
|
+
return true;
|
|
72818
|
+
if (!pattern.includes("*") && !pattern.includes("?"))
|
|
72819
|
+
return false;
|
|
72820
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
72821
|
+
const re = new RegExp(`^${escaped}$`);
|
|
72822
|
+
return re.test(methodArn);
|
|
72823
|
+
}
|
|
72824
|
+
function evaluateCachedLambdaPolicy(cached, methodArn) {
|
|
72825
|
+
const policy = cached.policy;
|
|
72826
|
+
if (!policy || typeof policy !== "object") {
|
|
72827
|
+
return { ...cached, allow: false };
|
|
72828
|
+
}
|
|
72829
|
+
const stmts = policy["Statement"];
|
|
72830
|
+
if (!Array.isArray(stmts)) {
|
|
72831
|
+
return { ...cached, allow: false };
|
|
72832
|
+
}
|
|
72833
|
+
const allow = stmts.some((stmt) => {
|
|
72834
|
+
if (!stmt || typeof stmt !== "object" || Array.isArray(stmt))
|
|
72835
|
+
return false;
|
|
72836
|
+
const s = stmt;
|
|
72837
|
+
if (s["Effect"] !== "Allow")
|
|
72838
|
+
return false;
|
|
72839
|
+
const resources = Array.isArray(s["Resource"]) ? s["Resource"] : [s["Resource"]];
|
|
72840
|
+
return resources.some((r) => typeof r === "string" && resourceMatches(r, methodArn));
|
|
72841
|
+
});
|
|
72842
|
+
return { ...cached, allow };
|
|
72843
|
+
}
|
|
72844
|
+
|
|
72845
|
+
// src/local/cognito-jwt.ts
|
|
72846
|
+
import { createPublicKey, createVerify } from "node:crypto";
|
|
72847
|
+
var DEFAULT_JWKS_TTL_MS = 60 * 60 * 1e3;
|
|
72848
|
+
var FAILURE_JWKS_TTL_MS = 60 * 1e3;
|
|
72849
|
+
function createJwksCache(opts = {}) {
|
|
72850
|
+
const fetchImpl = opts.fetchImpl ?? (async (url) => globalThis.fetch(url));
|
|
72851
|
+
const now = opts.now ?? (() => Date.now());
|
|
72852
|
+
const ttlMs = opts.ttlMs ?? DEFAULT_JWKS_TTL_MS;
|
|
72853
|
+
const failureTtlMs = opts.failureTtlMs ?? FAILURE_JWKS_TTL_MS;
|
|
72854
|
+
const map = /* @__PURE__ */ new Map();
|
|
72855
|
+
return {
|
|
72856
|
+
async fetchAndCache(jwksUrl) {
|
|
72857
|
+
const cached = map.get(jwksUrl);
|
|
72858
|
+
if (cached && cached.expiresAt > now())
|
|
72859
|
+
return cached;
|
|
72860
|
+
const logger = getLogger().child("cognito-jwt");
|
|
72861
|
+
try {
|
|
72862
|
+
const response = await fetchImpl(jwksUrl);
|
|
72863
|
+
if (!response.ok) {
|
|
72864
|
+
throw new Error(`JWKS fetch returned HTTP ${response.status}`);
|
|
72865
|
+
}
|
|
72866
|
+
const body = await response.text();
|
|
72867
|
+
const parsed = JSON.parse(body);
|
|
72868
|
+
const keys = Array.isArray(parsed.keys) ? parsed.keys : [];
|
|
72869
|
+
const byKid = /* @__PURE__ */ new Map();
|
|
72870
|
+
for (const k of keys) {
|
|
72871
|
+
if (!k || typeof k !== "object" || Array.isArray(k))
|
|
72872
|
+
continue;
|
|
72873
|
+
const obj = k;
|
|
72874
|
+
if (typeof obj["kid"] === "string" && typeof obj["n"] === "string" && typeof obj["e"] === "string" && typeof obj["kty"] === "string") {
|
|
72875
|
+
byKid.set(obj["kid"], {
|
|
72876
|
+
kid: obj["kid"],
|
|
72877
|
+
n: obj["n"],
|
|
72878
|
+
e: obj["e"],
|
|
72879
|
+
kty: obj["kty"],
|
|
72880
|
+
...typeof obj["alg"] === "string" && { alg: obj["alg"] },
|
|
72881
|
+
...typeof obj["use"] === "string" && { use: obj["use"] }
|
|
72882
|
+
});
|
|
72883
|
+
}
|
|
72884
|
+
}
|
|
72885
|
+
const entry = {
|
|
72886
|
+
byKid,
|
|
72887
|
+
expiresAt: now() + ttlMs,
|
|
72888
|
+
passThrough: false
|
|
72889
|
+
};
|
|
72890
|
+
map.set(jwksUrl, entry);
|
|
72891
|
+
return entry;
|
|
72892
|
+
} catch (err) {
|
|
72893
|
+
logger.warn(
|
|
72894
|
+
`JWKS unreachable at ${jwksUrl}: ${err instanceof Error ? err.message : String(err)}. JWT validation will allow all tokens \u2014 local dev fallback. Configure network access to the JWKS URL to enable real signature verification.`
|
|
72895
|
+
);
|
|
72896
|
+
const entry = {
|
|
72897
|
+
byKid: /* @__PURE__ */ new Map(),
|
|
72898
|
+
expiresAt: now() + failureTtlMs,
|
|
72899
|
+
passThrough: true
|
|
72900
|
+
};
|
|
72901
|
+
map.set(jwksUrl, entry);
|
|
72902
|
+
return entry;
|
|
72903
|
+
}
|
|
72904
|
+
},
|
|
72905
|
+
peek(jwksUrl) {
|
|
72906
|
+
return map.get(jwksUrl);
|
|
72907
|
+
},
|
|
72908
|
+
clear() {
|
|
72909
|
+
map.clear();
|
|
72910
|
+
}
|
|
72911
|
+
};
|
|
72912
|
+
}
|
|
72913
|
+
function buildCognitoJwksUrl(region, userPoolId) {
|
|
72914
|
+
return `https://cognito-idp.${region}.amazonaws.com/${userPoolId}/.well-known/jwks.json`;
|
|
72915
|
+
}
|
|
72916
|
+
function buildJwksUrlFromIssuer(issuer) {
|
|
72917
|
+
const stripped = issuer.replace(/\/+$/, "");
|
|
72918
|
+
return `${stripped}/.well-known/jwks.json`;
|
|
72919
|
+
}
|
|
72920
|
+
async function verifyCognitoJwt(authorizer, authorizationHeader, jwksCache, opts = {}) {
|
|
72921
|
+
const now = opts.now ?? (() => Date.now());
|
|
72922
|
+
const token = extractBearer(authorizationHeader);
|
|
72923
|
+
if (!token) {
|
|
72924
|
+
return { allow: false, identityHash: void 0, ttlSeconds: 0 };
|
|
72925
|
+
}
|
|
72926
|
+
const jwksUrl = buildCognitoJwksUrl(authorizer.region, authorizer.userPoolId);
|
|
72927
|
+
const expectedIssuer = `https://cognito-idp.${authorizer.region}.amazonaws.com/${authorizer.userPoolId}`;
|
|
72928
|
+
return verifyAndShape(token, jwksUrl, expectedIssuer, void 0, jwksCache, opts.warned, now);
|
|
72929
|
+
}
|
|
72930
|
+
async function verifyJwtAuthorizer(authorizer, authorizationHeader, jwksCache, opts = {}) {
|
|
72931
|
+
const now = opts.now ?? (() => Date.now());
|
|
72932
|
+
const token = extractBearer(authorizationHeader);
|
|
72933
|
+
if (!token) {
|
|
72934
|
+
return { allow: false, identityHash: void 0, ttlSeconds: 0 };
|
|
72935
|
+
}
|
|
72936
|
+
const jwksUrl = authorizer.region && authorizer.userPoolId ? buildCognitoJwksUrl(authorizer.region, authorizer.userPoolId) : buildJwksUrlFromIssuer(authorizer.issuer);
|
|
72937
|
+
return verifyAndShape(
|
|
72938
|
+
token,
|
|
72939
|
+
jwksUrl,
|
|
72940
|
+
authorizer.issuer.replace(/\/+$/, ""),
|
|
72941
|
+
authorizer.audience,
|
|
72942
|
+
jwksCache,
|
|
72943
|
+
opts.warned,
|
|
72944
|
+
now
|
|
72945
|
+
);
|
|
72946
|
+
}
|
|
72947
|
+
async function verifyAndShape(token, jwksUrl, expectedIssuer, expectedAudience, jwksCache, warned, now) {
|
|
72948
|
+
const identityHash = buildIdentityHash([token]);
|
|
72949
|
+
const jwks = await jwksCache.fetchAndCache(jwksUrl);
|
|
72950
|
+
if (jwks.passThrough) {
|
|
72951
|
+
if (warned && !warned.has(jwksUrl)) {
|
|
72952
|
+
warned.add(jwksUrl);
|
|
72953
|
+
getLogger().child("cognito-jwt").warn(
|
|
72954
|
+
`JWKS pass-through mode for ${jwksUrl}: token accepted without signature verification.`
|
|
72955
|
+
);
|
|
72956
|
+
}
|
|
72957
|
+
const parsed2 = parseJwt(token);
|
|
72958
|
+
if (parsed2) {
|
|
72959
|
+
return shapeAllowResult(parsed2, identityHash, now);
|
|
72960
|
+
}
|
|
72961
|
+
return {
|
|
72962
|
+
allow: true,
|
|
72963
|
+
principalId: "unknown",
|
|
72964
|
+
context: {},
|
|
72965
|
+
identityHash,
|
|
72966
|
+
ttlSeconds: 0
|
|
72967
|
+
};
|
|
72968
|
+
}
|
|
72969
|
+
const parsed = parseJwt(token);
|
|
72970
|
+
if (!parsed) {
|
|
72971
|
+
return { allow: false, identityHash, ttlSeconds: 0 };
|
|
72972
|
+
}
|
|
72973
|
+
const kid = parsed.header["kid"];
|
|
72974
|
+
if (typeof kid !== "string") {
|
|
72975
|
+
return { allow: false, identityHash, ttlSeconds: 0 };
|
|
72976
|
+
}
|
|
72977
|
+
const key = jwks.byKid.get(kid);
|
|
72978
|
+
if (!key) {
|
|
72979
|
+
return { allow: false, identityHash, ttlSeconds: 0 };
|
|
72980
|
+
}
|
|
72981
|
+
if (!verifyRs256(token, key)) {
|
|
72982
|
+
return { allow: false, identityHash, ttlSeconds: 0 };
|
|
72983
|
+
}
|
|
72984
|
+
const claims = parsed.payload;
|
|
72985
|
+
if (typeof claims["exp"] !== "number" || claims["exp"] * 1e3 <= now()) {
|
|
72986
|
+
return { allow: false, identityHash, ttlSeconds: 0 };
|
|
72987
|
+
}
|
|
72988
|
+
if (typeof claims["iss"] !== "string" || claims["iss"].replace(/\/+$/, "") !== expectedIssuer) {
|
|
72989
|
+
return { allow: false, identityHash, ttlSeconds: 0 };
|
|
72990
|
+
}
|
|
72991
|
+
if (expectedAudience && expectedAudience.length > 0) {
|
|
72992
|
+
const aud = claims["aud"];
|
|
72993
|
+
const clientId = claims["client_id"];
|
|
72994
|
+
const audValues = Array.isArray(aud) ? aud : aud !== void 0 ? [aud] : [];
|
|
72995
|
+
const matches = audValues.some((v) => typeof v === "string" && expectedAudience.includes(v)) || typeof clientId === "string" && expectedAudience.includes(clientId);
|
|
72996
|
+
if (!matches) {
|
|
72997
|
+
return { allow: false, identityHash, ttlSeconds: 0 };
|
|
72998
|
+
}
|
|
72999
|
+
}
|
|
73000
|
+
return shapeAllowResult(parsed, identityHash, now);
|
|
73001
|
+
}
|
|
73002
|
+
function shapeAllowResult(parsed, identityHash, now) {
|
|
73003
|
+
const claims = parsed.payload;
|
|
73004
|
+
const principalId = pickStringClaim(claims, "sub") ?? pickStringClaim(claims, "cognito:username") ?? pickStringClaim(claims, "username") ?? "unknown";
|
|
73005
|
+
const expMs = typeof claims["exp"] === "number" ? claims["exp"] * 1e3 : 0;
|
|
73006
|
+
const remainingSeconds = Math.max(0, Math.floor((expMs - now()) / 1e3));
|
|
73007
|
+
const ttlSeconds = Math.min(300, remainingSeconds);
|
|
73008
|
+
return {
|
|
73009
|
+
allow: true,
|
|
73010
|
+
principalId,
|
|
73011
|
+
context: claims,
|
|
73012
|
+
identityHash,
|
|
73013
|
+
ttlSeconds
|
|
73014
|
+
};
|
|
73015
|
+
}
|
|
73016
|
+
function pickStringClaim(claims, key) {
|
|
73017
|
+
const v = claims[key];
|
|
73018
|
+
return typeof v === "string" ? v : void 0;
|
|
73019
|
+
}
|
|
73020
|
+
function extractBearer(header) {
|
|
73021
|
+
if (!header)
|
|
73022
|
+
return void 0;
|
|
73023
|
+
const m = /^\s*Bearer\s+(.+)\s*$/i.exec(header);
|
|
73024
|
+
if (!m)
|
|
73025
|
+
return void 0;
|
|
73026
|
+
return m[1].trim();
|
|
73027
|
+
}
|
|
73028
|
+
function parseJwt(token) {
|
|
73029
|
+
const parts = token.split(".");
|
|
73030
|
+
if (parts.length !== 3)
|
|
73031
|
+
return void 0;
|
|
73032
|
+
try {
|
|
73033
|
+
const headerJson = base64UrlDecodeToString(parts[0]);
|
|
73034
|
+
const payloadJson = base64UrlDecodeToString(parts[1]);
|
|
73035
|
+
const header = JSON.parse(headerJson);
|
|
73036
|
+
const payload = JSON.parse(payloadJson);
|
|
73037
|
+
return {
|
|
73038
|
+
header,
|
|
73039
|
+
payload,
|
|
73040
|
+
signingInput: `${parts[0]}.${parts[1]}`,
|
|
73041
|
+
signatureB64: parts[2]
|
|
73042
|
+
};
|
|
73043
|
+
} catch {
|
|
73044
|
+
return void 0;
|
|
73045
|
+
}
|
|
73046
|
+
}
|
|
73047
|
+
function verifyRs256(token, key) {
|
|
73048
|
+
const parts = token.split(".");
|
|
73049
|
+
if (parts.length !== 3)
|
|
73050
|
+
return false;
|
|
73051
|
+
const signingInput = `${parts[0]}.${parts[1]}`;
|
|
73052
|
+
const signature = base64UrlDecodeToBuffer(parts[2]);
|
|
73053
|
+
try {
|
|
73054
|
+
const publicKey = createPublicKey({
|
|
73055
|
+
key: { kty: key.kty, n: key.n, e: key.e },
|
|
73056
|
+
format: "jwk"
|
|
73057
|
+
});
|
|
73058
|
+
const verifier = createVerify("RSA-SHA256");
|
|
73059
|
+
verifier.update(signingInput);
|
|
73060
|
+
verifier.end();
|
|
73061
|
+
return verifier.verify(publicKey, signature);
|
|
73062
|
+
} catch {
|
|
73063
|
+
return false;
|
|
73064
|
+
}
|
|
73065
|
+
}
|
|
73066
|
+
function base64UrlDecodeToString(input) {
|
|
73067
|
+
return base64UrlDecodeToBuffer(input).toString("utf-8");
|
|
73068
|
+
}
|
|
73069
|
+
function base64UrlDecodeToBuffer(input) {
|
|
73070
|
+
const padded = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
73071
|
+
const padding = padded.length % 4 === 0 ? "" : "=".repeat(4 - padded.length % 4);
|
|
73072
|
+
return Buffer.from(padded + padding, "base64");
|
|
73073
|
+
}
|
|
73074
|
+
|
|
73075
|
+
// src/local/http-server.ts
|
|
73076
|
+
async function startApiServer(opts) {
|
|
73077
|
+
const logger = getLogger().child("start-api");
|
|
73078
|
+
const server = createServer2((req, res) => {
|
|
73079
|
+
handleRequest(req, res, opts).catch((err) => {
|
|
73080
|
+
logger.error(
|
|
73081
|
+
`Unhandled request error: ${err instanceof Error ? err.stack ?? err.message : String(err)}`
|
|
73082
|
+
);
|
|
73083
|
+
if (!res.headersSent) {
|
|
73084
|
+
writeError(res, 502);
|
|
73085
|
+
}
|
|
73086
|
+
});
|
|
73087
|
+
});
|
|
73088
|
+
server.on("connection", (socket) => {
|
|
73089
|
+
socket.setNoDelay(true);
|
|
73090
|
+
});
|
|
73091
|
+
const { actualPort, actualHost } = await new Promise(
|
|
73092
|
+
(resolveListen, rejectListen) => {
|
|
73093
|
+
server.once("error", rejectListen);
|
|
73094
|
+
server.listen(opts.port, opts.host, () => {
|
|
73095
|
+
const addr = server.address();
|
|
73096
|
+
if (addr === null || typeof addr === "string") {
|
|
73097
|
+
rejectListen(new Error("Could not determine listening address"));
|
|
73098
|
+
return;
|
|
73099
|
+
}
|
|
73100
|
+
resolveListen({ actualPort: addr.port, actualHost: opts.host });
|
|
73101
|
+
});
|
|
73102
|
+
}
|
|
73103
|
+
);
|
|
73104
|
+
let closed = false;
|
|
73105
|
+
return {
|
|
73106
|
+
port: actualPort,
|
|
73107
|
+
host: actualHost,
|
|
73108
|
+
server,
|
|
73109
|
+
close: async () => {
|
|
73110
|
+
if (closed)
|
|
73111
|
+
return;
|
|
73112
|
+
closed = true;
|
|
73113
|
+
await new Promise((resolveClose) => {
|
|
73114
|
+
server.close(() => resolveClose());
|
|
73115
|
+
server.closeAllConnections?.();
|
|
73116
|
+
});
|
|
73117
|
+
}
|
|
73118
|
+
};
|
|
73119
|
+
}
|
|
73120
|
+
async function handleRequest(req, res, opts) {
|
|
73121
|
+
const logger = getLogger().child("start-api");
|
|
73122
|
+
const bodyBuf = await readBody(req);
|
|
73123
|
+
const rawUrl = req.url ?? "/";
|
|
73124
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
73125
|
+
const requestPath = rawUrl.split("?")[0] ?? "/";
|
|
73126
|
+
const flatRoutes = opts.routes.map((r) => r.route);
|
|
73127
|
+
const match = matchRoute(method, requestPath, flatRoutes);
|
|
73128
|
+
if (!match) {
|
|
73129
|
+
writeError(res, 404, '{"message":"Not Found"}');
|
|
73130
|
+
return;
|
|
73131
|
+
}
|
|
73132
|
+
const matchedEntry = opts.routes.find(
|
|
73133
|
+
(r) => r.route.declaredAt === match.route.declaredAt && r.route.method === match.route.method
|
|
73134
|
+
);
|
|
73135
|
+
const authorizer = matchedEntry?.authorizer;
|
|
73136
|
+
const snapshot = {
|
|
73137
|
+
method,
|
|
73138
|
+
rawUrl,
|
|
73139
|
+
headers: collectHeaders(req),
|
|
73140
|
+
body: bodyBuf,
|
|
73141
|
+
...req.socket.remoteAddress !== void 0 && { sourceIp: req.socket.remoteAddress }
|
|
73142
|
+
};
|
|
73143
|
+
const matchCtx = {
|
|
73144
|
+
route: match.route,
|
|
73145
|
+
pathParameters: match.pathParameters,
|
|
73146
|
+
matchedPath: requestPath
|
|
73147
|
+
};
|
|
73148
|
+
let baseEvent = match.route.apiVersion === "v1" ? buildRestV1Event(snapshot, matchCtx) : buildHttpApiV2Event(snapshot, matchCtx);
|
|
73149
|
+
let authResult;
|
|
73150
|
+
if (authorizer) {
|
|
73151
|
+
let outcome;
|
|
73152
|
+
try {
|
|
73153
|
+
outcome = await runAuthorizerPass(
|
|
73154
|
+
authorizer,
|
|
73155
|
+
snapshot,
|
|
73156
|
+
matchCtx,
|
|
73157
|
+
opts,
|
|
73158
|
+
baseEvent["requestContext"]
|
|
73159
|
+
);
|
|
73160
|
+
} catch (err) {
|
|
73161
|
+
logger.error(
|
|
73162
|
+
`Authorizer ${authorizer.logicalId} threw for ${match.route.declaredAt}: ${err instanceof Error ? err.message : String(err)}`
|
|
73163
|
+
);
|
|
73164
|
+
writeAuthRejection(res, match.route.apiVersion, "policy-deny");
|
|
73165
|
+
return;
|
|
73166
|
+
}
|
|
73167
|
+
if (!outcome.result.allow) {
|
|
73168
|
+
writeAuthRejection(res, match.route.apiVersion, outcome.denyKind ?? "policy-deny");
|
|
73169
|
+
return;
|
|
73170
|
+
}
|
|
73171
|
+
authResult = outcome.result;
|
|
73172
|
+
const overlay = buildOverlay(authorizer, authResult);
|
|
73173
|
+
if (overlay) {
|
|
73174
|
+
baseEvent = applyAuthorizerOverlay(baseEvent, overlay);
|
|
73175
|
+
}
|
|
73176
|
+
}
|
|
73177
|
+
let handle;
|
|
73178
|
+
try {
|
|
73179
|
+
handle = await opts.pool.acquire(match.route.lambdaLogicalId);
|
|
73180
|
+
} catch (err) {
|
|
73181
|
+
logger.error(
|
|
73182
|
+
`Failed to acquire container for ${match.route.lambdaLogicalId}: ${err instanceof Error ? err.message : String(err)}`
|
|
73183
|
+
);
|
|
73184
|
+
writeError(res, 502);
|
|
73185
|
+
return;
|
|
73186
|
+
}
|
|
73187
|
+
try {
|
|
73188
|
+
const invokeResult = await invokeRie(
|
|
73189
|
+
handle.containerHost,
|
|
73190
|
+
handle.hostPort,
|
|
73191
|
+
baseEvent,
|
|
73192
|
+
opts.rieTimeoutMs
|
|
73193
|
+
);
|
|
73194
|
+
const translated = translateLambdaResponse(invokeResult.payload, match.route.apiVersion);
|
|
73195
|
+
res.statusCode = translated.statusCode;
|
|
73196
|
+
for (const [name, value] of Object.entries(translated.headers)) {
|
|
73197
|
+
res.setHeader(name, value);
|
|
73198
|
+
}
|
|
73199
|
+
if (translated.cookies.length > 0) {
|
|
73200
|
+
res.setHeader("set-cookie", translated.cookies);
|
|
73201
|
+
}
|
|
73202
|
+
res.end(translated.body);
|
|
73203
|
+
} catch (err) {
|
|
73204
|
+
logger.error(
|
|
73205
|
+
`RIE invoke failed for ${match.route.lambdaLogicalId}: ${err instanceof Error ? err.message : String(err)}`
|
|
73206
|
+
);
|
|
73207
|
+
if (!res.headersSent) {
|
|
73208
|
+
writeError(res, 502);
|
|
73209
|
+
} else {
|
|
73210
|
+
res.end();
|
|
73211
|
+
}
|
|
73212
|
+
} finally {
|
|
73213
|
+
opts.pool.release(handle);
|
|
73214
|
+
}
|
|
73215
|
+
}
|
|
73216
|
+
async function runAuthorizerPass(authorizer, snapshot, matchCtx, opts, requestContextV2) {
|
|
73217
|
+
const headers = lowercaseSingularHeaders(snapshot.headers);
|
|
73218
|
+
const queryStringParameters = parseQueryStringSingular(snapshot.rawUrl);
|
|
73219
|
+
const sourceIp = pickSourceIp(matchCtx.route.apiVersion, requestContextV2, snapshot);
|
|
73220
|
+
const reqSnap = {
|
|
73221
|
+
method: snapshot.method.toUpperCase(),
|
|
73222
|
+
headers,
|
|
73223
|
+
queryStringParameters,
|
|
73224
|
+
pathParameters: matchCtx.pathParameters,
|
|
73225
|
+
sourceIp,
|
|
73226
|
+
matchedPath: matchCtx.matchedPath,
|
|
73227
|
+
stage: matchCtx.route.stage
|
|
73228
|
+
};
|
|
73229
|
+
const methodArn = buildMethodArn({
|
|
73230
|
+
apiId: "local",
|
|
73231
|
+
accountId: "123456789012",
|
|
73232
|
+
stage: matchCtx.route.stage,
|
|
73233
|
+
method: snapshot.method,
|
|
73234
|
+
path: matchCtx.matchedPath
|
|
73235
|
+
});
|
|
73236
|
+
const cache2 = opts.authorizerCache;
|
|
73237
|
+
if (authorizer.kind === "lambda-token") {
|
|
73238
|
+
const token = headers[authorizer.tokenHeader];
|
|
73239
|
+
if (!token) {
|
|
73240
|
+
return { result: { allow: false }, denyKind: "missing-identity" };
|
|
73241
|
+
}
|
|
73242
|
+
if (cache2) {
|
|
73243
|
+
const cached = cache2.get(authorizer.logicalId, hashOne(token));
|
|
73244
|
+
if (cached) {
|
|
73245
|
+
if (cached.policy !== void 0) {
|
|
73246
|
+
return shapeOutcome(evaluateCachedLambdaPolicy(cached, methodArn));
|
|
73247
|
+
}
|
|
73248
|
+
return shapeOutcome(cached);
|
|
73249
|
+
}
|
|
73250
|
+
}
|
|
73251
|
+
const result2 = await invokeTokenAuthorizer(authorizer, reqSnap, {
|
|
73252
|
+
pool: opts.pool,
|
|
73253
|
+
rieTimeoutMs: opts.rieTimeoutMs,
|
|
73254
|
+
methodArn,
|
|
73255
|
+
mockAccountId: "123456789012",
|
|
73256
|
+
mockApiId: "local"
|
|
73257
|
+
});
|
|
73258
|
+
if (cache2 && result2.identityHash !== void 0) {
|
|
73259
|
+
cache2.set(
|
|
73260
|
+
authorizer.logicalId,
|
|
73261
|
+
result2.identityHash,
|
|
73262
|
+
authorizer.resultTtlSeconds,
|
|
73263
|
+
stripHash(result2)
|
|
73264
|
+
);
|
|
73265
|
+
}
|
|
73266
|
+
return shapeOutcome(stripHash(result2));
|
|
73267
|
+
}
|
|
73268
|
+
if (authorizer.kind === "lambda-request") {
|
|
73269
|
+
const { identityHash, missing } = computeRequestIdentityHash(authorizer, reqSnap);
|
|
73270
|
+
if (missing) {
|
|
73271
|
+
return { result: { allow: false }, denyKind: "missing-identity" };
|
|
73272
|
+
}
|
|
73273
|
+
if (cache2 && authorizer.resultTtlSeconds > 0) {
|
|
73274
|
+
const cached = cache2.get(authorizer.logicalId, identityHash);
|
|
73275
|
+
if (cached) {
|
|
73276
|
+
if (cached.policy !== void 0) {
|
|
73277
|
+
return shapeOutcome(evaluateCachedLambdaPolicy(cached, methodArn));
|
|
73278
|
+
}
|
|
73279
|
+
return shapeOutcome(cached);
|
|
73280
|
+
}
|
|
73281
|
+
}
|
|
73282
|
+
const result2 = await invokeRequestAuthorizer(authorizer, reqSnap, {
|
|
73283
|
+
pool: opts.pool,
|
|
73284
|
+
rieTimeoutMs: opts.rieTimeoutMs,
|
|
73285
|
+
methodArn,
|
|
73286
|
+
mockAccountId: "123456789012",
|
|
73287
|
+
mockApiId: "local"
|
|
73288
|
+
});
|
|
73289
|
+
if (cache2 && result2.identityHash !== void 0 && authorizer.resultTtlSeconds > 0) {
|
|
73290
|
+
cache2.set(
|
|
73291
|
+
authorizer.logicalId,
|
|
73292
|
+
result2.identityHash,
|
|
73293
|
+
authorizer.resultTtlSeconds,
|
|
73294
|
+
stripHash(result2)
|
|
73295
|
+
);
|
|
73296
|
+
}
|
|
73297
|
+
return shapeOutcome(stripHash(result2));
|
|
73298
|
+
}
|
|
73299
|
+
if (!opts.jwksCache) {
|
|
73300
|
+
return { result: { allow: false }, denyKind: "policy-deny" };
|
|
73301
|
+
}
|
|
73302
|
+
const authHeader = headers["authorization"];
|
|
73303
|
+
const jwksOpts = { ...opts.jwksWarnedUrls && { warned: opts.jwksWarnedUrls } };
|
|
73304
|
+
if (authorizer.kind === "cognito") {
|
|
73305
|
+
if (cache2 && authHeader !== void 0) {
|
|
73306
|
+
const cached = cache2.get(authorizer.logicalId, hashOne(authHeader));
|
|
73307
|
+
if (cached)
|
|
73308
|
+
return shapeOutcome(cached);
|
|
73309
|
+
}
|
|
73310
|
+
const result2 = await verifyCognitoJwt(authorizer, authHeader, opts.jwksCache, jwksOpts);
|
|
73311
|
+
if (cache2 && result2.identityHash !== void 0 && result2.ttlSeconds > 0) {
|
|
73312
|
+
cache2.set(
|
|
73313
|
+
authorizer.logicalId,
|
|
73314
|
+
result2.identityHash,
|
|
73315
|
+
result2.ttlSeconds,
|
|
73316
|
+
stripHashAndTtl(result2)
|
|
73317
|
+
);
|
|
73318
|
+
}
|
|
73319
|
+
if (!result2.allow && authHeader === void 0) {
|
|
73320
|
+
return { result: stripHashAndTtl(result2), denyKind: "missing-identity" };
|
|
73321
|
+
}
|
|
73322
|
+
return shapeOutcome(stripHashAndTtl(result2));
|
|
73323
|
+
}
|
|
73324
|
+
if (cache2 && authHeader !== void 0) {
|
|
73325
|
+
const cached = cache2.get(authorizer.logicalId, hashOne(authHeader));
|
|
73326
|
+
if (cached)
|
|
73327
|
+
return shapeOutcome(cached);
|
|
73328
|
+
}
|
|
73329
|
+
const result = await verifyJwtAuthorizer(authorizer, authHeader, opts.jwksCache, jwksOpts);
|
|
73330
|
+
if (cache2 && result.identityHash !== void 0 && result.ttlSeconds > 0) {
|
|
73331
|
+
cache2.set(
|
|
73332
|
+
authorizer.logicalId,
|
|
73333
|
+
result.identityHash,
|
|
73334
|
+
result.ttlSeconds,
|
|
73335
|
+
stripHashAndTtl(result)
|
|
73336
|
+
);
|
|
73337
|
+
}
|
|
73338
|
+
if (!result.allow && authHeader === void 0) {
|
|
73339
|
+
return { result: stripHashAndTtl(result), denyKind: "missing-identity" };
|
|
73340
|
+
}
|
|
73341
|
+
return shapeOutcome(stripHashAndTtl(result));
|
|
73342
|
+
}
|
|
73343
|
+
function shapeOutcome(result) {
|
|
73344
|
+
if (result.allow)
|
|
73345
|
+
return { result };
|
|
73346
|
+
return { result, denyKind: "policy-deny" };
|
|
73347
|
+
}
|
|
73348
|
+
function pickSourceIp(apiVersion, requestContext, snapshot) {
|
|
73349
|
+
if (apiVersion === "v1") {
|
|
73350
|
+
const identity = requestContext["identity"];
|
|
73351
|
+
if (identity && typeof identity === "object" && !Array.isArray(identity) && typeof identity["sourceIp"] === "string") {
|
|
73352
|
+
return identity["sourceIp"];
|
|
73353
|
+
}
|
|
73354
|
+
} else {
|
|
73355
|
+
const http = requestContext["http"];
|
|
73356
|
+
if (http && typeof http === "object" && !Array.isArray(http) && typeof http["sourceIp"] === "string") {
|
|
73357
|
+
return http["sourceIp"];
|
|
73358
|
+
}
|
|
73359
|
+
}
|
|
73360
|
+
return snapshot.sourceIp ?? "127.0.0.1";
|
|
73361
|
+
}
|
|
73362
|
+
function buildOverlay(authorizer, result) {
|
|
73363
|
+
if (authorizer.kind === "lambda-token" || authorizer.kind === "lambda-request") {
|
|
73364
|
+
const isV2 = authorizer.kind === "lambda-request" && authorizer.apiVersion === "v2";
|
|
73365
|
+
return isV2 ? {
|
|
73366
|
+
kind: "lambda-http-v2",
|
|
73367
|
+
...result.principalId !== void 0 && { principalId: result.principalId },
|
|
73368
|
+
...result.context && { context: result.context }
|
|
73369
|
+
} : {
|
|
73370
|
+
kind: "lambda-rest-v1",
|
|
73371
|
+
...result.principalId !== void 0 && { principalId: result.principalId },
|
|
73372
|
+
...result.context && { context: result.context }
|
|
73373
|
+
};
|
|
73374
|
+
}
|
|
73375
|
+
if (authorizer.kind === "cognito") {
|
|
73376
|
+
return { kind: "cognito-rest-v1", claims: result.context ?? {} };
|
|
73377
|
+
}
|
|
73378
|
+
return { kind: "jwt-http-v2", claims: result.context ?? {} };
|
|
73379
|
+
}
|
|
73380
|
+
function writeAuthRejection(res, apiVersion, denyKind) {
|
|
73381
|
+
if (apiVersion === "v2") {
|
|
73382
|
+
writeError(res, 401, '{"message":"Unauthorized"}');
|
|
73383
|
+
return;
|
|
73384
|
+
}
|
|
73385
|
+
if (denyKind === "missing-identity") {
|
|
73386
|
+
writeError(res, 401, '{"message":"Unauthorized"}');
|
|
73387
|
+
return;
|
|
73388
|
+
}
|
|
73389
|
+
writeError(res, 403, '{"message":"Forbidden"}');
|
|
73390
|
+
}
|
|
73391
|
+
function hashOne(value) {
|
|
73392
|
+
return value;
|
|
73393
|
+
}
|
|
73394
|
+
function stripHash(r) {
|
|
73395
|
+
const { identityHash, ...rest } = r;
|
|
73396
|
+
return rest;
|
|
73397
|
+
}
|
|
73398
|
+
function stripHashAndTtl(r) {
|
|
73399
|
+
const { identityHash, ttlSeconds, ...rest } = r;
|
|
73400
|
+
return rest;
|
|
73401
|
+
}
|
|
73402
|
+
function lowercaseSingularHeaders(raw) {
|
|
73403
|
+
const out = {};
|
|
73404
|
+
for (const [name, values] of Object.entries(raw)) {
|
|
73405
|
+
out[name.toLowerCase()] = values.join(",");
|
|
73406
|
+
}
|
|
73407
|
+
return out;
|
|
73408
|
+
}
|
|
73409
|
+
function parseQueryStringSingular(rawUrl) {
|
|
73410
|
+
const q = rawUrl.indexOf("?");
|
|
73411
|
+
if (q < 0)
|
|
73412
|
+
return {};
|
|
73413
|
+
const raw = rawUrl.slice(q + 1);
|
|
73414
|
+
if (raw.length === 0)
|
|
73415
|
+
return {};
|
|
73416
|
+
const out = {};
|
|
73417
|
+
for (const pair of raw.split("&")) {
|
|
73418
|
+
if (pair.length === 0)
|
|
73419
|
+
continue;
|
|
73420
|
+
const eq = pair.indexOf("=");
|
|
73421
|
+
const rawKey = eq === -1 ? pair : pair.slice(0, eq);
|
|
73422
|
+
const rawValue = eq === -1 ? "" : pair.slice(eq + 1);
|
|
73423
|
+
let key = rawKey;
|
|
73424
|
+
let value = rawValue;
|
|
73425
|
+
try {
|
|
73426
|
+
key = decodeURIComponent(rawKey);
|
|
73427
|
+
} catch {
|
|
73428
|
+
}
|
|
73429
|
+
try {
|
|
73430
|
+
value = decodeURIComponent(rawValue);
|
|
73431
|
+
} catch {
|
|
73432
|
+
}
|
|
73433
|
+
out[key] = value;
|
|
73434
|
+
}
|
|
73435
|
+
return out;
|
|
73436
|
+
}
|
|
73437
|
+
function readBody(req) {
|
|
73438
|
+
return new Promise((resolveBody, rejectBody) => {
|
|
73439
|
+
const chunks = [];
|
|
73440
|
+
req.on("data", (chunk) => {
|
|
73441
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
73442
|
+
});
|
|
73443
|
+
req.on("end", () => resolveBody(Buffer.concat(chunks)));
|
|
73444
|
+
req.on("error", rejectBody);
|
|
73445
|
+
});
|
|
73446
|
+
}
|
|
73447
|
+
function collectHeaders(req) {
|
|
73448
|
+
const out = {};
|
|
73449
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
73450
|
+
if (Array.isArray(value)) {
|
|
73451
|
+
out[name] = value;
|
|
73452
|
+
} else if (typeof value === "string") {
|
|
73453
|
+
out[name] = [value];
|
|
73454
|
+
}
|
|
73455
|
+
}
|
|
73456
|
+
return out;
|
|
73457
|
+
}
|
|
73458
|
+
function writeError(res, statusCode, body = '{"message":"Internal server error"}') {
|
|
73459
|
+
res.statusCode = statusCode;
|
|
73460
|
+
res.setHeader("content-type", "application/json");
|
|
73461
|
+
res.setHeader("content-length", String(Buffer.byteLength(body, "utf-8")));
|
|
73462
|
+
res.end(body);
|
|
73463
|
+
}
|
|
73464
|
+
|
|
73465
|
+
// src/local/authorizer-cache.ts
|
|
73466
|
+
function createAuthorizerCache(opts = {}) {
|
|
73467
|
+
const now = opts.now ?? (() => Date.now());
|
|
73468
|
+
const map = /* @__PURE__ */ new Map();
|
|
73469
|
+
const buildKey = (auth, identity) => `${auth}\0${identity}`;
|
|
73470
|
+
const sweep = () => {
|
|
73471
|
+
const t = now();
|
|
73472
|
+
for (const [k, v] of map) {
|
|
73473
|
+
if (v.expiresAt <= t)
|
|
73474
|
+
map.delete(k);
|
|
73475
|
+
}
|
|
73476
|
+
};
|
|
73477
|
+
return {
|
|
73478
|
+
get(authorizerLogicalId, identityHash) {
|
|
73479
|
+
const key = buildKey(authorizerLogicalId, identityHash);
|
|
73480
|
+
const entry = map.get(key);
|
|
73481
|
+
if (!entry)
|
|
73482
|
+
return void 0;
|
|
73483
|
+
if (entry.expiresAt <= now()) {
|
|
73484
|
+
map.delete(key);
|
|
73485
|
+
return void 0;
|
|
73486
|
+
}
|
|
73487
|
+
return entry.result;
|
|
73488
|
+
},
|
|
73489
|
+
set(authorizerLogicalId, identityHash, ttlSeconds, result) {
|
|
73490
|
+
if (ttlSeconds <= 0)
|
|
73491
|
+
return;
|
|
73492
|
+
const key = buildKey(authorizerLogicalId, identityHash);
|
|
73493
|
+
map.set(key, { expiresAt: now() + ttlSeconds * 1e3, result });
|
|
73494
|
+
},
|
|
73495
|
+
clear() {
|
|
73496
|
+
map.clear();
|
|
73497
|
+
},
|
|
73498
|
+
size() {
|
|
73499
|
+
sweep();
|
|
73500
|
+
return map.size;
|
|
73501
|
+
}
|
|
73502
|
+
};
|
|
73503
|
+
}
|
|
73504
|
+
|
|
73505
|
+
// src/cli/commands/local-start-api.ts
|
|
73506
|
+
async function localStartApiCommand(options) {
|
|
73507
|
+
const logger = getLogger();
|
|
73508
|
+
if (options.verbose) {
|
|
73509
|
+
logger.setLevel("debug");
|
|
73510
|
+
}
|
|
73511
|
+
warnIfDeprecatedRegion(options);
|
|
73512
|
+
await applyRoleArnIfSet({ roleArn: options.roleArn, region: options.region });
|
|
73513
|
+
await ensureDockerAvailable();
|
|
73514
|
+
const appCmd = resolveApp(options.app);
|
|
73515
|
+
if (!appCmd) {
|
|
73516
|
+
throw new Error('No CDK app specified. Pass --app, set CDKD_APP, or add "app" to cdk.json.');
|
|
72295
73517
|
}
|
|
72296
73518
|
logger.info("Synthesizing CDK app...");
|
|
72297
73519
|
const synthesizer = new Synthesizer();
|
|
@@ -72314,11 +73536,13 @@ async function localStartApiCommand(options) {
|
|
|
72314
73536
|
"No supported API routes were discovered. cdkd local start-api supports AWS::ApiGateway::* (REST v1), AWS::ApiGatewayV2::* (HTTP), and AWS::Lambda::Url (Function URL) with AWS_PROXY integrations only."
|
|
72315
73537
|
);
|
|
72316
73538
|
}
|
|
72317
|
-
const
|
|
73539
|
+
const routesWithAuth = attachAuthorizers(targetStacks, routes);
|
|
73540
|
+
const lambdaIds = uniqueLambdaIds(routes, routesWithAuth);
|
|
72318
73541
|
const overrides = readEnvOverridesFile(options.envVars);
|
|
72319
73542
|
const debugPortBase = options.debugPortBase ? parseDebugPort(options.debugPortBase) : void 0;
|
|
72320
73543
|
const specs = /* @__PURE__ */ new Map();
|
|
72321
73544
|
const inlineTmpDirs = /* @__PURE__ */ new Set();
|
|
73545
|
+
const layerTmpDirs = /* @__PURE__ */ new Set();
|
|
72322
73546
|
for (let i = 0; i < lambdaIds.length; i++) {
|
|
72323
73547
|
const logicalId = lambdaIds[i];
|
|
72324
73548
|
const spec = await buildContainerSpec({
|
|
@@ -72329,7 +73553,8 @@ async function localStartApiCommand(options) {
|
|
|
72329
73553
|
containerHost: options.containerHost,
|
|
72330
73554
|
...debugPortBase !== void 0 && { debugPort: debugPortBase + i },
|
|
72331
73555
|
stsRegion: options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"],
|
|
72332
|
-
inlineTmpDirs
|
|
73556
|
+
inlineTmpDirs,
|
|
73557
|
+
layerTmpDirs
|
|
72333
73558
|
});
|
|
72334
73559
|
specs.set(logicalId, spec);
|
|
72335
73560
|
}
|
|
@@ -72368,12 +73593,20 @@ async function localStartApiCommand(options) {
|
|
|
72368
73593
|
if (!Number.isFinite(port) || port < 0 || port > 65535) {
|
|
72369
73594
|
throw new Error(`--port must be 0..65535 (got ${options.port}).`);
|
|
72370
73595
|
}
|
|
73596
|
+
const authorizerCache = createAuthorizerCache();
|
|
73597
|
+
const jwksCache = createJwksCache();
|
|
73598
|
+
const jwksWarnedUrls = /* @__PURE__ */ new Set();
|
|
73599
|
+
await prewarmJwks(routesWithAuth, jwksCache);
|
|
73600
|
+
warnVpcConfigLambdas(routesWithAuth, targetStacks);
|
|
72371
73601
|
const server = await startApiServer({
|
|
72372
|
-
routes,
|
|
73602
|
+
routes: routesWithAuth,
|
|
72373
73603
|
pool,
|
|
72374
73604
|
rieTimeoutMs,
|
|
72375
73605
|
host: options.host,
|
|
72376
|
-
port
|
|
73606
|
+
port,
|
|
73607
|
+
authorizerCache,
|
|
73608
|
+
jwksCache,
|
|
73609
|
+
jwksWarnedUrls
|
|
72377
73610
|
});
|
|
72378
73611
|
printRouteTable(routes);
|
|
72379
73612
|
logger.info(
|
|
@@ -72416,6 +73649,15 @@ async function localStartApiCommand(options) {
|
|
|
72416
73649
|
);
|
|
72417
73650
|
}
|
|
72418
73651
|
}
|
|
73652
|
+
for (const dir of layerTmpDirs) {
|
|
73653
|
+
try {
|
|
73654
|
+
rmSync2(dir, { recursive: true, force: true });
|
|
73655
|
+
} catch (err) {
|
|
73656
|
+
logger.warn(
|
|
73657
|
+
`Failed to remove merged-layers tmpdir ${dir}: ${err instanceof Error ? err.message : String(err)}`
|
|
73658
|
+
);
|
|
73659
|
+
}
|
|
73660
|
+
}
|
|
72419
73661
|
process.exit(exitCode);
|
|
72420
73662
|
};
|
|
72421
73663
|
process.on("SIGINT", () => {
|
|
@@ -72450,7 +73692,7 @@ function pickTargetStacks(stacks, pattern) {
|
|
|
72450
73692
|
`Multi-stack app: pass --stack <name> to pick a target. Available stacks: ${stacks.map((s) => s.stackName).join(", ")}.`
|
|
72451
73693
|
);
|
|
72452
73694
|
}
|
|
72453
|
-
function uniqueLambdaIds(routes) {
|
|
73695
|
+
function uniqueLambdaIds(routes, routesWithAuth) {
|
|
72454
73696
|
const seen = /* @__PURE__ */ new Set();
|
|
72455
73697
|
const out = [];
|
|
72456
73698
|
for (const r of routes) {
|
|
@@ -72459,8 +73701,67 @@ function uniqueLambdaIds(routes) {
|
|
|
72459
73701
|
out.push(r.lambdaLogicalId);
|
|
72460
73702
|
}
|
|
72461
73703
|
}
|
|
73704
|
+
for (const entry of routesWithAuth) {
|
|
73705
|
+
const auth = entry.authorizer;
|
|
73706
|
+
if (!auth)
|
|
73707
|
+
continue;
|
|
73708
|
+
if (auth.kind === "lambda-token" || auth.kind === "lambda-request") {
|
|
73709
|
+
if (!seen.has(auth.lambdaLogicalId)) {
|
|
73710
|
+
seen.add(auth.lambdaLogicalId);
|
|
73711
|
+
out.push(auth.lambdaLogicalId);
|
|
73712
|
+
}
|
|
73713
|
+
}
|
|
73714
|
+
}
|
|
72462
73715
|
return out;
|
|
72463
73716
|
}
|
|
73717
|
+
async function prewarmJwks(routesWithAuth, jwksCache) {
|
|
73718
|
+
const urls = /* @__PURE__ */ new Set();
|
|
73719
|
+
for (const entry of routesWithAuth) {
|
|
73720
|
+
const auth = entry.authorizer;
|
|
73721
|
+
if (!auth)
|
|
73722
|
+
continue;
|
|
73723
|
+
if (auth.kind === "cognito") {
|
|
73724
|
+
urls.add(buildCognitoJwksUrl(auth.region, auth.userPoolId));
|
|
73725
|
+
} else if (auth.kind === "jwt") {
|
|
73726
|
+
const url = auth.region && auth.userPoolId ? buildCognitoJwksUrl(auth.region, auth.userPoolId) : buildJwksUrlFromIssuer(auth.issuer);
|
|
73727
|
+
urls.add(url);
|
|
73728
|
+
}
|
|
73729
|
+
}
|
|
73730
|
+
await Promise.all([...urls].map((u) => jwksCache.fetchAndCache(u)));
|
|
73731
|
+
}
|
|
73732
|
+
function warnVpcConfigLambdas(routesWithAuth, stacks) {
|
|
73733
|
+
const logger = getLogger();
|
|
73734
|
+
const seen = /* @__PURE__ */ new Set();
|
|
73735
|
+
const reachable = [];
|
|
73736
|
+
for (const entry of routesWithAuth) {
|
|
73737
|
+
if (!seen.has(entry.route.lambdaLogicalId)) {
|
|
73738
|
+
seen.add(entry.route.lambdaLogicalId);
|
|
73739
|
+
reachable.push(entry.route.lambdaLogicalId);
|
|
73740
|
+
}
|
|
73741
|
+
const auth = entry.authorizer;
|
|
73742
|
+
if (auth && (auth.kind === "lambda-token" || auth.kind === "lambda-request")) {
|
|
73743
|
+
if (!seen.has(auth.lambdaLogicalId)) {
|
|
73744
|
+
seen.add(auth.lambdaLogicalId);
|
|
73745
|
+
reachable.push(auth.lambdaLogicalId);
|
|
73746
|
+
}
|
|
73747
|
+
}
|
|
73748
|
+
}
|
|
73749
|
+
for (const logicalId of reachable) {
|
|
73750
|
+
for (const stack of stacks) {
|
|
73751
|
+
const resource = stack.template.Resources?.[logicalId];
|
|
73752
|
+
if (!resource || resource.Type !== "AWS::Lambda::Function")
|
|
73753
|
+
continue;
|
|
73754
|
+
const props = resource.Properties ?? {};
|
|
73755
|
+
const vpcConfig = props["VpcConfig"];
|
|
73756
|
+
if (vpcConfig && typeof vpcConfig === "object" && Object.keys(vpcConfig).length > 0) {
|
|
73757
|
+
logger.warn(
|
|
73758
|
+
`Lambda ${logicalId} has VpcConfig \u2014 local container will reach external services via the host's network, NOT through the deployed VPC's NAT/private subnets. Calls to private RDS/ElastiCache will fail. See docs/cli-reference.md (cdkd local start-api \u2014 Limitations) for details.`
|
|
73759
|
+
);
|
|
73760
|
+
}
|
|
73761
|
+
break;
|
|
73762
|
+
}
|
|
73763
|
+
}
|
|
73764
|
+
}
|
|
72464
73765
|
async function buildContainerSpec(args) {
|
|
72465
73766
|
const {
|
|
72466
73767
|
logicalId,
|
|
@@ -72470,7 +73771,8 @@ async function buildContainerSpec(args) {
|
|
|
72470
73771
|
containerHost,
|
|
72471
73772
|
debugPort,
|
|
72472
73773
|
stsRegion,
|
|
72473
|
-
inlineTmpDirs
|
|
73774
|
+
inlineTmpDirs,
|
|
73775
|
+
layerTmpDirs
|
|
72474
73776
|
} = args;
|
|
72475
73777
|
const lambda = resolveLambdaByLogicalId(logicalId, stacks);
|
|
72476
73778
|
const codeDir = lambda.codePath ?? materializeInlineCode(
|
|
@@ -72479,6 +73781,7 @@ async function buildContainerSpec(args) {
|
|
|
72479
73781
|
resolveRuntimeFileExtension(lambda.runtime),
|
|
72480
73782
|
inlineTmpDirs
|
|
72481
73783
|
);
|
|
73784
|
+
const optDir = materializeLambdaLayers(lambda.layers, layerTmpDirs);
|
|
72482
73785
|
const templateEnv = getTemplateEnv(lambda.resource);
|
|
72483
73786
|
const envResult = resolveEnvVars(logicalId, templateEnv, overrides);
|
|
72484
73787
|
for (const key of envResult.unresolved) {
|
|
@@ -72514,10 +73817,23 @@ async function buildContainerSpec(args) {
|
|
|
72514
73817
|
codeDir,
|
|
72515
73818
|
env: dockerEnv,
|
|
72516
73819
|
containerHost,
|
|
73820
|
+
...optDir !== void 0 && { optDir },
|
|
72517
73821
|
...debugPort !== void 0 && { debugPort }
|
|
72518
73822
|
};
|
|
72519
73823
|
return spec;
|
|
72520
73824
|
}
|
|
73825
|
+
function materializeLambdaLayers(layers, layerTmpDirs) {
|
|
73826
|
+
if (layers.length === 0)
|
|
73827
|
+
return void 0;
|
|
73828
|
+
if (layers.length === 1)
|
|
73829
|
+
return layers[0].assetPath;
|
|
73830
|
+
const dir = mkdtempSync2(path.join(tmpdir2(), "cdkd-local-start-api-layers-"));
|
|
73831
|
+
for (const layer of layers) {
|
|
73832
|
+
cpSync(layer.assetPath, dir, { recursive: true, force: true });
|
|
73833
|
+
}
|
|
73834
|
+
layerTmpDirs.add(dir);
|
|
73835
|
+
return dir;
|
|
73836
|
+
}
|
|
72521
73837
|
function resolveLambdaByLogicalId(logicalId, stacks) {
|
|
72522
73838
|
for (const stack of stacks) {
|
|
72523
73839
|
const resource = stack.template.Resources?.[logicalId];
|
|
@@ -72548,6 +73864,7 @@ function resolveLambdaByLogicalId(logicalId, stacks) {
|
|
|
72548
73864
|
if (!inlineCode) {
|
|
72549
73865
|
codePath = resolveAssetCodePath2(stack, logicalId, resource);
|
|
72550
73866
|
}
|
|
73867
|
+
const layers = resolveLambdaLayers(stack, logicalId, props);
|
|
72551
73868
|
return {
|
|
72552
73869
|
kind: "zip",
|
|
72553
73870
|
stack,
|
|
@@ -72558,6 +73875,7 @@ function resolveLambdaByLogicalId(logicalId, stacks) {
|
|
|
72558
73875
|
memoryMb,
|
|
72559
73876
|
timeoutSec,
|
|
72560
73877
|
codePath,
|
|
73878
|
+
layers,
|
|
72561
73879
|
...inlineCode !== void 0 && { inlineCode }
|
|
72562
73880
|
};
|
|
72563
73881
|
}
|
|
@@ -72701,7 +74019,7 @@ function parseDebugPort(raw) {
|
|
|
72701
74019
|
}
|
|
72702
74020
|
function createLocalStartApiCommand() {
|
|
72703
74021
|
const startApi = new Command14("start-api").description(
|
|
72704
|
-
"Run a long-running local HTTP server that maps API Gateway routes (REST v1, HTTP API, Function URL) to Lambda invocations against the AWS Lambda Runtime Interface Emulator (Docker required)."
|
|
74022
|
+
"Run a long-running local HTTP server that maps API Gateway routes (REST v1, HTTP API, Function URL) to Lambda invocations against the AWS Lambda Runtime Interface Emulator (Docker required). Supports Lambda TOKEN/REQUEST authorizers and Cognito User Pool / HTTP v2 JWT authorizers; when JWKS is unreachable, JWT authorizers fall back to pass-through (every token accepted) with a warn line \u2014 local dev fallback. VPC-config Lambdas run locally and surface a warn line at startup; their containers do NOT get attached to the deployed VPC subnets, so calls to private RDS / ElastiCache will fail."
|
|
72705
74023
|
).addOption(
|
|
72706
74024
|
new Option7("--port <port>", "HTTP server port (default: auto-allocate)").default("0")
|
|
72707
74025
|
).addOption(new Option7("--host <host>", "Bind address").default("127.0.0.1")).addOption(new Option7("--stack <name>", "Stack to start (single-stack apps auto-detect)")).addOption(
|
|
@@ -72744,123 +74062,170 @@ async function localInvokeCommand(target, options) {
|
|
|
72744
74062
|
logger.setLevel("debug");
|
|
72745
74063
|
}
|
|
72746
74064
|
warnIfDeprecatedRegion(options);
|
|
72747
|
-
|
|
72748
|
-
|
|
72749
|
-
|
|
72750
|
-
|
|
72751
|
-
|
|
72752
|
-
|
|
72753
|
-
|
|
72754
|
-
|
|
72755
|
-
|
|
72756
|
-
|
|
72757
|
-
|
|
72758
|
-
|
|
72759
|
-
|
|
72760
|
-
|
|
72761
|
-
|
|
72762
|
-
|
|
72763
|
-
|
|
72764
|
-
|
|
72765
|
-
|
|
72766
|
-
|
|
72767
|
-
|
|
72768
|
-
|
|
72769
|
-
|
|
72770
|
-
|
|
72771
|
-
|
|
72772
|
-
|
|
72773
|
-
|
|
72774
|
-
|
|
72775
|
-
|
|
72776
|
-
|
|
72777
|
-
|
|
72778
|
-
}
|
|
72779
|
-
if (
|
|
72780
|
-
|
|
72781
|
-
|
|
72782
|
-
|
|
72783
|
-
|
|
72784
|
-
|
|
72785
|
-
logger.debug(`--from-state: substituted env var ${key} from cdkd state`);
|
|
72786
|
-
}
|
|
72787
|
-
for (const { key, reason } of audit.unresolved) {
|
|
72788
|
-
logger.warn(
|
|
72789
|
-
`--from-state: could not substitute env var ${key} (${reason}). Override it via --env-vars or it will be dropped.`
|
|
74065
|
+
let imagePlan;
|
|
74066
|
+
let containerId;
|
|
74067
|
+
let stopLogs;
|
|
74068
|
+
let sigintHandler;
|
|
74069
|
+
const cleanup = async () => {
|
|
74070
|
+
if (stopLogs) {
|
|
74071
|
+
try {
|
|
74072
|
+
stopLogs();
|
|
74073
|
+
} catch (err) {
|
|
74074
|
+
getLogger().debug(
|
|
74075
|
+
`streamLogs stop failed: ${err instanceof Error ? err.message : String(err)}`
|
|
74076
|
+
);
|
|
74077
|
+
}
|
|
74078
|
+
}
|
|
74079
|
+
if (containerId) {
|
|
74080
|
+
try {
|
|
74081
|
+
await removeContainer(containerId);
|
|
74082
|
+
} catch (err) {
|
|
74083
|
+
getLogger().debug(
|
|
74084
|
+
`removeContainer(${containerId}) failed: ${err instanceof Error ? err.message : String(err)}`
|
|
74085
|
+
);
|
|
74086
|
+
}
|
|
74087
|
+
}
|
|
74088
|
+
if (imagePlan?.inlineTmpDir) {
|
|
74089
|
+
try {
|
|
74090
|
+
rmSync3(imagePlan.inlineTmpDir, { recursive: true, force: true });
|
|
74091
|
+
} catch (err) {
|
|
74092
|
+
getLogger().debug(
|
|
74093
|
+
`Failed to remove inline-code tmpdir ${imagePlan.inlineTmpDir}: ${err instanceof Error ? err.message : String(err)}`
|
|
74094
|
+
);
|
|
74095
|
+
}
|
|
74096
|
+
}
|
|
74097
|
+
if (imagePlan?.layersTmpDir) {
|
|
74098
|
+
try {
|
|
74099
|
+
rmSync3(imagePlan.layersTmpDir, { recursive: true, force: true });
|
|
74100
|
+
} catch (err) {
|
|
74101
|
+
getLogger().debug(
|
|
74102
|
+
`Failed to remove merged-layers tmpdir ${imagePlan.layersTmpDir}: ${err instanceof Error ? err.message : String(err)}`
|
|
72790
74103
|
);
|
|
72791
74104
|
}
|
|
72792
74105
|
}
|
|
72793
|
-
}
|
|
72794
|
-
const overrides = readEnvOverridesFile2(options.envVars);
|
|
72795
|
-
const envResult = resolveEnvVars(lambda.logicalId, templateEnv, overrides);
|
|
72796
|
-
for (const key of envResult.unresolved) {
|
|
72797
|
-
if (stateAudit && stateAudit.unresolved.some((u) => u.key === key))
|
|
72798
|
-
continue;
|
|
72799
|
-
logger.warn(
|
|
72800
|
-
`Environment variable ${key} contains a CloudFormation intrinsic and was dropped. Override it with --env-vars (e.g. {"${lambda.logicalId}":{"${key}":"<literal>"}}) or pass --from-state to recover deployed values.`
|
|
72801
|
-
);
|
|
72802
|
-
}
|
|
72803
|
-
if (options.fromState && !options.assumeRole && stateForRoleHint) {
|
|
72804
|
-
suggestAssumeRoleFromState(stateForRoleHint, lambda.logicalId);
|
|
72805
|
-
}
|
|
72806
|
-
const event = await readEvent(options);
|
|
72807
|
-
const dockerEnv = {
|
|
72808
|
-
AWS_LAMBDA_FUNCTION_NAME: lambda.logicalId,
|
|
72809
|
-
AWS_LAMBDA_FUNCTION_MEMORY_SIZE: String(lambda.memoryMb),
|
|
72810
|
-
AWS_LAMBDA_FUNCTION_TIMEOUT: String(lambda.timeoutSec),
|
|
72811
|
-
AWS_LAMBDA_FUNCTION_VERSION: "$LATEST",
|
|
72812
|
-
AWS_LAMBDA_LOG_GROUP_NAME: `/aws/lambda/${lambda.logicalId}`,
|
|
72813
|
-
AWS_LAMBDA_LOG_STREAM_NAME: "local",
|
|
72814
|
-
...envResult.resolved
|
|
72815
74106
|
};
|
|
72816
|
-
|
|
72817
|
-
|
|
72818
|
-
|
|
72819
|
-
|
|
72820
|
-
|
|
72821
|
-
|
|
72822
|
-
if (stsRegion)
|
|
72823
|
-
dockerEnv["AWS_REGION"] = stsRegion;
|
|
72824
|
-
} else {
|
|
72825
|
-
forwardAwsEnv2(dockerEnv);
|
|
72826
|
-
}
|
|
72827
|
-
let debugPort;
|
|
72828
|
-
if (options.debugPort) {
|
|
72829
|
-
debugPort = Number(options.debugPort);
|
|
72830
|
-
if (!Number.isInteger(debugPort) || debugPort <= 0 || debugPort > 65535) {
|
|
72831
|
-
throw new Error(`--debug-port must be an integer in 1..65535, got '${options.debugPort}'`);
|
|
74107
|
+
try {
|
|
74108
|
+
await applyRoleArnIfSet({ roleArn: options.roleArn, region: options.region });
|
|
74109
|
+
await ensureDockerAvailable();
|
|
74110
|
+
const appCmd = resolveApp(options.app);
|
|
74111
|
+
if (!appCmd) {
|
|
74112
|
+
throw new Error('No CDK app specified. Pass --app, set CDKD_APP, or add "app" to cdk.json.');
|
|
72832
74113
|
}
|
|
72833
|
-
|
|
72834
|
-
|
|
74114
|
+
logger.info("Synthesizing CDK app...");
|
|
74115
|
+
const synthesizer = new Synthesizer();
|
|
74116
|
+
const context = parseContextOptions(options.context);
|
|
74117
|
+
const synthOpts = {
|
|
74118
|
+
app: appCmd,
|
|
74119
|
+
output: options.output,
|
|
74120
|
+
...options.region && { region: options.region },
|
|
74121
|
+
...options.profile && { profile: options.profile },
|
|
74122
|
+
...Object.keys(context).length > 0 && { context }
|
|
74123
|
+
};
|
|
74124
|
+
const { stacks } = await synthesizer.synthesize(synthOpts);
|
|
74125
|
+
const lambda = resolveLambdaTarget(target, stacks);
|
|
74126
|
+
const targetLabel = lambda.kind === "zip" ? lambda.runtime : "container image";
|
|
74127
|
+
logger.info(`Target: ${lambda.stack.stackName}/${lambda.logicalId} (${targetLabel})`);
|
|
74128
|
+
imagePlan = await resolveImagePlan(lambda, options);
|
|
74129
|
+
let stateAudit;
|
|
74130
|
+
let templateEnv = getTemplateEnv2(lambda.resource);
|
|
74131
|
+
let stateForRoleHint;
|
|
74132
|
+
if (options.fromState) {
|
|
74133
|
+
const loaded = await loadStateForStack(lambda.stack.stackName, lambda.stack.region, {
|
|
74134
|
+
...options.stackRegion !== void 0 && { stackRegion: options.stackRegion },
|
|
74135
|
+
...options.stateBucket !== void 0 && { stateBucket: options.stateBucket },
|
|
74136
|
+
statePrefix: options.statePrefix,
|
|
74137
|
+
...options.region !== void 0 && { region: options.region },
|
|
74138
|
+
...options.profile !== void 0 && { profile: options.profile }
|
|
74139
|
+
});
|
|
74140
|
+
if (loaded) {
|
|
74141
|
+
stateForRoleHint = loaded.state;
|
|
74142
|
+
const { env, audit } = substituteEnvVarsFromState(templateEnv, loaded.state.resources);
|
|
74143
|
+
templateEnv = env;
|
|
74144
|
+
stateAudit = audit;
|
|
74145
|
+
for (const key of audit.resolvedKeys) {
|
|
74146
|
+
logger.debug(`--from-state: substituted env var ${key} from cdkd state`);
|
|
74147
|
+
}
|
|
74148
|
+
for (const { key, reason } of audit.unresolved) {
|
|
74149
|
+
logger.warn(
|
|
74150
|
+
`--from-state: could not substitute env var ${key} (${reason}). Override it via --env-vars or it will be dropped.`
|
|
74151
|
+
);
|
|
74152
|
+
}
|
|
74153
|
+
}
|
|
74154
|
+
}
|
|
74155
|
+
const overrides = readEnvOverridesFile2(options.envVars);
|
|
74156
|
+
const envResult = resolveEnvVars(lambda.logicalId, templateEnv, overrides);
|
|
74157
|
+
for (const key of envResult.unresolved) {
|
|
74158
|
+
if (stateAudit && stateAudit.unresolved.some((u) => u.key === key))
|
|
74159
|
+
continue;
|
|
72835
74160
|
logger.warn(
|
|
72836
|
-
|
|
74161
|
+
`Environment variable ${key} contains a CloudFormation intrinsic and was dropped. Override it with --env-vars (e.g. {"${lambda.logicalId}":{"${key}":"<literal>"}}) or pass --from-state to recover deployed values.`
|
|
72837
74162
|
);
|
|
72838
74163
|
}
|
|
72839
|
-
|
|
72840
|
-
|
|
72841
|
-
|
|
72842
|
-
|
|
72843
|
-
|
|
72844
|
-
|
|
72845
|
-
|
|
72846
|
-
|
|
72847
|
-
|
|
72848
|
-
|
|
72849
|
-
|
|
72850
|
-
|
|
72851
|
-
|
|
72852
|
-
|
|
72853
|
-
|
|
72854
|
-
|
|
72855
|
-
|
|
72856
|
-
|
|
72857
|
-
|
|
72858
|
-
|
|
72859
|
-
|
|
72860
|
-
}
|
|
72861
|
-
|
|
72862
|
-
|
|
72863
|
-
|
|
74164
|
+
if (options.fromState && !options.assumeRole && stateForRoleHint) {
|
|
74165
|
+
suggestAssumeRoleFromState(stateForRoleHint, lambda.logicalId);
|
|
74166
|
+
}
|
|
74167
|
+
const event = await readEvent(options);
|
|
74168
|
+
const dockerEnv = {
|
|
74169
|
+
AWS_LAMBDA_FUNCTION_NAME: lambda.logicalId,
|
|
74170
|
+
AWS_LAMBDA_FUNCTION_MEMORY_SIZE: String(lambda.memoryMb),
|
|
74171
|
+
AWS_LAMBDA_FUNCTION_TIMEOUT: String(lambda.timeoutSec),
|
|
74172
|
+
AWS_LAMBDA_FUNCTION_VERSION: "$LATEST",
|
|
74173
|
+
AWS_LAMBDA_LOG_GROUP_NAME: `/aws/lambda/${lambda.logicalId}`,
|
|
74174
|
+
AWS_LAMBDA_LOG_STREAM_NAME: "local",
|
|
74175
|
+
...envResult.resolved
|
|
74176
|
+
};
|
|
74177
|
+
if (options.assumeRole) {
|
|
74178
|
+
const stsRegion = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"];
|
|
74179
|
+
const creds = await assumeLambdaExecutionRole2(options.assumeRole, stsRegion);
|
|
74180
|
+
dockerEnv["AWS_ACCESS_KEY_ID"] = creds.accessKeyId;
|
|
74181
|
+
dockerEnv["AWS_SECRET_ACCESS_KEY"] = creds.secretAccessKey;
|
|
74182
|
+
dockerEnv["AWS_SESSION_TOKEN"] = creds.sessionToken;
|
|
74183
|
+
if (stsRegion)
|
|
74184
|
+
dockerEnv["AWS_REGION"] = stsRegion;
|
|
74185
|
+
} else {
|
|
74186
|
+
forwardAwsEnv2(dockerEnv);
|
|
74187
|
+
}
|
|
74188
|
+
let debugPort;
|
|
74189
|
+
if (options.debugPort) {
|
|
74190
|
+
debugPort = Number(options.debugPort);
|
|
74191
|
+
if (!Number.isInteger(debugPort) || debugPort <= 0 || debugPort > 65535) {
|
|
74192
|
+
throw new Error(`--debug-port must be an integer in 1..65535, got '${options.debugPort}'`);
|
|
74193
|
+
}
|
|
74194
|
+
dockerEnv["NODE_OPTIONS"] = `--inspect-brk=0.0.0.0:${debugPort}`;
|
|
74195
|
+
if (lambda.kind === "image") {
|
|
74196
|
+
logger.warn(
|
|
74197
|
+
"--debug-port sets NODE_OPTIONS unconditionally on container Lambdas. If the image's runtime is not Node.js, this flag is a no-op."
|
|
74198
|
+
);
|
|
74199
|
+
}
|
|
74200
|
+
}
|
|
74201
|
+
const hostPort = await pickFreePort();
|
|
74202
|
+
const containerHost = options.containerHost;
|
|
74203
|
+
if (lambda.layers.length > 0) {
|
|
74204
|
+
logger.info(
|
|
74205
|
+
`Mounting ${lambda.layers.length} Lambda layer${lambda.layers.length === 1 ? "" : "s"} at /opt`
|
|
74206
|
+
);
|
|
74207
|
+
}
|
|
74208
|
+
logger.info(`Starting container (image=${imagePlan.image}, port=${hostPort})...`);
|
|
74209
|
+
containerId = await runDetached({
|
|
74210
|
+
image: imagePlan.image,
|
|
74211
|
+
mounts: imagePlan.mounts,
|
|
74212
|
+
extraMounts: imagePlan.extraMounts,
|
|
74213
|
+
env: dockerEnv,
|
|
74214
|
+
cmd: imagePlan.cmd,
|
|
74215
|
+
hostPort,
|
|
74216
|
+
host: containerHost,
|
|
74217
|
+
...debugPort !== void 0 && { debugPort },
|
|
74218
|
+
...imagePlan.platform !== void 0 && { platform: imagePlan.platform },
|
|
74219
|
+
...imagePlan.entryPoint !== void 0 && { entryPoint: imagePlan.entryPoint },
|
|
74220
|
+
...imagePlan.workingDir !== void 0 && { workingDir: imagePlan.workingDir }
|
|
74221
|
+
});
|
|
74222
|
+
stopLogs = streamLogs(containerId);
|
|
74223
|
+
sigintHandler = () => {
|
|
74224
|
+
void cleanup().then(() => {
|
|
74225
|
+
process.exit(130);
|
|
74226
|
+
});
|
|
74227
|
+
};
|
|
74228
|
+
process.on("SIGINT", sigintHandler);
|
|
72864
74229
|
await waitForRieReady(containerHost, hostPort, 5e3);
|
|
72865
74230
|
const invokeTimeoutMs = Math.max(3e4, lambda.timeoutSec * 2 * 1e3);
|
|
72866
74231
|
const result = await invokeRie(containerHost, hostPort, event, invokeTimeoutMs);
|
|
@@ -72868,18 +74233,9 @@ async function localInvokeCommand(target, options) {
|
|
|
72868
74233
|
process.stdout.write(`${result.raw}
|
|
72869
74234
|
`);
|
|
72870
74235
|
} finally {
|
|
72871
|
-
|
|
72872
|
-
|
|
72873
|
-
await
|
|
72874
|
-
if (imagePlan.inlineTmpDir) {
|
|
72875
|
-
try {
|
|
72876
|
-
rmSync3(imagePlan.inlineTmpDir, { recursive: true, force: true });
|
|
72877
|
-
} catch (err) {
|
|
72878
|
-
getLogger().debug(
|
|
72879
|
-
`Failed to remove inline-code tmpdir ${imagePlan.inlineTmpDir}: ${err instanceof Error ? err.message : String(err)}`
|
|
72880
|
-
);
|
|
72881
|
-
}
|
|
72882
|
-
}
|
|
74236
|
+
if (sigintHandler)
|
|
74237
|
+
process.off("SIGINT", sigintHandler);
|
|
74238
|
+
await cleanup();
|
|
72883
74239
|
}
|
|
72884
74240
|
}
|
|
72885
74241
|
async function resolveImagePlan(lambda, options) {
|
|
@@ -72901,11 +74257,31 @@ async function resolveZipImagePlan(lambda, options) {
|
|
|
72901
74257
|
}
|
|
72902
74258
|
const image = resolveRuntimeImage(lambda.runtime);
|
|
72903
74259
|
await pullImage(image, options.pull === false);
|
|
74260
|
+
const layerPlan = materializeLambdaLayers2(lambda.layers);
|
|
72904
74261
|
return {
|
|
72905
74262
|
image,
|
|
72906
74263
|
mounts: [{ hostPath: codeDir, containerPath: "/var/task", readOnly: true }],
|
|
74264
|
+
extraMounts: layerPlan.mount ? [layerPlan.mount] : [],
|
|
72907
74265
|
cmd: [lambda.handler],
|
|
72908
|
-
...inlineTmpDir !== void 0 && { inlineTmpDir }
|
|
74266
|
+
...inlineTmpDir !== void 0 && { inlineTmpDir },
|
|
74267
|
+
...layerPlan.tmpDir !== void 0 && { layersTmpDir: layerPlan.tmpDir }
|
|
74268
|
+
};
|
|
74269
|
+
}
|
|
74270
|
+
function materializeLambdaLayers2(layers) {
|
|
74271
|
+
if (layers.length === 0)
|
|
74272
|
+
return {};
|
|
74273
|
+
if (layers.length === 1) {
|
|
74274
|
+
return {
|
|
74275
|
+
mount: { hostPath: layers[0].assetPath, containerPath: "/opt", readOnly: true }
|
|
74276
|
+
};
|
|
74277
|
+
}
|
|
74278
|
+
const tmpDir = mkdtempSync3(path2.join(tmpdir3(), "cdkd-local-invoke-layers-"));
|
|
74279
|
+
for (const layer of layers) {
|
|
74280
|
+
cpSync2(layer.assetPath, tmpDir, { recursive: true, force: true });
|
|
74281
|
+
}
|
|
74282
|
+
return {
|
|
74283
|
+
mount: { hostPath: tmpDir, containerPath: "/opt", readOnly: true },
|
|
74284
|
+
tmpDir
|
|
72909
74285
|
};
|
|
72910
74286
|
}
|
|
72911
74287
|
async function resolveContainerImagePlan(lambda, options) {
|
|
@@ -72938,6 +74314,7 @@ async function resolveContainerImagePlan(lambda, options) {
|
|
|
72938
74314
|
return {
|
|
72939
74315
|
image: imageRef,
|
|
72940
74316
|
mounts: [],
|
|
74317
|
+
extraMounts: [],
|
|
72941
74318
|
cmd: lambda.imageConfig.command ?? [],
|
|
72942
74319
|
platform,
|
|
72943
74320
|
...lambda.imageConfig.entryPoint && lambda.imageConfig.entryPoint.length > 0 && {
|
|
@@ -73256,7 +74633,7 @@ function reorderArgs(argv) {
|
|
|
73256
74633
|
}
|
|
73257
74634
|
async function main() {
|
|
73258
74635
|
const program = new Command16();
|
|
73259
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
74636
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.74.0");
|
|
73260
74637
|
program.addCommand(createBootstrapCommand());
|
|
73261
74638
|
program.addCommand(createSynthCommand());
|
|
73262
74639
|
program.addCommand(createListCommand());
|