cdk-local 0.50.0 → 0.52.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 CHANGED
@@ -72,7 +72,7 @@ Full flags, precedence, and `--from-cfn-stack` resolution: [docs/cli-reference.m
72
72
  | `cdkl start-service <Service>` | the ECS **service** | just the service's replicas (no load balancer) | workers / queue consumers / Service-Connect-only services, or to hit the containers directly |
73
73
  | `cdkl start-alb <Alb>` | the **ALB** | the ECS service(s) behind it **plus** a local **front-door** on each listener port | an `ApplicationLoadBalancedFargateService`-style service you want to reach the way external traffic does |
74
74
 
75
- `start-alb` discovers the ECS service(s) behind the ALB's HTTP listeners, boots their replicas, and stands up a host-side front-door on each listener port that round-robins across the replicas — one stable host endpoint, like behind a real load balancer. It honors **`path-pattern` and `host-header` listener rules**, **weighted** forwards, and **`redirect` / `fixed-response`** actions, so a listener routing `/api/*` (or `api.example.com`) to one service and everything else to another — or returning a redirect / canned response — is reproduced locally. A **`TargetType: lambda`** target group is served by invoking the backing Lambda locally (the request is translated to the ALB `requestContext.elb` event and run through the Lambda RIE), so a forward can mix ECS and Lambda targets.
75
+ `start-alb` discovers the ECS service(s) behind the ALB's HTTP listeners, boots their replicas, and stands up a host-side front-door on each listener port that round-robins across the replicas — one stable host endpoint, like behind a real load balancer. It honors **every ALB listener-rule condition** — `path-pattern`, `host-header`, `http-header`, `http-request-method`, `query-string`, and `source-ip` plus **weighted** forwards and **`redirect` / `fixed-response`** actions, so a listener routing `/api/*` (or `api.example.com`, or `X-Tenant: acme`, or `POST` writes, or `?version=2`, or `10.0.0.0/8`) to one service and everything else to another — or returning a redirect / canned response — is reproduced locally. A **`TargetType: lambda`** target group is served by invoking the backing Lambda locally (the request is translated to the ALB `requestContext.elb` event and run through the Lambda RIE), so a forward can mix ECS and Lambda targets.
76
76
 
77
77
  ```bash
78
78
  cdkl start-alb MyStack/WebAlb --lb-port 80=8080 # remap the privileged listener port 80 (macOS)
@@ -80,7 +80,7 @@ curl http://127.0.0.1:8080/ # default action -> the default service (roun
80
80
  curl http://127.0.0.1:8080/api/x # path-pattern rule /api/* -> the api service
81
81
  ```
82
82
 
83
- Resolution model + scope (HTTP listeners, `path-pattern` + `host-header` rules, weighted forwards, `redirect` / `fixed-response`, ECS **and** Lambda targets; `http-header` / `query-string` / `source-ip` conditions deferred): [docs/cli-reference.md](docs/cli-reference.md#cdkl-start-alb-run-an-alb-fronted-service-locally).
83
+ Resolution model + scope (HTTP listeners, all six ALB rule-condition fields, weighted forwards, `redirect` / `fixed-response`, ECS **and** Lambda targets): [docs/cli-reference.md](docs/cli-reference.md#cdkl-start-alb-run-an-alb-fronted-service-locally).
84
84
 
85
85
  ## Override env vars without a state source
86
86
 
@@ -104,7 +104,7 @@ Format + full precedence: [docs/cli-reference.md](docs/cli-reference.md).
104
104
  | `AWS::ECS::TaskDefinition` (run-task) | ✓ |
105
105
  | `AWS::ECS::Service` (start-service) | ✓ |
106
106
  | `AWS::ServiceDiscovery::*` (Cloud Map / Service Connect) | ✓ |
107
- | `AWS::ElasticLoadBalancingV2::*` (start-alb: ALB front-door; `path-pattern` + `host-header` rules, weighted forward, redirect / fixed-response; ECS + Lambda targets) | ✓ |
107
+ | `AWS::ElasticLoadBalancingV2::*` (start-alb: ALB front-door; all six listener-rule condition fields, weighted forward, redirect / fixed-response; ECS + Lambda targets) | ✓ |
108
108
  | `AWS::BedrockAgentCore::Runtime` (invoke-agentcore, container + fromCodeAsset/fromS3 artifacts, HTTP + MCP) | ✓ |
109
109
 
110
110
  Lambda runs on every current AWS Lambda runtime — Node.js (18/20/22/24), Python (3.11–3.14), Ruby (3.2/3.3), Java (8.al2/11/17/21), .NET (6/8), and the OS-only `provided.al2` / `provided.al2023`. The retired `go1.x` runtime is rejected with a pointer to migrate to `provided.al2023`.
package/dist/cli.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { a as createLocalStartApiCommand } from "./cloud-map-resolver-BvhnCkSe.js";
3
- import { a as createLocalRunTaskCommand, i as createLocalStartServiceCommand, o as createLocalInvokeAgentCoreCommand, r as createLocalStartAlbCommand, s as createLocalInvokeCommand, t as createLocalListCommand } from "./local-list-C6xuYlRB.js";
2
+ import { a as createLocalStartApiCommand } from "./cloud-map-resolver-D0OCnW6R.js";
3
+ import { a as createLocalRunTaskCommand, i as createLocalStartServiceCommand, o as createLocalInvokeAgentCoreCommand, r as createLocalStartAlbCommand, s as createLocalInvokeCommand, t as createLocalListCommand } from "./local-list-v8D51s5a.js";
4
4
  import { Command } from "commander";
5
5
 
6
6
  //#region src/cli/index.ts
7
7
  const program = new Command();
8
- program.name("cdkl").description("Run AWS CDK stacks locally with Docker.").version("0.50.0");
8
+ program.name("cdkl").description("Run AWS CDK stacks locally with Docker.").version("0.52.0");
9
9
  program.addCommand(createLocalInvokeCommand());
10
10
  program.addCommand(createLocalInvokeAgentCoreCommand());
11
11
  program.addCommand(createLocalStartApiCommand());
@@ -1327,13 +1327,86 @@ function extractJwtAuthorizer(authorizerConfig, logicalId) {
1327
1327
  const toStringArray = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : void 0;
1328
1328
  const allowedAudience = toStringArray(cfg["AllowedAudience"]);
1329
1329
  const allowedClients = toStringArray(cfg["AllowedClients"]);
1330
+ const allowedScopes = toStringArray(cfg["AllowedScopes"]);
1331
+ const customClaims = extractCustomClaims(cfg["CustomClaims"], logicalId);
1330
1332
  return {
1331
1333
  discoveryUrl,
1332
1334
  ...allowedAudience && allowedAudience.length > 0 && { allowedAudience },
1333
- ...allowedClients && allowedClients.length > 0 && { allowedClients }
1335
+ ...allowedClients && allowedClients.length > 0 && { allowedClients },
1336
+ ...allowedScopes && allowedScopes.length > 0 && { allowedScopes },
1337
+ ...customClaims && customClaims.length > 0 && { customClaims }
1334
1338
  };
1335
1339
  }
1336
1340
  /**
1341
+ * Parse a `CustomJWTAuthorizer.CustomClaims[]` array into
1342
+ * {@link AgentCoreCustomClaim}s. The template shape (synthesized by the L2):
1343
+ *
1344
+ * ```
1345
+ * {
1346
+ * InboundTokenClaimName: <claim name>,
1347
+ * InboundTokenClaimValueType: 'STRING' | 'STRING_ARRAY',
1348
+ * AuthorizingClaimMatchValue: {
1349
+ * ClaimMatchOperator: 'EQUALS' | 'CONTAINS' | 'CONTAINS_ANY',
1350
+ * ClaimMatchValue: { MatchValueString?: ..., MatchValueStringList?: [...] }
1351
+ * }
1352
+ * }
1353
+ * ```
1354
+ *
1355
+ * Each entry that fails to parse (missing name / unknown type / unknown
1356
+ * operator / wrong value shape) is warn-and-skipped — the deployed runtime
1357
+ * would reject a token that violates ANY claim rule, so dropping a rule we
1358
+ * can't evaluate is the safer side (under-restrictive in `--no-verify-auth`
1359
+ * paths, fine elsewhere because the surviving rules still gate the token).
1360
+ */
1361
+ function extractCustomClaims(raw, logicalId) {
1362
+ if (!Array.isArray(raw)) return void 0;
1363
+ const out = [];
1364
+ for (const entry of raw) {
1365
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
1366
+ const e = entry;
1367
+ const name = e["InboundTokenClaimName"];
1368
+ const valueType = e["InboundTokenClaimValueType"];
1369
+ const matchObj = e["AuthorizingClaimMatchValue"];
1370
+ if (typeof name !== "string" || name.length === 0) continue;
1371
+ if (valueType !== "STRING" && valueType !== "STRING_ARRAY") {
1372
+ getLogger().warn(`AgentCore Runtime '${logicalId}' CustomClaims entry '${name}' has unsupported InboundTokenClaimValueType '${String(valueType)}' (expected STRING / STRING_ARRAY); skipping.`);
1373
+ continue;
1374
+ }
1375
+ if (!matchObj || typeof matchObj !== "object" || Array.isArray(matchObj)) continue;
1376
+ const m = matchObj;
1377
+ const operator = m["ClaimMatchOperator"];
1378
+ const matchValue = m["ClaimMatchValue"];
1379
+ if (operator !== "EQUALS" && operator !== "CONTAINS" && operator !== "CONTAINS_ANY") {
1380
+ getLogger().warn(`AgentCore Runtime '${logicalId}' CustomClaims entry '${name}' has unsupported ClaimMatchOperator '${String(operator)}' (expected EQUALS / CONTAINS / CONTAINS_ANY); skipping.`);
1381
+ continue;
1382
+ }
1383
+ if (!matchValue || typeof matchValue !== "object" || Array.isArray(matchValue)) continue;
1384
+ const mv = matchValue;
1385
+ let value;
1386
+ if (operator === "CONTAINS_ANY") {
1387
+ const list = mv["MatchValueStringList"];
1388
+ if (Array.isArray(list)) {
1389
+ value = list.filter((x) => typeof x === "string");
1390
+ if (value.length === 0) value = void 0;
1391
+ }
1392
+ } else {
1393
+ const s = mv["MatchValueString"];
1394
+ if (typeof s === "string" && s.length > 0) value = s;
1395
+ }
1396
+ if (value === void 0) {
1397
+ getLogger().warn(`AgentCore Runtime '${logicalId}' CustomClaims entry '${name}' has no usable MatchValueString / MatchValueStringList for operator ${operator}; skipping.`);
1398
+ continue;
1399
+ }
1400
+ out.push({
1401
+ name,
1402
+ valueType,
1403
+ operator,
1404
+ value
1405
+ });
1406
+ }
1407
+ return out;
1408
+ }
1409
+ /**
1337
1410
  * Validate `ProtocolConfiguration`. Serves `HTTP` (the default when absent)
1338
1411
  * and `MCP`; `A2A` / `AGUI` speak other wire contracts and hard-error with a
1339
1412
  * pointer to the follow-up.
@@ -9249,7 +9322,7 @@ async function verifyCognitoJwt(authorizer, authorizationHeader, jwksCache, opts
9249
9322
  identityHash,
9250
9323
  ttlSeconds: 0
9251
9324
  };
9252
- return verifyAndShape(token, buildCognitoJwksUrl(selectedPool.region, selectedPool.userPoolId), buildCognitoIssuer(selectedPool.region, selectedPool.userPoolId), void 0, jwksCache, opts.warned, now);
9325
+ return verifyAndShape(token, buildCognitoJwksUrl(selectedPool.region, selectedPool.userPoolId), buildCognitoIssuer(selectedPool.region, selectedPool.userPoolId), void 0, void 0, void 0, jwksCache, opts.warned, now);
9253
9326
  }
9254
9327
  /**
9255
9328
  * Verify a Bearer JWT against an HTTP v2 JWT authorizer's `JwtConfiguration`.
@@ -9262,7 +9335,7 @@ async function verifyJwtAuthorizer(authorizer, authorizationHeader, jwksCache, o
9262
9335
  identityHash: void 0,
9263
9336
  ttlSeconds: 0
9264
9337
  };
9265
- return verifyAndShape(token, authorizer.region && authorizer.userPoolId ? buildCognitoJwksUrl(authorizer.region, authorizer.userPoolId) : buildJwksUrlFromIssuer(authorizer.issuer), authorizer.issuer.replace(/\/+$/, ""), authorizer.audience, jwksCache, opts.warned, now);
9338
+ return verifyAndShape(token, authorizer.region && authorizer.userPoolId ? buildCognitoJwksUrl(authorizer.region, authorizer.userPoolId) : buildJwksUrlFromIssuer(authorizer.issuer), authorizer.issuer.replace(/\/+$/, ""), authorizer.audience, void 0, void 0, jwksCache, opts.warned, now);
9266
9339
  }
9267
9340
  /**
9268
9341
  * Verify a Bearer JWT against an OIDC-discovery-URL authorizer (Bedrock
@@ -9314,9 +9387,9 @@ async function verifyJwtViaDiscovery(authorizer, authorizationHeader, jwksCache,
9314
9387
  };
9315
9388
  }
9316
9389
  const allowlist = [...authorizer.allowedAudience ?? [], ...authorizer.allowedClients ?? []];
9317
- return verifyAndShape(token, jwksUri, issuer.replace(/\/+$/, ""), allowlist.length > 0 ? allowlist : void 0, jwksCache, opts.warned, now);
9390
+ return verifyAndShape(token, jwksUri, issuer.replace(/\/+$/, ""), allowlist.length > 0 ? allowlist : void 0, authorizer.allowedScopes, authorizer.customClaims, jwksCache, opts.warned, now);
9318
9391
  }
9319
- async function verifyAndShape(token, jwksUrl, expectedIssuer, expectedAudience, jwksCache, warned, now) {
9392
+ async function verifyAndShape(token, jwksUrl, expectedIssuer, expectedAudience, requiredScopes, customClaims, jwksCache, warned, now) {
9320
9393
  const identityHash = buildIdentityHash([token]);
9321
9394
  const jwks = await jwksCache.fetchAndCache(jwksUrl);
9322
9395
  if (jwks.passThrough) {
@@ -9377,9 +9450,59 @@ async function verifyAndShape(token, jwksUrl, expectedIssuer, expectedAudience,
9377
9450
  ttlSeconds: 0
9378
9451
  };
9379
9452
  }
9453
+ if (requiredScopes && requiredScopes.length > 0) {
9454
+ if (!verifyRequiredScopes(claims["scope"], requiredScopes)) return {
9455
+ allow: false,
9456
+ identityHash,
9457
+ ttlSeconds: 0
9458
+ };
9459
+ }
9460
+ if (customClaims && customClaims.length > 0) {
9461
+ for (const rule of customClaims) if (!verifyCustomClaim(claims[rule.name], rule)) return {
9462
+ allow: false,
9463
+ identityHash,
9464
+ ttlSeconds: 0
9465
+ };
9466
+ }
9380
9467
  return shapeAllowResult(parsed, identityHash, now);
9381
9468
  }
9382
9469
  /**
9470
+ * The OAuth `scope` claim is a space-separated string. The token is allowed
9471
+ * iff every required scope is present (allowlist as REQUIRED, not OR).
9472
+ */
9473
+ function verifyRequiredScopes(scopeClaim, requiredScopes) {
9474
+ const tokenScopes = typeof scopeClaim === "string" ? scopeClaim.split(/\s+/).filter((s) => s.length > 0) : Array.isArray(scopeClaim) ? scopeClaim.filter((s) => typeof s === "string") : [];
9475
+ return requiredScopes.every((s) => tokenScopes.includes(s));
9476
+ }
9477
+ /**
9478
+ * Verify a single `CustomJWTAuthorizer.CustomClaims` rule against the token's
9479
+ * claim value:
9480
+ *
9481
+ * - `STRING` + `EQUALS` — claim is a string equal to `value`.
9482
+ * - `STRING_ARRAY` + `CONTAINS` — claim is an array containing `value`.
9483
+ * - `STRING_ARRAY` + `CONTAINS_ANY` — claim is an array sharing at least one
9484
+ * entry with `value` (an array of allowed strings).
9485
+ *
9486
+ * A missing or wrong-typed claim fails the rule.
9487
+ */
9488
+ function verifyCustomClaim(claimValue, rule) {
9489
+ if (rule.valueType === "STRING") {
9490
+ if (rule.operator !== "EQUALS" || typeof rule.value !== "string") return false;
9491
+ return typeof claimValue === "string" && claimValue === rule.value;
9492
+ }
9493
+ if (!Array.isArray(claimValue)) return false;
9494
+ const tokenValues = claimValue.filter((v) => typeof v === "string");
9495
+ if (rule.operator === "CONTAINS") {
9496
+ if (typeof rule.value !== "string") return false;
9497
+ return tokenValues.includes(rule.value);
9498
+ }
9499
+ if (rule.operator === "CONTAINS_ANY") {
9500
+ if (!Array.isArray(rule.value)) return false;
9501
+ return rule.value.some((v) => tokenValues.includes(v));
9502
+ }
9503
+ return false;
9504
+ }
9505
+ /**
9383
9506
  * Construct the Allow result for a verified JWT. The handler-side context
9384
9507
  * is the parsed claim map; principalId mirrors Cognito's deployed
9385
9508
  * behavior (the `sub` claim, falling back to `username` then `cognito:username`).
@@ -17599,4 +17722,4 @@ function extractDnsRecords(serviceProps) {
17599
17722
 
17600
17723
  //#endregion
17601
17724
  export { attachAuthorizers as $, substituteAgainstState as $t, buildHttpApiV2Event as A, AGENTCORE_RUNTIME_TYPE as An, buildDockerImage as At, ConnectionRegistry as B, readCdkPathOrUndefined as Bn, streamLogs as Bt, computeRequestIdentityHash as C, discoverWebSocketApisOrThrow as Cn, getDockerImageBySourceHash as Ct, matchRoute as D, resolveLambdaArnIntrinsic as Dn, buildContainerImage as Dt, invokeTokenAuthorizer as E, pickRefLogicalId as En, architectureToPlatform as Et, tryParseStatus as F, derivePseudoParametersFromRegion as Fn, execEnvForSecrets as Ft, buildDisconnectEvent as G, withErrorHandling as Gn, TASK_ROLE_ACCOUNT_PLACEHOLDER as Gt, handleConnectionsRequest as H, CdkLocalError as Hn, resolveRuntimeFileExtension as Ht, VtlEvaluationError as I, substituteImagePlaceholders as In, pickFreePort as It, buildJwksUrlFromIssuer as J, commonOptions as Jn, derivePartitionAndUrlSuffix as Jt, buildMessageEvent as K, applyRoleArnIfSet as Kn, applyCrossStackResolverToTask as Kt, HOST_GATEWAY_MIN_VERSION as L, tryResolveImageFnJoin as Ln, pullImage as Lt, evaluateResponseParameters as M, pickAgentCoreCandidateStack as Mn, SENSITIVE_ENV_KEYS as Mt, pickResponseTemplate as N, resolveAgentCoreTarget as Nn, appendEnvFlags as Nt, translateLambdaResponse as O, AGENTCORE_HTTP_PROTOCOL as On, parseEcrUri as Ot, selectIntegrationResponse as P, resolveLambdaTarget as Pn, ensureDockerAvailable as Pt, verifyJwtViaDiscovery as Q, warnIfDeprecatedRegion as Qn, applyDeployedEnvFallback as Qt, probeHostGatewaySupport as R, matchStacks as Rn, removeContainer as Rt, buildMethodArn as S, discoverWebSocketApis as Sn, AssetManifestLoader as St, invokeRequestAuthorizer as T, discoverRoutes as Tn, waitForRieReady as Tt, parseConnectionsPath as U, LocalInvokeBuildError as Un, resolveRuntimeImage as Ut, buildMgmtEndpointEnvUrl as V, resolveCdkPathToLogicalIds as Vn, resolveRuntimeCodeMountPath as Vt, buildConnectEvent as W, LocalStartServiceError as Wn, EcsTaskResolutionError as Wt, verifyCognitoJwt as X, deprecatedRegionOption as Xn, parseEcsTarget as Xt, createJwksCache as Y, contextOptions as Yn, detectEcsImageResolutionNeeds as Yt, verifyJwtAuthorizer as Z, parseContextOptions as Zn, resolveEcsTaskTarget as Zt, readMtlsMaterialsFromDisk as _, Synthesizer as _n, computeCodeImageTag as _t, createLocalStartApiCommand as a, LocalStateSourceError as an, invokeAgentCoreWs as at, resolveServiceIntegrationParameters as b, countTargets as bn, writeProfileCredentialsFile as bt, resolveProfileCredentials as c, rejectExplicitCfnStackWithMultipleStacks as cn, MCP_PROTOCOL_VERSION as ct, attachStageContext as d, resolveCfnStackName as dn, AGENTCORE_SESSION_ID_HEADER as dt, substituteAgainstStateAsync as en, applyCorsResponseHeaders as et, buildStageMap as f, CfnLocalStateProvider as fn, invokeAgentCore as ft, groupRoutesByServer as g, resolveWatchConfig as gn, buildAgentCoreCodeImage as gt, filterRoutesByApiIdentifiers as h, resolveApp as hn, SUPPORTED_CODE_RUNTIMES as ht, getPublishedHostPort as i, materializeLayerFromArn as in, matchPreflight as it, buildRestV1Event as j, AgentCoreResolutionError as jn, DockerRunnerError as jt, applyAuthorizerOverlay as k, AGENTCORE_MCP_PROTOCOL as kn, pullEcrImage as kt, createAuthorizerCache as l, resolveCfnFallbackRegion as ln, mcpInvokeOnce as lt, filterRoutesByApiIdentifier as m, resolveSsmParameters as mn, downloadAndExtractS3Bundle as mt, CloudMapRegistry as n, substituteEnvVarsFromStateAsync as nn, buildCorsConfigFromCloudFrontChain as nt, createWatchPredicates as o, createLocalStateProvider as on, MCP_CONTAINER_PORT as ot, availableApiIdentifiers as p, collectSsmParameterRefs as pn, waitForAgentCorePing as pt, buildCognitoJwksUrl as q, appOptions as qn, checkVolumeHostPath as qt, getContainerNetworkIp as r, resolveEnvVars as rn, isFunctionUrlOacFronted as rt, resolveApiTargetSubset as s, isCfnFlagPresent as sn, MCP_PATH as st, buildCloudMapIndex as t, substituteEnvVarsFromState as tn, buildCorsConfigByApiId as tt, createFileWatcher as u, resolveCfnRegion as un, parseSseForJsonRpc as ut, startApiServer as v, resolveMultiTarget as vn, renderCodeDockerfile as vt, evaluateCachedLambdaPolicy as w, parseSelectionExpressionPath as wn, invokeRie as wt, defaultCredentialsLoader as x, listTargets as xn, singleFlight as xt, resolveSelectionExpression as y, resolveSingleTarget as yn, toCmdArgv as yt, bufferToBody as z, buildCdkPathIndex as zn, runDetached as zt };
17602
- //# sourceMappingURL=cloud-map-resolver-BvhnCkSe.js.map
17725
+ //# sourceMappingURL=cloud-map-resolver-D0OCnW6R.js.map