Rule Detector
The Rule Detector raises alerts based on a configurable set of rules.
In/out
Input and output schemas in the pipeline
| Schema | Description | |
|---|---|---|
| Input | ParserSchema | Structured log |
| Output | DetectorSchema | Alert / finding |
At a glance
| Learns from training data | Auto-configuration | Needs events |
Federation |
|---|---|---|---|
| ❌ | ❌ | ❌ | ❌ |
Description
The detector analyzes parsed logs one by one and checks which rules are triggered. When alerts are produced, the triggered rules and their messages are recorded in the alertDetails field of the output schema. The score field contains the number of rules that triggered.
Available rules
| Rule name | Description | Requires arguments | Enabled by default |
|---|---|---|---|
| R001 - TemplateNotFound | Check whether the parser assigned a template to the log | No | Yes |
| R002 - SpecificKeyword | Check for one or more user-specified keywords in the log content | list of words | No |
| R003 - CheckForExceptions | Check for words commonly associated with exceptions or failures | No | Yes |
| R004 - ErrorLevelFound | If a Level field exists, check whether it indicates an error level | No | Yes |
Notes on table columns:
- Rule name: Identifier used in configuration.
- Description: What the rule checks.
- Requires arguments: Whether the rule needs additional arguments.
- Enabled by default: Whether the rule is active when not explicitly overridden.
Example
import yaml
from detectmatelibrary import schemas
from detectmatelibrary.detectors.rule_detector import RuleDetector
with open("docs/examples/detectors/rule_based.yaml") as f:
config = yaml.safe_load(f)
detector = RuleDetector(name="RuleDetector", config=config)
def parsed(event_id: int, log: str, level: str = "INFO") -> schemas.ParserSchema:
"""A parsed log; EventID -1 means the parser found no matching template."""
return schemas.ParserSchema({"eventID": event_id, "log": log, "headerVariables": {"Level": level}})
logs = [
parsed(0, "Accepted password for alice from 10.0.0.1 port 22"),
parsed(-1, "kernel: unexpected garbled line"),
parsed(3, "alice : TTY=pts/0 ; COMMAND=/usr/bin/sudo su"),
parsed(1, "Failed password for root from 10.0.0.9 port 22", level="ERROR"),
]
for log in logs:
alert = detector.process(log)
print(dict(alert["alertDetails"]) if alert else None)
# None
# {'R001 - TemplateNotFound': 'No template found by parser'}
# {'R002 - SpecificKeyword': "Found word 'sudo' in the logs"}
# {'R004 - ErrorLevelFound': 'Error found'}
Configuration file
The configuration used by the example above. It sets only what this use case needs; every other parameter keeps its default (see Configuration arguments).
detectors:
RuleDetector:
method_type: rule_detector
params:
rules: # no training: every rule is checked on every log
- rule: R001 - TemplateNotFound
- rule: R002 - SpecificKeyword
args: [sudo, passwd] # words to look for in the raw log
- rule: R004 - ErrorLevelFound
The same file works unchanged in both places a detector runs:
- Library: load it with
yaml.safe_loadand pass the dict asconfig=, as in the example. The key underdetectors:must match the detector'sname. - DetectMateService: use it as the service's detector configuration.
Configuration arguments
All parameters this detector accepts, grouped by the YAML block they go in. Scope tells whether a parameter is specific to this detector or shared with other detectors (see the Detectors overview). Where this detector changes a shared default, the shared value is shown in brackets.
Top level
| Field | Type | Default | Scope | Description |
|---|---|---|---|---|
method_type |
string | rule_detector | shared | Indicates what type of method it is. |
auto_config |
boolean | True | shared | Runs the configuration step before the training process. Has no effect unless data_use_configure is set. |
events |
object | {} | shared | Events configuration dict keyed by event_id. |
global |
object | {} | shared | Instances monitoring event-independent header variables (e.g. hostname, level), keyed by instance name. Written as global in YAML. |
params
| Field | Type | Default | Scope | Description |
|---|---|---|---|---|
rules |
array | [{'rule': 'R001 - TemplateNotFound'}, {'rule': 'R003 - CheckForExceptions'}, {'rule': 'R004 - ErrorLevelFound'}] | specific | List of rules to evaluate, each a dict with a 'rule' name and an optional 'args' list. |
start_id |
integer | 10 | shared | Number used to start the unique ID generator. |
data_use_training |
integer, null | None | shared | Number of logs used for training. None skips training. |
data_use_configure |
integer, null | None | shared | Number of logs used for the configuration step. None skips it. |
use_config_data_as_training |
boolean | True | shared | Combine the configured data in the training process if True. |
train_buffer_max_records |
integer | 100000 | shared | Configure records kept in memory for training (use_config_data_as_training) before the buffer spills to Parquet files on disk, in parts of this many records. |
train_buffer_dir |
string, null | None | shared | Local directory for the spilled training buffer. None uses the system temp directory (TMPDIR). Each spill goes to a private detectmate-train-* directory, removed after training reads it; a killed process leaves it behind. |
parser |
string | PARSER | shared | Name of the parser used. |