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