--- myst: html_meta: "description lang=en": "suricata-check is a command line utility to provide feedback on Suricata rules to by detecting issues through static analysis." "keywords": "Suricata, suricata-check, Extend, Checker" --- # Writing checkers ## CheckerInterface In order to write a new checker, you must extend the `suricata_check.checkers.interface.CheckerInterface` and implement the `_check_rule` function, which takes a rule (`idstools.rule.Rule`) as input and returns a collection of issues (`suricata.check.typing.IssuesType`). The most minimal checker, looks as follows: ```python import idstools.rule from suricata_check.checkers.interface import CheckerInterface from suricata_check.utils.typing import ISSUES_TYPE class ExampleChecker(CheckerInterface): codes = dict() def _check_rule( self: "ExampleChecker", rule: idstools.rule.Rule, ) -> ISSUES_TYPE: issues: ISSUES_TYPE = [] return issues ``` ## Detecting issues To detect issues, you can use utility functions provided in `suricata_check.utils.checker`. A lot of utility functions exist, and you are encouraged to check out the [Checker API Reference](https://suricata-check.teuwen.net/autoapi/suricata_check/utils/checker/index.html) for a complete overview. For example, it contains utility functions to check whether a Suricata option is (not) set, and enables asserting that atleast one or all option values are (not) equal to a certain value or regular expression. All you have to do to add new issue types is, to add the desired issue code (e.g. `E000`) to the `codes` field of the class, and append a new `Issue` to the list of `issues` that is returned at the end of `_check_rule` depending on the output of the utlity function called from `suricata_check.utils.checker`. For example, we can add two new issue types as follows: ```python import logging import idstools.rule from suricata_check.checkers.interface import CheckerInterface from suricata_check.utils import checker from suricata_check.utils.typing import ISSUES_TYPE, Issue class ExampleChecker(CheckerInterface): codes = { "E000": {"severity": logging.INFO}, "E001": {"severity": logging.INFO}, } def _check_rule( self: "ExampleChecker", rule: idstools.rule.Rule, ) -> ISSUES_TYPE: issues: ISSUES_TYPE = [] if checker.is_rule_option_set(rule, "msg"): issues.append( Issue( code="E000", message="This rule sets the `msg` field!", ) ) if checker.is_rule_option_equal_to(rule, "sid", "1234"): issues.append( Issue( code="E001", message="This rule has sid `1234`, which seems temporary.\nDo not forget to change it to an actual sid!", ) ) return issues ``` ## Using custom checkers In order to use your newly written checker, you should either install the package as an extension and install it as described in [Releasing checkers as a package](#releasing-checkers-as-a-package) or you need to make use of the API and import your checker as documented in [API Usage](./api_usage.md#selecting-checkers). ## Testing checkers To make testing of checkers easier, we have provided the `suricata_check.tests.GenericChecker` class that one can inherit in a test suite to write a new checker. The minimal change required is that `__run_around_tests` si implemented to set the `checker` field of the `GenericChecker` class to an instance of the checker being tested. ```python import logging import os import sys import pytest from suricata_check.tests import GenericChecker sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) import suricata_check_extension_example class TestExample(GenericChecker): @pytest.fixture(autouse=True) def __run_around_tests(self): logging.basicConfig(level=logging.DEBUG) self.checker = suricata_check_extension_example.checkers.ExampleChecker() def __main__(): pytest.main() ``` Out of the box, no rules are actually tested but the structure of the codes provided in the `codes` field of the checker are tested. ### Asserting expected issues for rules Usually, it is desirable to have atleast two tests for each issue type, i.e. one rule for which the issue is present and one rule for which it is not. To write a test, create an `idstools.rule.Rule` object by using `idstools.rule.parse` and pass this rule object to `self._test_issue` while also providing the issue code to check for and a boolean to indicate whether the issue should (not) be raised. The `GenericChecker._test_issue` function will under the hood perform various assertions, in addition to whether the issue is raised or not such as checking whether any undocumented issue codes are emitted, and whether the raised issue has the required metadata to describe the checker that raised the code. For example, to write tests for the two issues we created earlier, we can use the following code: ```python import logging import os import sys import idstools.rule import pytest from suricata_check.tests import GenericChecker sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) import suricata_check_extension_example class TestExample(GenericChecker): @pytest.fixture(autouse=True) def __run_around_tests(self): logging.basicConfig(level=logging.DEBUG) self.checker = suricata_check_extension_example.checkers.ExampleChecker() def test_e000_bad(self): rule = idstools.rule.parse( """alert ip any any -> any any (msg:"Test"; sid:1;)""", ) self._test_issue(rule, "E000", True) def test_e000_good(self): rule = idstools.rule.parse( """alert ip any any -> any any (sid:1;)""", ) self._test_issue(rule, "E000", False) def test_e001_bad(self): rule = idstools.rule.parse( """alert ip any any -> any any (msg:"Test"; sid:1234;)""", ) self._test_issue(rule, "E001", True) def test_e001_good(self): rule = idstools.rule.parse( """alert ip any any -> any any (msg:"Test"; sid:20101234;)""", ) self._test_issue(rule, "E001", False) def __main__(): pytest.main() ``` ## Releasing checkers as a package It is possible to extend `suricata-check` with additional checkers and release them in a seperate package. An example of such an extension is given in the [suricata-check-extension-example](https://github.com/Koen1999/suricata-check-extension-example) project. Note that for extensions to be automatically discovered by the CLI, their module name should begin with `suricata_check_`, they should expose `suricata_check_extension.__version__`, and their checkers should implement the `CheckerInterface`.