Add support for threshold rules (#65)

This commit is contained in:
Justin Ibarra
2020-07-16 19:06:34 -05:00
committed by GitHub
parent f1b669e59d
commit 7647699e2b
3 changed files with 34 additions and 10 deletions
+3 -2
View File
@@ -18,8 +18,9 @@ from eql import load_dump
from .misc import PYTHON_LICENSE, nested_set
from . import rule_loader
from .packaging import PACKAGE_FILE, Package, manage_versions, RELEASE_DIR
from .rule import RULE_TYPE_OPTIONS, Rule
from .rule import Rule
from .rule_formatter import toml_write
from .schema import RULE_TYPES
from .utils import get_path, clear_caches
@@ -35,7 +36,7 @@ def root():
@click.argument('path', type=click.Path(dir_okay=False))
@click.option('--config', '-c', type=click.Path(exists=True, dir_okay=False), help='Rule or config file')
@click.option('--required-only', is_flag=True, help='Only prompt for required fields')
@click.option('--rule-type', '-t', type=click.Choice(RULE_TYPE_OPTIONS), help='Type of rule to create')
@click.option('--rule-type', '-t', type=click.Choice(RULE_TYPES), help='Type of rule to create')
def create_rule(path, config, required_only, rule_type):
"""Create a detection rule."""
config = load_dump(config) if config else {}
+8 -4
View File
@@ -14,12 +14,11 @@ import kql
from . import ecs, beats
from .attack import TACTICS, build_threat_map_entry, technique_lookup
from .rule_formatter import nested_normalize, toml_write
from .schema import metadata_schema, schema_validate, get_schema
from .schema import RULE_TYPES, metadata_schema, schema_validate, get_schema
from .utils import get_path, clear_caches, cached
RULES_DIR = get_path("rules")
RULE_TYPE_OPTIONS = ['machine_learning', 'query', 'saved_id']
_META_SCHEMA_REQ_DEFAULTS = {}
@@ -209,8 +208,8 @@ class Rule(object):
kwargs = copy.deepcopy(kwargs)
while rule_type not in RULE_TYPE_OPTIONS:
rule_type = click.prompt('Rule type ({})'.format(', '.join(RULE_TYPE_OPTIONS)))
while rule_type not in RULE_TYPES:
rule_type = click.prompt('Rule type ({})'.format(', '.join(RULE_TYPES)))
schema = get_schema(rule_type)
props = schema['properties']
@@ -245,6 +244,11 @@ class Rule(object):
contents[name] = threat_map
continue
if name == 'threshold':
contents[name] = {n: schema_prompt(f'threshold {n}', required=n in options['required'], **opts)
for n, opts in options['properties'].items()}
continue
if kwargs.get(name):
contents[name] = schema_prompt(kwargs.pop(name))
continue
+23 -4
View File
@@ -33,9 +33,13 @@ NONFORMATTED_FIELDS = 'note',
# enabled defaults to false instead of true
# version is a required field that must exist
# rule types
MACHINE_LEARNING = 'machine_learning'
SAVED_QUERY = 'saved_query'
QUERY = 'query'
THRESHOLD = 'threshold'
RULE_TYPES = [MACHINE_LEARNING, SAVED_QUERY, QUERY, THRESHOLD]
class FilterMetadata(jsl.Document):
@@ -100,6 +104,13 @@ class SeverityMapping(jsl.Document):
severity = jsl.StringField(required=False)
class ThresholdMapping(jsl.Document):
"""Threshold mapping."""
field = jsl.StringField(required=False)
value = jsl.IntField(minimum=1, required=True)
class ThreatTactic(jsl.Document):
"""Threat tactics."""
@@ -168,6 +179,11 @@ class SiemRuleApiSchema(jsl.Document):
ml_scope.machine_learning_job_id = jsl.StringField(required=True)
ml_scope.type = jsl.StringField(enum=[MACHINE_LEARNING], required=True, default=MACHINE_LEARNING)
with jsl.Scope(SAVED_QUERY) as saved_id_scope:
saved_id_scope.index = jsl.ArrayField(jsl.StringField(), required=False)
saved_id_scope.saved_id = jsl.StringField(required=True)
saved_id_scope.type = jsl.StringField(enum=[SAVED_QUERY], required=True, default=SAVED_QUERY)
with jsl.Scope(QUERY) as query_scope:
query_scope.index = jsl.ArrayField(jsl.StringField(), required=False)
# this is not required per the API but we will enforce it here
@@ -175,10 +191,13 @@ class SiemRuleApiSchema(jsl.Document):
query_scope.query = jsl.StringField(required=True)
query_scope.type = jsl.StringField(enum=[QUERY], required=True, default=QUERY)
with jsl.Scope(SAVED_QUERY) as saved_id_scope:
saved_id_scope.index = jsl.ArrayField(jsl.StringField(), required=False)
saved_id_scope.saved_id = jsl.StringField(required=True)
saved_id_scope.type = jsl.StringField(enum=[SAVED_QUERY], required=True, default=SAVED_QUERY)
with jsl.Scope(THRESHOLD) as threshold_scope:
threshold_scope.index = jsl.ArrayField(jsl.StringField(), required=False)
# this is not required per the API but we will enforce it here
threshold_scope.language = jsl.StringField(enum=['kuery', 'lucene'], required=True, default='kuery')
threshold_scope.query = jsl.StringField(required=True)
threshold_scope.type = jsl.StringField(enum=[THRESHOLD], required=True, default=THRESHOLD)
threshold_scope.threshold = jsl.DocumentField(ThresholdMapping, required=True)
class VersionedApiSchema(SiemRuleApiSchema):