From 060070bf341ac8acea5d194b10913e7df0b0a4e8 Mon Sep 17 00:00:00 2001 From: Zac Brown Date: Wed, 29 Nov 2023 13:26:17 -0800 Subject: [PATCH] Add schema support for Correlation Rules (#416) --- panther_analysis_tool/constants.py | 2 + .../analysis_config_schema.json | 232 ++++++++++++++++-- panther_analysis_tool/main.py | 7 + panther_analysis_tool/schemas.py | 39 +++ 4 files changed, 266 insertions(+), 14 deletions(-) diff --git a/panther_analysis_tool/constants.py b/panther_analysis_tool/constants.py index 84792be4..b2765fc4 100644 --- a/panther_analysis_tool/constants.py +++ b/panther_analysis_tool/constants.py @@ -5,6 +5,7 @@ from schema import Schema from panther_analysis_tool.schemas import ( + CORRELATION_RULE_SCHEMA, DATA_MODEL_SCHEMA, DERIVED_SCHEMA, GLOBAL_SCHEMA, @@ -79,6 +80,7 @@ class AnalysisTypes: AnalysisTypes.RULE: RULE_SCHEMA, AnalysisTypes.DERIVED: DERIVED_SCHEMA, AnalysisTypes.SCHEDULED_RULE: RULE_SCHEMA, + AnalysisTypes.CORRELATION_RULE: CORRELATION_RULE_SCHEMA, } SET_FIELDS = [ diff --git a/panther_analysis_tool/detection_schemas/analysis_config_schema.json b/panther_analysis_tool/detection_schemas/analysis_config_schema.json index 758d1008..f0feaad2 100644 --- a/panther_analysis_tool/detection_schemas/analysis_config_schema.json +++ b/panther_analysis_tool/detection_schemas/analysis_config_schema.json @@ -1,5 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/panther-labs/panther/pkg/detections/sdl/schema/simple-detection-schema", "$ref": "#/$defs/SimpleDetectionSchema", "$defs": { "AssociatedLogType": { @@ -563,6 +564,17 @@ "type": "object", "required": ["OnlyOne"] }, + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "None": { + "$ref": "#/$defs/MatchExpressions" + } + }, + "additionalProperties": false, + "type": "object", + "required": ["None"] + }, { "$schema": "https://json-schema.org/draft/2020-12/schema", "properties": { @@ -899,6 +911,185 @@ "additionalProperties": false, "type": "object", "required": ["Condition", "Values"] + }, + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "EventEvaluationOrder": { + "type": "string", + "enum": ["Chronological", "ReverseChronological"] + }, + "LookbackWindowMinutes": { + "type": "integer" + }, + "Schedule": { + "properties": { + "CronExpression": { + "type": "string" + }, + "RateMinutes": { + "type": "integer" + }, + "TimeoutMinutes": { + "type": "integer" + } + }, + "additionalProperties": false, + "type": "object", + "required": ["TimeoutMinutes"] + }, + "Sequence": { + "items": { + "properties": { + "ID": { + "type": "string" + }, + "RuleID": { + "type": "string" + }, + "SignalMatch": { + "type": "string" + }, + "MinMatchCount": { + "type": "integer" + }, + "MaxMatchCount": { + "type": "integer" + }, + "Absence": { + "type": "boolean" + } + }, + "additionalProperties": false, + "type": "object" + }, + "type": "array" + }, + "Transitions": { + "items": { + "properties": { + "ID": { + "type": "string" + }, + "From": { + "type": "string" + }, + "To": { + "type": "string" + }, + "WithinTimeFrameMinutes": { + "type": "integer" + }, + "Match": { + "items": { + "properties": { + "On": { + "type": "string" + }, + "From": { + "type": "string" + }, + "To": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + "type": "array", + "maxItems": 1, + "minItems": 1 + } + }, + "additionalProperties": false, + "type": "object", + "required": ["From", "To"] + }, + "type": "array" + } + }, + "additionalProperties": false, + "type": "object", + "required": ["Schedule", "Sequence"] + }, + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "EventEvaluationOrder": { + "type": "string", + "enum": ["Chronological", "ReverseChronological"] + }, + "LookbackWindowMinutes": { + "type": "integer" + }, + "Schedule": { + "properties": { + "CronExpression": { + "type": "string" + }, + "RateMinutes": { + "type": "integer" + }, + "TimeoutMinutes": { + "type": "integer" + } + }, + "additionalProperties": false, + "type": "object", + "required": ["TimeoutMinutes"] + }, + "Group": { + "items": { + "properties": { + "ID": { + "type": "string" + }, + "RuleID": { + "type": "string" + }, + "SignalMatch": { + "type": "string" + }, + "MinMatchCount": { + "type": "integer" + }, + "MaxMatchCount": { + "type": "integer" + }, + "Absence": { + "type": "boolean" + } + }, + "additionalProperties": false, + "type": "object" + }, + "type": "array" + }, + "MatchCriteria": { + "patternProperties": { + ".*": { + "items": { + "properties": { + "GroupID": { + "type": "string" + }, + "Match": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": ["GroupID", "Match"] + }, + "type": "array" + } + }, + "type": "object" + } + }, + "additionalProperties": false, + "type": "object", + "required": ["Schedule", "Group"] } ], "type": "object" @@ -1020,7 +1211,8 @@ "scheduled_rule", "scheduled_query", "lookup_table", - "saved_query" + "saved_query", + "correlation_rule" ] }, "Description": { @@ -1054,7 +1246,19 @@ "type": "string" }, "Severity": { - "type": "string" + "type": "string", + "enum": [ + "Info", + "INFO", + "Low", + "LOW", + "Medium", + "MEDIUM", + "High", + "HIGH", + "Critical", + "CRITICAL" + ] }, "Tags": { "items": { @@ -1155,12 +1359,12 @@ "Schema": { "type": "string" }, - "PackDefinition": { - "$ref": "#/$defs/PackDefinition" - }, "PackID": { "type": "string" }, + "PackDefinition": { + "$ref": "#/$defs/PackDefinition" + }, "Detection": { "$ref": "#/$defs/MatchExpressions" }, @@ -1195,27 +1399,27 @@ }, "Test": { "properties": { + "Name": { + "type": "string" + }, "ExpectedResult": { "type": "boolean" }, - "Log": true, "LogType": { "type": "string" }, + "ResourceType": { + "type": "string" + }, "Mocks": { "$ref": "#/$defs/Mocks" }, - "Name": { - "type": "string" - }, - "Resource": true, - "ResourceType": { - "type": "string" - } + "Log": true, + "Resource": true }, "additionalProperties": false, "type": "object", - "required": ["ExpectedResult", "Name"] + "required": ["Name", "ExpectedResult"] } } } diff --git a/panther_analysis_tool/main.py b/panther_analysis_tool/main.py index b0240dcd..1b05adea 100644 --- a/panther_analysis_tool/main.py +++ b/panther_analysis_tool/main.py @@ -130,6 +130,7 @@ BackendNotFoundException, add_path_to_filename, convert_unicode, + is_correlation_rule, is_derived_detection, is_simple_detection, ) @@ -911,6 +912,12 @@ def setup_run_tests( # pylint: disable=too-many-locals,too-many-arguments filters=analysis_spec.get(BACKEND_FILTERS_ANALYSIS_SPEC_KEY) or None, ) + if is_correlation_rule(analysis_spec): + logging.warning( + "Skipping Correlation Rule '%s', testing not supported", analysis_spec.get("RuleID") + ) + continue + if is_simple_detection(analysis_spec) or is_derived_detection(analysis_spec): # skip tests when the body is empty if not analysis_spec.get("body"): diff --git a/panther_analysis_tool/schemas.py b/panther_analysis_tool/schemas.py index 12de9282..58776638 100644 --- a/panther_analysis_tool/schemas.py +++ b/panther_analysis_tool/schemas.py @@ -56,6 +56,7 @@ def validate( TYPE_SCHEMA = Schema( { "AnalysisType": Or( + "correlation_rule", "datamodel", "global", "pack", @@ -221,6 +222,44 @@ def validate( ignore_extra_keys=False, ) +CORRELATION_RULE_SCHEMA = Schema( + { + "AnalysisType": "correlation_rule", + "RuleID": And(str, NAME_ID_VALIDATION_REGEX), + "Enabled": bool, + "Detection": object, + "Severity": Or("Info", "Low", "Medium", "High", "Critical"), + Optional("Description"): str, + Optional("DedupPeriodMinutes"): int, + Optional("InlineFilters"): object, + Optional("DisplayName"): And(str, NAME_ID_VALIDATION_REGEX), + Optional("OnlyUseBaseRiskScore"): bool, + Optional("OutputIds"): [str], + Optional("Reference"): str, + Optional("Runbook"): str, + Optional("SummaryAttributes"): [str], + Optional("Threshold"): int, + Optional("Tags"): [str], + Optional("Reports"): {str: list}, + Optional("Tests"): [ + { + "Name": str, + Optional( + "LogType" + ): str, # Not needed anymore, optional for backwards compatibility + "ExpectedResult": bool, + "Log": object, + Optional("Mocks"): [MOCK_SCHEMA], + } + ], + Optional("DynamicSeverities"): object, + Optional("AlertTitle"): str, + Optional("AlertContext"): object, + Optional("GroupBy"): object, + }, + ignore_extra_keys=False, +) + SAVED_QUERY_SCHEMA = Schema( { "AnalysisType": Or("saved_query"),