Source code for suricata_check.checkers.styleguide._sid

  1"""`SidChecker`."""
  2
  3import logging
  4from collections.abc import Mapping, Sequence
  5from types import MappingProxyType
  6from typing import Optional
  7
  8from suricata_check.checkers.interface import CheckerInterface
  9from suricata_check.utils.checker import get_rule_option
 10from suricata_check.utils.checker_typing import ISSUES_TYPE, Issue
 11from suricata_check.utils.regex_provider import get_regex_provider
 12from suricata_check.utils.rule import Rule
 13
 14SID_ALLOCATION: Mapping[str, Sequence[tuple[int, int]]] = {
 15    "local": [(1000000, 1999999)],
 16    "ET OPEN": [
 17        (2000000, 2103999),
 18        (2400000, 2609999),
 19    ],
 20    "ET": [(2700000, 2799999)],
 21    "ETPRO": [(2800000, 2899999)],
 22}
 23
 24_regex_provider = get_regex_provider()
 25
 26_MSG_PREFIX_REGEX = _regex_provider.compile(r"^\"([A-Z0-9 ]*).*\"$")
 27
 28_logger = logging.getLogger(__name__)
 29
 30
[docs] 31class SidChecker(CheckerInterface): 32 """The `SidChecker` contains several checks based on the Suricata SID allocation. 33 34 Specifically, the `SidChecker` checks for the following: 35 S300: Allocation to reserved SID range, whereas no range is reserved for the rule. 36 37 S301: Allocation to unallocated SID range, whereas local range should be used. 38 39 S302: Allocation to wrong reserved SID range, whereas another reserved range should be used. 40 41 S303: Allocation to unallocated SID range, whereas a reserved range should be used. 42 """ 43 44 codes = MappingProxyType( 45 { 46 "S300": {"severity": logging.INFO}, 47 "S301": {"severity": logging.INFO}, 48 "S302": {"severity": logging.INFO}, 49 "S303": {"severity": logging.INFO}, 50 }, 51 ) 52 53 def _check_rule( 54 self: "SidChecker", 55 rule: Rule, 56 ) -> ISSUES_TYPE: 57 issues: ISSUES_TYPE = [] 58 59 sid = get_rule_option(rule, "sid") 60 msg = get_rule_option(rule, "msg") 61 62 if sid is not None and msg is not None: 63 sid = int(sid) 64 range_name = self.__get_range_name(sid, SID_ALLOCATION) 65 prefix = self.__get_msg_prefix(msg) 66 67 if ( 68 prefix not in SID_ALLOCATION.keys() 69 and range_name is not None 70 and range_name != "local" 71 ): 72 issues.append( 73 Issue( 74 code="S300", 75 message=f"""\ 76Allocation to reserved SID range, whereas no range is reserved for the rule. 77Consider using an sid in one of the following ranges: {SID_ALLOCATION["local"]}.\ 78""", 79 ), 80 ) 81 82 if prefix not in SID_ALLOCATION.keys() and range_name is None: 83 issues.append( 84 Issue( 85 code="S301", 86 message=f"""\ 87Allocation to unallocated SID range, whereas local range should be used. 88Consider using an sid in one of the following ranges: {SID_ALLOCATION["local"]}.\ 89""", 90 ), 91 ) 92 93 if prefix in SID_ALLOCATION.keys() and ( 94 range_name is not None 95 and not (prefix + " ").startswith(range_name + " ") 96 and not (range_name + " ").startswith(prefix + " ") 97 ): 98 issues.append( 99 Issue( 100 code="S302", 101 message=f"""\ 102Allocation to wrong reserved SID range, whereas another reserved range should be used. 103Consider using an sid in one of the following ranges: {SID_ALLOCATION[prefix]}.\ 104""", 105 ), 106 ) 107 108 if prefix in SID_ALLOCATION.keys() and range_name is None: 109 issues.append( 110 Issue( 111 code="S303", 112 message=f"""\ 113Allocation to unallocated SID range, whereas a reserved range should be used. 114Consider using an sid in one of the following ranges: {SID_ALLOCATION[prefix]}.\ 115""", 116 ), 117 ) 118 119 return issues 120 121 @staticmethod 122 def __in_range(sid: int, sid_range: Sequence[tuple[int, int]]) -> bool: 123 for start, end in sid_range: 124 if start <= sid <= end: 125 return True 126 127 return False 128 129 @staticmethod 130 def __get_range_name( 131 sid: int, 132 ranges: Mapping[str, Sequence[tuple[int, int]]], 133 ) -> Optional[str]: 134 for range_name, sid_range in ranges.items(): 135 for start, end in sid_range: 136 if start <= sid <= end: 137 _logger.debug("Detected sid from range: %s", range_name) 138 return range_name 139 return None 140 141 @staticmethod 142 def __get_msg_prefix(msg: str) -> str: 143 match = _MSG_PREFIX_REGEX.match(msg) 144 assert match is not None 145 146 parts = match.group(1).strip().split(" ") 147 prefix: str = "" 148 for i in list(reversed(range(len(parts)))): 149 prefix = " ".join(parts[: i + 1]) 150 if prefix in SID_ALLOCATION.keys() or " " not in prefix: 151 break 152 153 _logger.debug("Detected prefix: %s", prefix) 154 155 return prefix