Source code for suricata_check.utils.checker_typing

  1"""The `suricata_check.typing` module contains all types used by the `suricata-check` package."""
  2
  3import json
  4from collections.abc import Iterable, MutableMapping, MutableSequence
  5from dataclasses import dataclass, field
  6from typing import (
  7    Optional,
  8    TypeVar,
  9)
 10
 11import idstools.rule
 12
 13
[docs] 14class InvalidRuleError(RuntimeError): 15 """Raised when an invalid rule is detected. 16 17 Note that some rules may be invalid due to not following the Suricata rule syntax. 18 Rules following the syntax, but considered invalid by Suricata due to missing options need not raise this error. 19 Rules for which this error is not raised are not neccessarily syntactically correct but can be processed by suricata-check. 20 """ 21 22 def __init__(self: "InvalidRuleError", message: str) -> None: 23 """Initializes the `InvalidRuleError` with the raw rule as message.""" 24 super().__init__(message)
25 26
[docs] 27@dataclass 28class Issue: 29 """The `Issue` dataclass represents a single issue found in a rule.""" 30 31 code: str 32 message: str 33 severity: Optional[int] = None 34 checker: Optional[str] = None 35
[docs] 36 def to_dict(self: "Issue") -> dict[str, str]: 37 """Returns the Issue represented as a dictionary.""" 38 d = { 39 "code": self.code, 40 "message": self.message, 41 } 42 43 if self.checker is not None: 44 d["checker"] = self.checker 45 46 return d
47 48 @property 49 def hash(self: "Issue") -> int: 50 """Returns a unique hash that can be used as a fingerprint for the issue.""" 51 return hash(tuple(sorted(self.to_dict().items()))) 52
[docs] 53 def __repr__(self: "Issue") -> str: 54 """Returns the Issue represented as a string.""" 55 return json.dumps(self.to_dict())
56 57 58ISSUES_TYPE = MutableSequence[Issue] 59SIMPLE_SUMMARY_TYPE = MutableMapping[str, int] 60RULE_SUMMARY_TYPE = SIMPLE_SUMMARY_TYPE 61EXTENSIVE_SUMMARY_TYPE = MutableMapping[str, SIMPLE_SUMMARY_TYPE] 62 63Cls = TypeVar("Cls") 64 65
[docs] 66def get_all_subclasses(cls: type[Cls]) -> Iterable[type[Cls]]: 67 """Returns all class types that subclass the provided type.""" 68 all_subclasses = [] 69 70 for subclass in cls.__subclasses__(): 71 all_subclasses.append(subclass) 72 all_subclasses.extend(get_all_subclasses(subclass)) 73 74 return all_subclasses
75 76
[docs] 77@dataclass 78class RuleReport: 79 """The `RuleReport` dataclass represents a rule, together with information on its location and detected issues.""" 80 81 rule: idstools.rule.Rule 82 summary: Optional[RULE_SUMMARY_TYPE] = None 83 line_begin: Optional[int] = None 84 line_end: Optional[int] = None 85 86 _issues: ISSUES_TYPE = field(default_factory=list, init=False) 87 88 @property 89 def issues(self: "RuleReport") -> ISSUES_TYPE: 90 """List of issues found in the rule.""" 91 return self._issues 92
[docs] 93 def add_issue(self: "RuleReport", issue: Issue) -> None: 94 """Adds an issue to the report.""" 95 self._issues.append(issue)
96
[docs] 97 def add_issues(self: "RuleReport", issues: ISSUES_TYPE) -> None: 98 """Adds an issue to the report.""" 99 for issue in issues: 100 self._issues.append(issue)
101
[docs] 102 def to_dict(self: "RuleReport") -> dict[str, str]: 103 """Returns the RuleReport represented as a dictionary.""" 104 d = { 105 "rule": self.rule["raw"], 106 "issues": [issue.to_dict() for issue in self.issues], 107 } 108 109 if self.summary is not None: 110 d["summary"] = self.summary 111 112 if self.line_begin is not None or self.line_end is not None: 113 d["lines"] = {} 114 115 if self.line_begin is not None: 116 d["lines"]["begin"] = self.line_begin 117 118 if self.line_begin is not None: 119 d["lines"]["end"] = self.line_end 120 121 return d
122
[docs] 123 def __repr__(self: "RuleReport") -> str: 124 """Returns the RuleReport represented as a string.""" 125 return json.dumps(self.to_dict())
126 127 128RULE_REPORTS_TYPE = MutableSequence[RuleReport] 129 130
[docs] 131@dataclass 132class OutputSummary: 133 """The `OutputSummary` dataclass represent a collection of summaries on the output of `suricata_check`.""" 134 135 overall_summary: SIMPLE_SUMMARY_TYPE 136 issues_by_group: SIMPLE_SUMMARY_TYPE 137 issues_by_type: EXTENSIVE_SUMMARY_TYPE
138 139
[docs] 140@dataclass 141class OutputReport: 142 """The `OutputSummary` dataclass represent the `suricata_check`, consisting of rule reports and summaries.""" 143 144 _rules: RULE_REPORTS_TYPE = field(default_factory=list, init=False) 145 summary: Optional[OutputSummary] = None 146 147 def __init__( 148 self: "OutputReport", 149 rules: RULE_REPORTS_TYPE = [], 150 summary: Optional[OutputSummary] = None, 151 ) -> None: 152 """Initialized the `OutputReport`, optionally with a list of rules and/or a summary.""" 153 self._rules = [] 154 for rule in rules: 155 self.add_rule(rule) 156 self.summary = summary 157 super().__init__() 158 159 @property 160 def rules(self: "OutputReport") -> RULE_REPORTS_TYPE: 161 """List of rules contained in the report.""" 162 return self._rules 163
[docs] 164 def add_rule(self: "OutputReport", rule_report: RuleReport) -> None: 165 """Adds an rule to the report.""" 166 self._rules.append(rule_report)