From 891b17277e9467efea2ecbc02124a40beeebee00 Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Fri, 1 May 2026 13:40:20 +0200 Subject: [PATCH] ci: check dist rules So that we catch if we add a typo like app-layer-event:snmp.version_mismatchZZZ; --- .github/workflows/builds.yml | 1 + scripts/check-dist-rules.py | 106 +++++++++++++++++++++++++++++++++ scripts/docrules/docrules.yaml | 8 +++ 3 files changed, 115 insertions(+) create mode 100644 scripts/check-dist-rules.py diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 082429bd72..8b2fd2c917 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -3064,6 +3064,7 @@ jobs: - run: make distcheck env: DISTCHECK_CONFIGURE_FLAGS: "--enable-unittests --enable-debug --enable-geoip --enable-profiling --enable-profiling-locks --enable-dpdk --enable-ebpf --enable-ebpf-build" + - run: python3 scripts/check-dist-rules.py - run: test -e doc/userguide/suricata.1 - run: test -e doc/userguide/userguide.pdf - name: Building Rust documentation diff --git a/scripts/check-dist-rules.py b/scripts/check-dist-rules.py new file mode 100644 index 0000000000..ebb6b2f075 --- /dev/null +++ b/scripts/check-dist-rules.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +""" +Test Suricata rules in rules subdirectory +""" + +from __future__ import annotations + +import argparse +import shutil +import subprocess +import tempfile +from pathlib import Path + + +def resolve_suricata_bin(repo_root: Path, configured: Optional[str]) -> Path: + if configured: + return Path(configured) + + in_path = shutil.which("suricata") + if in_path: + return Path(in_path) + + candidates = [repo_root / "src" / "suricata", repo_root / "suricata"] + for candidate in candidates: + if candidate.exists(): + return candidate + + raise SystemExit( + "Unable to find Suricata binary. Use --suricata-bin to provide it." + ) + + +def check_rule_with_suricata( + rule_file: str, + suricata_bin: Path, + suricata_yaml: Path, +) -> Tuple[bool, str]: + with tempfile.TemporaryDirectory(prefix="dist-rule-check-") as tmpdir: + cmd = [ + str(suricata_bin), + "-T", + "-c", str(suricata_yaml), + "-S", str(rule_file), + '--strict-rule-keywords=all', + "-l", tmpdir, + ] + proc = subprocess.run( + cmd, + check=False, + capture_output=True, + text=True, + ) + + combined = proc.stderr.strip() + return proc.returncode == 0, combined + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Check Suricata rules from doc RST example-rule containers." + ) + parser.add_argument( + "--suricata-bin", + default=None, + help="Path to Suricata binary (default: auto-detect)", + ) + parser.add_argument( + "--suricata-yaml", + default=None, + help="Path to suricata.yaml (default: /suricata.yaml)", + ) + args = parser.parse_args() + + repo_root = Path(__file__).resolve().parents[1] + suricata_bin = resolve_suricata_bin(repo_root, args.suricata_bin) + suricata_yaml = ( + Path(args.suricata_yaml) + if args.suricata_yaml + else (repo_root / "scripts" / "docrules" / "docrules.yaml") + ) + if not suricata_yaml.exists(): + raise SystemExit( + f"suricata.yaml not found: {suricata_yaml}. Use --suricata-yaml." + ) + + for rule_file in sorted(Path(repo_root / "rules").rglob("*.rules")): + is_valid, output_text = check_rule_with_suricata( + rule_file, + suricata_bin, + suricata_yaml, + ) + if not is_valid: + print( + ( + f"Invalid rule in #{rule_file}\n" + f"Suricata stderr:\n{output_text}\n" + ), + end="\n", + ) + return 1 + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/docrules/docrules.yaml b/scripts/docrules/docrules.yaml index c83be5cbcb..32e69e57ec 100644 --- a/scripts/docrules/docrules.yaml +++ b/scripts/docrules/docrules.yaml @@ -21,8 +21,16 @@ datasets: app-layer: protocols: + dnp3: + enabled: yes + detection-ports: + dp: 20000 pgsql: enabled: yes + enip: + enabled: yes + modbus: + enabled: yes reputation-categories-file: scripts/docrules/docrulesrep.lst reputation-files: