Update cardinality field in schema for threshold rules (#1349)

* Make cardinality array in schema for threshold rules
* update master, 7.12, 7.13, and 7.14 schemas with cardinality fix
* fix 7.12 downgrade to handle cardinality as an array

* Add two new rules to detect agent spoofing

Co-authored-by: Ross Wolf <31489089+rw-access@users.noreply.github.com>

(cherry picked from commit 163d9e3864)
This commit is contained in:
Justin Ibarra
2021-07-21 08:32:54 -08:00
committed by github-actions[bot]
parent 34b37c0bfd
commit e9b2ebab2d
11 changed files with 117 additions and 77 deletions
+3 -3
View File
@@ -183,7 +183,7 @@ class BaseRuleData(MarshmallowDataclassMixin):
def save_schema(cls):
"""Save the schema as a jsonschema."""
fields: List[dataclasses.Field] = dataclasses.fields(cls)
type_field = next(field for field in fields if field.name == "type")
type_field = next(f for f in fields if f.name == "type")
rule_type = typing.get_args(type_field.type)[0] if cls != BaseRuleData else "base"
schema = cls.jsonschema()
version_dir = SCHEMA_DIR / "master"
@@ -256,9 +256,9 @@ class ThresholdQueryRuleData(QueryRuleData):
field: str
value: definitions.ThresholdValue
field: List[definitions.NonEmptyStr]
field: definitions.CardinalityFields
value: definitions.ThresholdValue
cardinality: Optional[ThresholdCardinality]
cardinality: Optional[List[ThresholdCardinality]]
type: Literal["threshold"]
threshold: ThresholdMapping
+1 -1
View File
@@ -206,5 +206,5 @@ def toml_write(rule_contents, outfile=None):
_do_write(data, _contents)
finally:
if needs_close:
if needs_close and hasattr(outfile, "close"):
outfile.close()
+1 -1
View File
@@ -123,7 +123,7 @@ def downgrade_threshold_to_7_11(version: Version, api_contents: dict) -> dict:
if len(threshold_field) > 1:
raise ValueError('Cannot downgrade a threshold rule that has multiple threshold fields defined')
if threshold.get('cardinality', {}).get('field') or threshold.get('cardinality', {}).get('value'):
if threshold.get('cardinality'):
raise ValueError('Cannot downgrade a threshold rule that has a defined cardinality')
api_contents = api_contents.copy()
+5 -3
View File
@@ -5,7 +5,7 @@
"""Custom shared definitions for schemas."""
from typing import Literal, Final
from typing import List, Literal, Final
from marshmallow import validate
from marshmallow_dataclass import NewType
@@ -44,19 +44,21 @@ TIMELINE_TEMPLATES: Final[dict] = {
}
NonEmptyStr = NewType('NonEmptyStr', str, validate=validate.Length(min=1))
BranchVer = NewType('BranchVer', str, validate=validate.Regexp(BRANCH_PATTERN))
CardinalityFields = NewType('CardinalityFields', List[NonEmptyStr], validate=validate.Length(min=0, max=3))
CodeString = NewType("CodeString", str)
ConditionSemVer = NewType('ConditionSemVer', str, validate=validate.Regexp(CONDITION_VERSION_PATTERN))
Date = NewType('Date', str, validate=validate.Regexp(DATE_PATTERN))
FilterLanguages = Literal["kuery", "lucene"]
Interval = NewType('Interval', str, validate=validate.Regexp(INTERVAL_PATTERN))
PositiveInteger = NewType('PositiveInteger', int, validate=validate.Range(min=1))
Markdown = NewType("MarkdownField", CodeString)
Maturity = Literal['development', 'experimental', 'beta', 'production', 'deprecated']
MaxSignals = NewType("MaxSignals", int, validate=validate.Range(min=1))
NonEmptyStr = NewType('NonEmptyStr', str, validate=validate.Length(min=1))
Operator = Literal['equals']
OSType = Literal['windows', 'linux', 'macos']
PositiveInteger = NewType('PositiveInteger', int, validate=validate.Range(min=1))
RiskScore = NewType("MaxSignals", int, validate=validate.Range(min=1, max=100))
RuleType = Literal['query', 'saved_query', 'machine_learning', 'eql', 'threshold', 'threat_match']
SemVer = NewType('SemVer', str, validate=validate.Regexp(VERSION_PATTERN))
+31 -20
View File
@@ -919,35 +919,46 @@
"additionalProperties": false,
"properties": {
"cardinality": {
"additionalProperties": false,
"properties": {
"field": {
"type": "string"
},
"value": {
"minimum": 1,
"type": "integer"
}
},
"required": [
"field",
"value"
],
"type": "object"
},
"field": {
"items": {
"default": "",
"type": "string"
"additionalProperties": false,
"properties": {
"field": {
"type": "string"
},
"value": {
"description": "ThresholdValue",
"format": "integer",
"minimum": 1,
"type": "number"
}
},
"required": [
"field",
"value"
],
"type": "object"
},
"type": "array"
},
"field": {
"description": "CardinalityFields",
"items": {
"description": "NonEmptyStr",
"minLength": 1,
"type": "string"
},
"maxItems": 3,
"type": "array"
},
"value": {
"description": "ThresholdValue",
"format": "integer",
"minimum": 1,
"type": "integer"
"type": "number"
}
},
"required": [
"field",
"value"
],
"type": "object"
+32 -15
View File
@@ -112,6 +112,9 @@
"type": "string"
},
"operator": {
"enum": [
"equals"
],
"type": "string"
},
"value": {
@@ -151,6 +154,9 @@
"type": "string"
},
"operator": {
"enum": [
"equals"
],
"type": "string"
},
"severity": {
@@ -178,6 +184,9 @@
"additionalProperties": false,
"properties": {
"framework": {
"enum": [
"MITRE ATT&CK"
],
"type": "string"
},
"tactic": {
@@ -265,30 +274,35 @@
"additionalProperties": false,
"properties": {
"cardinality": {
"additionalProperties": false,
"properties": {
"field": {
"type": "string"
"items": {
"additionalProperties": false,
"properties": {
"field": {
"type": "string"
},
"value": {
"description": "ThresholdValue",
"format": "integer",
"minimum": 1,
"type": "number"
}
},
"value": {
"description": "ThresholdValue",
"format": "integer",
"minimum": 1,
"type": "number"
}
"required": [
"field",
"value"
],
"type": "object"
},
"required": [
"field",
"value"
],
"type": "object"
"type": "array"
},
"field": {
"description": "CardinalityFields",
"items": {
"description": "NonEmptyStr",
"minLength": 1,
"type": "string"
},
"maxItems": 3,
"type": "array"
},
"value": {
@@ -336,6 +350,9 @@
"type": "string"
},
"type": {
"enum": [
"threshold"
],
"type": "string"
}
},
+20 -15
View File
@@ -274,30 +274,35 @@
"additionalProperties": false,
"properties": {
"cardinality": {
"additionalProperties": false,
"properties": {
"field": {
"type": "string"
"items": {
"additionalProperties": false,
"properties": {
"field": {
"type": "string"
},
"value": {
"description": "ThresholdValue",
"format": "integer",
"minimum": 1,
"type": "number"
}
},
"value": {
"description": "ThresholdValue",
"format": "integer",
"minimum": 1,
"type": "number"
}
"required": [
"field",
"value"
],
"type": "object"
},
"required": [
"field",
"value"
],
"type": "object"
"type": "array"
},
"field": {
"description": "CardinalityFields",
"items": {
"description": "NonEmptyStr",
"minLength": 1,
"type": "string"
},
"maxItems": 3,
"type": "array"
},
"value": {
+20 -15
View File
@@ -274,30 +274,35 @@
"additionalProperties": false,
"properties": {
"cardinality": {
"additionalProperties": false,
"properties": {
"field": {
"type": "string"
"items": {
"additionalProperties": false,
"properties": {
"field": {
"type": "string"
},
"value": {
"description": "ThresholdValue",
"format": "integer",
"minimum": 1,
"type": "number"
}
},
"value": {
"description": "ThresholdValue",
"format": "integer",
"minimum": 1,
"type": "number"
}
"required": [
"field",
"value"
],
"type": "object"
},
"required": [
"field",
"value"
],
"type": "object"
"type": "array"
},
"field": {
"description": "CardinalityFields",
"items": {
"description": "NonEmptyStr",
"minLength": 1,
"type": "string"
},
"maxItems": 3,
"type": "array"
},
"value": {
+1 -1
View File
@@ -9,4 +9,4 @@
"7.14.0":
beats: "master" # TODO: 7.14.x
ecs: "1.10.0"
ecs: "master" # TODO: master came out after 7.13.0 release
+1 -1
View File
@@ -8,7 +8,7 @@ PyYAML~=5.3
eql==0.9.9
elasticsearch~=7.9
XlsxWriter~=1.3.6
marshmallow~=3.10.0
marshmallow~=3.12.2
marshmallow-dataclass[union]~=8.4
# test deps
+2 -2
View File
@@ -86,10 +86,10 @@ class TestSchemas(unittest.TestCase):
cls.v712_threshold_rule = dict(copy.deepcopy(cls.v79_threshold_contents), threshold={
'field': ['destination.bytes', 'process.args'],
'value': 75,
'cardinality': {
'cardinality': [{
'field': 'user.name',
'value': 2
}
}]
})
def test_query_downgrade(self):