@go-to-k/cdkd 0.71.0 → 0.73.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 +27 -5
- package/dist/cli.js +1409 -167
- package/dist/cli.js.map +4 -4
- package/dist/go-to-k-cdkd-0.73.0.tgz +0 -0
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.71.0.tgz +0 -0
package/dist/cli.js
CHANGED
|
@@ -70703,41 +70703,6 @@ function runForeground(cmd, args) {
|
|
|
70703
70703
|
|
|
70704
70704
|
// src/local/docker-image-builder.ts
|
|
70705
70705
|
import { createHash as createHash2 } from "node:crypto";
|
|
70706
|
-
async function buildContainerImage(asset, cdkOutDir, options) {
|
|
70707
|
-
const tag = computeLocalTag(asset.source);
|
|
70708
|
-
const platform = architectureToPlatform(options.architecture);
|
|
70709
|
-
const logger = getLogger().child("local-invoke-build");
|
|
70710
|
-
logger.info(`Building container image (platform=${platform})...`);
|
|
70711
|
-
logger.debug(`Local tag: ${tag}`);
|
|
70712
|
-
await buildDockerImage(asset, cdkOutDir, tag, {
|
|
70713
|
-
platform,
|
|
70714
|
-
wrapError: (stderr) => new LocalInvokeBuildError(
|
|
70715
|
-
`docker build failed for container Lambda asset (${asset.source.directory}): ${stderr}`
|
|
70716
|
-
)
|
|
70717
|
-
});
|
|
70718
|
-
return tag;
|
|
70719
|
-
}
|
|
70720
|
-
function architectureToPlatform(architecture) {
|
|
70721
|
-
return architecture === "arm64" ? "linux/arm64" : "linux/amd64";
|
|
70722
|
-
}
|
|
70723
|
-
function computeLocalTag(source) {
|
|
70724
|
-
const hash = createHash2("sha256");
|
|
70725
|
-
hash.update(source.directory);
|
|
70726
|
-
hash.update("\0");
|
|
70727
|
-
hash.update(source.dockerFile ?? "");
|
|
70728
|
-
hash.update("\0");
|
|
70729
|
-
hash.update(source.dockerBuildTarget ?? "");
|
|
70730
|
-
hash.update("\0");
|
|
70731
|
-
if (source.dockerBuildArgs) {
|
|
70732
|
-
for (const [k, v] of Object.entries(source.dockerBuildArgs)) {
|
|
70733
|
-
hash.update(k);
|
|
70734
|
-
hash.update("=");
|
|
70735
|
-
hash.update(v);
|
|
70736
|
-
hash.update("\0");
|
|
70737
|
-
}
|
|
70738
|
-
}
|
|
70739
|
-
return `cdkd-local-invoke-${hash.digest("hex").slice(0, 16)}`;
|
|
70740
|
-
}
|
|
70741
70706
|
|
|
70742
70707
|
// src/local/ecr-puller.ts
|
|
70743
70708
|
import { execFile as execFile4, spawn as spawn4 } from "node:child_process";
|
|
@@ -70845,6 +70810,14 @@ async function verifyImageInLocalCache(imageUri) {
|
|
|
70845
70810
|
);
|
|
70846
70811
|
}
|
|
70847
70812
|
}
|
|
70813
|
+
async function isImageInLocalCache(imageRef) {
|
|
70814
|
+
try {
|
|
70815
|
+
await execFileAsync4("docker", ["image", "inspect", imageRef]);
|
|
70816
|
+
return true;
|
|
70817
|
+
} catch {
|
|
70818
|
+
return false;
|
|
70819
|
+
}
|
|
70820
|
+
}
|
|
70848
70821
|
function runForeground2(cmd, args) {
|
|
70849
70822
|
return new Promise((resolve6, reject) => {
|
|
70850
70823
|
const proc = spawn4(cmd, args, { stdio: "inherit" });
|
|
@@ -70858,6 +70831,53 @@ function runForeground2(cmd, args) {
|
|
|
70858
70831
|
});
|
|
70859
70832
|
}
|
|
70860
70833
|
|
|
70834
|
+
// src/local/docker-image-builder.ts
|
|
70835
|
+
async function buildContainerImage(asset, cdkOutDir, options) {
|
|
70836
|
+
const tag = computeLocalTag(asset.source);
|
|
70837
|
+
const platform = architectureToPlatform(options.architecture);
|
|
70838
|
+
const logger = getLogger().child("local-invoke-build");
|
|
70839
|
+
if (options.noBuild === true) {
|
|
70840
|
+
logger.info(`Skipping docker build (--no-build). Verifying ${tag} is in local registry...`);
|
|
70841
|
+
if (!await isImageInLocalCache(tag)) {
|
|
70842
|
+
throw new LocalInvokeBuildError(
|
|
70843
|
+
`image '${tag}' not in local registry and --no-build is set; remove --no-build or run \`docker build\` manually first.`
|
|
70844
|
+
);
|
|
70845
|
+
}
|
|
70846
|
+
logger.debug(`Local tag ${tag} is cached; skipping build.`);
|
|
70847
|
+
return tag;
|
|
70848
|
+
}
|
|
70849
|
+
logger.info(`Building container image (platform=${platform})...`);
|
|
70850
|
+
logger.debug(`Local tag: ${tag}`);
|
|
70851
|
+
await buildDockerImage(asset, cdkOutDir, tag, {
|
|
70852
|
+
platform,
|
|
70853
|
+
wrapError: (stderr) => new LocalInvokeBuildError(
|
|
70854
|
+
`docker build failed for container Lambda asset (${asset.source.directory}): ${stderr}`
|
|
70855
|
+
)
|
|
70856
|
+
});
|
|
70857
|
+
return tag;
|
|
70858
|
+
}
|
|
70859
|
+
function architectureToPlatform(architecture) {
|
|
70860
|
+
return architecture === "arm64" ? "linux/arm64" : "linux/amd64";
|
|
70861
|
+
}
|
|
70862
|
+
function computeLocalTag(source) {
|
|
70863
|
+
const hash = createHash2("sha256");
|
|
70864
|
+
hash.update(source.directory);
|
|
70865
|
+
hash.update("\0");
|
|
70866
|
+
hash.update(source.dockerFile ?? "");
|
|
70867
|
+
hash.update("\0");
|
|
70868
|
+
hash.update(source.dockerBuildTarget ?? "");
|
|
70869
|
+
hash.update("\0");
|
|
70870
|
+
if (source.dockerBuildArgs) {
|
|
70871
|
+
for (const [k, v] of Object.entries(source.dockerBuildArgs)) {
|
|
70872
|
+
hash.update(k);
|
|
70873
|
+
hash.update("=");
|
|
70874
|
+
hash.update(v);
|
|
70875
|
+
hash.update("\0");
|
|
70876
|
+
}
|
|
70877
|
+
}
|
|
70878
|
+
return `cdkd-local-invoke-${hash.digest("hex").slice(0, 16)}`;
|
|
70879
|
+
}
|
|
70880
|
+
|
|
70861
70881
|
// src/local/rie-client.ts
|
|
70862
70882
|
import { setTimeout as delay } from "node:timers/promises";
|
|
70863
70883
|
var INVOKE_PATH = "/2015-03-31/functions/function/invocations";
|
|
@@ -71758,6 +71778,48 @@ function buildRestV1Event(req, ctx, opts = {}) {
|
|
|
71758
71778
|
};
|
|
71759
71779
|
return event;
|
|
71760
71780
|
}
|
|
71781
|
+
function applyAuthorizerOverlay(event, overlay) {
|
|
71782
|
+
const requestContext = event["requestContext"] ?? {};
|
|
71783
|
+
let authorizer;
|
|
71784
|
+
switch (overlay.kind) {
|
|
71785
|
+
case "lambda-rest-v1": {
|
|
71786
|
+
authorizer = {
|
|
71787
|
+
...overlay.principalId !== void 0 && { principalId: overlay.principalId },
|
|
71788
|
+
...overlay.context ?? {}
|
|
71789
|
+
};
|
|
71790
|
+
break;
|
|
71791
|
+
}
|
|
71792
|
+
case "lambda-http-v2": {
|
|
71793
|
+
authorizer = {
|
|
71794
|
+
lambda: {
|
|
71795
|
+
...overlay.principalId !== void 0 && { principalId: overlay.principalId },
|
|
71796
|
+
...overlay.context ?? {}
|
|
71797
|
+
}
|
|
71798
|
+
};
|
|
71799
|
+
break;
|
|
71800
|
+
}
|
|
71801
|
+
case "cognito-rest-v1": {
|
|
71802
|
+
authorizer = { claims: { ...overlay.claims } };
|
|
71803
|
+
break;
|
|
71804
|
+
}
|
|
71805
|
+
case "jwt-http-v2": {
|
|
71806
|
+
authorizer = {
|
|
71807
|
+
jwt: {
|
|
71808
|
+
claims: { ...overlay.claims },
|
|
71809
|
+
scopes: overlay.scopes ?? []
|
|
71810
|
+
}
|
|
71811
|
+
};
|
|
71812
|
+
break;
|
|
71813
|
+
}
|
|
71814
|
+
}
|
|
71815
|
+
return {
|
|
71816
|
+
...event,
|
|
71817
|
+
requestContext: {
|
|
71818
|
+
...requestContext,
|
|
71819
|
+
authorizer
|
|
71820
|
+
}
|
|
71821
|
+
};
|
|
71822
|
+
}
|
|
71761
71823
|
function splitRawUrl(rawUrl) {
|
|
71762
71824
|
const q = rawUrl.indexOf("?");
|
|
71763
71825
|
if (q === -1)
|
|
@@ -72124,143 +72186,1246 @@ function isPlaceholder(segment) {
|
|
|
72124
72186
|
return /^\{[^/{}+]+\}$/.test(segment);
|
|
72125
72187
|
}
|
|
72126
72188
|
|
|
72127
|
-
// src/local/
|
|
72128
|
-
|
|
72129
|
-
const
|
|
72130
|
-
|
|
72131
|
-
|
|
72132
|
-
|
|
72133
|
-
|
|
72189
|
+
// src/local/authorizer-resolver.ts
|
|
72190
|
+
function resolveRestV1Authorizer(authorizerLogicalId, template, stackName, declaredAt) {
|
|
72191
|
+
const authResource = template.Resources?.[authorizerLogicalId];
|
|
72192
|
+
if (!authResource || authResource.Type !== "AWS::ApiGateway::Authorizer") {
|
|
72193
|
+
throw new RouteDiscoveryError(
|
|
72194
|
+
`${declaredAt}: AuthorizerId '${authorizerLogicalId}' does not point at an AWS::ApiGateway::Authorizer in stack '${stackName}'.`
|
|
72195
|
+
);
|
|
72196
|
+
}
|
|
72197
|
+
const props = authResource.Properties ?? {};
|
|
72198
|
+
const type = props["Type"];
|
|
72199
|
+
if (type === "TOKEN") {
|
|
72200
|
+
const lambdaLogicalId = resolveLambdaArn(
|
|
72201
|
+
props["AuthorizerUri"],
|
|
72202
|
+
`${stackName}/${authorizerLogicalId}.AuthorizerUri`
|
|
72203
|
+
);
|
|
72204
|
+
const identitySource = typeof props["IdentitySource"] === "string" ? props["IdentitySource"] : "method.request.header.Authorization";
|
|
72205
|
+
const tokenHeader = parseRestV1HeaderSelector(identitySource, stackName, authorizerLogicalId);
|
|
72206
|
+
const ttl = parseTtl(props["AuthorizerResultTtlInSeconds"], 300, 3600);
|
|
72207
|
+
return {
|
|
72208
|
+
kind: "lambda-token",
|
|
72209
|
+
logicalId: authorizerLogicalId,
|
|
72210
|
+
lambdaLogicalId,
|
|
72211
|
+
tokenHeader,
|
|
72212
|
+
resultTtlSeconds: ttl,
|
|
72213
|
+
declaredAt
|
|
72214
|
+
};
|
|
72215
|
+
}
|
|
72216
|
+
if (type === "REQUEST") {
|
|
72217
|
+
const lambdaLogicalId = resolveLambdaArn(
|
|
72218
|
+
props["AuthorizerUri"],
|
|
72219
|
+
`${stackName}/${authorizerLogicalId}.AuthorizerUri`
|
|
72220
|
+
);
|
|
72221
|
+
const identitySources = parseRestV1IdentitySources(
|
|
72222
|
+
typeof props["IdentitySource"] === "string" ? props["IdentitySource"] : ""
|
|
72223
|
+
);
|
|
72224
|
+
const ttl = parseTtl(props["AuthorizerResultTtlInSeconds"], 300, 3600);
|
|
72225
|
+
return {
|
|
72226
|
+
kind: "lambda-request",
|
|
72227
|
+
logicalId: authorizerLogicalId,
|
|
72228
|
+
lambdaLogicalId,
|
|
72229
|
+
identitySources,
|
|
72230
|
+
resultTtlSeconds: ttl,
|
|
72231
|
+
apiVersion: "v1",
|
|
72232
|
+
declaredAt
|
|
72233
|
+
};
|
|
72234
|
+
}
|
|
72235
|
+
if (type === "COGNITO_USER_POOLS") {
|
|
72236
|
+
const arns = props["ProviderARNs"];
|
|
72237
|
+
if (!Array.isArray(arns) || arns.length === 0) {
|
|
72238
|
+
throw new RouteDiscoveryError(
|
|
72239
|
+
`${stackName}/${authorizerLogicalId}: COGNITO_USER_POOLS authorizer is missing ProviderARNs.`
|
|
72134
72240
|
);
|
|
72135
|
-
if (!res.headersSent) {
|
|
72136
|
-
writeError(res, 502);
|
|
72137
|
-
}
|
|
72138
|
-
});
|
|
72139
|
-
});
|
|
72140
|
-
server.on("connection", (socket) => {
|
|
72141
|
-
socket.setNoDelay(true);
|
|
72142
|
-
});
|
|
72143
|
-
const { actualPort, actualHost } = await new Promise(
|
|
72144
|
-
(resolveListen, rejectListen) => {
|
|
72145
|
-
server.once("error", rejectListen);
|
|
72146
|
-
server.listen(opts.port, opts.host, () => {
|
|
72147
|
-
const addr = server.address();
|
|
72148
|
-
if (addr === null || typeof addr === "string") {
|
|
72149
|
-
rejectListen(new Error("Could not determine listening address"));
|
|
72150
|
-
return;
|
|
72151
|
-
}
|
|
72152
|
-
resolveListen({ actualPort: addr.port, actualHost: opts.host });
|
|
72153
|
-
});
|
|
72154
72241
|
}
|
|
72242
|
+
const arn = pickStringFromArn(arns[0], `${stackName}/${authorizerLogicalId}.ProviderARNs[0]`);
|
|
72243
|
+
const parsed = parseCognitoUserPoolArn(arn, `${stackName}/${authorizerLogicalId}`);
|
|
72244
|
+
return {
|
|
72245
|
+
kind: "cognito",
|
|
72246
|
+
logicalId: authorizerLogicalId,
|
|
72247
|
+
userPoolArn: arn,
|
|
72248
|
+
region: parsed.region,
|
|
72249
|
+
userPoolId: parsed.userPoolId,
|
|
72250
|
+
declaredAt
|
|
72251
|
+
};
|
|
72252
|
+
}
|
|
72253
|
+
throw new RouteDiscoveryError(
|
|
72254
|
+
`${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).`
|
|
72155
72255
|
);
|
|
72156
|
-
let closed = false;
|
|
72157
|
-
return {
|
|
72158
|
-
port: actualPort,
|
|
72159
|
-
host: actualHost,
|
|
72160
|
-
server,
|
|
72161
|
-
close: async () => {
|
|
72162
|
-
if (closed)
|
|
72163
|
-
return;
|
|
72164
|
-
closed = true;
|
|
72165
|
-
await new Promise((resolveClose) => {
|
|
72166
|
-
server.close(() => resolveClose());
|
|
72167
|
-
server.closeAllConnections?.();
|
|
72168
|
-
});
|
|
72169
|
-
}
|
|
72170
|
-
};
|
|
72171
72256
|
}
|
|
72172
|
-
|
|
72173
|
-
const
|
|
72174
|
-
|
|
72175
|
-
|
|
72176
|
-
|
|
72177
|
-
const requestPath = rawUrl.split("?")[0] ?? "/";
|
|
72178
|
-
const match = matchRoute(method, requestPath, opts.routes);
|
|
72179
|
-
if (!match) {
|
|
72180
|
-
writeError(res, 404, '{"message":"Not Found"}');
|
|
72181
|
-
return;
|
|
72182
|
-
}
|
|
72183
|
-
const snapshot = {
|
|
72184
|
-
method,
|
|
72185
|
-
rawUrl,
|
|
72186
|
-
headers: collectHeaders(req),
|
|
72187
|
-
body: bodyBuf,
|
|
72188
|
-
...req.socket.remoteAddress !== void 0 && { sourceIp: req.socket.remoteAddress }
|
|
72189
|
-
};
|
|
72190
|
-
const matchCtx = {
|
|
72191
|
-
route: match.route,
|
|
72192
|
-
pathParameters: match.pathParameters,
|
|
72193
|
-
matchedPath: requestPath
|
|
72194
|
-
};
|
|
72195
|
-
const event = match.route.apiVersion === "v1" ? buildRestV1Event(snapshot, matchCtx) : buildHttpApiV2Event(snapshot, matchCtx);
|
|
72196
|
-
let handle;
|
|
72197
|
-
try {
|
|
72198
|
-
handle = await opts.pool.acquire(match.route.lambdaLogicalId);
|
|
72199
|
-
} catch (err) {
|
|
72200
|
-
logger.error(
|
|
72201
|
-
`Failed to acquire container for ${match.route.lambdaLogicalId}: ${err instanceof Error ? err.message : String(err)}`
|
|
72257
|
+
function resolveHttpApiAuthorizer(authorizerLogicalId, routeAuthorizationScopes, template, stackName, declaredAt) {
|
|
72258
|
+
const authResource = template.Resources?.[authorizerLogicalId];
|
|
72259
|
+
if (!authResource || authResource.Type !== "AWS::ApiGatewayV2::Authorizer") {
|
|
72260
|
+
throw new RouteDiscoveryError(
|
|
72261
|
+
`${declaredAt}: AuthorizerId '${authorizerLogicalId}' does not point at an AWS::ApiGatewayV2::Authorizer in stack '${stackName}'.`
|
|
72202
72262
|
);
|
|
72203
|
-
writeError(res, 502);
|
|
72204
|
-
return;
|
|
72205
72263
|
}
|
|
72206
|
-
|
|
72207
|
-
|
|
72208
|
-
|
|
72209
|
-
|
|
72210
|
-
|
|
72211
|
-
|
|
72264
|
+
const props = authResource.Properties ?? {};
|
|
72265
|
+
const authType = props["AuthorizerType"];
|
|
72266
|
+
if (authType === "REQUEST") {
|
|
72267
|
+
const lambdaLogicalId = resolveLambdaArn(
|
|
72268
|
+
props["AuthorizerUri"],
|
|
72269
|
+
`${stackName}/${authorizerLogicalId}.AuthorizerUri`
|
|
72212
72270
|
);
|
|
72213
|
-
const
|
|
72214
|
-
|
|
72215
|
-
|
|
72216
|
-
|
|
72217
|
-
|
|
72218
|
-
|
|
72219
|
-
|
|
72271
|
+
const identitySources = parseHttpV2IdentitySources(props["IdentitySource"]);
|
|
72272
|
+
const ttl = parseTtl(props["AuthorizerResultTtlInSeconds"], 0, 3600);
|
|
72273
|
+
return {
|
|
72274
|
+
kind: "lambda-request",
|
|
72275
|
+
logicalId: authorizerLogicalId,
|
|
72276
|
+
lambdaLogicalId,
|
|
72277
|
+
identitySources,
|
|
72278
|
+
resultTtlSeconds: ttl,
|
|
72279
|
+
apiVersion: "v2",
|
|
72280
|
+
declaredAt
|
|
72281
|
+
};
|
|
72282
|
+
}
|
|
72283
|
+
if (authType === "JWT") {
|
|
72284
|
+
const jwt = props["JwtConfiguration"];
|
|
72285
|
+
if (!jwt || typeof jwt !== "object") {
|
|
72286
|
+
throw new RouteDiscoveryError(
|
|
72287
|
+
`${stackName}/${authorizerLogicalId}: AWS::ApiGatewayV2::Authorizer.JwtConfiguration is required for AuthorizerType=JWT.`
|
|
72288
|
+
);
|
|
72220
72289
|
}
|
|
72221
|
-
|
|
72222
|
-
|
|
72223
|
-
|
|
72224
|
-
|
|
72225
|
-
|
|
72226
|
-
|
|
72227
|
-
writeError(res, 502);
|
|
72228
|
-
} else {
|
|
72229
|
-
res.end();
|
|
72290
|
+
const obj = jwt;
|
|
72291
|
+
const issuer = obj["Issuer"];
|
|
72292
|
+
if (typeof issuer !== "string" || issuer.length === 0) {
|
|
72293
|
+
throw new RouteDiscoveryError(
|
|
72294
|
+
`${stackName}/${authorizerLogicalId}: JwtConfiguration.Issuer must be a string.`
|
|
72295
|
+
);
|
|
72230
72296
|
}
|
|
72231
|
-
|
|
72232
|
-
|
|
72297
|
+
const audienceRaw = obj["Audience"];
|
|
72298
|
+
const audience = Array.isArray(audienceRaw) ? audienceRaw.filter((s) => typeof s === "string") : [];
|
|
72299
|
+
const cognito = parseCognitoIssuer(issuer);
|
|
72300
|
+
return {
|
|
72301
|
+
kind: "jwt",
|
|
72302
|
+
logicalId: authorizerLogicalId,
|
|
72303
|
+
issuer,
|
|
72304
|
+
audience,
|
|
72305
|
+
...cognito && { region: cognito.region, userPoolId: cognito.userPoolId },
|
|
72306
|
+
declaredAt
|
|
72307
|
+
};
|
|
72233
72308
|
}
|
|
72309
|
+
throw new RouteDiscoveryError(
|
|
72310
|
+
`${stackName}/${authorizerLogicalId}: AWS::ApiGatewayV2::Authorizer.AuthorizerType '${String(authType)}' is not supported by cdkd local start-api (only REQUEST / JWT).`
|
|
72311
|
+
);
|
|
72234
72312
|
}
|
|
72235
|
-
function
|
|
72236
|
-
|
|
72237
|
-
const
|
|
72238
|
-
|
|
72239
|
-
|
|
72240
|
-
|
|
72241
|
-
|
|
72242
|
-
|
|
72243
|
-
|
|
72244
|
-
}
|
|
72245
|
-
function collectHeaders(req) {
|
|
72246
|
-
const out = {};
|
|
72247
|
-
for (const [name, value] of Object.entries(req.headers)) {
|
|
72248
|
-
if (Array.isArray(value)) {
|
|
72249
|
-
out[name] = value;
|
|
72250
|
-
} else if (typeof value === "string") {
|
|
72251
|
-
out[name] = [value];
|
|
72313
|
+
function resolveLambdaArn(value, location) {
|
|
72314
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
72315
|
+
const obj = value;
|
|
72316
|
+
if ("Ref" in obj && typeof obj["Ref"] === "string")
|
|
72317
|
+
return obj["Ref"];
|
|
72318
|
+
if ("Fn::GetAtt" in obj) {
|
|
72319
|
+
const arg = obj["Fn::GetAtt"];
|
|
72320
|
+
if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && arg[1] === "Arn") {
|
|
72321
|
+
return arg[0];
|
|
72322
|
+
}
|
|
72252
72323
|
}
|
|
72253
|
-
|
|
72254
|
-
|
|
72255
|
-
|
|
72256
|
-
|
|
72257
|
-
|
|
72258
|
-
|
|
72259
|
-
|
|
72260
|
-
|
|
72261
|
-
|
|
72262
|
-
|
|
72263
|
-
|
|
72324
|
+
if ("Fn::Join" in obj) {
|
|
72325
|
+
const join9 = obj["Fn::Join"];
|
|
72326
|
+
if (Array.isArray(join9) && join9.length === 2 && Array.isArray(join9[1])) {
|
|
72327
|
+
const parts = join9[1];
|
|
72328
|
+
const literal = parts.filter((p) => typeof p === "string").join("");
|
|
72329
|
+
if (literal.includes(":lambda:path/2015-03-31/functions/")) {
|
|
72330
|
+
for (const p of parts) {
|
|
72331
|
+
if (p && typeof p === "object" && !Array.isArray(p)) {
|
|
72332
|
+
const inner = p;
|
|
72333
|
+
const arg = inner["Fn::GetAtt"];
|
|
72334
|
+
if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && arg[1] === "Arn") {
|
|
72335
|
+
return arg[0];
|
|
72336
|
+
}
|
|
72337
|
+
}
|
|
72338
|
+
}
|
|
72339
|
+
}
|
|
72340
|
+
}
|
|
72341
|
+
}
|
|
72342
|
+
}
|
|
72343
|
+
throw new RouteDiscoveryError(
|
|
72344
|
+
`${location}: only { Ref }, { Fn::GetAtt: [..., 'Arn'] }, or the REST v1 invoke-ARN Fn::Join wrapper are supported (got ${shortJson2(value)}).`
|
|
72345
|
+
);
|
|
72346
|
+
}
|
|
72347
|
+
function parseRestV1HeaderSelector(identitySource, stackName, authorizerLogicalId) {
|
|
72348
|
+
const m = /^method\.request\.header\.([A-Za-z0-9-_]+)$/.exec(identitySource.trim());
|
|
72349
|
+
if (!m) {
|
|
72350
|
+
throw new RouteDiscoveryError(
|
|
72351
|
+
`${stackName}/${authorizerLogicalId}: TOKEN authorizer IdentitySource '${identitySource}' must be 'method.request.header.<HeaderName>'.`
|
|
72352
|
+
);
|
|
72353
|
+
}
|
|
72354
|
+
return m[1].toLowerCase();
|
|
72355
|
+
}
|
|
72356
|
+
function parseRestV1IdentitySources(raw) {
|
|
72357
|
+
const out = [];
|
|
72358
|
+
for (const tokenRaw of raw.split(",")) {
|
|
72359
|
+
const token = tokenRaw.trim();
|
|
72360
|
+
if (token.length === 0)
|
|
72361
|
+
continue;
|
|
72362
|
+
const headerMatch = /^method\.request\.header\.([A-Za-z0-9-_]+)$/.exec(token);
|
|
72363
|
+
if (headerMatch) {
|
|
72364
|
+
out.push({ kind: "header", name: headerMatch[1].toLowerCase() });
|
|
72365
|
+
continue;
|
|
72366
|
+
}
|
|
72367
|
+
const queryMatch = /^method\.request\.querystring\.([A-Za-z0-9-_]+)$/.exec(token);
|
|
72368
|
+
if (queryMatch) {
|
|
72369
|
+
out.push({ kind: "query", name: queryMatch[1] });
|
|
72370
|
+
continue;
|
|
72371
|
+
}
|
|
72372
|
+
const contextMatch = /^context\.([A-Za-z0-9._-]+)$/.exec(token);
|
|
72373
|
+
if (contextMatch) {
|
|
72374
|
+
out.push({ kind: "context", name: contextMatch[1] });
|
|
72375
|
+
continue;
|
|
72376
|
+
}
|
|
72377
|
+
const stageMatch = /^stageVariables\.([A-Za-z0-9._-]+)$/.exec(token);
|
|
72378
|
+
if (stageMatch) {
|
|
72379
|
+
out.push({ kind: "stage-variable", name: stageMatch[1] });
|
|
72380
|
+
continue;
|
|
72381
|
+
}
|
|
72382
|
+
out.push({ kind: "header", name: token.toLowerCase() });
|
|
72383
|
+
}
|
|
72384
|
+
return out;
|
|
72385
|
+
}
|
|
72386
|
+
function parseHttpV2IdentitySources(raw) {
|
|
72387
|
+
if (!Array.isArray(raw))
|
|
72388
|
+
return [];
|
|
72389
|
+
const out = [];
|
|
72390
|
+
for (const entry of raw) {
|
|
72391
|
+
if (typeof entry !== "string")
|
|
72392
|
+
continue;
|
|
72393
|
+
const headerMatch = /^\$request\.header\.([A-Za-z0-9-_]+)$/.exec(entry);
|
|
72394
|
+
if (headerMatch) {
|
|
72395
|
+
out.push({ kind: "header", name: headerMatch[1].toLowerCase() });
|
|
72396
|
+
continue;
|
|
72397
|
+
}
|
|
72398
|
+
const queryMatch = /^\$request\.querystring\.([A-Za-z0-9-_]+)$/.exec(entry);
|
|
72399
|
+
if (queryMatch) {
|
|
72400
|
+
out.push({ kind: "query", name: queryMatch[1] });
|
|
72401
|
+
continue;
|
|
72402
|
+
}
|
|
72403
|
+
out.push({ kind: "header", name: entry.toLowerCase() });
|
|
72404
|
+
}
|
|
72405
|
+
return out;
|
|
72406
|
+
}
|
|
72407
|
+
function parseCognitoUserPoolArn(arn, location) {
|
|
72408
|
+
const m = /^arn:aws[a-z0-9-]*:cognito-idp:([a-z0-9-]+):[0-9]+:userpool\/(.+)$/.exec(arn);
|
|
72409
|
+
if (!m) {
|
|
72410
|
+
throw new RouteDiscoveryError(
|
|
72411
|
+
`${location}: malformed Cognito User Pool ARN '${arn}'. Expected 'arn:aws:cognito-idp:<region>:<account>:userpool/<id>'.`
|
|
72412
|
+
);
|
|
72413
|
+
}
|
|
72414
|
+
return { region: m[1], userPoolId: m[2] };
|
|
72415
|
+
}
|
|
72416
|
+
function parseCognitoIssuer(issuer) {
|
|
72417
|
+
const m = /^https:\/\/cognito-idp\.([a-z0-9-]+)\.amazonaws\.com\/([^/]+)\/?$/.exec(issuer);
|
|
72418
|
+
if (!m)
|
|
72419
|
+
return void 0;
|
|
72420
|
+
return { region: m[1], userPoolId: m[2] };
|
|
72421
|
+
}
|
|
72422
|
+
function pickStringFromArn(value, location) {
|
|
72423
|
+
if (typeof value === "string")
|
|
72424
|
+
return value;
|
|
72425
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
72426
|
+
const obj = value;
|
|
72427
|
+
if ("Fn::GetAtt" in obj) {
|
|
72428
|
+
const arg = obj["Fn::GetAtt"];
|
|
72429
|
+
if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && arg[1] === "Arn") {
|
|
72430
|
+
throw new RouteDiscoveryError(
|
|
72431
|
+
`${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.`
|
|
72432
|
+
);
|
|
72433
|
+
}
|
|
72434
|
+
}
|
|
72435
|
+
}
|
|
72436
|
+
throw new RouteDiscoveryError(
|
|
72437
|
+
`${location}: ProviderARNs[0] must be a literal string (got ${shortJson2(value)}).`
|
|
72438
|
+
);
|
|
72439
|
+
}
|
|
72440
|
+
function parseTtl(raw, fallback, max) {
|
|
72441
|
+
if (typeof raw !== "number" || !Number.isFinite(raw) || raw < 0)
|
|
72442
|
+
return fallback;
|
|
72443
|
+
return Math.min(Math.trunc(raw), max);
|
|
72444
|
+
}
|
|
72445
|
+
function buildIdentityHash(parts) {
|
|
72446
|
+
return parts.map((p) => p ?? "").join("\0");
|
|
72447
|
+
}
|
|
72448
|
+
function attachAuthorizers(stacks, routes) {
|
|
72449
|
+
const stackByRoute = /* @__PURE__ */ new Map();
|
|
72450
|
+
for (const stack of stacks) {
|
|
72451
|
+
const prefix = `${stack.stackName}/`;
|
|
72452
|
+
for (const route of routes) {
|
|
72453
|
+
if (route.declaredAt.startsWith(prefix))
|
|
72454
|
+
stackByRoute.set(route.declaredAt, stack);
|
|
72455
|
+
}
|
|
72456
|
+
}
|
|
72457
|
+
const out = [];
|
|
72458
|
+
const errors = [];
|
|
72459
|
+
for (const route of routes) {
|
|
72460
|
+
const stack = stackByRoute.get(route.declaredAt);
|
|
72461
|
+
if (!stack) {
|
|
72462
|
+
out.push({ route });
|
|
72463
|
+
continue;
|
|
72464
|
+
}
|
|
72465
|
+
try {
|
|
72466
|
+
const authorizer = detectAuthorizer(route, stack);
|
|
72467
|
+
out.push({ route, ...authorizer && { authorizer } });
|
|
72468
|
+
} catch (err) {
|
|
72469
|
+
errors.push(err instanceof Error ? err.message : String(err));
|
|
72470
|
+
}
|
|
72471
|
+
}
|
|
72472
|
+
if (errors.length > 0) {
|
|
72473
|
+
throw new RouteDiscoveryError(
|
|
72474
|
+
`cdkd local start-api: ${errors.length} authorizer error(s):
|
|
72475
|
+
` + errors.map((e) => ` - ${e}`).join("\n")
|
|
72476
|
+
);
|
|
72477
|
+
}
|
|
72478
|
+
return out;
|
|
72479
|
+
}
|
|
72480
|
+
function detectAuthorizer(route, stack) {
|
|
72481
|
+
const slash = route.declaredAt.indexOf("/");
|
|
72482
|
+
if (slash < 0)
|
|
72483
|
+
return void 0;
|
|
72484
|
+
const logicalId = route.declaredAt.slice(slash + 1);
|
|
72485
|
+
const resource = stack.template.Resources?.[logicalId];
|
|
72486
|
+
if (!resource)
|
|
72487
|
+
return void 0;
|
|
72488
|
+
if (resource.Type === "AWS::ApiGateway::Method") {
|
|
72489
|
+
return detectRestV1Authorizer(resource, logicalId, stack);
|
|
72490
|
+
}
|
|
72491
|
+
if (resource.Type === "AWS::ApiGatewayV2::Route") {
|
|
72492
|
+
return detectHttpApiAuthorizer(resource, logicalId, stack);
|
|
72493
|
+
}
|
|
72494
|
+
return void 0;
|
|
72495
|
+
}
|
|
72496
|
+
function detectRestV1Authorizer(methodResource, methodLogicalId, stack) {
|
|
72497
|
+
const props = methodResource.Properties ?? {};
|
|
72498
|
+
const authType = props["AuthorizationType"];
|
|
72499
|
+
if (authType === void 0 || authType === "NONE")
|
|
72500
|
+
return void 0;
|
|
72501
|
+
const authorizerId = props["AuthorizerId"];
|
|
72502
|
+
const refLogicalId = pickRefLogicalId2(authorizerId);
|
|
72503
|
+
if (authType === "AWS_IAM") {
|
|
72504
|
+
throw new RouteDiscoveryError(
|
|
72505
|
+
`${stack.stackName}/${methodLogicalId}: REST v1 AWS_IAM authorization is not supported by cdkd local start-api (deferred follow-up PR).`
|
|
72506
|
+
);
|
|
72507
|
+
}
|
|
72508
|
+
if (!refLogicalId) {
|
|
72509
|
+
throw new RouteDiscoveryError(
|
|
72510
|
+
`${stack.stackName}/${methodLogicalId}: AuthorizationType='${String(authType)}' but AuthorizerId is missing or not a {Ref:...}.`
|
|
72511
|
+
);
|
|
72512
|
+
}
|
|
72513
|
+
return resolveRestV1Authorizer(
|
|
72514
|
+
refLogicalId,
|
|
72515
|
+
stack.template,
|
|
72516
|
+
stack.stackName,
|
|
72517
|
+
`${stack.stackName}/${methodLogicalId}`
|
|
72518
|
+
);
|
|
72519
|
+
}
|
|
72520
|
+
function detectHttpApiAuthorizer(routeResource, routeLogicalId, stack) {
|
|
72521
|
+
const props = routeResource.Properties ?? {};
|
|
72522
|
+
const authType = props["AuthorizationType"];
|
|
72523
|
+
if (authType === void 0 || authType === "NONE")
|
|
72524
|
+
return void 0;
|
|
72525
|
+
const authorizerId = props["AuthorizerId"];
|
|
72526
|
+
const refLogicalId = pickRefLogicalId2(authorizerId);
|
|
72527
|
+
if (!refLogicalId) {
|
|
72528
|
+
throw new RouteDiscoveryError(
|
|
72529
|
+
`${stack.stackName}/${routeLogicalId}: AuthorizationType='${String(authType)}' but AuthorizerId is missing or not a {Ref:...}.`
|
|
72530
|
+
);
|
|
72531
|
+
}
|
|
72532
|
+
const scopesRaw = props["AuthorizationScopes"];
|
|
72533
|
+
const scopes = Array.isArray(scopesRaw) ? scopesRaw.filter((s) => typeof s === "string") : void 0;
|
|
72534
|
+
return resolveHttpApiAuthorizer(
|
|
72535
|
+
refLogicalId,
|
|
72536
|
+
scopes,
|
|
72537
|
+
stack.template,
|
|
72538
|
+
stack.stackName,
|
|
72539
|
+
`${stack.stackName}/${routeLogicalId}`
|
|
72540
|
+
);
|
|
72541
|
+
}
|
|
72542
|
+
function pickRefLogicalId2(value) {
|
|
72543
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
72544
|
+
const ref = value["Ref"];
|
|
72545
|
+
if (typeof ref === "string")
|
|
72546
|
+
return ref;
|
|
72547
|
+
}
|
|
72548
|
+
return null;
|
|
72549
|
+
}
|
|
72550
|
+
function shortJson2(value) {
|
|
72551
|
+
try {
|
|
72552
|
+
const s = JSON.stringify(value);
|
|
72553
|
+
return s.length > 200 ? `${s.slice(0, 200)}\u2026` : s;
|
|
72554
|
+
} catch {
|
|
72555
|
+
return String(value);
|
|
72556
|
+
}
|
|
72557
|
+
}
|
|
72558
|
+
|
|
72559
|
+
// src/local/lambda-authorizer.ts
|
|
72560
|
+
function buildMethodArn(opts) {
|
|
72561
|
+
const region = opts.region ?? "local";
|
|
72562
|
+
const trimmedPath = opts.path.replace(/^\//, "");
|
|
72563
|
+
return `arn:aws:execute-api:${region}:${opts.accountId}:${opts.apiId}/${opts.stage}/${opts.method.toUpperCase()}/${trimmedPath}`;
|
|
72564
|
+
}
|
|
72565
|
+
async function invokeTokenAuthorizer(authorizer, request, ctx) {
|
|
72566
|
+
const token = request.headers[authorizer.tokenHeader];
|
|
72567
|
+
if (!token || token.length === 0) {
|
|
72568
|
+
return { allow: false, identityHash: void 0 };
|
|
72569
|
+
}
|
|
72570
|
+
const event = {
|
|
72571
|
+
type: "TOKEN",
|
|
72572
|
+
authorizationToken: token,
|
|
72573
|
+
methodArn: ctx.methodArn
|
|
72574
|
+
};
|
|
72575
|
+
const identityHash = buildIdentityHash([token]);
|
|
72576
|
+
const result = await invokeAuthorizerLambda(authorizer.lambdaLogicalId, event, ctx);
|
|
72577
|
+
return parseLambdaAuthorizerResponse(result, ctx.methodArn, identityHash);
|
|
72578
|
+
}
|
|
72579
|
+
async function invokeRequestAuthorizer(authorizer, request, ctx) {
|
|
72580
|
+
const { identityHash, missing } = computeRequestIdentityHash(authorizer, request);
|
|
72581
|
+
if (missing) {
|
|
72582
|
+
return { allow: false, identityHash: void 0 };
|
|
72583
|
+
}
|
|
72584
|
+
const event = authorizer.apiVersion === "v1" ? buildRequestEventV1(authorizer, request, ctx) : buildRequestEventV2(authorizer, request, ctx);
|
|
72585
|
+
const result = await invokeAuthorizerLambda(authorizer.lambdaLogicalId, event, ctx);
|
|
72586
|
+
if (authorizer.apiVersion === "v2") {
|
|
72587
|
+
return parseHttpV2RequestResponse(result, ctx.methodArn, identityHash);
|
|
72588
|
+
}
|
|
72589
|
+
return parseLambdaAuthorizerResponse(result, ctx.methodArn, identityHash);
|
|
72590
|
+
}
|
|
72591
|
+
function extractIdentityValue(sel, request) {
|
|
72592
|
+
switch (sel.kind) {
|
|
72593
|
+
case "header":
|
|
72594
|
+
return request.headers[sel.name];
|
|
72595
|
+
case "query":
|
|
72596
|
+
return request.queryStringParameters[sel.name];
|
|
72597
|
+
case "context":
|
|
72598
|
+
return void 0;
|
|
72599
|
+
case "stage-variable":
|
|
72600
|
+
return void 0;
|
|
72601
|
+
}
|
|
72602
|
+
}
|
|
72603
|
+
function computeRequestIdentityHash(authorizer, request) {
|
|
72604
|
+
const identityValues = authorizer.identitySources.map(
|
|
72605
|
+
(sel) => extractIdentityValue(sel, request)
|
|
72606
|
+
);
|
|
72607
|
+
const missing = authorizer.apiVersion === "v1" && authorizer.identitySources.length > 0 && identityValues.every((v) => v === void 0 || v === "");
|
|
72608
|
+
return { identityHash: buildIdentityHash(identityValues), missing };
|
|
72609
|
+
}
|
|
72610
|
+
function buildRequestEventV1(authorizer, request, ctx) {
|
|
72611
|
+
return {
|
|
72612
|
+
type: "REQUEST",
|
|
72613
|
+
methodArn: ctx.methodArn,
|
|
72614
|
+
resource: request.matchedPath,
|
|
72615
|
+
path: request.matchedPath,
|
|
72616
|
+
httpMethod: request.method,
|
|
72617
|
+
headers: request.headers,
|
|
72618
|
+
multiValueHeaders: Object.fromEntries(
|
|
72619
|
+
Object.entries(request.headers).map(([k, v]) => [k, v.split(",")])
|
|
72620
|
+
),
|
|
72621
|
+
queryStringParameters: request.queryStringParameters,
|
|
72622
|
+
multiValueQueryStringParameters: Object.fromEntries(
|
|
72623
|
+
Object.entries(request.queryStringParameters).map(([k, v]) => [k, [v]])
|
|
72624
|
+
),
|
|
72625
|
+
pathParameters: request.pathParameters,
|
|
72626
|
+
stageVariables: null,
|
|
72627
|
+
requestContext: {
|
|
72628
|
+
accountId: ctx.mockAccountId,
|
|
72629
|
+
apiId: ctx.mockApiId,
|
|
72630
|
+
httpMethod: request.method,
|
|
72631
|
+
identity: { sourceIp: request.sourceIp },
|
|
72632
|
+
path: `/${request.stage}${request.matchedPath}`,
|
|
72633
|
+
stage: request.stage
|
|
72634
|
+
},
|
|
72635
|
+
authorizationToken: request.headers[authorizer.identitySources[0]?.name ?? "authorization"]
|
|
72636
|
+
};
|
|
72637
|
+
}
|
|
72638
|
+
function buildRequestEventV2(_authorizer, request, ctx) {
|
|
72639
|
+
return {
|
|
72640
|
+
version: "2.0",
|
|
72641
|
+
type: "REQUEST",
|
|
72642
|
+
routeArn: ctx.methodArn,
|
|
72643
|
+
identitySource: [],
|
|
72644
|
+
// Honored by AWS but not interpreted by user code.
|
|
72645
|
+
routeKey: `${request.method} ${request.matchedPath}`,
|
|
72646
|
+
rawPath: request.matchedPath,
|
|
72647
|
+
rawQueryString: "",
|
|
72648
|
+
headers: request.headers,
|
|
72649
|
+
queryStringParameters: request.queryStringParameters,
|
|
72650
|
+
pathParameters: request.pathParameters,
|
|
72651
|
+
stageVariables: null,
|
|
72652
|
+
requestContext: {
|
|
72653
|
+
accountId: ctx.mockAccountId,
|
|
72654
|
+
apiId: ctx.mockApiId,
|
|
72655
|
+
domainName: "localhost",
|
|
72656
|
+
domainPrefix: "local",
|
|
72657
|
+
http: {
|
|
72658
|
+
method: request.method,
|
|
72659
|
+
path: request.matchedPath,
|
|
72660
|
+
protocol: "HTTP/1.1",
|
|
72661
|
+
sourceIp: request.sourceIp,
|
|
72662
|
+
userAgent: request.headers["user-agent"] ?? ""
|
|
72663
|
+
},
|
|
72664
|
+
requestId: "local-authorizer",
|
|
72665
|
+
routeKey: `${request.method} ${request.matchedPath}`,
|
|
72666
|
+
stage: request.stage,
|
|
72667
|
+
time: "",
|
|
72668
|
+
timeEpoch: 0
|
|
72669
|
+
}
|
|
72670
|
+
};
|
|
72671
|
+
}
|
|
72672
|
+
async function invokeAuthorizerLambda(lambdaLogicalId, event, ctx) {
|
|
72673
|
+
const handle = await ctx.pool.acquire(lambdaLogicalId);
|
|
72674
|
+
try {
|
|
72675
|
+
const result = await invokeRie(handle.containerHost, handle.hostPort, event, ctx.rieTimeoutMs);
|
|
72676
|
+
return result.payload;
|
|
72677
|
+
} finally {
|
|
72678
|
+
ctx.pool.release(handle);
|
|
72679
|
+
}
|
|
72680
|
+
}
|
|
72681
|
+
function parseLambdaAuthorizerResponse(payload, methodArn, identityHash) {
|
|
72682
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
72683
|
+
return { allow: false, identityHash };
|
|
72684
|
+
}
|
|
72685
|
+
const obj = payload;
|
|
72686
|
+
const principalId = typeof obj["principalId"] === "string" ? obj["principalId"] : void 0;
|
|
72687
|
+
const context = obj["context"] && typeof obj["context"] === "object" && !Array.isArray(obj["context"]) ? obj["context"] : void 0;
|
|
72688
|
+
const policy = obj["policyDocument"];
|
|
72689
|
+
if (!policy || typeof policy !== "object") {
|
|
72690
|
+
return {
|
|
72691
|
+
allow: false,
|
|
72692
|
+
identityHash,
|
|
72693
|
+
...principalId !== void 0 && { principalId },
|
|
72694
|
+
...context && { context }
|
|
72695
|
+
};
|
|
72696
|
+
}
|
|
72697
|
+
const stmts = policy["Statement"];
|
|
72698
|
+
if (!Array.isArray(stmts)) {
|
|
72699
|
+
return {
|
|
72700
|
+
allow: false,
|
|
72701
|
+
identityHash,
|
|
72702
|
+
...principalId !== void 0 && { principalId },
|
|
72703
|
+
...context && { context },
|
|
72704
|
+
policy
|
|
72705
|
+
};
|
|
72706
|
+
}
|
|
72707
|
+
const allow = stmts.some((stmt) => {
|
|
72708
|
+
if (!stmt || typeof stmt !== "object" || Array.isArray(stmt))
|
|
72709
|
+
return false;
|
|
72710
|
+
const s = stmt;
|
|
72711
|
+
if (s["Effect"] !== "Allow")
|
|
72712
|
+
return false;
|
|
72713
|
+
const resources = Array.isArray(s["Resource"]) ? s["Resource"] : [s["Resource"]];
|
|
72714
|
+
return resources.some((r) => typeof r === "string" && resourceMatches(r, methodArn));
|
|
72715
|
+
});
|
|
72716
|
+
return {
|
|
72717
|
+
allow,
|
|
72718
|
+
identityHash,
|
|
72719
|
+
...principalId !== void 0 && { principalId },
|
|
72720
|
+
...context && { context },
|
|
72721
|
+
policy
|
|
72722
|
+
};
|
|
72723
|
+
}
|
|
72724
|
+
function parseHttpV2RequestResponse(payload, methodArn, identityHash) {
|
|
72725
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
72726
|
+
const obj = payload;
|
|
72727
|
+
if (typeof obj["isAuthorized"] === "boolean") {
|
|
72728
|
+
const context = obj["context"] && typeof obj["context"] === "object" && !Array.isArray(obj["context"]) ? obj["context"] : void 0;
|
|
72729
|
+
return {
|
|
72730
|
+
allow: obj["isAuthorized"],
|
|
72731
|
+
identityHash,
|
|
72732
|
+
...context && { context }
|
|
72733
|
+
};
|
|
72734
|
+
}
|
|
72735
|
+
}
|
|
72736
|
+
return parseLambdaAuthorizerResponse(payload, methodArn, identityHash);
|
|
72737
|
+
}
|
|
72738
|
+
function resourceMatches(pattern, methodArn) {
|
|
72739
|
+
if (pattern === methodArn)
|
|
72740
|
+
return true;
|
|
72741
|
+
if (!pattern.includes("*") && !pattern.includes("?"))
|
|
72742
|
+
return false;
|
|
72743
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
72744
|
+
const re = new RegExp(`^${escaped}$`);
|
|
72745
|
+
return re.test(methodArn);
|
|
72746
|
+
}
|
|
72747
|
+
function evaluateCachedLambdaPolicy(cached, methodArn) {
|
|
72748
|
+
const policy = cached.policy;
|
|
72749
|
+
if (!policy || typeof policy !== "object") {
|
|
72750
|
+
return { ...cached, allow: false };
|
|
72751
|
+
}
|
|
72752
|
+
const stmts = policy["Statement"];
|
|
72753
|
+
if (!Array.isArray(stmts)) {
|
|
72754
|
+
return { ...cached, allow: false };
|
|
72755
|
+
}
|
|
72756
|
+
const allow = stmts.some((stmt) => {
|
|
72757
|
+
if (!stmt || typeof stmt !== "object" || Array.isArray(stmt))
|
|
72758
|
+
return false;
|
|
72759
|
+
const s = stmt;
|
|
72760
|
+
if (s["Effect"] !== "Allow")
|
|
72761
|
+
return false;
|
|
72762
|
+
const resources = Array.isArray(s["Resource"]) ? s["Resource"] : [s["Resource"]];
|
|
72763
|
+
return resources.some((r) => typeof r === "string" && resourceMatches(r, methodArn));
|
|
72764
|
+
});
|
|
72765
|
+
return { ...cached, allow };
|
|
72766
|
+
}
|
|
72767
|
+
|
|
72768
|
+
// src/local/cognito-jwt.ts
|
|
72769
|
+
import { createPublicKey, createVerify } from "node:crypto";
|
|
72770
|
+
var DEFAULT_JWKS_TTL_MS = 60 * 60 * 1e3;
|
|
72771
|
+
var FAILURE_JWKS_TTL_MS = 60 * 1e3;
|
|
72772
|
+
function createJwksCache(opts = {}) {
|
|
72773
|
+
const fetchImpl = opts.fetchImpl ?? (async (url) => globalThis.fetch(url));
|
|
72774
|
+
const now = opts.now ?? (() => Date.now());
|
|
72775
|
+
const ttlMs = opts.ttlMs ?? DEFAULT_JWKS_TTL_MS;
|
|
72776
|
+
const failureTtlMs = opts.failureTtlMs ?? FAILURE_JWKS_TTL_MS;
|
|
72777
|
+
const map = /* @__PURE__ */ new Map();
|
|
72778
|
+
return {
|
|
72779
|
+
async fetchAndCache(jwksUrl) {
|
|
72780
|
+
const cached = map.get(jwksUrl);
|
|
72781
|
+
if (cached && cached.expiresAt > now())
|
|
72782
|
+
return cached;
|
|
72783
|
+
const logger = getLogger().child("cognito-jwt");
|
|
72784
|
+
try {
|
|
72785
|
+
const response = await fetchImpl(jwksUrl);
|
|
72786
|
+
if (!response.ok) {
|
|
72787
|
+
throw new Error(`JWKS fetch returned HTTP ${response.status}`);
|
|
72788
|
+
}
|
|
72789
|
+
const body = await response.text();
|
|
72790
|
+
const parsed = JSON.parse(body);
|
|
72791
|
+
const keys = Array.isArray(parsed.keys) ? parsed.keys : [];
|
|
72792
|
+
const byKid = /* @__PURE__ */ new Map();
|
|
72793
|
+
for (const k of keys) {
|
|
72794
|
+
if (!k || typeof k !== "object" || Array.isArray(k))
|
|
72795
|
+
continue;
|
|
72796
|
+
const obj = k;
|
|
72797
|
+
if (typeof obj["kid"] === "string" && typeof obj["n"] === "string" && typeof obj["e"] === "string" && typeof obj["kty"] === "string") {
|
|
72798
|
+
byKid.set(obj["kid"], {
|
|
72799
|
+
kid: obj["kid"],
|
|
72800
|
+
n: obj["n"],
|
|
72801
|
+
e: obj["e"],
|
|
72802
|
+
kty: obj["kty"],
|
|
72803
|
+
...typeof obj["alg"] === "string" && { alg: obj["alg"] },
|
|
72804
|
+
...typeof obj["use"] === "string" && { use: obj["use"] }
|
|
72805
|
+
});
|
|
72806
|
+
}
|
|
72807
|
+
}
|
|
72808
|
+
const entry = {
|
|
72809
|
+
byKid,
|
|
72810
|
+
expiresAt: now() + ttlMs,
|
|
72811
|
+
passThrough: false
|
|
72812
|
+
};
|
|
72813
|
+
map.set(jwksUrl, entry);
|
|
72814
|
+
return entry;
|
|
72815
|
+
} catch (err) {
|
|
72816
|
+
logger.warn(
|
|
72817
|
+
`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.`
|
|
72818
|
+
);
|
|
72819
|
+
const entry = {
|
|
72820
|
+
byKid: /* @__PURE__ */ new Map(),
|
|
72821
|
+
expiresAt: now() + failureTtlMs,
|
|
72822
|
+
passThrough: true
|
|
72823
|
+
};
|
|
72824
|
+
map.set(jwksUrl, entry);
|
|
72825
|
+
return entry;
|
|
72826
|
+
}
|
|
72827
|
+
},
|
|
72828
|
+
peek(jwksUrl) {
|
|
72829
|
+
return map.get(jwksUrl);
|
|
72830
|
+
},
|
|
72831
|
+
clear() {
|
|
72832
|
+
map.clear();
|
|
72833
|
+
}
|
|
72834
|
+
};
|
|
72835
|
+
}
|
|
72836
|
+
function buildCognitoJwksUrl(region, userPoolId) {
|
|
72837
|
+
return `https://cognito-idp.${region}.amazonaws.com/${userPoolId}/.well-known/jwks.json`;
|
|
72838
|
+
}
|
|
72839
|
+
function buildJwksUrlFromIssuer(issuer) {
|
|
72840
|
+
const stripped = issuer.replace(/\/+$/, "");
|
|
72841
|
+
return `${stripped}/.well-known/jwks.json`;
|
|
72842
|
+
}
|
|
72843
|
+
async function verifyCognitoJwt(authorizer, authorizationHeader, jwksCache, opts = {}) {
|
|
72844
|
+
const now = opts.now ?? (() => Date.now());
|
|
72845
|
+
const token = extractBearer(authorizationHeader);
|
|
72846
|
+
if (!token) {
|
|
72847
|
+
return { allow: false, identityHash: void 0, ttlSeconds: 0 };
|
|
72848
|
+
}
|
|
72849
|
+
const jwksUrl = buildCognitoJwksUrl(authorizer.region, authorizer.userPoolId);
|
|
72850
|
+
const expectedIssuer = `https://cognito-idp.${authorizer.region}.amazonaws.com/${authorizer.userPoolId}`;
|
|
72851
|
+
return verifyAndShape(token, jwksUrl, expectedIssuer, void 0, jwksCache, opts.warned, now);
|
|
72852
|
+
}
|
|
72853
|
+
async function verifyJwtAuthorizer(authorizer, authorizationHeader, jwksCache, opts = {}) {
|
|
72854
|
+
const now = opts.now ?? (() => Date.now());
|
|
72855
|
+
const token = extractBearer(authorizationHeader);
|
|
72856
|
+
if (!token) {
|
|
72857
|
+
return { allow: false, identityHash: void 0, ttlSeconds: 0 };
|
|
72858
|
+
}
|
|
72859
|
+
const jwksUrl = authorizer.region && authorizer.userPoolId ? buildCognitoJwksUrl(authorizer.region, authorizer.userPoolId) : buildJwksUrlFromIssuer(authorizer.issuer);
|
|
72860
|
+
return verifyAndShape(
|
|
72861
|
+
token,
|
|
72862
|
+
jwksUrl,
|
|
72863
|
+
authorizer.issuer.replace(/\/+$/, ""),
|
|
72864
|
+
authorizer.audience,
|
|
72865
|
+
jwksCache,
|
|
72866
|
+
opts.warned,
|
|
72867
|
+
now
|
|
72868
|
+
);
|
|
72869
|
+
}
|
|
72870
|
+
async function verifyAndShape(token, jwksUrl, expectedIssuer, expectedAudience, jwksCache, warned, now) {
|
|
72871
|
+
const identityHash = buildIdentityHash([token]);
|
|
72872
|
+
const jwks = await jwksCache.fetchAndCache(jwksUrl);
|
|
72873
|
+
if (jwks.passThrough) {
|
|
72874
|
+
if (warned && !warned.has(jwksUrl)) {
|
|
72875
|
+
warned.add(jwksUrl);
|
|
72876
|
+
getLogger().child("cognito-jwt").warn(
|
|
72877
|
+
`JWKS pass-through mode for ${jwksUrl}: token accepted without signature verification.`
|
|
72878
|
+
);
|
|
72879
|
+
}
|
|
72880
|
+
const parsed2 = parseJwt(token);
|
|
72881
|
+
if (parsed2) {
|
|
72882
|
+
return shapeAllowResult(parsed2, identityHash, now);
|
|
72883
|
+
}
|
|
72884
|
+
return {
|
|
72885
|
+
allow: true,
|
|
72886
|
+
principalId: "unknown",
|
|
72887
|
+
context: {},
|
|
72888
|
+
identityHash,
|
|
72889
|
+
ttlSeconds: 0
|
|
72890
|
+
};
|
|
72891
|
+
}
|
|
72892
|
+
const parsed = parseJwt(token);
|
|
72893
|
+
if (!parsed) {
|
|
72894
|
+
return { allow: false, identityHash, ttlSeconds: 0 };
|
|
72895
|
+
}
|
|
72896
|
+
const kid = parsed.header["kid"];
|
|
72897
|
+
if (typeof kid !== "string") {
|
|
72898
|
+
return { allow: false, identityHash, ttlSeconds: 0 };
|
|
72899
|
+
}
|
|
72900
|
+
const key = jwks.byKid.get(kid);
|
|
72901
|
+
if (!key) {
|
|
72902
|
+
return { allow: false, identityHash, ttlSeconds: 0 };
|
|
72903
|
+
}
|
|
72904
|
+
if (!verifyRs256(token, key)) {
|
|
72905
|
+
return { allow: false, identityHash, ttlSeconds: 0 };
|
|
72906
|
+
}
|
|
72907
|
+
const claims = parsed.payload;
|
|
72908
|
+
if (typeof claims["exp"] !== "number" || claims["exp"] * 1e3 <= now()) {
|
|
72909
|
+
return { allow: false, identityHash, ttlSeconds: 0 };
|
|
72910
|
+
}
|
|
72911
|
+
if (typeof claims["iss"] !== "string" || claims["iss"].replace(/\/+$/, "") !== expectedIssuer) {
|
|
72912
|
+
return { allow: false, identityHash, ttlSeconds: 0 };
|
|
72913
|
+
}
|
|
72914
|
+
if (expectedAudience && expectedAudience.length > 0) {
|
|
72915
|
+
const aud = claims["aud"];
|
|
72916
|
+
const clientId = claims["client_id"];
|
|
72917
|
+
const audValues = Array.isArray(aud) ? aud : aud !== void 0 ? [aud] : [];
|
|
72918
|
+
const matches = audValues.some((v) => typeof v === "string" && expectedAudience.includes(v)) || typeof clientId === "string" && expectedAudience.includes(clientId);
|
|
72919
|
+
if (!matches) {
|
|
72920
|
+
return { allow: false, identityHash, ttlSeconds: 0 };
|
|
72921
|
+
}
|
|
72922
|
+
}
|
|
72923
|
+
return shapeAllowResult(parsed, identityHash, now);
|
|
72924
|
+
}
|
|
72925
|
+
function shapeAllowResult(parsed, identityHash, now) {
|
|
72926
|
+
const claims = parsed.payload;
|
|
72927
|
+
const principalId = pickStringClaim(claims, "sub") ?? pickStringClaim(claims, "cognito:username") ?? pickStringClaim(claims, "username") ?? "unknown";
|
|
72928
|
+
const expMs = typeof claims["exp"] === "number" ? claims["exp"] * 1e3 : 0;
|
|
72929
|
+
const remainingSeconds = Math.max(0, Math.floor((expMs - now()) / 1e3));
|
|
72930
|
+
const ttlSeconds = Math.min(300, remainingSeconds);
|
|
72931
|
+
return {
|
|
72932
|
+
allow: true,
|
|
72933
|
+
principalId,
|
|
72934
|
+
context: claims,
|
|
72935
|
+
identityHash,
|
|
72936
|
+
ttlSeconds
|
|
72937
|
+
};
|
|
72938
|
+
}
|
|
72939
|
+
function pickStringClaim(claims, key) {
|
|
72940
|
+
const v = claims[key];
|
|
72941
|
+
return typeof v === "string" ? v : void 0;
|
|
72942
|
+
}
|
|
72943
|
+
function extractBearer(header) {
|
|
72944
|
+
if (!header)
|
|
72945
|
+
return void 0;
|
|
72946
|
+
const m = /^\s*Bearer\s+(.+)\s*$/i.exec(header);
|
|
72947
|
+
if (!m)
|
|
72948
|
+
return void 0;
|
|
72949
|
+
return m[1].trim();
|
|
72950
|
+
}
|
|
72951
|
+
function parseJwt(token) {
|
|
72952
|
+
const parts = token.split(".");
|
|
72953
|
+
if (parts.length !== 3)
|
|
72954
|
+
return void 0;
|
|
72955
|
+
try {
|
|
72956
|
+
const headerJson = base64UrlDecodeToString(parts[0]);
|
|
72957
|
+
const payloadJson = base64UrlDecodeToString(parts[1]);
|
|
72958
|
+
const header = JSON.parse(headerJson);
|
|
72959
|
+
const payload = JSON.parse(payloadJson);
|
|
72960
|
+
return {
|
|
72961
|
+
header,
|
|
72962
|
+
payload,
|
|
72963
|
+
signingInput: `${parts[0]}.${parts[1]}`,
|
|
72964
|
+
signatureB64: parts[2]
|
|
72965
|
+
};
|
|
72966
|
+
} catch {
|
|
72967
|
+
return void 0;
|
|
72968
|
+
}
|
|
72969
|
+
}
|
|
72970
|
+
function verifyRs256(token, key) {
|
|
72971
|
+
const parts = token.split(".");
|
|
72972
|
+
if (parts.length !== 3)
|
|
72973
|
+
return false;
|
|
72974
|
+
const signingInput = `${parts[0]}.${parts[1]}`;
|
|
72975
|
+
const signature = base64UrlDecodeToBuffer(parts[2]);
|
|
72976
|
+
try {
|
|
72977
|
+
const publicKey = createPublicKey({
|
|
72978
|
+
key: { kty: key.kty, n: key.n, e: key.e },
|
|
72979
|
+
format: "jwk"
|
|
72980
|
+
});
|
|
72981
|
+
const verifier = createVerify("RSA-SHA256");
|
|
72982
|
+
verifier.update(signingInput);
|
|
72983
|
+
verifier.end();
|
|
72984
|
+
return verifier.verify(publicKey, signature);
|
|
72985
|
+
} catch {
|
|
72986
|
+
return false;
|
|
72987
|
+
}
|
|
72988
|
+
}
|
|
72989
|
+
function base64UrlDecodeToString(input) {
|
|
72990
|
+
return base64UrlDecodeToBuffer(input).toString("utf-8");
|
|
72991
|
+
}
|
|
72992
|
+
function base64UrlDecodeToBuffer(input) {
|
|
72993
|
+
const padded = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
72994
|
+
const padding = padded.length % 4 === 0 ? "" : "=".repeat(4 - padded.length % 4);
|
|
72995
|
+
return Buffer.from(padded + padding, "base64");
|
|
72996
|
+
}
|
|
72997
|
+
|
|
72998
|
+
// src/local/http-server.ts
|
|
72999
|
+
async function startApiServer(opts) {
|
|
73000
|
+
const logger = getLogger().child("start-api");
|
|
73001
|
+
const server = createServer2((req, res) => {
|
|
73002
|
+
handleRequest(req, res, opts).catch((err) => {
|
|
73003
|
+
logger.error(
|
|
73004
|
+
`Unhandled request error: ${err instanceof Error ? err.stack ?? err.message : String(err)}`
|
|
73005
|
+
);
|
|
73006
|
+
if (!res.headersSent) {
|
|
73007
|
+
writeError(res, 502);
|
|
73008
|
+
}
|
|
73009
|
+
});
|
|
73010
|
+
});
|
|
73011
|
+
server.on("connection", (socket) => {
|
|
73012
|
+
socket.setNoDelay(true);
|
|
73013
|
+
});
|
|
73014
|
+
const { actualPort, actualHost } = await new Promise(
|
|
73015
|
+
(resolveListen, rejectListen) => {
|
|
73016
|
+
server.once("error", rejectListen);
|
|
73017
|
+
server.listen(opts.port, opts.host, () => {
|
|
73018
|
+
const addr = server.address();
|
|
73019
|
+
if (addr === null || typeof addr === "string") {
|
|
73020
|
+
rejectListen(new Error("Could not determine listening address"));
|
|
73021
|
+
return;
|
|
73022
|
+
}
|
|
73023
|
+
resolveListen({ actualPort: addr.port, actualHost: opts.host });
|
|
73024
|
+
});
|
|
73025
|
+
}
|
|
73026
|
+
);
|
|
73027
|
+
let closed = false;
|
|
73028
|
+
return {
|
|
73029
|
+
port: actualPort,
|
|
73030
|
+
host: actualHost,
|
|
73031
|
+
server,
|
|
73032
|
+
close: async () => {
|
|
73033
|
+
if (closed)
|
|
73034
|
+
return;
|
|
73035
|
+
closed = true;
|
|
73036
|
+
await new Promise((resolveClose) => {
|
|
73037
|
+
server.close(() => resolveClose());
|
|
73038
|
+
server.closeAllConnections?.();
|
|
73039
|
+
});
|
|
73040
|
+
}
|
|
73041
|
+
};
|
|
73042
|
+
}
|
|
73043
|
+
async function handleRequest(req, res, opts) {
|
|
73044
|
+
const logger = getLogger().child("start-api");
|
|
73045
|
+
const bodyBuf = await readBody(req);
|
|
73046
|
+
const rawUrl = req.url ?? "/";
|
|
73047
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
73048
|
+
const requestPath = rawUrl.split("?")[0] ?? "/";
|
|
73049
|
+
const flatRoutes = opts.routes.map((r) => r.route);
|
|
73050
|
+
const match = matchRoute(method, requestPath, flatRoutes);
|
|
73051
|
+
if (!match) {
|
|
73052
|
+
writeError(res, 404, '{"message":"Not Found"}');
|
|
73053
|
+
return;
|
|
73054
|
+
}
|
|
73055
|
+
const matchedEntry = opts.routes.find(
|
|
73056
|
+
(r) => r.route.declaredAt === match.route.declaredAt && r.route.method === match.route.method
|
|
73057
|
+
);
|
|
73058
|
+
const authorizer = matchedEntry?.authorizer;
|
|
73059
|
+
const snapshot = {
|
|
73060
|
+
method,
|
|
73061
|
+
rawUrl,
|
|
73062
|
+
headers: collectHeaders(req),
|
|
73063
|
+
body: bodyBuf,
|
|
73064
|
+
...req.socket.remoteAddress !== void 0 && { sourceIp: req.socket.remoteAddress }
|
|
73065
|
+
};
|
|
73066
|
+
const matchCtx = {
|
|
73067
|
+
route: match.route,
|
|
73068
|
+
pathParameters: match.pathParameters,
|
|
73069
|
+
matchedPath: requestPath
|
|
73070
|
+
};
|
|
73071
|
+
let baseEvent = match.route.apiVersion === "v1" ? buildRestV1Event(snapshot, matchCtx) : buildHttpApiV2Event(snapshot, matchCtx);
|
|
73072
|
+
let authResult;
|
|
73073
|
+
if (authorizer) {
|
|
73074
|
+
let outcome;
|
|
73075
|
+
try {
|
|
73076
|
+
outcome = await runAuthorizerPass(
|
|
73077
|
+
authorizer,
|
|
73078
|
+
snapshot,
|
|
73079
|
+
matchCtx,
|
|
73080
|
+
opts,
|
|
73081
|
+
baseEvent["requestContext"]
|
|
73082
|
+
);
|
|
73083
|
+
} catch (err) {
|
|
73084
|
+
logger.error(
|
|
73085
|
+
`Authorizer ${authorizer.logicalId} threw for ${match.route.declaredAt}: ${err instanceof Error ? err.message : String(err)}`
|
|
73086
|
+
);
|
|
73087
|
+
writeAuthRejection(res, match.route.apiVersion, "policy-deny");
|
|
73088
|
+
return;
|
|
73089
|
+
}
|
|
73090
|
+
if (!outcome.result.allow) {
|
|
73091
|
+
writeAuthRejection(res, match.route.apiVersion, outcome.denyKind ?? "policy-deny");
|
|
73092
|
+
return;
|
|
73093
|
+
}
|
|
73094
|
+
authResult = outcome.result;
|
|
73095
|
+
const overlay = buildOverlay(authorizer, authResult);
|
|
73096
|
+
if (overlay) {
|
|
73097
|
+
baseEvent = applyAuthorizerOverlay(baseEvent, overlay);
|
|
73098
|
+
}
|
|
73099
|
+
}
|
|
73100
|
+
let handle;
|
|
73101
|
+
try {
|
|
73102
|
+
handle = await opts.pool.acquire(match.route.lambdaLogicalId);
|
|
73103
|
+
} catch (err) {
|
|
73104
|
+
logger.error(
|
|
73105
|
+
`Failed to acquire container for ${match.route.lambdaLogicalId}: ${err instanceof Error ? err.message : String(err)}`
|
|
73106
|
+
);
|
|
73107
|
+
writeError(res, 502);
|
|
73108
|
+
return;
|
|
73109
|
+
}
|
|
73110
|
+
try {
|
|
73111
|
+
const invokeResult = await invokeRie(
|
|
73112
|
+
handle.containerHost,
|
|
73113
|
+
handle.hostPort,
|
|
73114
|
+
baseEvent,
|
|
73115
|
+
opts.rieTimeoutMs
|
|
73116
|
+
);
|
|
73117
|
+
const translated = translateLambdaResponse(invokeResult.payload, match.route.apiVersion);
|
|
73118
|
+
res.statusCode = translated.statusCode;
|
|
73119
|
+
for (const [name, value] of Object.entries(translated.headers)) {
|
|
73120
|
+
res.setHeader(name, value);
|
|
73121
|
+
}
|
|
73122
|
+
if (translated.cookies.length > 0) {
|
|
73123
|
+
res.setHeader("set-cookie", translated.cookies);
|
|
73124
|
+
}
|
|
73125
|
+
res.end(translated.body);
|
|
73126
|
+
} catch (err) {
|
|
73127
|
+
logger.error(
|
|
73128
|
+
`RIE invoke failed for ${match.route.lambdaLogicalId}: ${err instanceof Error ? err.message : String(err)}`
|
|
73129
|
+
);
|
|
73130
|
+
if (!res.headersSent) {
|
|
73131
|
+
writeError(res, 502);
|
|
73132
|
+
} else {
|
|
73133
|
+
res.end();
|
|
73134
|
+
}
|
|
73135
|
+
} finally {
|
|
73136
|
+
opts.pool.release(handle);
|
|
73137
|
+
}
|
|
73138
|
+
}
|
|
73139
|
+
async function runAuthorizerPass(authorizer, snapshot, matchCtx, opts, requestContextV2) {
|
|
73140
|
+
const headers = lowercaseSingularHeaders(snapshot.headers);
|
|
73141
|
+
const queryStringParameters = parseQueryStringSingular(snapshot.rawUrl);
|
|
73142
|
+
const sourceIp = pickSourceIp(matchCtx.route.apiVersion, requestContextV2, snapshot);
|
|
73143
|
+
const reqSnap = {
|
|
73144
|
+
method: snapshot.method.toUpperCase(),
|
|
73145
|
+
headers,
|
|
73146
|
+
queryStringParameters,
|
|
73147
|
+
pathParameters: matchCtx.pathParameters,
|
|
73148
|
+
sourceIp,
|
|
73149
|
+
matchedPath: matchCtx.matchedPath,
|
|
73150
|
+
stage: matchCtx.route.stage
|
|
73151
|
+
};
|
|
73152
|
+
const methodArn = buildMethodArn({
|
|
73153
|
+
apiId: "local",
|
|
73154
|
+
accountId: "123456789012",
|
|
73155
|
+
stage: matchCtx.route.stage,
|
|
73156
|
+
method: snapshot.method,
|
|
73157
|
+
path: matchCtx.matchedPath
|
|
73158
|
+
});
|
|
73159
|
+
const cache2 = opts.authorizerCache;
|
|
73160
|
+
if (authorizer.kind === "lambda-token") {
|
|
73161
|
+
const token = headers[authorizer.tokenHeader];
|
|
73162
|
+
if (!token) {
|
|
73163
|
+
return { result: { allow: false }, denyKind: "missing-identity" };
|
|
73164
|
+
}
|
|
73165
|
+
if (cache2) {
|
|
73166
|
+
const cached = cache2.get(authorizer.logicalId, hashOne(token));
|
|
73167
|
+
if (cached) {
|
|
73168
|
+
if (cached.policy !== void 0) {
|
|
73169
|
+
return shapeOutcome(evaluateCachedLambdaPolicy(cached, methodArn));
|
|
73170
|
+
}
|
|
73171
|
+
return shapeOutcome(cached);
|
|
73172
|
+
}
|
|
73173
|
+
}
|
|
73174
|
+
const result2 = await invokeTokenAuthorizer(authorizer, reqSnap, {
|
|
73175
|
+
pool: opts.pool,
|
|
73176
|
+
rieTimeoutMs: opts.rieTimeoutMs,
|
|
73177
|
+
methodArn,
|
|
73178
|
+
mockAccountId: "123456789012",
|
|
73179
|
+
mockApiId: "local"
|
|
73180
|
+
});
|
|
73181
|
+
if (cache2 && result2.identityHash !== void 0) {
|
|
73182
|
+
cache2.set(
|
|
73183
|
+
authorizer.logicalId,
|
|
73184
|
+
result2.identityHash,
|
|
73185
|
+
authorizer.resultTtlSeconds,
|
|
73186
|
+
stripHash(result2)
|
|
73187
|
+
);
|
|
73188
|
+
}
|
|
73189
|
+
return shapeOutcome(stripHash(result2));
|
|
73190
|
+
}
|
|
73191
|
+
if (authorizer.kind === "lambda-request") {
|
|
73192
|
+
const { identityHash, missing } = computeRequestIdentityHash(authorizer, reqSnap);
|
|
73193
|
+
if (missing) {
|
|
73194
|
+
return { result: { allow: false }, denyKind: "missing-identity" };
|
|
73195
|
+
}
|
|
73196
|
+
if (cache2 && authorizer.resultTtlSeconds > 0) {
|
|
73197
|
+
const cached = cache2.get(authorizer.logicalId, identityHash);
|
|
73198
|
+
if (cached) {
|
|
73199
|
+
if (cached.policy !== void 0) {
|
|
73200
|
+
return shapeOutcome(evaluateCachedLambdaPolicy(cached, methodArn));
|
|
73201
|
+
}
|
|
73202
|
+
return shapeOutcome(cached);
|
|
73203
|
+
}
|
|
73204
|
+
}
|
|
73205
|
+
const result2 = await invokeRequestAuthorizer(authorizer, reqSnap, {
|
|
73206
|
+
pool: opts.pool,
|
|
73207
|
+
rieTimeoutMs: opts.rieTimeoutMs,
|
|
73208
|
+
methodArn,
|
|
73209
|
+
mockAccountId: "123456789012",
|
|
73210
|
+
mockApiId: "local"
|
|
73211
|
+
});
|
|
73212
|
+
if (cache2 && result2.identityHash !== void 0 && authorizer.resultTtlSeconds > 0) {
|
|
73213
|
+
cache2.set(
|
|
73214
|
+
authorizer.logicalId,
|
|
73215
|
+
result2.identityHash,
|
|
73216
|
+
authorizer.resultTtlSeconds,
|
|
73217
|
+
stripHash(result2)
|
|
73218
|
+
);
|
|
73219
|
+
}
|
|
73220
|
+
return shapeOutcome(stripHash(result2));
|
|
73221
|
+
}
|
|
73222
|
+
if (!opts.jwksCache) {
|
|
73223
|
+
return { result: { allow: false }, denyKind: "policy-deny" };
|
|
73224
|
+
}
|
|
73225
|
+
const authHeader = headers["authorization"];
|
|
73226
|
+
const jwksOpts = { ...opts.jwksWarnedUrls && { warned: opts.jwksWarnedUrls } };
|
|
73227
|
+
if (authorizer.kind === "cognito") {
|
|
73228
|
+
if (cache2 && authHeader !== void 0) {
|
|
73229
|
+
const cached = cache2.get(authorizer.logicalId, hashOne(authHeader));
|
|
73230
|
+
if (cached)
|
|
73231
|
+
return shapeOutcome(cached);
|
|
73232
|
+
}
|
|
73233
|
+
const result2 = await verifyCognitoJwt(authorizer, authHeader, opts.jwksCache, jwksOpts);
|
|
73234
|
+
if (cache2 && result2.identityHash !== void 0 && result2.ttlSeconds > 0) {
|
|
73235
|
+
cache2.set(
|
|
73236
|
+
authorizer.logicalId,
|
|
73237
|
+
result2.identityHash,
|
|
73238
|
+
result2.ttlSeconds,
|
|
73239
|
+
stripHashAndTtl(result2)
|
|
73240
|
+
);
|
|
73241
|
+
}
|
|
73242
|
+
if (!result2.allow && authHeader === void 0) {
|
|
73243
|
+
return { result: stripHashAndTtl(result2), denyKind: "missing-identity" };
|
|
73244
|
+
}
|
|
73245
|
+
return shapeOutcome(stripHashAndTtl(result2));
|
|
73246
|
+
}
|
|
73247
|
+
if (cache2 && authHeader !== void 0) {
|
|
73248
|
+
const cached = cache2.get(authorizer.logicalId, hashOne(authHeader));
|
|
73249
|
+
if (cached)
|
|
73250
|
+
return shapeOutcome(cached);
|
|
73251
|
+
}
|
|
73252
|
+
const result = await verifyJwtAuthorizer(authorizer, authHeader, opts.jwksCache, jwksOpts);
|
|
73253
|
+
if (cache2 && result.identityHash !== void 0 && result.ttlSeconds > 0) {
|
|
73254
|
+
cache2.set(
|
|
73255
|
+
authorizer.logicalId,
|
|
73256
|
+
result.identityHash,
|
|
73257
|
+
result.ttlSeconds,
|
|
73258
|
+
stripHashAndTtl(result)
|
|
73259
|
+
);
|
|
73260
|
+
}
|
|
73261
|
+
if (!result.allow && authHeader === void 0) {
|
|
73262
|
+
return { result: stripHashAndTtl(result), denyKind: "missing-identity" };
|
|
73263
|
+
}
|
|
73264
|
+
return shapeOutcome(stripHashAndTtl(result));
|
|
73265
|
+
}
|
|
73266
|
+
function shapeOutcome(result) {
|
|
73267
|
+
if (result.allow)
|
|
73268
|
+
return { result };
|
|
73269
|
+
return { result, denyKind: "policy-deny" };
|
|
73270
|
+
}
|
|
73271
|
+
function pickSourceIp(apiVersion, requestContext, snapshot) {
|
|
73272
|
+
if (apiVersion === "v1") {
|
|
73273
|
+
const identity = requestContext["identity"];
|
|
73274
|
+
if (identity && typeof identity === "object" && !Array.isArray(identity) && typeof identity["sourceIp"] === "string") {
|
|
73275
|
+
return identity["sourceIp"];
|
|
73276
|
+
}
|
|
73277
|
+
} else {
|
|
73278
|
+
const http = requestContext["http"];
|
|
73279
|
+
if (http && typeof http === "object" && !Array.isArray(http) && typeof http["sourceIp"] === "string") {
|
|
73280
|
+
return http["sourceIp"];
|
|
73281
|
+
}
|
|
73282
|
+
}
|
|
73283
|
+
return snapshot.sourceIp ?? "127.0.0.1";
|
|
73284
|
+
}
|
|
73285
|
+
function buildOverlay(authorizer, result) {
|
|
73286
|
+
if (authorizer.kind === "lambda-token" || authorizer.kind === "lambda-request") {
|
|
73287
|
+
const isV2 = authorizer.kind === "lambda-request" && authorizer.apiVersion === "v2";
|
|
73288
|
+
return isV2 ? {
|
|
73289
|
+
kind: "lambda-http-v2",
|
|
73290
|
+
...result.principalId !== void 0 && { principalId: result.principalId },
|
|
73291
|
+
...result.context && { context: result.context }
|
|
73292
|
+
} : {
|
|
73293
|
+
kind: "lambda-rest-v1",
|
|
73294
|
+
...result.principalId !== void 0 && { principalId: result.principalId },
|
|
73295
|
+
...result.context && { context: result.context }
|
|
73296
|
+
};
|
|
73297
|
+
}
|
|
73298
|
+
if (authorizer.kind === "cognito") {
|
|
73299
|
+
return { kind: "cognito-rest-v1", claims: result.context ?? {} };
|
|
73300
|
+
}
|
|
73301
|
+
return { kind: "jwt-http-v2", claims: result.context ?? {} };
|
|
73302
|
+
}
|
|
73303
|
+
function writeAuthRejection(res, apiVersion, denyKind) {
|
|
73304
|
+
if (apiVersion === "v2") {
|
|
73305
|
+
writeError(res, 401, '{"message":"Unauthorized"}');
|
|
73306
|
+
return;
|
|
73307
|
+
}
|
|
73308
|
+
if (denyKind === "missing-identity") {
|
|
73309
|
+
writeError(res, 401, '{"message":"Unauthorized"}');
|
|
73310
|
+
return;
|
|
73311
|
+
}
|
|
73312
|
+
writeError(res, 403, '{"message":"Forbidden"}');
|
|
73313
|
+
}
|
|
73314
|
+
function hashOne(value) {
|
|
73315
|
+
return value;
|
|
73316
|
+
}
|
|
73317
|
+
function stripHash(r) {
|
|
73318
|
+
const { identityHash, ...rest } = r;
|
|
73319
|
+
return rest;
|
|
73320
|
+
}
|
|
73321
|
+
function stripHashAndTtl(r) {
|
|
73322
|
+
const { identityHash, ttlSeconds, ...rest } = r;
|
|
73323
|
+
return rest;
|
|
73324
|
+
}
|
|
73325
|
+
function lowercaseSingularHeaders(raw) {
|
|
73326
|
+
const out = {};
|
|
73327
|
+
for (const [name, values] of Object.entries(raw)) {
|
|
73328
|
+
out[name.toLowerCase()] = values.join(",");
|
|
73329
|
+
}
|
|
73330
|
+
return out;
|
|
73331
|
+
}
|
|
73332
|
+
function parseQueryStringSingular(rawUrl) {
|
|
73333
|
+
const q = rawUrl.indexOf("?");
|
|
73334
|
+
if (q < 0)
|
|
73335
|
+
return {};
|
|
73336
|
+
const raw = rawUrl.slice(q + 1);
|
|
73337
|
+
if (raw.length === 0)
|
|
73338
|
+
return {};
|
|
73339
|
+
const out = {};
|
|
73340
|
+
for (const pair of raw.split("&")) {
|
|
73341
|
+
if (pair.length === 0)
|
|
73342
|
+
continue;
|
|
73343
|
+
const eq = pair.indexOf("=");
|
|
73344
|
+
const rawKey = eq === -1 ? pair : pair.slice(0, eq);
|
|
73345
|
+
const rawValue = eq === -1 ? "" : pair.slice(eq + 1);
|
|
73346
|
+
let key = rawKey;
|
|
73347
|
+
let value = rawValue;
|
|
73348
|
+
try {
|
|
73349
|
+
key = decodeURIComponent(rawKey);
|
|
73350
|
+
} catch {
|
|
73351
|
+
}
|
|
73352
|
+
try {
|
|
73353
|
+
value = decodeURIComponent(rawValue);
|
|
73354
|
+
} catch {
|
|
73355
|
+
}
|
|
73356
|
+
out[key] = value;
|
|
73357
|
+
}
|
|
73358
|
+
return out;
|
|
73359
|
+
}
|
|
73360
|
+
function readBody(req) {
|
|
73361
|
+
return new Promise((resolveBody, rejectBody) => {
|
|
73362
|
+
const chunks = [];
|
|
73363
|
+
req.on("data", (chunk) => {
|
|
73364
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
73365
|
+
});
|
|
73366
|
+
req.on("end", () => resolveBody(Buffer.concat(chunks)));
|
|
73367
|
+
req.on("error", rejectBody);
|
|
73368
|
+
});
|
|
73369
|
+
}
|
|
73370
|
+
function collectHeaders(req) {
|
|
73371
|
+
const out = {};
|
|
73372
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
73373
|
+
if (Array.isArray(value)) {
|
|
73374
|
+
out[name] = value;
|
|
73375
|
+
} else if (typeof value === "string") {
|
|
73376
|
+
out[name] = [value];
|
|
73377
|
+
}
|
|
73378
|
+
}
|
|
73379
|
+
return out;
|
|
73380
|
+
}
|
|
73381
|
+
function writeError(res, statusCode, body = '{"message":"Internal server error"}') {
|
|
73382
|
+
res.statusCode = statusCode;
|
|
73383
|
+
res.setHeader("content-type", "application/json");
|
|
73384
|
+
res.setHeader("content-length", String(Buffer.byteLength(body, "utf-8")));
|
|
73385
|
+
res.end(body);
|
|
73386
|
+
}
|
|
73387
|
+
|
|
73388
|
+
// src/local/authorizer-cache.ts
|
|
73389
|
+
function createAuthorizerCache(opts = {}) {
|
|
73390
|
+
const now = opts.now ?? (() => Date.now());
|
|
73391
|
+
const map = /* @__PURE__ */ new Map();
|
|
73392
|
+
const buildKey = (auth, identity) => `${auth}\0${identity}`;
|
|
73393
|
+
const sweep = () => {
|
|
73394
|
+
const t = now();
|
|
73395
|
+
for (const [k, v] of map) {
|
|
73396
|
+
if (v.expiresAt <= t)
|
|
73397
|
+
map.delete(k);
|
|
73398
|
+
}
|
|
73399
|
+
};
|
|
73400
|
+
return {
|
|
73401
|
+
get(authorizerLogicalId, identityHash) {
|
|
73402
|
+
const key = buildKey(authorizerLogicalId, identityHash);
|
|
73403
|
+
const entry = map.get(key);
|
|
73404
|
+
if (!entry)
|
|
73405
|
+
return void 0;
|
|
73406
|
+
if (entry.expiresAt <= now()) {
|
|
73407
|
+
map.delete(key);
|
|
73408
|
+
return void 0;
|
|
73409
|
+
}
|
|
73410
|
+
return entry.result;
|
|
73411
|
+
},
|
|
73412
|
+
set(authorizerLogicalId, identityHash, ttlSeconds, result) {
|
|
73413
|
+
if (ttlSeconds <= 0)
|
|
73414
|
+
return;
|
|
73415
|
+
const key = buildKey(authorizerLogicalId, identityHash);
|
|
73416
|
+
map.set(key, { expiresAt: now() + ttlSeconds * 1e3, result });
|
|
73417
|
+
},
|
|
73418
|
+
clear() {
|
|
73419
|
+
map.clear();
|
|
73420
|
+
},
|
|
73421
|
+
size() {
|
|
73422
|
+
sweep();
|
|
73423
|
+
return map.size;
|
|
73424
|
+
}
|
|
73425
|
+
};
|
|
73426
|
+
}
|
|
73427
|
+
|
|
73428
|
+
// src/cli/commands/local-start-api.ts
|
|
72264
73429
|
async function localStartApiCommand(options) {
|
|
72265
73430
|
const logger = getLogger();
|
|
72266
73431
|
if (options.verbose) {
|
|
@@ -72294,7 +73459,8 @@ async function localStartApiCommand(options) {
|
|
|
72294
73459
|
"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."
|
|
72295
73460
|
);
|
|
72296
73461
|
}
|
|
72297
|
-
const
|
|
73462
|
+
const routesWithAuth = attachAuthorizers(targetStacks, routes);
|
|
73463
|
+
const lambdaIds = uniqueLambdaIds(routes, routesWithAuth);
|
|
72298
73464
|
const overrides = readEnvOverridesFile(options.envVars);
|
|
72299
73465
|
const debugPortBase = options.debugPortBase ? parseDebugPort(options.debugPortBase) : void 0;
|
|
72300
73466
|
const specs = /* @__PURE__ */ new Map();
|
|
@@ -72348,12 +73514,20 @@ async function localStartApiCommand(options) {
|
|
|
72348
73514
|
if (!Number.isFinite(port) || port < 0 || port > 65535) {
|
|
72349
73515
|
throw new Error(`--port must be 0..65535 (got ${options.port}).`);
|
|
72350
73516
|
}
|
|
73517
|
+
const authorizerCache = createAuthorizerCache();
|
|
73518
|
+
const jwksCache = createJwksCache();
|
|
73519
|
+
const jwksWarnedUrls = /* @__PURE__ */ new Set();
|
|
73520
|
+
await prewarmJwks(routesWithAuth, jwksCache);
|
|
73521
|
+
warnVpcConfigLambdas(routesWithAuth, targetStacks);
|
|
72351
73522
|
const server = await startApiServer({
|
|
72352
|
-
routes,
|
|
73523
|
+
routes: routesWithAuth,
|
|
72353
73524
|
pool,
|
|
72354
73525
|
rieTimeoutMs,
|
|
72355
73526
|
host: options.host,
|
|
72356
|
-
port
|
|
73527
|
+
port,
|
|
73528
|
+
authorizerCache,
|
|
73529
|
+
jwksCache,
|
|
73530
|
+
jwksWarnedUrls
|
|
72357
73531
|
});
|
|
72358
73532
|
printRouteTable(routes);
|
|
72359
73533
|
logger.info(
|
|
@@ -72430,7 +73604,7 @@ function pickTargetStacks(stacks, pattern) {
|
|
|
72430
73604
|
`Multi-stack app: pass --stack <name> to pick a target. Available stacks: ${stacks.map((s) => s.stackName).join(", ")}.`
|
|
72431
73605
|
);
|
|
72432
73606
|
}
|
|
72433
|
-
function uniqueLambdaIds(routes) {
|
|
73607
|
+
function uniqueLambdaIds(routes, routesWithAuth) {
|
|
72434
73608
|
const seen = /* @__PURE__ */ new Set();
|
|
72435
73609
|
const out = [];
|
|
72436
73610
|
for (const r of routes) {
|
|
@@ -72439,8 +73613,67 @@ function uniqueLambdaIds(routes) {
|
|
|
72439
73613
|
out.push(r.lambdaLogicalId);
|
|
72440
73614
|
}
|
|
72441
73615
|
}
|
|
73616
|
+
for (const entry of routesWithAuth) {
|
|
73617
|
+
const auth = entry.authorizer;
|
|
73618
|
+
if (!auth)
|
|
73619
|
+
continue;
|
|
73620
|
+
if (auth.kind === "lambda-token" || auth.kind === "lambda-request") {
|
|
73621
|
+
if (!seen.has(auth.lambdaLogicalId)) {
|
|
73622
|
+
seen.add(auth.lambdaLogicalId);
|
|
73623
|
+
out.push(auth.lambdaLogicalId);
|
|
73624
|
+
}
|
|
73625
|
+
}
|
|
73626
|
+
}
|
|
72442
73627
|
return out;
|
|
72443
73628
|
}
|
|
73629
|
+
async function prewarmJwks(routesWithAuth, jwksCache) {
|
|
73630
|
+
const urls = /* @__PURE__ */ new Set();
|
|
73631
|
+
for (const entry of routesWithAuth) {
|
|
73632
|
+
const auth = entry.authorizer;
|
|
73633
|
+
if (!auth)
|
|
73634
|
+
continue;
|
|
73635
|
+
if (auth.kind === "cognito") {
|
|
73636
|
+
urls.add(buildCognitoJwksUrl(auth.region, auth.userPoolId));
|
|
73637
|
+
} else if (auth.kind === "jwt") {
|
|
73638
|
+
const url = auth.region && auth.userPoolId ? buildCognitoJwksUrl(auth.region, auth.userPoolId) : buildJwksUrlFromIssuer(auth.issuer);
|
|
73639
|
+
urls.add(url);
|
|
73640
|
+
}
|
|
73641
|
+
}
|
|
73642
|
+
await Promise.all([...urls].map((u) => jwksCache.fetchAndCache(u)));
|
|
73643
|
+
}
|
|
73644
|
+
function warnVpcConfigLambdas(routesWithAuth, stacks) {
|
|
73645
|
+
const logger = getLogger();
|
|
73646
|
+
const seen = /* @__PURE__ */ new Set();
|
|
73647
|
+
const reachable = [];
|
|
73648
|
+
for (const entry of routesWithAuth) {
|
|
73649
|
+
if (!seen.has(entry.route.lambdaLogicalId)) {
|
|
73650
|
+
seen.add(entry.route.lambdaLogicalId);
|
|
73651
|
+
reachable.push(entry.route.lambdaLogicalId);
|
|
73652
|
+
}
|
|
73653
|
+
const auth = entry.authorizer;
|
|
73654
|
+
if (auth && (auth.kind === "lambda-token" || auth.kind === "lambda-request")) {
|
|
73655
|
+
if (!seen.has(auth.lambdaLogicalId)) {
|
|
73656
|
+
seen.add(auth.lambdaLogicalId);
|
|
73657
|
+
reachable.push(auth.lambdaLogicalId);
|
|
73658
|
+
}
|
|
73659
|
+
}
|
|
73660
|
+
}
|
|
73661
|
+
for (const logicalId of reachable) {
|
|
73662
|
+
for (const stack of stacks) {
|
|
73663
|
+
const resource = stack.template.Resources?.[logicalId];
|
|
73664
|
+
if (!resource || resource.Type !== "AWS::Lambda::Function")
|
|
73665
|
+
continue;
|
|
73666
|
+
const props = resource.Properties ?? {};
|
|
73667
|
+
const vpcConfig = props["VpcConfig"];
|
|
73668
|
+
if (vpcConfig && typeof vpcConfig === "object" && Object.keys(vpcConfig).length > 0) {
|
|
73669
|
+
logger.warn(
|
|
73670
|
+
`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.`
|
|
73671
|
+
);
|
|
73672
|
+
}
|
|
73673
|
+
break;
|
|
73674
|
+
}
|
|
73675
|
+
}
|
|
73676
|
+
}
|
|
72444
73677
|
async function buildContainerSpec(args) {
|
|
72445
73678
|
const {
|
|
72446
73679
|
logicalId,
|
|
@@ -72681,7 +73914,7 @@ function parseDebugPort(raw) {
|
|
|
72681
73914
|
}
|
|
72682
73915
|
function createLocalStartApiCommand() {
|
|
72683
73916
|
const startApi = new Command14("start-api").description(
|
|
72684
|
-
"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)."
|
|
73917
|
+
"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."
|
|
72685
73918
|
).addOption(
|
|
72686
73919
|
new Option7("--port <port>", "HTTP server port (default: auto-allocate)").default("0")
|
|
72687
73920
|
).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(
|
|
@@ -72895,7 +74128,11 @@ async function resolveContainerImagePlan(lambda, options) {
|
|
|
72895
74128
|
let imageRef;
|
|
72896
74129
|
if (localBuild) {
|
|
72897
74130
|
imageRef = await buildContainerImage(localBuild.asset, localBuild.cdkOutDir, {
|
|
72898
|
-
architecture: lambda.architecture
|
|
74131
|
+
architecture: lambda.architecture,
|
|
74132
|
+
// `options.build === false` triggers the no-build path: skip
|
|
74133
|
+
// `docker build` and verify the deterministic tag is already
|
|
74134
|
+
// cached. Default `true` (build as usual). Closes #233.
|
|
74135
|
+
noBuild: options.build === false
|
|
72899
74136
|
});
|
|
72900
74137
|
} else {
|
|
72901
74138
|
if (!parseEcrUri(lambda.imageUri)) {
|
|
@@ -73171,6 +74408,11 @@ function createLocalCommand() {
|
|
|
73171
74408
|
"--no-pull",
|
|
73172
74409
|
"Skip docker pull (use cached image) \u2014 no-op for IMAGE local-build path; `docker build` does not pull base layers by default"
|
|
73173
74410
|
)
|
|
74411
|
+
).addOption(
|
|
74412
|
+
new Option8(
|
|
74413
|
+
"--no-build",
|
|
74414
|
+
"Skip docker build on the IMAGE local-build path (use the previously-built tag). Requires the deterministic tag to already be in the local registry; errors with an actionable message when missing. No-op for ZIP Lambdas and the IMAGE ECR-pull path. Compatible with --no-pull."
|
|
74415
|
+
)
|
|
73174
74416
|
).addOption(new Option8("--debug-port <port>", "Node --inspect-brk port (default: off)")).addOption(
|
|
73175
74417
|
new Option8("--container-host <host>", "Host to bind the RIE port to").default("127.0.0.1")
|
|
73176
74418
|
).addOption(
|
|
@@ -73227,7 +74469,7 @@ function reorderArgs(argv) {
|
|
|
73227
74469
|
}
|
|
73228
74470
|
async function main() {
|
|
73229
74471
|
const program = new Command16();
|
|
73230
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
74472
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.73.0");
|
|
73231
74473
|
program.addCommand(createBootstrapCommand());
|
|
73232
74474
|
program.addCommand(createSynthCommand());
|
|
73233
74475
|
program.addCommand(createListCommand());
|