Source code for suricata_check.utils.checker

  1"""The `suricata_check.utils.checker` module contains several utilities for developing rule checkers."""
  2
  3import logging
  4from collections.abc import Iterable, Sequence
  5from functools import lru_cache
  6from typing import Optional, Union
  7
  8import idstools.rule
  9
 10from suricata_check.utils.regex import (
 11    ALL_DETECTION_KEYWORDS,
 12    ALL_KEYWORDS,
 13    ALL_METADATA_KEYWORDS,
 14    BUFFER_KEYWORDS,
 15    STICKY_BUFFER_NAMING,
 16    get_regex_provider,
 17    get_variable_groups,
 18)
 19
 20_LRU_CACHE_SIZE = 10
 21
 22
 23_logger = logging.getLogger(__name__)
 24
 25_regex_provider = get_regex_provider()
 26
 27
[docs] 28def check_rule_option_recognition(rule: idstools.rule.Rule) -> None: 29 """Checks whether all rule options and metadata options are recognized. 30 31 Unrecognized options will be logged as a warning in `suricata-check.log` 32 """ 33 for option in rule["options"]: 34 name = option["name"] 35 if name not in ALL_KEYWORDS: 36 _logger.warning( 37 "Option %s from rule %s is not recognized.", 38 name, 39 rule["sid"], 40 ) 41 42 for option in rule["metadata"]: 43 name = _regex_provider.split(r"\s+", option)[0] 44 if name not in ALL_METADATA_KEYWORDS: 45 _logger.warning( 46 "Metadata option %s from rule %s is not recognized.", 47 name, 48 rule["sid"], 49 )
50 51
[docs] 52def is_rule_option_set(rule: idstools.rule.Rule, name: str) -> bool: 53 """Checks whether a rule has a certain option set. 54 55 Args: 56 rule (idstools.rule.Rule): rule to be inspected 57 name (str): name of the option 58 59 Returns: 60 bool: True iff the option is set atleast once 61 62 """ 63 return __is_rule_option_set(rule, name)
64 65 66@lru_cache(maxsize=_LRU_CACHE_SIZE) 67def __is_rule_option_set(rule: idstools.rule.Rule, name: str) -> bool: 68 if name not in ( 69 "action", 70 "proto", 71 "source_addr", 72 "source_port", 73 "direction", 74 "dest_addr", 75 "dest_port", 76 ): 77 if name not in ALL_KEYWORDS: 78 _logger.warning("Requested a non-recognized keyword: %s", name) 79 80 for option in rule["options"]: 81 if option["name"] == name: 82 return True 83 84 return False 85 86 if name not in rule: 87 return False 88 89 if rule[name] is None: 90 return False 91 92 if rule[name] == "": 93 return False 94 95 return True 96 97
[docs] 98def get_rule_suboptions( 99 rule: idstools.rule.Rule, name: str, warn: bool = True 100) -> Sequence[tuple[str, Optional[str]]]: 101 """Returns a list of suboptions set in a rule.""" 102 values = get_rule_options(rule, name) 103 valid_suboptions: list[tuple[str, Optional[str]]] = [] 104 for value in values: 105 if value is None: 106 continue 107 values = value.split(",") 108 suboptions: list[Optional[tuple[str, Optional[str]]]] = [ 109 __split_suboption(suboption, warn=warn) for suboption in values 110 ] 111 # Filter out suboptions that could not be parsed 112 valid_suboptions += [ 113 suboption for suboption in suboptions if suboption is not None 114 ] 115 116 return valid_suboptions
117 118 119def __split_suboption( 120 suboption: str, warn: bool 121) -> Optional[tuple[str, Optional[str]]]: 122 suboption = suboption.strip() 123 124 splitted = suboption.split(" ") 125 splitted = [s.strip() for s in splitted] 126 127 if len(splitted) == 1: 128 return (splitted[0], None) 129 if len(splitted) == 2: # noqa: PLR2004 130 return tuple(splitted) # type: ignore reportReturnType 131 132 if warn: 133 _logger.warning("Failed to split suboption: %s", suboption) 134 135 return None 136 137
[docs] 138def is_rule_suboption_set(rule: idstools.rule.Rule, name: str, sub_name: str) -> bool: 139 """Checks if a suboption within an option is set.""" 140 suboptions = get_rule_suboptions(rule, name) 141 _logger.debug(suboptions) 142 143 if sub_name in [suboption[0] for suboption in suboptions]: 144 return True 145 146 return False
147 148
[docs] 149def get_rule_suboption( 150 rule: idstools.rule.Rule, name: str, sub_name: str 151) -> Optional[str]: 152 """Returns a suboption within an option is set.""" 153 suboptions = get_rule_suboptions(rule, name) 154 155 for suboption in suboptions: 156 if sub_name == suboption[0]: 157 return suboption[1] 158 159 msg = f"Option {name} not found in rule." 160 _logger.debug(msg) 161 return None
162 163
[docs] 164def count_rule_options( 165 rule: idstools.rule.Rule, 166 name: Union[str, Iterable[str]], 167) -> int: 168 """Counts how often an option is set in a rule. 169 170 Args: 171 rule (idstools.rule.Rule): rule to be inspected 172 name (Union[str, Iterable[str]]): name or names of the option 173 174 Returns: 175 int: The number of times an option is set 176 177 """ 178 if not isinstance(name, str): 179 name = tuple(sorted(name)) 180 return __count_rule_options(rule, name)
181 182 183@lru_cache(maxsize=_LRU_CACHE_SIZE) 184def __count_rule_options( 185 rule: idstools.rule.Rule, 186 name: Union[str, Iterable[str]], 187) -> int: 188 count = 0 189 190 if not isinstance(name, str): 191 for single_name in name: 192 count += count_rule_options(rule, single_name) 193 return count 194 195 if name not in ( 196 "action", 197 "proto", 198 "source_addr", 199 "source_port", 200 "direction", 201 "dest_addr", 202 "dest_port", 203 ): 204 if name not in ALL_KEYWORDS: 205 _logger.warning("Requested a non-recognized keyword: %s", name) 206 207 for option in rule["options"]: 208 if option["name"] == name: 209 count += 1 210 211 if count == 0 and is_rule_option_set(rule, name): 212 count = 1 213 214 return count 215 216
[docs] 217def get_rule_option(rule: idstools.rule.Rule, name: str) -> Optional[str]: 218 """Retrieves one option of a rule with a certain name. 219 220 If an option is set multiple times, it returns only one indeterminately. 221 222 Args: 223 rule (idstools.rule.Rule): rule to be inspected 224 name (str): name of the option 225 226 Returns: 227 Optional[str]: The value of the option or None if it was not set. 228 229 """ 230 return __get_rule_option(rule, name)
231 232 233@lru_cache(maxsize=_LRU_CACHE_SIZE) 234def __get_rule_option(rule: idstools.rule.Rule, name: str) -> Optional[str]: 235 options = get_rule_options(rule, name) 236 237 if len(options) == 0: 238 msg = f"Option {name} not found in rule." 239 _logger.debug(msg) 240 return None 241 242 if len(options) == 0: 243 msg = f"Cannot unambiguously determine the value of {name} because it is set multiple times." 244 _logger.warning(msg) 245 return None 246 247 return options[0] 248 249
[docs] 250def get_rule_options( 251 rule: idstools.rule.Rule, 252 name: Union[str, Iterable[str]], 253) -> Sequence[Optional[str]]: 254 """Retrieves all options of a rule with a certain name. 255 256 Args: 257 rule (idstools.rule.Rule): rule to be inspected 258 name (Union[str, Iterable[str]]): name or names of the option 259 260 Returns: 261 Sequence[str]: The values of the option. 262 263 """ 264 if not isinstance(name, str): 265 name = tuple(sorted(name)) 266 return __get_rule_options(rule, name)
267 268 269@lru_cache(maxsize=_LRU_CACHE_SIZE) 270def __get_rule_options( 271 rule: idstools.rule.Rule, 272 name: Union[str, Iterable[str]], 273 warn_not_found: bool = True, 274) -> Sequence[str]: 275 values = [] 276 277 if not isinstance(name, str): 278 for single_name in name: 279 values.extend(__get_rule_options(rule, single_name, warn_not_found=False)) 280 return values 281 282 if name not in ( 283 "action", 284 "proto", 285 "source_addr", 286 "source_port", 287 "direction", 288 "dest_addr", 289 "dest_port", 290 ): 291 if name not in ALL_KEYWORDS: 292 _logger.warning("Requested a non-recognized keyword: %s", name) 293 294 for option in rule["options"]: 295 if option["name"] == name: 296 values.append(option["value"]) 297 elif name in rule: 298 values.append(rule[name]) 299 300 if warn_not_found and len(values) == 0: 301 msg = f"Option {name} not found in rule {rule}." 302 _logger.debug(msg) 303 304 return values 305 306
[docs] 307def is_rule_option_equal_to(rule: idstools.rule.Rule, name: str, value: str) -> bool: 308 """Checks whether a rule has a certain option set to a certain value. 309 310 If the option is set multiple times, it will return True if atleast one option matches the value. 311 312 Args: 313 rule (idstools.rule.Rule): rule to be inspected 314 name (str): name of the option 315 value (str): value to check for 316 317 Returns: 318 bool: True iff the rule has the option set to the value atleast once 319 320 """ 321 if not is_rule_option_set(rule, name): 322 return False 323 324 values = get_rule_options(rule, name) 325 326 for val in values: 327 if value is None: 328 continue 329 if val == value: 330 return True 331 332 return False
333 334
[docs] 335def is_rule_suboption_equal_to( 336 rule: idstools.rule.Rule, name: str, sub_name: str, value: str 337) -> bool: 338 """Checks whether a rule has a certain suboption set to a certain value. 339 340 If the suboption is set multiple times, it will return True if atleast one option matches the value. 341 342 Args: 343 rule (idstools.rule.Rule): rule to be inspected 344 name (str): name of the option 345 sub_name (str): name of the suboption 346 value (str): value to check for 347 348 Returns: 349 bool: True iff the rule has the option set to the value atleast once 350 351 """ 352 if not is_rule_suboption_set(rule, name, sub_name): 353 return False 354 355 values = get_rule_suboptions(rule, name) 356 357 for key, val in values: 358 if key == sub_name and val == value: 359 return True 360 361 return False
362 363
[docs] 364def is_rule_option_equal_to_regex( 365 rule: idstools.rule.Rule, 366 name: str, 367 regex, # re.Pattern or regex.Pattern # noqa: ANN001 368) -> bool: 369 """Checks whether a rule has a certain option set to match a certain regex. 370 371 If the option is set multiple times, it will return True if atleast one option matches the regex. 372 373 Args: 374 rule (idstools.rule.Rule): rule to be inspected 375 name (str): name of the option 376 regex (Union[re.Pattern, regex.Pattern]): regex to check for 377 378 Returns: 379 bool: True iff the rule has atleast one option matching the regex 380 381 """ 382 if not is_rule_option_set(rule, name): 383 return False 384 385 values = get_rule_options(rule, name) 386 387 for value in values: 388 if value is None: 389 continue 390 if regex.match(value) is not None: 391 return True 392 393 return False
394 395
[docs] 396def is_rule_suboption_equal_to_regex( 397 rule: idstools.rule.Rule, 398 name: str, 399 sub_name: str, 400 regex, # re.Pattern or regex.Pattern # noqa: ANN001 401) -> bool: 402 """Checks whether a rule has a certain option set to match a certain regex. 403 404 If the option is set multiple times, it will return True if atleast one option matches the regex. 405 406 Args: 407 rule (idstools.rule.Rule): rule to be inspected 408 name (str): name of the option 409 sub_name (str): name of the suboption 410 regex (Union[re.Pattern, regex.Pattern]): regex to check for 411 412 Returns: 413 bool: True iff the rule has atleast one option matching the regex 414 415 """ 416 if not is_rule_suboption_set(rule, name, sub_name): 417 return False 418 419 values = get_rule_suboptions(rule, name) 420 421 for key, value in values: 422 if key == sub_name and regex.match(value) is not None: 423 return True 424 425 return False
426 427
[docs] 428def is_rule_option_always_equal_to_regex( 429 rule: idstools.rule.Rule, 430 name: str, 431 regex, # re.Pattern or regex.Pattern # noqa: ANN001 432) -> Optional[bool]: 433 """Checks whether a rule has a certain option set to match a certain regex. 434 435 If the option is set multiple times, it will return True if all options match the regex. 436 Returns none if the rule option is not set. 437 438 Args: 439 rule (idstools.rule.Rule): rule to be inspected 440 name (str): name of the option 441 regex (Union[re.Pattern, regex.Pattern]): regex to check for 442 443 Returns: 444 bool: True iff the rule has all options matching the regex 445 446 """ 447 if not is_rule_option_set(rule, name): 448 return None 449 450 values = get_rule_options(rule, name) 451 452 for value in values: 453 if value is None: 454 return False 455 if regex.match(value) is None: 456 return False 457 458 return True
459 460
[docs] 461def is_rule_suboption_always_equal_to_regex( 462 rule: idstools.rule.Rule, 463 name: str, 464 sub_name: str, 465 regex, # re.Pattern or regex.Pattern # noqa: ANN001 466) -> Optional[bool]: 467 """Checks whether a rule has a certain option set to match a certain regex. 468 469 If the option is set multiple times, it will return True if all options match the regex. 470 Returns none if the rule option is not set. 471 472 Args: 473 rule (idstools.rule.Rule): rule to be inspected 474 name (str): name of the option 475 sub_name (str): name of the suboption 476 regex (Union[re.Pattern, regex.Pattern]): regex to check for 477 478 Returns: 479 bool: True iff the rule has all options matching the regex 480 481 """ 482 if not is_rule_suboption_set(rule, name, sub_name): 483 return None 484 485 values = get_rule_suboptions(rule, name) 486 487 for key, value in values: 488 if key == sub_name and regex.match(value) is None: 489 return False 490 491 return True
492 493
[docs] 494def are_rule_options_equal_to_regex( 495 rule: idstools.rule.Rule, 496 names: Iterable[str], 497 regex, # re.Pattern or regex.Pattern # noqa: ANN001 498) -> bool: 499 """Checks whether a rule has certain options set to match a certain regex. 500 501 If multiple options are set, it will return True if atleast one option matches the regex. 502 503 Args: 504 rule (idstools.rule.Rule): rule to be inspected 505 names (Iterable[str]): names of the options 506 regex (Union[re.Pattern, regex.Pattern]): regex to check for 507 508 Returns: 509 bool: True iff the rule has atleast one option matching the regex 510 511 """ 512 for name in names: 513 if is_rule_option_equal_to_regex(rule, name, regex): 514 return True 515 516 return False
517 518
[docs] 519def is_rule_option_one_of( 520 rule: idstools.rule.Rule, 521 name: str, 522 possible_values: Union[Sequence[str], set[str]], 523) -> bool: 524 """Checks whether a rule has a certain option set to a one of certain values. 525 526 If the option is set multiple times, it will return True if atleast one option matches a value. 527 528 Args: 529 rule (idstools.rule.Rule): rule to be inspected 530 name (str): name of the option 531 possible_values (Iterable[str]): values to check for 532 533 Returns: 534 bool: True iff the rule has the option set to one of the values atleast once 535 536 """ 537 if not is_rule_option_set(rule, name): 538 return False 539 540 values = get_rule_options(rule, name) 541 542 for value in values: 543 if value is None: 544 continue 545 if value in possible_values: 546 return True 547 548 return False
549 550
[docs] 551def get_rule_sticky_buffer_naming(rule: idstools.rule.Rule) -> list[tuple[str, str]]: 552 """Returns a list of tuples containing the name of a sticky buffer, and the modifier alternative.""" 553 sticky_buffer_naming = [] 554 for option in rule["options"]: 555 if option["name"] in STICKY_BUFFER_NAMING: 556 sticky_buffer_naming.append( 557 (option["name"], STICKY_BUFFER_NAMING[option["name"]]), 558 ) 559 560 return sticky_buffer_naming
561 562
[docs] 563def get_all_variable_groups(rule: idstools.rule.Rule) -> list[str]: 564 """Returns a list of variable groups such as $HTTP_SERVERS in a rule.""" 565 return __get_all_variable_groups(rule)
566 567 568@lru_cache(maxsize=_LRU_CACHE_SIZE) 569def __get_all_variable_groups(rule: idstools.rule.Rule) -> list[str]: 570 variable_groups = [] 571 for name in ( 572 "source_addr", 573 "source_port", 574 "direction", 575 "dest_addr", 576 "dest_port", 577 ): 578 if is_rule_option_set(rule, name): 579 value = get_rule_option(rule, name) 580 assert value is not None 581 variable_groups += get_variable_groups(value) 582 583 return variable_groups 584 585
[docs] 586def get_rule_option_positions( 587 rule: idstools.rule.Rule, name: str, sequence: Optional[tuple[str, ...]] = None 588) -> Sequence[int]: 589 """Finds the positions of an option in the rule body. 590 591 Optionally takes a sequence of options to use instead of `rule['options']`. 592 """ 593 return __get_rule_option_positions(rule, name, sequence=sequence)
594 595 596@lru_cache(maxsize=_LRU_CACHE_SIZE) 597def __get_rule_option_positions( 598 rule: idstools.rule.Rule, name: str, sequence: Optional[tuple[str, ...]] = None 599) -> Sequence[int]: 600 provided_sequence = True 601 if sequence is None: 602 sequence = tuple(option["name"] for option in rule["options"]) 603 provided_sequence = False 604 605 positions = [] 606 for i, option in enumerate(sequence): 607 if option == name: 608 positions.append(i) 609 610 if not provided_sequence and len(positions) == 0 and is_rule_option_set(rule, name): 611 msg = f"Cannot determine position of {name} option since it is not part of the sequence of detection keywords." 612 _logger.critical(msg) 613 raise ValueError(msg) 614 615 return tuple(sorted(positions)) 616 617
[docs] 618def get_rule_option_position(rule: idstools.rule.Rule, name: str) -> Optional[int]: 619 """Finds the position of an option in the rule body. 620 621 Return None if the option is not set or set multiple times. 622 """ 623 return __get_rule_option_position(rule, name)
624 625 626@lru_cache(maxsize=_LRU_CACHE_SIZE) 627def __get_rule_option_position(rule: idstools.rule.Rule, name: str) -> Optional[int]: 628 positions = get_rule_option_positions(rule, name) 629 630 if len(positions) == 0: 631 _logger.debug( 632 "Cannot unambigously determine the position of the %s option since it it not set.", 633 name, 634 ) 635 return None 636 637 if len(positions) == 1: 638 return positions[0] 639 640 _logger.debug( 641 "Cannot unambigously determine the position of the %s option since it is set multiple times.", 642 name, 643 ) 644 return None 645 646
[docs] 647def is_rule_option_first(rule: idstools.rule.Rule, name: str) -> Optional[int]: 648 """Checks if a rule option is positioned at the beginning of the body.""" 649 position = get_rule_option_position(rule, name) 650 651 if position is None: 652 _logger.debug("Cannot unambiguously determine if option %s first.", name) 653 return None 654 655 if position == 0: 656 return True 657 658 return False
659 660
[docs] 661def is_rule_option_last(rule: idstools.rule.Rule, name: str) -> Optional[bool]: 662 """Checks if a rule option is positioned at the end of the body.""" 663 position = get_rule_option_position(rule, name) 664 665 if position is None: 666 _logger.debug("Cannot unambiguously determine if option %s last.", name) 667 return None 668 669 if position == len(rule["options"]) - 1: 670 return True 671 672 return False
673 674
[docs] 675def get_rule_options_positions( 676 rule: idstools.rule.Rule, 677 names: Iterable[str], 678 sequence: Optional[Iterable[str]] = None, 679) -> Iterable[int]: 680 """Finds the positions of several options in the rule body.""" 681 return __get_rule_options_positions( 682 rule, tuple(sorted(names)), sequence=tuple(sequence) if sequence else None 683 )
684 685 686@lru_cache(maxsize=_LRU_CACHE_SIZE) 687def __get_rule_options_positions( 688 rule: idstools.rule.Rule, 689 names: Iterable[str], 690 sequence: Optional[tuple[str, ...]] = None, 691) -> Iterable[int]: 692 positions = [] 693 694 for name in names: 695 positions.extend(get_rule_option_positions(rule, name, sequence=sequence)) 696 697 return tuple(sorted(positions)) 698 699
[docs] 700def is_rule_option_put_before( 701 rule: idstools.rule.Rule, 702 name: str, 703 other_names: Union[Sequence[str], set[str]], 704 sequence: Optional[Iterable[str]] = None, 705) -> Optional[bool]: 706 """Checks whether a rule option is placed before one or more other options.""" 707 return __is_rule_option_put_before( 708 rule, 709 name, 710 tuple(sorted(other_names)), 711 sequence=tuple(sequence) if sequence else None, 712 )
713 714 715@lru_cache(maxsize=_LRU_CACHE_SIZE) 716def __is_rule_option_put_before( 717 rule: idstools.rule.Rule, 718 name: str, 719 other_names: Union[Sequence[str], set[str]], 720 sequence: Optional[tuple[str, ...]] = None, 721) -> Optional[bool]: 722 if len(other_names) == 0: 723 _logger.debug( 724 "Cannot unambiguously determine if option %s is put before empty Iterable of other options.", 725 name, 726 ) 727 return None 728 729 positions = get_rule_option_positions(rule, name, sequence=sequence) 730 731 if name in other_names: 732 _logger.debug("Excluding name %s from other_names because of overlap.", name) 733 other_names = set(other_names).difference({name}) 734 735 other_positions = get_rule_options_positions(rule, other_names, sequence=sequence) 736 737 for other_position in other_positions: 738 for position in positions: 739 if position < other_position: 740 return True 741 return False 742 743
[docs] 744def is_rule_option_always_put_before( 745 rule: idstools.rule.Rule, 746 name: str, 747 other_names: Union[Sequence[str], set[str]], 748 sequence: Optional[Iterable[str]] = None, 749) -> Optional[bool]: 750 """Checks whether a rule option is placed before one or more other options.""" 751 return __is_rule_option_always_put_before( 752 rule, 753 name, 754 tuple(sorted(other_names)), 755 sequence=tuple(sequence) if sequence else None, 756 )
757 758 759@lru_cache(maxsize=_LRU_CACHE_SIZE) 760def __is_rule_option_always_put_before( 761 rule: idstools.rule.Rule, 762 name: str, 763 other_names: Union[Sequence[str], set[str]], 764 sequence: Optional[tuple[str, ...]] = None, 765) -> Optional[bool]: 766 if len(other_names) == 0: 767 _logger.debug( 768 "Cannot unambiguously determine if option %s is put before empty Iterable of other options.", 769 name, 770 ) 771 return None 772 773 positions = get_rule_option_positions(rule, name, sequence=sequence) 774 775 if name in other_names: 776 _logger.debug("Excluding name %s from other_names because of overlap.", name) 777 other_names = set(other_names).difference({name}) 778 779 other_positions = get_rule_options_positions(rule, other_names, sequence=sequence) 780 781 for other_position in other_positions: 782 for position in positions: 783 if position >= other_position: 784 return False 785 return True 786 787
[docs] 788def are_rule_options_put_before( 789 rule: idstools.rule.Rule, 790 names: Union[Sequence[str], set[str]], 791 other_names: Union[Sequence[str], set[str]], 792 sequence: Optional[Iterable[str]] = None, 793) -> Optional[bool]: 794 """Checks whether rule options are placed before one or more other options.""" 795 if len(other_names) == 0: 796 _logger.debug( 797 "Cannot unambiguously determine if an empty Iterable of options are put before other options %s.", 798 other_names, 799 ) 800 return None 801 if len(other_names) == 0: 802 _logger.debug( 803 "Cannot unambiguously determine if options %s are put before empty Iterable of other options.", 804 names, 805 ) 806 return None 807 808 for name in names: 809 if is_rule_option_put_before(rule, name, other_names, sequence=sequence): 810 return True 811 return False
812 813
[docs] 814def are_rule_options_always_put_before( 815 rule: idstools.rule.Rule, 816 names: Iterable[str], 817 other_names: Sequence[str], 818 sequence: Optional[Iterable[str]] = None, 819) -> Optional[bool]: 820 """Checks whether rule options are placed before one or more other options.""" 821 if len(other_names) == 0: 822 _logger.debug( 823 "Cannot unambiguously determine if an empty Iterable of options are put before other options %s.", 824 other_names, 825 ) 826 return None 827 if len(other_names) == 0: 828 _logger.debug( 829 "Cannot unambiguously determine if options %s are put before empty Iterable of other options.", 830 names, 831 ) 832 return None 833 834 for name in names: 835 if not is_rule_option_put_before(rule, name, other_names, sequence=sequence): 836 return False 837 return True
838 839
[docs] 840def select_rule_options_by_regex( 841 rule: idstools.rule.Rule, regex # noqa: ANN001 842) -> Iterable[str]: 843 """Selects rule options present in rule matching a regular expression.""" 844 return __select_rule_options_by_regex(rule, regex)
845 846 847@lru_cache(maxsize=_LRU_CACHE_SIZE) 848def __select_rule_options_by_regex( 849 rule: idstools.rule.Rule, regex # noqa: ANN001 850) -> Iterable[str]: 851 options = [] 852 853 for option in rule["options"]: 854 name = option["name"] 855 if _regex_provider.match(regex, name): 856 options.append(name) 857 858 return tuple(sorted(options)) 859 860
[docs] 861def get_rule_keyword_sequences( 862 rule: idstools.rule.Rule, 863 seperator_keywords: Iterable[str] = BUFFER_KEYWORDS, 864 included_keywords: Iterable[str] = ALL_DETECTION_KEYWORDS, 865) -> Sequence[tuple[str, ...]]: 866 """Returns a sequence of sequences of detection options in a rule.""" 867 sequences: list[list[str]] = [] 868 869 # Relies on the assumption that the order of options in the rule is preserved while parsing 870 sequence_i = -1 871 first_seperator_seen = False 872 for option in rule["options"]: 873 name = option["name"] 874 if name in seperator_keywords: 875 if not first_seperator_seen: 876 if len(sequences) > 0: 877 sequences[sequence_i].append(name) 878 else: 879 sequence_i += 1 880 sequences.append([name]) 881 else: 882 sequence_i += 1 883 sequences.append([name]) 884 first_seperator_seen = True 885 elif name in included_keywords and sequence_i == -1: 886 sequence_i += 1 887 sequences.append([name]) 888 elif name in included_keywords: 889 sequences[sequence_i].append(name) 890 891 if len(sequences) == 0: 892 _logger.debug( 893 "No sequences found separated by %s in rule %s", 894 seperator_keywords, 895 rule["raw"], 896 ) 897 return () 898 899 for sequence in sequences: 900 assert len(sequence) > 0 901 902 result = tuple(tuple(sequence) for sequence in sequences) 903 904 _logger.debug( 905 "Detected sequences %s separated by %s in rule %s", 906 result, 907 seperator_keywords, 908 rule["raw"], 909 ) 910 911 return result