@jaypie/constructs 1.2.59 → 1.2.61

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.
@@ -19,3 +19,4 @@ export { resolveEnvironment, EnvironmentArrayItem, EnvironmentInput, } from "./r
19
19
  export { resolveHostedZone } from "./resolveHostedZone";
20
20
  export { resolveParamsAndSecrets } from "./resolveParamsAndSecrets";
21
21
  export { resolveSecrets, SecretsArrayItem, clearSecretsCache, clearAllSecretsCaches, } from "./resolveSecrets";
22
+ export { assertValidWafRuleNames, AWS_MANAGED_RULE_GROUPS, } from "./wafManagedRuleNames";
@@ -2,6 +2,7 @@ export interface JaypieLambdaEnvOptions {
2
2
  initialEnvironment?: {
3
3
  [key: string]: string;
4
4
  };
5
+ serviceTag?: string;
5
6
  }
6
7
  export declare function jaypieLambdaEnv(options?: JaypieLambdaEnvOptions): {
7
8
  [key: string]: string;
@@ -0,0 +1,33 @@
1
+ import * as wafv2 from "aws-cdk-lib/aws-wafv2";
2
+ /**
3
+ * Canonical sub-rule names for each AWS managed rule group, as published in the
4
+ * AWS WAF developer guide. Used to validate `waf.allow` and
5
+ * `waf.managedRuleOverrides` rule names at synth time — AWS WAF matches
6
+ * `RuleActionOverride` on the exact rule *name* and silently ignores names that
7
+ * match no rule, so a typo or a label/name casing mismatch (e.g. the label
8
+ * `…:NoUserAgent_Header` vs the rule name `NoUserAgent_HEADER`) becomes an
9
+ * undiagnosable no-op.
10
+ *
11
+ * Groups absent from this map (custom rule groups, or AWS groups not yet
12
+ * mirrored here) are not validated.
13
+ *
14
+ * @see https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html
15
+ */
16
+ export declare const AWS_MANAGED_RULE_GROUPS: Record<string, readonly string[]>;
17
+ /** One entry in a `waf.allow` list. Mirrors JaypieWafAllowEntry structurally. */
18
+ interface WafAllowEntryLike {
19
+ path: string | string[];
20
+ [ruleGroupKey: string]: string | string[] | undefined;
21
+ }
22
+ interface AssertValidWafRuleNamesOptions {
23
+ allow?: WafAllowEntryLike | WafAllowEntryLike[];
24
+ managedRuleOverrides?: Record<string, wafv2.CfnWebACL.RuleActionOverrideProperty[]>;
25
+ }
26
+ /**
27
+ * Throw a ConfigurationError if any `waf.allow` or `waf.managedRuleOverrides`
28
+ * rule name does not exist in its AWS managed rule group. Groups not present in
29
+ * AWS_MANAGED_RULE_GROUPS (custom groups) are skipped. A name that matches no
30
+ * rule would otherwise be silently ignored by AWS WAF.
31
+ */
32
+ export declare function assertValidWafRuleNames({ allow, managedRuleOverrides, }?: AssertValidWafRuleNamesOptions): void;
33
+ export {};
@@ -738,7 +738,7 @@ function isValidSubdomain(subdomain) {
738
738
  }
739
739
 
740
740
  function jaypieLambdaEnv(options = {}) {
741
- const { initialEnvironment = {} } = options;
741
+ const { initialEnvironment = {}, serviceTag } = options;
742
742
  // Start with empty environment - we'll only add valid values
743
743
  let environment = {};
744
744
  // First, add all valid string values from initialEnvironment
@@ -767,6 +767,11 @@ function jaypieLambdaEnv(options = {}) {
767
767
  environment[key] = defaultValue;
768
768
  }
769
769
  });
770
+ // Apply serviceTag as PROJECT_SERVICE unless explicitly overridden.
771
+ // Precedence: explicit environment > serviceTag > process.env.PROJECT_SERVICE
772
+ if (serviceTag && !environment.PROJECT_SERVICE) {
773
+ environment.PROJECT_SERVICE = serviceTag;
774
+ }
770
775
  // Default environment variables from process.env if present
771
776
  const defaultEnvVars = [
772
777
  "DATADOG_API_KEY_ARN",
@@ -1303,6 +1308,148 @@ function clearAllSecretsCaches() {
1303
1308
  // between test runs. For testing, use clearSecretsCache(scope) instead.
1304
1309
  }
1305
1310
 
1311
+ /**
1312
+ * Canonical sub-rule names for each AWS managed rule group, as published in the
1313
+ * AWS WAF developer guide. Used to validate `waf.allow` and
1314
+ * `waf.managedRuleOverrides` rule names at synth time — AWS WAF matches
1315
+ * `RuleActionOverride` on the exact rule *name* and silently ignores names that
1316
+ * match no rule, so a typo or a label/name casing mismatch (e.g. the label
1317
+ * `…:NoUserAgent_Header` vs the rule name `NoUserAgent_HEADER`) becomes an
1318
+ * undiagnosable no-op.
1319
+ *
1320
+ * Groups absent from this map (custom rule groups, or AWS groups not yet
1321
+ * mirrored here) are not validated.
1322
+ *
1323
+ * @see https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html
1324
+ */
1325
+ const AWS_MANAGED_RULE_GROUPS = {
1326
+ AWSManagedRulesAdminProtectionRuleSet: ["AdminProtection_URIPATH"],
1327
+ AWSManagedRulesAmazonIpReputationList: [
1328
+ "AWSManagedIPDDoSList",
1329
+ "AWSManagedIPReputationList",
1330
+ "AWSManagedReconnaissanceList",
1331
+ ],
1332
+ AWSManagedRulesAnonymousIpList: ["AnonymousIPList", "HostingProviderIPList"],
1333
+ AWSManagedRulesCommonRuleSet: [
1334
+ "CrossSiteScripting_BODY",
1335
+ "CrossSiteScripting_COOKIE",
1336
+ "CrossSiteScripting_QUERYARGUMENTS",
1337
+ "CrossSiteScripting_URIPATH",
1338
+ "EC2MetaDataSSRF_BODY",
1339
+ "EC2MetaDataSSRF_COOKIE",
1340
+ "EC2MetaDataSSRF_QUERYARGUMENTS",
1341
+ "EC2MetaDataSSRF_URIPATH",
1342
+ "GenericLFI_BODY",
1343
+ "GenericLFI_QUERYARGUMENTS",
1344
+ "GenericLFI_URIPATH",
1345
+ "GenericRFI_BODY",
1346
+ "GenericRFI_QUERYARGUMENTS",
1347
+ "GenericRFI_URIPATH",
1348
+ "NoUserAgent_HEADER",
1349
+ "RestrictedExtensions_QUERYARGUMENTS",
1350
+ "RestrictedExtensions_URIPATH",
1351
+ "SizeRestrictions_BODY",
1352
+ "SizeRestrictions_Cookie_HEADER",
1353
+ "SizeRestrictions_QUERYSTRING",
1354
+ "SizeRestrictions_URIPATH",
1355
+ "UserAgent_BadBots_HEADER",
1356
+ ],
1357
+ AWSManagedRulesKnownBadInputsRuleSet: [
1358
+ "ExploitablePaths_URIPATH",
1359
+ "Host_localhost_HEADER",
1360
+ "JavaDeserializationRCE_BODY",
1361
+ "JavaDeserializationRCE_HEADER",
1362
+ "JavaDeserializationRCE_QUERYSTRING",
1363
+ "JavaDeserializationRCE_URIPATH",
1364
+ "Log4JRCE_BODY",
1365
+ "Log4JRCE_HEADER",
1366
+ "Log4JRCE_QUERYSTRING",
1367
+ "Log4JRCE_URIPATH",
1368
+ "PROPFIND_METHOD",
1369
+ "ReactJSRCE_BODY",
1370
+ ],
1371
+ AWSManagedRulesLinuxRuleSet: ["LFI_HEADER", "LFI_QUERYSTRING", "LFI_URIPATH"],
1372
+ AWSManagedRulesPHPRuleSet: [
1373
+ "PHPHighRiskMethodsVariables_BODY",
1374
+ "PHPHighRiskMethodsVariables_HEADER",
1375
+ "PHPHighRiskMethodsVariables_QUERYSTRING",
1376
+ "PHPHighRiskMethodsVariables_URIPATH",
1377
+ ],
1378
+ AWSManagedRulesSQLiRuleSet: [
1379
+ "SQLiExtendedPatterns_BODY",
1380
+ "SQLiExtendedPatterns_HEADER",
1381
+ "SQLiExtendedPatterns_QUERYARGUMENTS",
1382
+ "SQLiExtendedPatterns_URIPATH",
1383
+ "SQLi_BODY",
1384
+ "SQLi_COOKIE",
1385
+ "SQLi_QUERYARGUMENTS",
1386
+ "SQLi_URIPATH",
1387
+ ],
1388
+ AWSManagedRulesUnixRuleSet: [
1389
+ "UNIXShellCommandsVariables_BODY",
1390
+ "UNIXShellCommandsVariables_HEADER",
1391
+ "UNIXShellCommandsVariables_QUERYSTRING",
1392
+ ],
1393
+ AWSManagedRulesWindowsRuleSet: [
1394
+ "PowerShellCommands_BODY",
1395
+ "PowerShellCommands_COOKIE",
1396
+ "PowerShellCommands_QUERYARGUMENTS",
1397
+ "WindowsShellCommands_BODY",
1398
+ "WindowsShellCommands_HEADER",
1399
+ "WindowsShellCommands_QUERYARGUMENTS",
1400
+ "WindowsShellCommands_QUERYSTRING",
1401
+ "WindowsShellCommands_URIPATH",
1402
+ ],
1403
+ AWSManagedRulesWordPressRuleSet: [
1404
+ "WordPressExploitableCommands_QUERYSTRING",
1405
+ "WordPressExploitablePaths_URIPATH",
1406
+ ],
1407
+ };
1408
+ /**
1409
+ * Throw a ConfigurationError if any `waf.allow` or `waf.managedRuleOverrides`
1410
+ * rule name does not exist in its AWS managed rule group. Groups not present in
1411
+ * AWS_MANAGED_RULE_GROUPS (custom groups) are skipped. A name that matches no
1412
+ * rule would otherwise be silently ignored by AWS WAF.
1413
+ */
1414
+ function assertValidWafRuleNames({ allow, managedRuleOverrides, } = {}) {
1415
+ // Collect (group → referenced rule name) pairs from both inputs.
1416
+ const references = [];
1417
+ if (managedRuleOverrides) {
1418
+ for (const [group, overrides] of Object.entries(managedRuleOverrides)) {
1419
+ for (const override of overrides ?? []) {
1420
+ if (override?.name)
1421
+ references.push({ group, ruleName: override.name });
1422
+ }
1423
+ }
1424
+ }
1425
+ const allowEntries = allow ? (Array.isArray(allow) ? allow : [allow]) : [];
1426
+ for (const entry of allowEntries) {
1427
+ for (const key of Object.keys(entry)) {
1428
+ if (key === "path")
1429
+ continue;
1430
+ const raw = entry[key];
1431
+ if (raw == null)
1432
+ continue;
1433
+ const ruleNames = Array.isArray(raw) ? raw : [raw];
1434
+ for (const ruleName of ruleNames) {
1435
+ references.push({ group: key, ruleName });
1436
+ }
1437
+ }
1438
+ }
1439
+ for (const { group, ruleName } of references) {
1440
+ const validNames = AWS_MANAGED_RULE_GROUPS[group];
1441
+ if (!validNames)
1442
+ continue; // Unknown/custom group — cannot validate
1443
+ if (!validNames.includes(ruleName)) {
1444
+ throw new errors.ConfigurationError(`WAF rule "${ruleName}" is not a rule in ${group}. AWS WAF matches ` +
1445
+ `RuleActionOverrides on the exact rule name and silently ignores ` +
1446
+ `unmatched names (note the label/name casing trap, e.g. ` +
1447
+ `"NoUserAgent_HEADER" not "NoUserAgent_Header"). Valid rule names: ` +
1448
+ `${validNames.join(", ")}.`);
1449
+ }
1450
+ }
1451
+ }
1452
+
1306
1453
  class JaypieApiGateway extends constructs.Construct {
1307
1454
  constructor(scope, id, props) {
1308
1455
  super(scope, id);
@@ -1496,7 +1643,7 @@ class JaypieLambda extends constructs.Construct {
1496
1643
  // Resolve environment from array or object syntax
1497
1644
  const initialEnvironment = resolveEnvironment(environmentInput);
1498
1645
  // Get base environment with defaults
1499
- const environment = jaypieLambdaEnv({ initialEnvironment });
1646
+ const environment = jaypieLambdaEnv({ initialEnvironment, serviceTag });
1500
1647
  // Resolve secrets from mixed array (strings and JaypieSecret instances)
1501
1648
  // Use Stack.of(this) to ensure secrets are shared at stack level across all constructs
1502
1649
  const secrets = resolveSecrets(cdk.Stack.of(this), secretsInput);
@@ -2796,6 +2943,8 @@ class JaypieDistribution extends constructs.Construct {
2796
2943
  else {
2797
2944
  // Create new WebACL
2798
2945
  const { allow, managedRuleOverrides, managedRuleScopeDowns, managedRules = DEFAULT_MANAGED_RULES$1, rateLimitPerIp = DEFAULT_RATE_LIMIT$1, } = wafConfig;
2946
+ // Fail synth on rule names AWS WAF would silently ignore (#362)
2947
+ assertValidWafRuleNames({ allow, managedRuleOverrides });
2799
2948
  const allowEntries = allow
2800
2949
  ? Array.isArray(allow)
2801
2950
  ? allow
@@ -4782,6 +4931,8 @@ class JaypieWebDeploymentBucket extends constructs.Construct {
4782
4931
  }
4783
4932
  else {
4784
4933
  const { managedRuleOverrides, managedRuleScopeDowns, managedRules = DEFAULT_MANAGED_RULES, rateLimitPerIp = DEFAULT_RATE_LIMIT, } = wafConfig;
4934
+ // Fail synth on rule names AWS WAF would silently ignore (#362)
4935
+ assertValidWafRuleNames({ managedRuleOverrides });
4785
4936
  let priority = 0;
4786
4937
  const rules = [];
4787
4938
  for (const ruleName of managedRules) {
@@ -5455,6 +5606,7 @@ class JaypieWebSocketTable extends constructs.Construct {
5455
5606
  }
5456
5607
  }
5457
5608
 
5609
+ exports.AWS_MANAGED_RULE_GROUPS = AWS_MANAGED_RULE_GROUPS;
5458
5610
  exports.CDK = CDK$2;
5459
5611
  exports.JaypieAccountLoggingBucket = JaypieAccountLoggingBucket;
5460
5612
  exports.JaypieApiGateway = JaypieApiGateway;
@@ -5491,6 +5643,7 @@ exports.JaypieWebSocket = JaypieWebSocket;
5491
5643
  exports.JaypieWebSocketLambda = JaypieWebSocketLambda;
5492
5644
  exports.JaypieWebSocketTable = JaypieWebSocketTable;
5493
5645
  exports.addDatadogLayers = addDatadogLayers;
5646
+ exports.assertValidWafRuleNames = assertValidWafRuleNames;
5494
5647
  exports.clearAllCertificateCaches = clearAllCertificateCaches;
5495
5648
  exports.clearAllSecretsCaches = clearAllSecretsCaches;
5496
5649
  exports.clearCertificateCache = clearCertificateCache;