I was looking to add support for Sigma rules in the otlp interface and noticed that
it uses the cosl library, especially the Rules class for rules normalization to a uniform
format, juju topology injection, and validation. Right now, the Rules class is tightly coupled to
Prometheus and Loki and addition of Sigma rules is not straight-forward.
Analysis of the rule format differences
Before I discuss how to restructure the Rules class, here are the key structural differences
between Prometheus / Loki and Sigma rule formats:
| Aspect |
Prometheus / Loki |
Sigma |
| Grouping |
Rules must live inside groups |
Each rule is independent, no concept of grouping |
| Identify |
Group name + alert / record name |
UUID |
| Detection logic |
expr field that has PromQL/ LogQL queries |
detection block with selection and condition |
| Data source |
Implicit (the scrape target) |
logsource field (product / category / field) |
| Validation |
Valdiated using cos-tool |
Validated using pySigma library |
| Classification of Rules |
Rules can be either RecordingRule or AlertingRule |
There is only one format |
Analysis of the current state of Rules class
The Rules class has the following public interfaces:
add: Add rules from dict to the existing ruleset.
add_path: Add rules from a directory.
inject_and_validate_rules: Inject Juju topology labels and validate rules using CosTool.
as_dict: Return standard rules file in dict representation (used to dump rules into the databag).
Internally, the class does the following:
- Ingest rules from multiple sources (either
add_path / add)
- Normalize the rules to the "official" format (the rules must have the
groups key)
- Injects Juju topology in:
- The group name (Prepends topology identifier + relative path to the group name, appends
_rules suffix)
- Labels
- Expressions (modifies
expr to contain the juju topology)
- Validates the syntax using
CosTool.
Questions
- I noticed that the Juju topology injection happens both when
add/add_path and
inject_and_validate_rules is called. The former is called on the requirer side while the latter is
used by the provider by injecting the same Juju Topology it receives from the requirer via databag.
Is there a need to do it both times? It seems a bit redundant to me.
- Do you see any value in combining
add and add_path into a single interface or keep it seperate?
Proposal
I propose the following new structure for Rules - a generic Rules and RuleBackend class.
Rules class
add(rule_dict, source=None): Parses a rule dict via backend.from_dict (with the instance's
topology) and appends the results to the internal _items: List[T].
add_path(dir_path, *, recursive=False): Reads rule files from a directory (or single file),
builds a RuleSourceInfo from the user, then delegates each file to backend.from_dict.
as_dict(): Returns the accumulated items in the backend's output format by calling
backend.as_dict(self._items).
inject_and_validate(rules, metadata): Reconstructs topology from the metadata dict, runs
backend.from_dict for injection, then backend.validate for checking. Returns an InjectResult.
RuleBackend class
file_suffixes (property): Returns the file extensions this backend handles (e.g.
[".rule", ".yml"] for Prometheus, [".yml", ".yaml"] for Sigma). Used by Rules._from_dir to filter files.
from_dict(rule_dict, *, source, topology): Takes a raw YAML-loaded dict, normalizes it into
the backend's internal type T, and injects Juju topology. Returns List[T].
validate(rules): Takes the serialized output and checks it for correctness. Prometheus delegates to cos-tool; Sigma checks for required fields.
as_dict(items): Converts List[T] into the backend's output format. Prometheus produces {"groups": [...]}, Sigma produces {"rules": [...]}.
Pros and Cons
Pros:
- Extensibility: Adding a new rule format means writing a new
RuleBackend subclass.
- Type safety:
Rules[T] ensures that what from_dict produces is what as_dict consumes.
Cons:
- This design also injects topology on both
add methods as well as the inject_and_validate
method. But this can just as easily be removed if we don't see any use case to have it both sides.
- What i don't like is the
RuleSourceInfo class leaking into the Sigma backend class as well.
Currently the public method add exposes group_name and group_name_prefix which are customizable values
specific to only the Prometheus/Loki backend. Groups are not a concept in Sigma rules. The RuleBackend template
forces the from_dict method to consume it. Even though the SigmaRuleBackend doesn't have to use it in the method, it will remain in the function definition.
Happy to discuss this further and implement it.
I was looking to add support for Sigma rules in the
otlpinterface and noticed thatit uses the
cosllibrary, especially theRulesclass for rules normalization to a uniformformat, juju topology injection, and validation. Right now, the
Rulesclass is tightly coupled toPrometheus and Loki and addition of Sigma rules is not straight-forward.
Analysis of the rule format differences
Before I discuss how to restructure the
Rulesclass, here are the key structural differencesbetween Prometheus / Loki and Sigma rule formats:
groupsexprfield that hasPromQL/LogQLqueriesdetectionblock withselectionandconditionlogsourcefield (product / category / field)cos-toolpySigmalibraryAnalysis of the current state of Rules class
The
Rulesclass has the following public interfaces:add: Add rules from dict to the existing ruleset.add_path: Add rules from a directory.inject_and_validate_rules: Inject Juju topology labels and validate rules using CosTool.as_dict: Return standard rules file in dict representation (used to dump rules into the databag).Internally, the class does the following:
add_path/add)groupskey)_rulessuffix)exprto contain the juju topology)CosTool.Questions
add/add_pathandinject_and_validate_rulesis called. The former is called on the requirer side while the latter isused by the provider by injecting the same Juju Topology it receives from the requirer via databag.
Is there a need to do it both times? It seems a bit redundant to me.
addandadd_pathinto a single interface or keep it seperate?Proposal
I propose the following new structure for Rules - a generic
RulesandRuleBackendclass.Rules class
add(rule_dict, source=None): Parses a rule dict via backend.from_dict (with the instance'stopology) and appends the results to the internal _items: List[T].
add_path(dir_path, *, recursive=False): Reads rule files from a directory (or single file),builds a
RuleSourceInfofrom the user, then delegates each file tobackend.from_dict.as_dict(): Returns the accumulated items in the backend's output format by callingbackend.as_dict(self._items).inject_and_validate(rules, metadata): Reconstructs topology from the metadata dict, runsbackend.from_dictfor injection, thenbackend.validatefor checking. Returns anInjectResult.RuleBackend class
file_suffixes(property): Returns the file extensions this backend handles (e.g.[".rule", ".yml"] for Prometheus, [".yml", ".yaml"] for Sigma). Used by
Rules._from_dirto filter files.from_dict(rule_dict, *, source, topology): Takes a raw YAML-loaded dict, normalizes it intothe backend's internal type T, and injects Juju topology. Returns
List[T].validate(rules): Takes the serialized output and checks it for correctness. Prometheus delegates to cos-tool; Sigma checks for required fields.as_dict(items): Converts List[T] into the backend's output format. Prometheus produces{"groups": [...]}, Sigma produces{"rules": [...]}.Pros and Cons
Pros:
RuleBackendsubclass.Rules[T]ensures that whatfrom_dictproduces is whatas_dictconsumes.Cons:
addmethods as well as theinject_and_validatemethod. But this can just as easily be removed if we don't see any use case to have it both sides.
RuleSourceInfoclass leaking into the Sigma backend class as well.Currently the public method
addexposesgroup_nameandgroup_name_prefixwhich are customizable valuesspecific to only the Prometheus/Loki backend. Groups are not a concept in Sigma rules. The
RuleBackendtemplateforces the
from_dictmethod to consume it. Even though theSigmaRuleBackenddoesn't have to use it in the method, it will remain in the function definition.Happy to discuss this further and implement it.