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 val == value: 328 return True 329 330 return False
331 332
[docs] 333def is_rule_suboption_equal_to( 334 rule: idstools.rule.Rule, name: str, sub_name: str, value: str 335) -> bool: 336 """Checks whether a rule has a certain suboption set to a certain value. 337 338 If the suboption is set multiple times, it will return True if atleast one option matches the value. 339 340 Args: 341 rule (idstools.rule.Rule): rule to be inspected 342 name (str): name of the option 343 sub_name (str): name of the suboption 344 value (str): value to check for 345 346 Returns: 347 bool: True iff the rule has the option set to the value atleast once 348 349 """ 350 if not is_rule_suboption_set(rule, name, sub_name): 351 return False 352 353 values = get_rule_suboptions(rule, name) 354 355 for key, val in values: 356 if key == sub_name and val == value: 357 return True 358 359 return False
360 361
[docs] 362def is_rule_option_equal_to_regex( 363 rule: idstools.rule.Rule, 364 name: str, 365 regex, # re.Pattern or regex.Pattern # noqa: ANN001 366) -> bool: 367 """Checks whether a rule has a certain option set to match a certain regex. 368 369 If the option is set multiple times, it will return True if atleast one option matches the regex. 370 371 Args: 372 rule (idstools.rule.Rule): rule to be inspected 373 name (str): name of the option 374 regex (Union[re.Pattern, regex.Pattern]): regex to check for 375 376 Returns: 377 bool: True iff the rule has atleast one option matching the regex 378 379 """ 380 if not is_rule_option_set(rule, name): 381 return False 382 383 values = get_rule_options(rule, name) 384 385 for value in values: 386 if value is None: 387 continue 388 if regex.match(value) is not None: 389 return True 390 391 return False
392 393
[docs] 394def is_rule_suboption_equal_to_regex( 395 rule: idstools.rule.Rule, 396 name: str, 397 sub_name: str, 398 regex, # re.Pattern or regex.Pattern # noqa: ANN001 399) -> bool: 400 """Checks whether a rule has a certain option set to match a certain regex. 401 402 If the option is set multiple times, it will return True if atleast one option matches the regex. 403 404 Args: 405 rule (idstools.rule.Rule): rule to be inspected 406 name (str): name of the option 407 sub_name (str): name of the suboption 408 regex (Union[re.Pattern, regex.Pattern]): regex to check for 409 410 Returns: 411 bool: True iff the rule has atleast one option matching the regex 412 413 """ 414 if not is_rule_suboption_set(rule, name, sub_name): 415 return False 416 417 values = get_rule_suboptions(rule, name) 418 419 for key, value in values: 420 if key == sub_name and regex.match(value) is not None: 421 return True 422 423 return False
424 425
[docs] 426def is_rule_option_always_equal_to_regex( 427 rule: idstools.rule.Rule, 428 name: str, 429 regex, # re.Pattern or regex.Pattern # noqa: ANN001 430) -> Optional[bool]: 431 """Checks whether a rule has a certain option set to match a certain regex. 432 433 If the option is set multiple times, it will return True if all options match the regex. 434 Returns none if the rule option is not set. 435 436 Args: 437 rule (idstools.rule.Rule): rule to be inspected 438 name (str): name of the option 439 regex (Union[re.Pattern, regex.Pattern]): regex to check for 440 441 Returns: 442 bool: True iff the rule has all options matching the regex 443 444 """ 445 if not is_rule_option_set(rule, name): 446 return None 447 448 values = get_rule_options(rule, name) 449 450 for value in values: 451 if value is None: 452 return False 453 if regex.match(value) is None: 454 return False 455 456 return True
457 458
[docs] 459def is_rule_suboption_always_equal_to_regex( 460 rule: idstools.rule.Rule, 461 name: str, 462 sub_name: str, 463 regex, # re.Pattern or regex.Pattern # noqa: ANN001 464) -> Optional[bool]: 465 """Checks whether a rule has a certain option set to match a certain regex. 466 467 If the option is set multiple times, it will return True if all options match the regex. 468 Returns none if the rule option is not set. 469 470 Args: 471 rule (idstools.rule.Rule): rule to be inspected 472 name (str): name of the option 473 sub_name (str): name of the suboption 474 regex (Union[re.Pattern, regex.Pattern]): regex to check for 475 476 Returns: 477 bool: True iff the rule has all options matching the regex 478 479 """ 480 if not is_rule_suboption_set(rule, name, sub_name): 481 return None 482 483 values = get_rule_suboptions(rule, name) 484 485 for key, value in values: 486 if key == sub_name and regex.match(value) is None: 487 return False 488 489 return True
490 491
[docs] 492def are_rule_options_equal_to_regex( 493 rule: idstools.rule.Rule, 494 names: Iterable[str], 495 regex, # re.Pattern or regex.Pattern # noqa: ANN001 496) -> bool: 497 """Checks whether a rule has certain options set to match a certain regex. 498 499 If multiple options are set, it will return True if atleast one option matches the regex. 500 501 Args: 502 rule (idstools.rule.Rule): rule to be inspected 503 names (Iterable[str]): names of the options 504 regex (Union[re.Pattern, regex.Pattern]): regex to check for 505 506 Returns: 507 bool: True iff the rule has atleast one option matching the regex 508 509 """ 510 for name in names: 511 if is_rule_option_equal_to_regex(rule, name, regex): 512 return True 513 514 return False
515 516
[docs] 517def is_rule_option_one_of( 518 rule: idstools.rule.Rule, 519 name: str, 520 possible_values: Union[Sequence[str], set[str]], 521) -> bool: 522 """Checks whether a rule has a certain option set to a one of certain values. 523 524 If the option is set multiple times, it will return True if atleast one option matches a value. 525 526 Args: 527 rule (idstools.rule.Rule): rule to be inspected 528 name (str): name of the option 529 possible_values (Iterable[str]): values to check for 530 531 Returns: 532 bool: True iff the rule has the option set to one of the values atleast once 533 534 """ 535 if not is_rule_option_set(rule, name): 536 return False 537 538 values = get_rule_options(rule, name) 539 540 for value in values: 541 if value is None: 542 continue 543 if value in possible_values: 544 return True 545 546 return False
547 548
[docs] 549def get_rule_sticky_buffer_naming(rule: idstools.rule.Rule) -> list[tuple[str, str]]: 550 """Returns a list of tuples containing the name of a sticky buffer, and the modifier alternative.""" 551 sticky_buffer_naming = [] 552 for option in rule["options"]: 553 if option["name"] in STICKY_BUFFER_NAMING: 554 sticky_buffer_naming.append( 555 (option["name"], STICKY_BUFFER_NAMING[option["name"]]), 556 ) 557 558 return sticky_buffer_naming
559 560
[docs] 561def get_all_variable_groups(rule: idstools.rule.Rule) -> list[str]: 562 """Returns a list of variable groups such as $HTTP_SERVERS in a rule.""" 563 return __get_all_variable_groups(rule)
564 565 566@lru_cache(maxsize=_LRU_CACHE_SIZE) 567def __get_all_variable_groups(rule: idstools.rule.Rule) -> list[str]: 568 variable_groups = [] 569 for name in ( 570 "source_addr", 571 "source_port", 572 "direction", 573 "dest_addr", 574 "dest_port", 575 ): 576 if is_rule_option_set(rule, name): 577 value = get_rule_option(rule, name) 578 assert value is not None 579 variable_groups += get_variable_groups(value) 580 581 return variable_groups 582 583
[docs] 584def get_rule_option_positions( 585 rule: idstools.rule.Rule, name: str, sequence: Optional[tuple[str, ...]] = None 586) -> Sequence[int]: 587 """Finds the positions of an option in the rule body. 588 589 Optionally takes a sequence of options to use instead of `rule['options']`. 590 """ 591 return __get_rule_option_positions(rule, name, sequence=sequence)
592 593 594@lru_cache(maxsize=_LRU_CACHE_SIZE) 595def __get_rule_option_positions( 596 rule: idstools.rule.Rule, name: str, sequence: Optional[tuple[str, ...]] = None 597) -> Sequence[int]: 598 provided_sequence = True 599 if sequence is None: 600 sequence = tuple(option["name"] for option in rule["options"]) 601 provided_sequence = False 602 603 positions = [] 604 for i, option in enumerate(sequence): 605 if option == name: 606 positions.append(i) 607 608 if not provided_sequence and len(positions) == 0 and is_rule_option_set(rule, name): 609 msg = f"Cannot determine position of {name} option since it is not part of the sequence of detection keywords." 610 _logger.critical(msg) 611 raise ValueError(msg) 612 613 return tuple(sorted(positions)) 614 615
[docs] 616def get_rule_option_position(rule: idstools.rule.Rule, name: str) -> Optional[int]: 617 """Finds the position of an option in the rule body. 618 619 Return None if the option is not set or set multiple times. 620 """ 621 return __get_rule_option_position(rule, name)
622 623 624@lru_cache(maxsize=_LRU_CACHE_SIZE) 625def __get_rule_option_position(rule: idstools.rule.Rule, name: str) -> Optional[int]: 626 positions = get_rule_option_positions(rule, name) 627 628 if len(positions) == 0: 629 _logger.debug( 630 "Cannot unambigously determine the position of the %s option since it it not set.", 631 name, 632 ) 633 return None 634 635 if len(positions) == 1: 636 return positions[0] 637 638 _logger.debug( 639 "Cannot unambigously determine the position of the %s option since it is set multiple times.", 640 name, 641 ) 642 return None 643 644
[docs] 645def is_rule_option_first(rule: idstools.rule.Rule, name: str) -> Optional[int]: 646 """Checks if a rule option is positioned at the beginning of the body.""" 647 position = get_rule_option_position(rule, name) 648 649 if position is None: 650 _logger.debug("Cannot unambiguously determine if option %s first.", name) 651 return None 652 653 if position == 0: 654 return True 655 656 return False
657 658
[docs] 659def is_rule_option_last(rule: idstools.rule.Rule, name: str) -> Optional[bool]: 660 """Checks if a rule option is positioned at the end of the body.""" 661 position = get_rule_option_position(rule, name) 662 663 if position is None: 664 _logger.debug("Cannot unambiguously determine if option %s last.", name) 665 return None 666 667 if position == len(rule["options"]) - 1: 668 return True 669 670 return False
671 672
[docs] 673def get_rule_options_positions( 674 rule: idstools.rule.Rule, 675 names: Iterable[str], 676 sequence: Optional[Iterable[str]] = None, 677) -> Iterable[int]: 678 """Finds the positions of several options in the rule body.""" 679 return __get_rule_options_positions( 680 rule, tuple(sorted(names)), sequence=tuple(sequence) if sequence else None 681 )
682 683 684@lru_cache(maxsize=_LRU_CACHE_SIZE) 685def __get_rule_options_positions( 686 rule: idstools.rule.Rule, 687 names: Iterable[str], 688 sequence: Optional[tuple[str, ...]] = None, 689) -> Iterable[int]: 690 positions = [] 691 692 for name in names: 693 positions.extend(get_rule_option_positions(rule, name, sequence=sequence)) 694 695 return tuple(sorted(positions)) 696 697
[docs] 698def is_rule_option_put_before( 699 rule: idstools.rule.Rule, 700 name: str, 701 other_names: Union[Sequence[str], set[str]], 702 sequence: Optional[Iterable[str]] = None, 703) -> Optional[bool]: 704 """Checks whether a rule option is placed before one or more other options.""" 705 return __is_rule_option_put_before( 706 rule, 707 name, 708 tuple(sorted(other_names)), 709 sequence=tuple(sequence) if sequence else None, 710 )
711 712 713@lru_cache(maxsize=_LRU_CACHE_SIZE) 714def __is_rule_option_put_before( 715 rule: idstools.rule.Rule, 716 name: str, 717 other_names: Union[Sequence[str], set[str]], 718 sequence: Optional[tuple[str, ...]] = None, 719) -> Optional[bool]: 720 if len(other_names) == 0: 721 _logger.debug( 722 "Cannot unambiguously determine if option %s is put before empty Iterable of other options.", 723 name, 724 ) 725 return None 726 727 positions = get_rule_option_positions(rule, name, sequence=sequence) 728 729 if name in other_names: 730 _logger.debug("Excluding name %s from other_names because of overlap.", name) 731 other_names = set(other_names).difference({name}) 732 733 other_positions = get_rule_options_positions(rule, other_names, sequence=sequence) 734 735 for other_position in other_positions: 736 for position in positions: 737 if position < other_position: 738 return True 739 return False 740 741
[docs] 742def is_rule_option_always_put_before( 743 rule: idstools.rule.Rule, 744 name: str, 745 other_names: Union[Sequence[str], set[str]], 746 sequence: Optional[Iterable[str]] = None, 747) -> Optional[bool]: 748 """Checks whether a rule option is placed before one or more other options.""" 749 return __is_rule_option_always_put_before( 750 rule, 751 name, 752 tuple(sorted(other_names)), 753 sequence=tuple(sequence) if sequence else None, 754 )
755 756 757@lru_cache(maxsize=_LRU_CACHE_SIZE) 758def __is_rule_option_always_put_before( 759 rule: idstools.rule.Rule, 760 name: str, 761 other_names: Union[Sequence[str], set[str]], 762 sequence: Optional[tuple[str, ...]] = None, 763) -> Optional[bool]: 764 if len(other_names) == 0: 765 _logger.debug( 766 "Cannot unambiguously determine if option %s is put before empty Iterable of other options.", 767 name, 768 ) 769 return None 770 771 positions = get_rule_option_positions(rule, name, sequence=sequence) 772 773 if name in other_names: 774 _logger.debug("Excluding name %s from other_names because of overlap.", name) 775 other_names = set(other_names).difference({name}) 776 777 other_positions = get_rule_options_positions(rule, other_names, sequence=sequence) 778 779 for other_position in other_positions: 780 for position in positions: 781 if position >= other_position: 782 return False 783 return True 784 785
[docs] 786def are_rule_options_put_before( 787 rule: idstools.rule.Rule, 788 names: Union[Sequence[str], set[str]], 789 other_names: Union[Sequence[str], set[str]], 790 sequence: Optional[Iterable[str]] = None, 791) -> Optional[bool]: 792 """Checks whether rule options are placed before one or more other options.""" 793 if len(other_names) == 0: 794 _logger.debug( 795 "Cannot unambiguously determine if an empty Iterable of options are put before other options %s.", 796 other_names, 797 ) 798 return None 799 if len(other_names) == 0: 800 _logger.debug( 801 "Cannot unambiguously determine if options %s are put before empty Iterable of other options.", 802 names, 803 ) 804 return None 805 806 for name in names: 807 if is_rule_option_put_before(rule, name, other_names, sequence=sequence): 808 return True 809 return False
810 811
[docs] 812def are_rule_options_always_put_before( 813 rule: idstools.rule.Rule, 814 names: Iterable[str], 815 other_names: Sequence[str], 816 sequence: Optional[Iterable[str]] = None, 817) -> Optional[bool]: 818 """Checks whether rule options are placed before one or more other options.""" 819 if len(other_names) == 0: 820 _logger.debug( 821 "Cannot unambiguously determine if an empty Iterable of options are put before other options %s.", 822 other_names, 823 ) 824 return None 825 if len(other_names) == 0: 826 _logger.debug( 827 "Cannot unambiguously determine if options %s are put before empty Iterable of other options.", 828 names, 829 ) 830 return None 831 832 for name in names: 833 if not is_rule_option_put_before(rule, name, other_names, sequence=sequence): 834 return False 835 return True
836 837
[docs] 838def select_rule_options_by_regex( 839 rule: idstools.rule.Rule, regex # noqa: ANN001 840) -> Iterable[str]: 841 """Selects rule options present in rule matching a regular expression.""" 842 return __select_rule_options_by_regex(rule, regex)
843 844 845@lru_cache(maxsize=_LRU_CACHE_SIZE) 846def __select_rule_options_by_regex( 847 rule: idstools.rule.Rule, regex # noqa: ANN001 848) -> Iterable[str]: 849 options = [] 850 851 for option in rule["options"]: 852 name = option["name"] 853 if _regex_provider.match(regex, name): 854 options.append(name) 855 856 return tuple(sorted(options)) 857 858
[docs] 859def get_rule_keyword_sequences( 860 rule: idstools.rule.Rule, 861 seperator_keywords: Iterable[str] = BUFFER_KEYWORDS, 862 included_keywords: Iterable[str] = ALL_DETECTION_KEYWORDS, 863) -> Sequence[tuple[str, ...]]: 864 """Returns a sequence of sequences of detection options in a rule.""" 865 sequences: list[list[str]] = [] 866 867 # Relies on the assumption that the order of options in the rule is preserved while parsing 868 sequence_i = -1 869 first_seperator_seen = False 870 for option in rule["options"]: 871 name = option["name"] 872 if name in seperator_keywords: 873 if not first_seperator_seen: 874 if len(sequences) > 0: 875 sequences[sequence_i].append(name) 876 else: 877 sequence_i += 1 878 sequences.append([name]) 879 else: 880 sequence_i += 1 881 sequences.append([name]) 882 first_seperator_seen = True 883 elif name in included_keywords and sequence_i == -1: 884 sequence_i += 1 885 sequences.append([name]) 886 elif name in included_keywords: 887 sequences[sequence_i].append(name) 888 889 if len(sequences) == 0: 890 _logger.debug( 891 "No sequences found separated by %s in rule %s", 892 seperator_keywords, 893 rule["raw"], 894 ) 895 return () 896 897 for sequence in sequences: 898 assert len(sequence) > 0 899 900 result = tuple(tuple(sequence) for sequence in sequences) 901 902 _logger.debug( 903 "Detected sequences %s separated by %s in rule %s", 904 result, 905 seperator_keywords, 906 rule["raw"], 907 ) 908 909 return result