@kensio/yulin 1.21.23 → 1.21.24

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.
Files changed (93) hide show
  1. package/dist/service/wafv2/association/sim-aws-waf-protected-resources.js +4 -0
  2. package/dist/service/wafv2/association/sim-waf-associations.d.ts +6 -0
  3. package/dist/service/wafv2/association/sim-waf-associations.js +17 -0
  4. package/dist/service/wafv2/association/sim-waf-protected-resource.d.ts +13 -8
  5. package/dist/service/wafv2/association/sim-waf-protected-resource.js +23 -23
  6. package/dist/service/wafv2/association/sim-waf-protected-resources.d.ts +11 -6
  7. package/dist/service/wafv2/association/sim-waf-protected-resources.js +10 -7
  8. package/dist/service/wafv2/association/sim-waf-unsimulated-resource.d.ts +33 -0
  9. package/dist/service/wafv2/association/sim-waf-unsimulated-resource.js +66 -0
  10. package/dist/service/wafv2/cfn/association/sim-cfn-waf-association-creator.js +5 -0
  11. package/dist/service/wafv2/cfn/ip-set/sim-cfn-waf-ip-set-config.js +1 -0
  12. package/dist/service/wafv2/cfn/ip-set/sim-cfn-waf-ip-set-creator.js +2 -0
  13. package/dist/service/wafv2/cfn/regex-pattern-set/sim-cfn-waf-regex-set-config.js +1 -0
  14. package/dist/service/wafv2/cfn/regex-pattern-set/sim-cfn-waf-regex-set-creator.js +2 -0
  15. package/dist/service/wafv2/cfn/sim-cfn-waf-resource-config.d.ts +4 -0
  16. package/dist/service/wafv2/cfn/sim-cfn-waf-resource-config.js +10 -0
  17. package/dist/service/wafv2/cfn/sim-cfn-waf-unsimulated.d.ts +10 -0
  18. package/dist/service/wafv2/cfn/sim-cfn-waf-unsimulated.js +12 -0
  19. package/dist/service/wafv2/cfn/web-acl/sim-cfn-waf-web-acl-config.d.ts +1 -13
  20. package/dist/service/wafv2/cfn/web-acl/sim-cfn-waf-web-acl-config.js +8 -21
  21. package/dist/service/wafv2/cfn/web-acl/sim-cfn-waf-web-acl-creator.d.ts +4 -9
  22. package/dist/service/wafv2/cfn/web-acl/sim-cfn-waf-web-acl-creator.js +7 -15
  23. package/dist/service/wafv2/command/association/sim-wafv2-association-commands.js +2 -1
  24. package/dist/service/wafv2/command/association/sim-wafv2-association-input.d.ts +10 -2
  25. package/dist/service/wafv2/command/association/sim-wafv2-association-input.js +25 -13
  26. package/dist/service/wafv2/command/ip-set/sim-wafv2-ip-set-commands.d.ts +3 -3
  27. package/dist/service/wafv2/command/ip-set/sim-wafv2-ip-set-commands.js +5 -5
  28. package/dist/service/wafv2/command/managed-rule-group/sim-wafv2-managed-rule-group-commands.js +4 -4
  29. package/dist/service/wafv2/command/regex-pattern-set/sim-wafv2-regex-pattern-set-commands.js +2 -2
  30. package/dist/service/wafv2/command/sim-wafv2-input.d.ts +0 -7
  31. package/dist/service/wafv2/command/sim-wafv2-input.js +1 -13
  32. package/dist/service/wafv2/command/web-acl/sim-waf-web-acl-output.js +1 -0
  33. package/dist/service/wafv2/command/web-acl/sim-wafv2-unsimulated-web-acl-input.d.ts +9 -12
  34. package/dist/service/wafv2/command/web-acl/sim-wafv2-unsimulated-web-acl-input.js +27 -28
  35. package/dist/service/wafv2/command/web-acl/sim-wafv2-web-acl-commands.d.ts +3 -3
  36. package/dist/service/wafv2/command/web-acl/sim-wafv2-web-acl-commands.js +6 -6
  37. package/dist/service/wafv2/command/web-acl/web-acl.command.d.ts +6 -0
  38. package/dist/service/wafv2/index.d.ts +3 -0
  39. package/dist/service/wafv2/index.js +1 -0
  40. package/dist/service/wafv2/managed/group/sim-waf-bot-control.d.ts +27 -0
  41. package/dist/service/wafv2/managed/group/sim-waf-bot-control.js +68 -0
  42. package/dist/service/wafv2/managed/group/sim-waf-ip-reputation.d.ts +18 -0
  43. package/dist/service/wafv2/managed/group/sim-waf-ip-reputation.js +51 -0
  44. package/dist/service/wafv2/managed/sim-waf-bot-declaration.d.ts +35 -0
  45. package/dist/service/wafv2/managed/sim-waf-bot-declaration.js +42 -0
  46. package/dist/service/wafv2/managed/sim-waf-managed-declaration.d.ts +35 -0
  47. package/dist/service/wafv2/managed/sim-waf-managed-declaration.js +30 -0
  48. package/dist/service/wafv2/managed/sim-waf-managed-group-configs.d.ts +11 -0
  49. package/dist/service/wafv2/managed/sim-waf-managed-group-configs.js +42 -0
  50. package/dist/service/wafv2/managed/sim-waf-managed-group-input.d.ts +5 -4
  51. package/dist/service/wafv2/managed/sim-waf-managed-group-input.js +7 -9
  52. package/dist/service/wafv2/managed/sim-waf-managed-group-statement.js +16 -14
  53. package/dist/service/wafv2/managed/sim-waf-managed-group.type.d.ts +2 -2
  54. package/dist/service/wafv2/managed/sim-waf-managed-rule-actions.d.ts +30 -0
  55. package/dist/service/wafv2/managed/sim-waf-managed-rule-actions.js +26 -0
  56. package/dist/service/wafv2/managed/sim-waf-managed-rule-groups.d.ts +5 -3
  57. package/dist/service/wafv2/managed/sim-waf-managed-rule-groups.js +10 -3
  58. package/dist/service/wafv2/managed/sim-waf-managed-rule.type.d.ts +11 -2
  59. package/dist/service/wafv2/managed/sim-waf-managed-rules.d.ts +14 -9
  60. package/dist/service/wafv2/managed/sim-waf-managed-rules.js +15 -5
  61. package/dist/service/wafv2/resource/sim-waf-resource.d.ts +10 -0
  62. package/dist/service/wafv2/resource/sim-waf-resource.js +12 -0
  63. package/dist/service/wafv2/resource/sim-waf-unsimulated-part.d.ts +24 -0
  64. package/dist/service/wafv2/resource/sim-waf-unsimulated-part.js +8 -0
  65. package/dist/service/wafv2/sim-wafv2.d.ts +12 -12
  66. package/dist/service/wafv2/sim-wafv2.fixture.d.ts +19 -1
  67. package/dist/service/wafv2/sim-wafv2.fixture.js +29 -0
  68. package/dist/service/wafv2/sim-wafv2.js +25 -23
  69. package/dist/service/wafv2/statement/sim-waf-field-to-match.type.d.ts +2 -2
  70. package/dist/service/wafv2/statement/sim-waf-rule-refusals.d.ts +6 -4
  71. package/dist/service/wafv2/statement/sim-waf-rule-refusals.js +7 -6
  72. package/dist/service/wafv2/statement/sim-waf-statement.d.ts +3 -3
  73. package/dist/service/wafv2/statement/sim-waf-statement.js +3 -3
  74. package/dist/service/wafv2/statement/sim-waf-statement.type.d.ts +3 -3
  75. package/dist/service/wafv2/statement/sim-waf-unsimulated-statement.js +1 -1
  76. package/dist/service/wafv2/web-acl/sim-waf-association-config.d.ts +14 -7
  77. package/dist/service/wafv2/web-acl/sim-waf-association-config.js +17 -27
  78. package/dist/service/wafv2/web-acl/sim-waf-rule.d.ts +15 -3
  79. package/dist/service/wafv2/web-acl/sim-waf-rule.js +46 -10
  80. package/dist/service/wafv2/web-acl/sim-waf-statement-capacity.d.ts +4 -4
  81. package/dist/service/wafv2/web-acl/sim-waf-statement-capacity.js +4 -4
  82. package/dist/service/wafv2/web-acl/sim-waf-unsimulated-body-limit.d.ts +6 -0
  83. package/dist/service/wafv2/web-acl/sim-waf-unsimulated-body-limit.js +23 -0
  84. package/dist/service/wafv2/web-acl/sim-waf-web-acl-capacity.js +2 -2
  85. package/dist/service/wafv2/web-acl/sim-waf-web-acl-configuration.d.ts +5 -0
  86. package/dist/service/wafv2/web-acl/sim-waf-web-acl.d.ts +11 -2
  87. package/dist/service/wafv2/web-acl/sim-waf-web-acl.js +20 -1
  88. package/docs/services/wafv2/README.md +243 -51
  89. package/package.json +2 -2
  90. package/dist/service/wafv2/cfn/web-acl/sim-cfn-waf-evaluatable-rules.d.ts +0 -15
  91. package/dist/service/wafv2/cfn/web-acl/sim-cfn-waf-evaluatable-rules.js +0 -21
  92. package/dist/service/wafv2/web-acl/sim-waf-unevaluatable-rules.d.ts +0 -25
  93. package/dist/service/wafv2/web-acl/sim-waf-unevaluatable-rules.js +0 -33
@@ -59,8 +59,8 @@ function nestedCapacity(statement) {
59
59
  *
60
60
  * A group is fixed at the capacity its owner gave it, and a scope-down
61
61
  * statement is charged on top. A group this simulation does not carry
62
- * contributes nothing, which cannot happen through a compiled web ACL: naming
63
- * one is refused where the rule is written.
62
+ * contributes nothing, so a web ACL holding one reports less than AWS would
63
+ * charge.
64
64
  */
65
65
  function managedGroupCapacity(statement) {
66
66
  const named = statement.ManagedRuleGroupStatement;
@@ -13,6 +13,11 @@ export interface SimWafWebAclConfiguration {
13
13
  readonly visibilityConfig?: unknown;
14
14
  readonly description?: string | undefined;
15
15
  readonly associationConfig?: SimWafAssociationConfigInput | undefined;
16
+ /**
17
+ * The members held as written and not acted on, such as `CaptchaConfig`,
18
+ * keyed by the name the API gives them.
19
+ */
20
+ readonly heldMembers?: Readonly<Record<string, unknown>> | undefined;
16
21
  }
17
22
  /**
18
23
  * Read the action a request no rule claims is given.
@@ -1,7 +1,7 @@
1
1
  import { type SimWafDecision } from "../evaluate/sim-waf-decision.js";
2
2
  import type { SimWafInspectedRequest } from "../evaluate/sim-waf-inspected-request.js";
3
3
  import { SimWafResource, type SimWafResourceProperties } from "../resource/sim-waf-resource.js";
4
- import { type SimWafBodyInspectionResourceType } from "./sim-waf-association-config.js";
4
+ import type { SimWafUnsimulatedPart } from "../resource/sim-waf-unsimulated-part.js";
5
5
  import type { SimWafWebAclRuleScope } from "./sim-waf-rule.type.js";
6
6
  import { type SimWafWebAclConfiguration } from "./sim-waf-web-acl-configuration.js";
7
7
  interface SimWafWebAclProperties extends SimWafResourceProperties, SimWafWebAclRuleScope {
@@ -33,6 +33,14 @@ export declare class SimWafWebAcl extends SimWafResource {
33
33
  * What this web ACL's rules add up to in capacity units.
34
34
  */
35
35
  get capacity(): number;
36
+ /**
37
+ * What this web ACL was written with and the simulation does not act on.
38
+ *
39
+ * A rule listed here claims no request, a member is held and does nothing,
40
+ * and a body inspection limit applies to no request. `GetWebACL` returns
41
+ * every one of them as written.
42
+ */
43
+ get unsimulated(): readonly SimWafUnsimulatedPart[];
36
44
  /**
37
45
  * What this web ACL was last written with, as the API reports it.
38
46
  */
@@ -42,6 +50,7 @@ export declare class SimWafWebAcl extends SimWafResource {
42
50
  *
43
51
  * The rules are compiled before anything is replaced, so a web ACL that
44
52
  * refuses an update keeps the rules it had rather than being left with none.
53
+ * The tags stay as they were created, since UpdateWebACL takes none.
45
54
  */
46
55
  reconfigure(configuration: SimWafWebAclConfiguration, lockToken: string | undefined): void;
47
56
  /**
@@ -51,7 +60,7 @@ export declare class SimWafWebAcl extends SimWafResource {
51
60
  * `AssociationConfig` raises it per resource type, and a web ACL written
52
61
  * without one reads the 16 KB default everything it protects has.
53
62
  */
54
- bodyInspectionLimitBytes(resourceType: SimWafBodyInspectionResourceType | undefined): number;
63
+ bodyInspectionLimitBytes(resourceType: string | undefined): number;
55
64
  /**
56
65
  * Decide what happens to one request.
57
66
  */
@@ -1,7 +1,8 @@
1
1
  import { simWafDecision, } from "../evaluate/sim-waf-decision.js";
2
2
  import { simWafEvaluateRules } from "../evaluate/sim-waf-evaluate-rules.js";
3
+ import { unsimulatedSimWafWebAclMembers } from "../command/web-acl/sim-wafv2-unsimulated-web-acl-input.js";
3
4
  import { SimWafResource, } from "../resource/sim-waf-resource.js";
4
- import { SimWafBodyInspectionLimits, } from "./sim-waf-association-config.js";
5
+ import { SimWafBodyInspectionLimits } from "./sim-waf-association-config.js";
5
6
  import { compileSimWafWebAclRules } from "./sim-waf-rules.js";
6
7
  import { simWafWebAclCapacity } from "./sim-waf-web-acl-capacity.js";
7
8
  import { readSimWafDefaultAction, } from "./sim-waf-web-acl-configuration.js";
@@ -49,6 +50,23 @@ export class SimWafWebAcl extends SimWafResource {
49
50
  get capacity() {
50
51
  return this.#capacity;
51
52
  }
53
+ /**
54
+ * What this web ACL was written with and the simulation does not act on.
55
+ *
56
+ * A rule listed here claims no request, a member is held and does nothing,
57
+ * and a body inspection limit applies to no request. `GetWebACL` returns
58
+ * every one of them as written.
59
+ */
60
+ get unsimulated() {
61
+ return [
62
+ ...this.#rules.flatMap(({ name, unsimulatedReason }) => unsimulatedReason === undefined
63
+ ? []
64
+ : [{ part: `Rules.${name}`, reason: unsimulatedReason }]),
65
+ ...unsimulatedSimWafWebAclMembers(this.#configuration.heldMembers ?? {}),
66
+ ...this.#bodyInspectionLimits.unsimulated,
67
+ ...super.unsimulated,
68
+ ];
69
+ }
52
70
  /**
53
71
  * What this web ACL was last written with, as the API reports it.
54
72
  */
@@ -60,6 +78,7 @@ export class SimWafWebAcl extends SimWafResource {
60
78
  *
61
79
  * The rules are compiled before anything is replaced, so a web ACL that
62
80
  * refuses an update keeps the rules it had rather than being left with none.
81
+ * The tags stay as they were created, since UpdateWebACL takes none.
63
82
  */
64
83
  reconfigure(configuration, lockToken) {
65
84
  const defaultAction = readSimWafDefaultAction(configuration);
@@ -302,13 +302,20 @@ both refused where the rule is written.
302
302
 
303
303
  ## The AWS managed rule groups
304
304
 
305
- Three of the AWS managed rule groups are simulated, so a stack that turns them on deploys and its
306
- traffic can be tested against them.
305
+ Six of the AWS managed rule groups are simulated, so a stack that turns them on deploys and its
306
+ traffic can be tested against them. The first three read the request.
307
307
 
308
308
  - `AWSManagedRulesCommonRuleSet`, the core rule set, 22 rules.
309
309
  - `AWSManagedRulesKnownBadInputsRuleSet`, 11 rules.
310
310
  - `AWSManagedRulesAdminProtectionRuleSet`, one rule.
311
311
 
312
+ The other three read the caller, and a test declares what they find (see
313
+ [Groups that read the caller](#groups-that-read-the-caller)).
314
+
315
+ - `AWSManagedRulesAmazonIpReputationList`, three rules.
316
+ - `AWSManagedRulesAnonymousIpList`, two rules.
317
+ - `AWSManagedRulesBotControlRuleSet` at the common inspection level, 19 rules.
318
+
312
319
  A group evaluates its rules in the order AWS documents them, adds the documented
313
320
  `awswaf:managed:aws:*` label to a request a rule claims, and blocks by that rule's action. The
314
321
  labels are on the decision, and they are what says which rule inside a group claimed the request.
@@ -405,7 +412,8 @@ AWS rule it stands for, and `managedRules().rules()` reports the tier of every o
405
412
  `ExploitablePaths_URIPATH`, `AdminProtection_URIPATH`, `JavaDeserializationRCE_*` and
406
413
  `UserAgent_BadBots_HEADER`.
407
414
  - **declared** detects nothing at all. The four `CrossSiteScripting_*` rules run AWS's own
408
- detection, and AWS documents none of it.
415
+ detection, and AWS documents none of it. Every rule of the IP reputation, anonymous IP and Bot
416
+ Control groups is declared too.
409
417
 
410
418
  The tiers under-detect against AWS and never over-detect. The usual reason to put WAF in a test is
411
419
  to find out whether an application's own traffic still gets through with the core rule set on. A
@@ -447,10 +455,125 @@ A match names the rule, in the spelling `RuleActionOverrides` and `DescribeManag
447
455
  (`CrossSiteScripting_QueryArguments`). A name no simulated group holds is refused where it was
448
456
  written.
449
457
 
450
- Anything outside the three groups is refused by name, and the refusal says which are simulated. The
451
- IP reputation and anonymous IP groups decide by caller address, and every request in this simulation
452
- comes from one client. Bot Control and the account takeover groups decide by behaviour across
453
- requests. The SQL injection group is undocumented in the way the cross-site scripting rules are.
458
+ A rule naming any other group is held and claims no request (see [Held input](#held-input)), and
459
+ the reason it gives says which groups are simulated. The account takeover and account creation
460
+ groups, and Bot Control's targeted level, decide by behaviour across requests and by tokens a
461
+ browser carries. The SQL injection group is undocumented in the way the cross-site scripting rules
462
+ are.
463
+
464
+ ### Groups that read the caller
465
+
466
+ The IP reputation, anonymous IP and Bot Control groups decide by who sent a request, and every
467
+ request in this simulation comes from `127.0.0.1`. Each of their rules is declared-only. The group
468
+ labels the request, takes the rule's documented action and honours overrides, as the groups that
469
+ read the request do.
470
+
471
+ `AWSManagedRulesAmazonIpReputationList` holds `AWSManagedIPReputationList` and
472
+ `AWSManagedReconnaissanceList`, which block, and `AWSManagedIPDDoSList`, which counts as AWS ships
473
+ it. `AWSManagedRulesAnonymousIpList` holds `AnonymousIPList` (VPNs, proxies and Tor) and
474
+ `HostingProviderIPList` (hosting and cloud providers other than AWS), which both block. A test
475
+ declares a match by rule name, as above, and says which requests come from a flagged address that
476
+ way.
477
+
478
+ Bot Control labels a request by the bot that sent it, and a verified bot is labelled and matches no
479
+ rule. A test declares the bot.
480
+
481
+ ```typescript sim-wafv2-bot-control
482
+ /**
483
+ * Counting with Bot Control and blocking unverified bots by label.
484
+ */
485
+
486
+ import { CreateWebACLCommand } from "@aws-sdk/client-wafv2";
487
+
488
+ import { SimAws } from "@kensio/yulin";
489
+
490
+ const waf = new SimAws().wafV2();
491
+
492
+ const visibility = {
493
+ SampledRequestsEnabled: false,
494
+ CloudWatchMetricsEnabled: false,
495
+ MetricName: "site",
496
+ };
497
+
498
+ const created = await waf.createWebAcl(
499
+ new CreateWebACLCommand({
500
+ Name: "site-acl",
501
+ Scope: "REGIONAL",
502
+ DefaultAction: { Allow: {} },
503
+ VisibilityConfig: visibility,
504
+ Rules: [
505
+ {
506
+ Name: "bot-control",
507
+ Priority: 0,
508
+ OverrideAction: { Count: {} },
509
+ Statement: {
510
+ ManagedRuleGroupStatement: {
511
+ VendorName: "AWS",
512
+ Name: "AWSManagedRulesBotControlRuleSet",
513
+ ManagedRuleGroupConfigs: [
514
+ {
515
+ AWSManagedRulesBotControlRuleSet: { InspectionLevel: "COMMON" },
516
+ },
517
+ ],
518
+ },
519
+ },
520
+ VisibilityConfig: visibility,
521
+ },
522
+ {
523
+ Name: "block-scrapers",
524
+ Priority: 1,
525
+ Action: { Block: {} },
526
+ Statement: {
527
+ LabelMatchStatement: {
528
+ Scope: "LABEL",
529
+ Key: "awswaf:managed:aws:bot-control:bot:category:scraping_framework",
530
+ },
531
+ },
532
+ VisibilityConfig: visibility,
533
+ },
534
+ ],
535
+ }),
536
+ );
537
+
538
+ waf.managedRules().onRequest("/search", {
539
+ bot: { category: "scraping_framework", name: "scrapy" },
540
+ });
541
+ waf.managedRules().onRequest("/", {
542
+ bot: { category: "search_engine", name: "googlebot", verified: true },
543
+ });
544
+
545
+ const webAclArn = created.Summary!.ARN;
546
+
547
+ const scraper = waf.evaluateRequest({
548
+ webAclArn,
549
+ request: new Request("https://example.test/search"),
550
+ });
551
+ const crawler = waf.evaluateRequest({
552
+ webAclArn,
553
+ request: new Request("https://example.test/"),
554
+ });
555
+
556
+ // "BLOCK" "block-scrapers"
557
+ console.log(scraper.action, scraper.terminatingRuleName);
558
+
559
+ // "ALLOW" true
560
+ console.log(
561
+ crawler.action,
562
+ crawler.labels.includes("awswaf:managed:aws:bot-control:bot:verified"),
563
+ );
564
+ ```
565
+
566
+ A `bot` declaration names the category in the spelling the `bot:category:` label uses
567
+ (`search_engine`, `http_library`, `scraping_framework` and the rest), and optionally the bot's
568
+ `name`, its `organization` and whether it is `verified`. An unverified bot matches its category's
569
+ rule (`CategorySearchEngine` and so on), which blocks by default. A verified bot matches no rule,
570
+ except a verified AI bot, which `CategoryAI` blocks as it does on AWS. Either way the group adds
571
+ `bot:category:`, `bot:name:` and `bot:organization:` labels where the declaration gives them, and
572
+ `bot:verified` or `bot:unverified`. The three `Signal*` rules are declared by rule name in
573
+ `matches`, and add their `signal:` label beside their own.
574
+
575
+ Bot Control runs at `COMMON` when `ManagedRuleGroupConfigs` names that level or when it is left out.
576
+ A rule naming the `TARGETED` level is held (see [Held input](#held-input)).
454
577
 
455
578
  ## Labels
456
579
 
@@ -704,9 +827,8 @@ await srv.close();
704
827
 
705
828
  `DisassociateWebACL` takes the web ACL back off. `GetWebACLForResource` reports the web ACL one
706
829
  stage carries, and `ListResourcesForWebACL` reports the stages one web ACL protects. That listing
707
- takes a `ResourceType` of `API_GATEWAY`. Real WAFv2 lists `APPLICATION_LOAD_BALANCER` for a request
708
- that names no type. Load balancers are outside this simulation, and a listing that names no type is
709
- refused.
830
+ takes a `ResourceType` of `API_GATEWAY`. A listing that names no type lists
831
+ `APPLICATION_LOAD_BALANCER`, as real WAFv2 does.
710
832
 
711
833
  Deleting the stage or the whole API takes the association with it. A stage deployed again under the
712
834
  same name carries no web ACL. A web ACL that is still in front of a stage cannot be deleted, and
@@ -717,9 +839,14 @@ because a distribution takes its web ACL from the distribution and not from `Ass
717
839
  ACL from another Region or another Account is refused, as it is on AWS.
718
840
 
719
841
  An API Gateway HTTP API stage is refused. AWS WAF has no resource type for one, and an association
720
- accepted here would let a test cover protection AWS never applies. Application Load Balancer,
721
- AppSync, App Runner, Amplify and Verified Access resources are refused as unsimulated, each naming
722
- what it would have protected.
842
+ accepted here would let a test cover protection AWS never applies.
843
+
844
+ An Application Load Balancer, an AppSync API, an App Runner service and a Verified Access instance
845
+ are associated, and `GetWebACLForResource` and `ListResourcesForWebACL` report them. Simulated
846
+ requests reach a web ACL through a REST API stage or a user pool alone, so the association is held
847
+ for reading back and listed by `unsimulatedParts` (see [Held input](#held-input)). Yulin takes the
848
+ resource as named, without looking for it. An Amplify app is refused. AWS takes a `CLOUDFRONT` web ACL from `us-east-1`
849
+ for one, and a simulated WAFv2 associates the web ACLs of its own Region only.
723
850
 
724
851
  ## Protecting a Cognito user pool
725
852
 
@@ -854,9 +981,10 @@ The web ACL and the pool belong to one Account and Region. A `CLOUDFRONT` scope
854
981
  because a distribution takes its web ACL from the distribution. A pool in another Account or another
855
982
  Region is refused as well.
856
983
 
857
- AWS also refuses a web ACL carrying `AWSManagedRulesATPRuleSet`, and it refuses the whole web ACL
858
- over the one rule group. Yulin turns that group away earlier, at `CreateWebACL`, along with every
859
- managed rule group outside the [three that are simulated](#the-aws-managed-rule-groups).
984
+ AWS also refuses a web ACL carrying `AWSManagedRulesATPRuleSet`, and it refuses the whole
985
+ association over the one rule group. `AssociateWebACL` refuses it here too. The web ACL itself is
986
+ created, holding the group without evaluating it, like every managed rule group outside the
987
+ [six that are simulated](#the-aws-managed-rule-groups).
860
988
 
861
989
  ### The request body is withheld at a hosted domain
862
990
 
@@ -967,7 +1095,7 @@ An update to a pattern set reaches the rules pointing at it. A reference resolve
967
1095
  the rule is written and reads its expressions when a request arrives, as it does on AWS.
968
1096
 
969
1097
  IP sets are created, read, updated, listed and deleted the same way. No rule reads one, for the
970
- reason in [Refusals](#refusals) below.
1098
+ reason in [Held input](#held-input) below.
971
1099
 
972
1100
  ## Deploying web ACLs with CloudFormation
973
1101
 
@@ -1040,29 +1168,26 @@ strings where the SDK takes a list of `RegexString` objects. Both are read here
1040
1168
  CloudFormation writes them.
1041
1169
 
1042
1170
  Every rule is compiled while the stack deploys. A rule this simulator will not evaluate (see
1043
- [Refusals](#refusals)) is left out of the web ACL, and the web ACL deploys with the rules that are
1044
- left. The rule that went missing is recorded on `stack.ignoredProperties`, under the logical ID that
1045
- declared it, and the reason is the one `CreateWebACL` gives an SDK caller.
1171
+ [Held input](#held-input)) is held by the web ACL and claims no request. It
1172
+ is recorded on `stack.ignoredProperties`, under the logical ID that declared it, with the reason
1173
+ `unsimulatedParts` gives.
1046
1174
 
1047
1175
  ```typescript
1048
- const [dropped] = stack.ignoredProperties;
1176
+ const [held] = stack.ignoredProperties;
1049
1177
 
1050
1178
  // "OrdersAcl Rules.block-countries"
1051
- console.log(`${dropped.logicalId} ${dropped.path}`);
1179
+ console.log(`${held.logicalId} ${held.path}`);
1052
1180
 
1053
1181
  // "Rule block-countries uses the statement kind GeoMatchStatement, which
1054
1182
  // Yulin does not simulate: ..."
1055
- console.log(dropped.reason);
1183
+ console.log(held.reason);
1056
1184
  ```
1057
1185
 
1058
- The web ACL is then real, and thinner than the one the template describes. Requests the dropped rule
1059
- would have blocked are served by whatever the web ACL is in front of. That is the size of what a
1060
- test loses, and `stack.ignoredProperties` is where to read it. An SDK caller writing the same rule
1061
- is refused outright, because a request that was answered and then quietly emptied is a worse answer
1062
- than a refusal.
1186
+ Requests the held rule would have blocked are served by whatever the web ACL is in front of. That
1187
+ is the size of what a test loses, and `stack.ignoredProperties` is where to read it.
1063
1188
 
1064
1189
  The same goes for a web ACL member with no behaviour behind it, such as `CaptchaConfig`. The web ACL
1065
- deploys without it and the member is recorded.
1190
+ holds it and the member is recorded.
1066
1191
 
1067
1192
  A web ACL nothing coherent could be deployed from still fails the stack. A `Scope` outside
1068
1193
  `REGIONAL` and `CLOUDFRONT`, a `Rules` written as an object, a `Name` written as a number. The
@@ -1080,8 +1205,8 @@ cover where the tail comes from.
1080
1205
  covers an API Gateway REST API stage and a Cognito user pool. It goes through `AssociateWebACL` and
1081
1206
  inherits that command's answers. An ARN naming an HTTP API stage fails the deployment, because AWS
1082
1207
  WAF protects no HTTP API and neither does real CloudFormation. An ARN naming a load balancer or an
1083
- AppSync API skips the association. AWS WAF protects both, and Yulin simulates a web ACL in front of
1084
- neither.
1208
+ AppSync API is associated, and `ResourceArn` is recorded on `stack.ignoredProperties` as an
1209
+ association held for reading back. An ARN naming an Amplify app skips the association.
1085
1210
 
1086
1211
  An association naming a web ACL from outside this simulation is skipped too, which covers a template
1087
1212
  naming one from a real account and one whose web ACL is in another Region. The stage or the pool
@@ -1143,7 +1268,8 @@ composite primary identifier of name, ID and scope. An association's physical ID
1143
1268
  and the web ACL ARN joined by a pipe, and it publishes no attributes.
1144
1269
 
1145
1270
  `AWS::WAFv2::RuleGroup` and `AWS::WAFv2::LoggingConfiguration` are recorded as unsupported and
1146
- stepped over. A rule naming a rule group is refused anyway, and there is no log here to write to.
1271
+ stepped over. A rule naming a rule group is held and not evaluated anyway, and there is no log here
1272
+ to write to.
1147
1273
 
1148
1274
  ## Scopes
1149
1275
 
@@ -1344,17 +1470,72 @@ const scoped = simSdk.simAws.accountRegionScope(
1344
1470
  console.log(scoped.wafV2().allWebAcls("REGIONAL")[0]?.name);
1345
1471
  ```
1346
1472
 
1347
- ## Refusals
1473
+ ## Held input
1474
+
1475
+ A write WAFv2 itself would accept succeeds, whatever it carries, and `GetWebACL` returns it as
1476
+ written. A rule using something outside the simulation is held, and it claims no request. `unsimulatedParts` lists each part the simulation leaves out, with the reason.
1477
+
1478
+ ```typescript sim-wafv2-unsimulated
1479
+ /**
1480
+ * Writing a web ACL with a rule Yulin does not evaluate.
1481
+ */
1482
+
1483
+ import { CreateWebACLCommand } from "@aws-sdk/client-wafv2";
1484
+
1485
+ import { SimAws } from "@kensio/yulin";
1486
+
1487
+ const waf = new SimAws().wafV2();
1488
+
1489
+ const visibility = {
1490
+ SampledRequestsEnabled: false,
1491
+ CloudWatchMetricsEnabled: false,
1492
+ MetricName: "site",
1493
+ };
1494
+
1495
+ const created = await waf.createWebAcl(
1496
+ new CreateWebACLCommand({
1497
+ Name: "site-acl",
1498
+ Scope: "REGIONAL",
1499
+ DefaultAction: { Allow: {} },
1500
+ VisibilityConfig: visibility,
1501
+ Rules: [
1502
+ {
1503
+ Name: "bot-control",
1504
+ Priority: 0,
1505
+ OverrideAction: { Count: {} },
1506
+ Statement: {
1507
+ ManagedRuleGroupStatement: {
1508
+ VendorName: "AWS",
1509
+ Name: "AWSManagedRulesBotControlRuleSet",
1510
+ ManagedRuleGroupConfigs: [
1511
+ {
1512
+ AWSManagedRulesBotControlRuleSet: { InspectionLevel: "COMMON" },
1513
+ },
1514
+ ],
1515
+ },
1516
+ },
1517
+ VisibilityConfig: visibility,
1518
+ },
1519
+ ],
1520
+ }),
1521
+ );
1348
1522
 
1349
- A rule Yulin cannot evaluate is refused by `CreateWebACL` and `UpdateWebACL`, naming the rule and
1350
- what in it was refused. A web ACL that accepted such a rule would allow a request AWS blocks, and a
1351
- silent hole in a security layer is worse than a missing one.
1523
+ const [part] = waf.unsimulatedParts(created.Summary!.ARN);
1352
1524
 
1353
- A template carrying one of these keeps the caution and drops the blast radius. The rule is left out,
1354
- the web ACL deploys with the rest of them, and the omission is recorded. See
1355
- [Deploying web ACLs with CloudFormation](#deploying-web-acls-with-cloudformation).
1525
+ // "Rules.bot-control"
1526
+ console.log(part?.part);
1527
+
1528
+ // "Rule bot-control uses the rule group member ManagedRuleGroupConfigs,
1529
+ // which Yulin does not simulate: ..."
1530
+ console.log(part?.reason);
1531
+ ```
1356
1532
 
1357
- These statement kinds are refused:
1533
+ The rule keeps its `Action` or `OverrideAction` as written and never matches. That is what AWS does
1534
+ with a request the rule would not have claimed. A request it would have claimed goes on to the next
1535
+ rule. An allow list (a `NotStatement` around an `IPSetReferenceStatement`) is the sharpest case,
1536
+ where AWS blocks every address outside the list and the simulation blocks none.
1537
+
1538
+ A rule is held for any of these statement kinds:
1358
1539
 
1359
1540
  - `IPSetReferenceStatement`, `GeoMatchStatement` and `AsnMatchStatement`. Every request in this
1360
1541
  simulation reports a source address of `127.0.0.1`, and a rule on where a request came from would
@@ -1362,23 +1543,34 @@ These statement kinds are refused:
1362
1543
  - `SqliMatchStatement` and `XssMatchStatement`. AWS publishes no description of the detection they
1363
1544
  run.
1364
1545
  - `RuleGroupReferenceStatement`. A rule group of your own is a resource in its own right, and none
1365
- is simulated. The three simulated AWS managed rule groups are named in a statement rather than
1546
+ is simulated. The six simulated AWS managed rule groups are named in a statement rather than
1366
1547
  created.
1548
+ - `ManagedRuleGroupStatement` naming a group outside the six, or carrying `Version` or
1549
+ `ExcludedRules`. `ManagedRuleGroupConfigs` holds the rule too, unless it runs Bot Control at
1550
+ `COMMON`.
1367
1551
 
1368
- A `RateBasedStatement` is evaluated (see [Rate limiting](#rate-limiting)). Two of its aggregation
1369
- key types are refused. `FORWARDED_IP` and `ForwardedIPConfig` read the address from a forwarding
1370
- header, which needs the source address variety an IP set is waiting on. `CUSTOM_KEYS` and
1552
+ A `RateBasedStatement` is evaluated (see [Rate limiting](#rate-limiting)). A rule using two of its
1553
+ aggregation key types is held. `FORWARDED_IP` and `ForwardedIPConfig` read the address from a
1554
+ forwarding header, which needs the source address variety an IP set is waiting on. `CUSTOM_KEYS` and
1371
1555
  `CustomKeys` aggregate on headers, cookies and query arguments, and are feasible and not part of
1372
1556
  this yet. `GetRateBasedStatementManagedKeys` is not simulated.
1373
1557
 
1374
- `JsonBody`, `HeaderOrder`, `UriFragment`, `JA3Fingerprint` and `JA4Fingerprint` are refused as
1375
- fields to match. The `Captcha` and `Challenge` actions are refused, along with the `CaptchaConfig`,
1376
- `ChallengeConfig` and `TokenDomains` that configure them, because a browser has to answer them.
1558
+ A rule is held for the `JsonBody`, `HeaderOrder`, `UriFragment`, `JA3Fingerprint` and
1559
+ `JA4Fingerprint` fields to match, and for the `Captcha` and `Challenge` actions and the rule-level
1560
+ `CaptchaConfig` and `ChallengeConfig`, because a browser has to answer them.
1561
+
1562
+ The web ACL holds these members as written, and they have no effect on a request. `CaptchaConfig`,
1563
+ `ChallengeConfig` and `TokenDomains` configure the browser-answered actions. `DataProtectionConfig` shapes web ACL
1564
+ logs, which Yulin leaves out. `OnSourceDDoSProtectionConfig` responds to
1565
+ traffic volume, and `ApplicationConfig` configures the WAF-hosted sign-in pages. An
1566
+ `AssociationConfig` limit for a resource type no simulated request passes through is held too.
1567
+
1568
+ Tags on a web ACL, an IP set or a regex pattern set are held, and `unsimulatedParts` lists them.
1569
+ `ListTagsForResource` is the operation that would read them back, and Yulin leaves it out. Logging, sampled requests and CloudWatch metrics for a
1570
+ web ACL are not simulated.
1377
1571
 
1378
- Tags, logging, sampled requests and CloudWatch metrics for a web ACL are not simulated.
1379
- `DataProtectionConfig`, `OnSourceDDoSProtectionConfig` and `ApplicationConfig` are refused for the
1380
- same reason, each naming what it would have configured. An `AssociationConfig` naming a resource
1381
- type this simulation has no association for is refused too.
1572
+ `DescribeManagedRuleGroup` refuses a group outside the six, and a `VersionName`. Its only possible
1573
+ answer would be invented rules.
1382
1574
 
1383
1575
  ## Supported operations
1384
1576
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kensio/yulin",
3
- "version": "1.21.23",
3
+ "version": "1.21.24",
4
4
  "description": "AWS system behaviour simulation for isolated unit testing",
5
5
  "repository": "https://github.com/KensioSoftware/yulin",
6
6
  "homepage": "https://yulinsim.dev/",
@@ -299,7 +299,7 @@
299
299
  "hyparquet": "^1.29.2",
300
300
  "hyparquet-writer": "^0.16.9",
301
301
  "node-sql-parser": "^5.4.0",
302
- "oxfmt": "^0.66.0",
302
+ "oxfmt": "^0.70.0",
303
303
  "oxlint": "^1.77.0",
304
304
  "oxlint-tsgolint": "^7.0.2001",
305
305
  "semantic-release": "25.0.9",
@@ -1,15 +0,0 @@
1
- import type { SimCfnResource } from "../../../cloudformation/resource/sim-cfn-resource.js";
2
- import type { SimCreateWebAclCommandInput } from "../../command/web-acl/web-acl.command.js";
3
- import type { SimWafV2 } from "../../sim-wafv2.js";
4
- import type { SimWafRuleInput } from "../../web-acl/sim-waf-rule.type.js";
5
- /**
6
- * The rules of a template web ACL to deploy, which are the ones this
7
- * simulation can evaluate.
8
- *
9
- * Each rule left out is recorded on the Resource under its own name, because a
10
- * web ACL is a list of rules that all look alike from the outside and the name
11
- * is the only thing that says which one went missing. The reason is the one
12
- * `CreateWebACL` refuses the rule with, so a reader gets the same sentence
13
- * wherever they meet it.
14
- */
15
- export declare function simCfnWafEvaluatableRules(wafV2: SimWafV2, resource: SimCfnResource, input: SimCreateWebAclCommandInput): readonly SimWafRuleInput[] | undefined;
@@ -1,21 +0,0 @@
1
- /**
2
- * The rules of a template web ACL to deploy, which are the ones this
3
- * simulation can evaluate.
4
- *
5
- * Each rule left out is recorded on the Resource under its own name, because a
6
- * web ACL is a list of rules that all look alike from the outside and the name
7
- * is the only thing that says which one went missing. The reason is the one
8
- * `CreateWebACL` refuses the rule with, so a reader gets the same sentence
9
- * wherever they meet it.
10
- */
11
- export function simCfnWafEvaluatableRules(wafV2, resource, input) {
12
- const unevaluatable = wafV2.unevaluatableWebAclRules(input);
13
- if (unevaluatable.length === 0) {
14
- return input.Rules;
15
- }
16
- const dropped = new Set(unevaluatable.map(({ index }) => index));
17
- for (const rule of unevaluatable) {
18
- resource.ignoreProperty(`Rules.${rule.name}`, rule.reason);
19
- }
20
- return input.Rules?.filter((_, index) => !dropped.has(index));
21
- }
@@ -1,25 +0,0 @@
1
- import type { SimWafRuleInput, SimWafRuleScope } from "./sim-waf-rule.type.js";
2
- /**
3
- * One rule of a web ACL this simulation cannot evaluate, and why.
4
- */
5
- export interface SimWafUnevaluatableRule {
6
- /** Where the rule sits in the list the web ACL was written with. */
7
- readonly index: number;
8
- /** The rule's name, as the reason and a record of it name it. */
9
- readonly name: string;
10
- /** What `CreateWebACL` refuses the rule with. */
11
- readonly reason: string;
12
- }
13
- /**
14
- * The rules of a web ACL this simulation cannot evaluate.
15
- *
16
- * Each rule is compiled on its own and the refusal is caught, so this answers
17
- * with exactly what `CreateWebACL` would refuse and nothing else. A rule the
18
- * simulation can evaluate is compiled twice, once here and once by the write
19
- * that follows, and compiling is a pure reading of the rule.
20
- *
21
- * Only a refusal saying the simulation does not go that far is collected. Rule
22
- * input WAFv2 itself will not take is left where it is, for the write to raise
23
- * as it always has.
24
- */
25
- export declare function unevaluatableSimWafRules(rules: readonly SimWafRuleInput[] | undefined, scope: SimWafRuleScope): readonly SimWafUnevaluatableRule[];
@@ -1,33 +0,0 @@
1
- import { SimWafUnsimulatedInputException } from "../error/sim-wafv2.error.js";
2
- import { SimWafRule } from "./sim-waf-rule.js";
3
- /**
4
- * The rules of a web ACL this simulation cannot evaluate.
5
- *
6
- * Each rule is compiled on its own and the refusal is caught, so this answers
7
- * with exactly what `CreateWebACL` would refuse and nothing else. A rule the
8
- * simulation can evaluate is compiled twice, once here and once by the write
9
- * that follows, and compiling is a pure reading of the rule.
10
- *
11
- * Only a refusal saying the simulation does not go that far is collected. Rule
12
- * input WAFv2 itself will not take is left where it is, for the write to raise
13
- * as it always has.
14
- */
15
- export function unevaluatableSimWafRules(rules, scope) {
16
- return (rules ?? []).flatMap((rule, index) => {
17
- const reason = unevaluatableReason(rule, scope);
18
- return reason === undefined
19
- ? []
20
- : [{ index, name: rule.Name ?? `at ${index}`, reason }];
21
- });
22
- }
23
- function unevaluatableReason(rule, scope) {
24
- try {
25
- SimWafRule.compile(rule, scope);
26
- }
27
- catch (error) {
28
- if (error instanceof SimWafUnsimulatedInputException) {
29
- return error.message;
30
- }
31
- }
32
- return undefined;
33
- }