Source code for suricata_check.checkers.interface._checker

  1"""The `suricata_check.checkers.interface.checker` module contains the `CheckerInterface`.
  2
  3Implementation of the `CheckerInterface` is neccessary for checker auto-discovery.
  4"""
  5
  6import abc
  7import logging
  8from collections.abc import Iterable, Mapping
  9from typing import Optional
 10
 11from suricata_check.utils.checker import get_rule_option, is_rule_option_set
 12from suricata_check.utils.checker_typing import ISSUES_TYPE
 13from suricata_check.utils.rule import Rule
 14
 15_logger = logging.getLogger(__name__)
 16
 17
[docs] 18class CheckerInterface: 19 """Interface for rule checkers returning a list of issues. 20 21 These checkers are automatically discovered through `suricata_check.get_checkers()`. 22 23 Each code should start with an upper case letter (may be multiple), followed by three digits. 24 In other words, each code should follow the following regex `[A-Z]{1,}[0-9]{3}` 25 26 We recommend using a letter to indicate the category of the issue, such as described in `README.md`. 27 Moreover, we suggest to reserve certain ranges of numbers for each checker. 28 29 """ 30 31 codes: Mapping[str, Mapping[str, int]] 32 """A Mapping of issue codes emitted by the checker to metadata for those issue types. 33 The metadata is structured in the form of a Mapping from attribute name to attribute value. 34 The one mandatory metadata attribute is severity, which must be one of the levels provided by the `logging` module""" 35 36 enabled_by_default: bool = True 37 """A boolean indicating if the checker is enabled by default when discovered automatically.""" 38 39 def __init__( 40 self: "CheckerInterface", 41 include: Optional[Iterable[str]] = None, 42 ) -> None: 43 """Initializes the checker given a list of issue codes to emit.""" 44 if include is None: 45 include = self.codes 46 _logger.debug("%s enabled with codes: %s", type(self).__name__, include) 47 self.include = include 48 49 super().__init__() 50
[docs] 51 def check_rule( 52 self: "CheckerInterface", 53 rule: Rule, 54 ) -> ISSUES_TYPE: 55 """Checks a rule and returns a list of issues found.""" 56 self.__log_rule_processing(rule) 57 return self.__add_checker_metadata( 58 self.__add_issue_metadata(self.__filter_issues(self._check_rule(rule))), 59 )
60 61 @abc.abstractmethod 62 def _check_rule( 63 self: "CheckerInterface", 64 rule: Rule, 65 ) -> ISSUES_TYPE: 66 """Checks a rule and returns a list of issues found.""" 67 68 def __log_rule_processing( 69 self: "CheckerInterface", 70 rule: Rule, 71 ) -> None: 72 sid: Optional[int] = None 73 if is_rule_option_set(rule, "sid"): 74 sid_str = get_rule_option(rule, "sid") 75 assert sid_str is not None 76 sid = int(sid_str) 77 78 _logger.debug("Running %s on rule %s", self.__class__.__name__, sid) 79 80 def __add_issue_metadata( 81 self: "CheckerInterface", 82 issues: ISSUES_TYPE, 83 ) -> ISSUES_TYPE: 84 """Given a list of issues, return the same list with metadata from the issue types.""" 85 for issue in issues: 86 metadata = self.codes[issue.code] 87 if "severity" in metadata: 88 issue.severity = metadata["severity"] 89 90 return issues 91 92 def __add_checker_metadata( 93 self: "CheckerInterface", 94 issues: ISSUES_TYPE, 95 ) -> ISSUES_TYPE: 96 """Given a list of issues, return the same list with metadata from the checker.""" 97 name = self.__class__.__name__ 98 99 for issue in issues: 100 issue.checker = name 101 102 return issues 103 104 def __filter_issues( 105 self: "CheckerInterface", 106 issues: ISSUES_TYPE, 107 ) -> ISSUES_TYPE: 108 """Given a list of issues, return the same list having filtered out disabled issue types.""" 109 filtered_issues = [] 110 111 for issue in issues: 112 if issue.code in self.include: 113 filtered_issues.append(issue) 114 elif issue.code not in self.codes: 115 _logger.warning( 116 "Issue with filtered code %s not found in checker %s", 117 issue.code, 118 self.__class__.__name__, 119 ) 120 121 return filtered_issues