From e0818a22f44a9414db613e56b3194518f5fd2a15 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Jan 2026 15:29:11 +0000 Subject: [PATCH] Add IP enrichment fields to detection_fields for enhanced Chronicle filtering Co-authored-by: seanthegeek <44679+seanthegeek@users.noreply.github.com> --- docs/source/google_secops.md | 32 +++++++++++++++++++++++++++++--- parsedmarc/google_secops.py | 26 +++++++++++++++++++++----- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/docs/source/google_secops.md b/docs/source/google_secops.md index 603b139..7ae1c13 100644 --- a/docs/source/google_secops.md +++ b/docs/source/google_secops.md @@ -48,8 +48,9 @@ Each event includes: - **principal**: Source IP address, location (country), and hostname (reverse DNS) - **target**: Domain name (from DMARC policy) - **security_result**: Severity level, description, and detection fields for dashboarding - - **detection_fields**: Key DMARC dimensions for filtering and grouping (e.g., `dmarc.disposition`, `dmarc.pass`, `dmarc.header_from`, `dmarc.report_org`) + - **detection_fields**: Key DMARC dimensions for filtering and grouping (e.g., `dmarc.disposition`, `dmarc.pass`, `dmarc.header_from`, `dmarc.report_org`, `dmarc.source_service_name`, `dmarc.source_service_type`) - All dashboard-relevant fields use `dmarc.*` or `smtp_tls.*` prefixes for easy identification + - Includes IP enrichment data (service name and type from reverse DNS mapping) for enhanced filtering - **additional.fields** (optional): Low-value context fields (e.g., detailed auth results) not typically used for dashboarding **Design Rationale**: DMARC dimensions are placed in `security_result[].detection_fields` rather than `additional.fields` because Chronicle dashboards, stats searches, and aggregations work best with UDM label arrays. The `additional.fields` is a protobuf Struct intended for opaque context and is not reliably queryable for dashboard operations. @@ -97,7 +98,9 @@ Each event includes: {"key": "dmarc.report_end", "value": "2018-06-19 23:59:59"}, {"key": "dmarc.row_count", "value": 1}, {"key": "dmarc.spf_result", "value": "pass"}, - {"key": "dmarc.dkim_result", "value": "pass"} + {"key": "dmarc.dkim_result", "value": "pass"}, + {"key": "dmarc.source_service_name", "value": "Example Mail Service"}, + {"key": "dmarc.source_service_type", "value": "email"} ] }], "additional": { @@ -133,7 +136,9 @@ Each event includes: "description": "DMARC forensic report: authentication failure (dmarc)", "detection_fields": [ {"key": "dmarc.auth_failure", "value": "dmarc"}, - {"key": "dmarc.reported_domain", "value": "example.com"} + {"key": "dmarc.reported_domain", "value": "example.com"}, + {"key": "dmarc.source_service_name", "value": "Example Mail Provider"}, + {"key": "dmarc.source_service_type", "value": "email"} ] }], "additional": { @@ -281,6 +286,27 @@ rule dmarc_forensic_failures { } ``` +### Find DMARC failures from specific mail service types + +```yara-l +rule dmarc_failures_by_service_type { + meta: + author = "parsedmarc" + description = "Detect DMARC failures from specific mail service types" + + events: + $e.metadata.product_name = "parsedmarc" + $e.event_type = "DMARC_AGGREGATE" + $e.security_result.detection_fields.key = "dmarc.pass" + $e.security_result.detection_fields.value = false + $e.security_result.detection_fields.key = "dmarc.source_service_type" + $e.security_result.detection_fields.value = "email" + + condition: + $e +} +``` + ### Find SMTP TLS failures ```yara-l diff --git a/parsedmarc/google_secops.py b/parsedmarc/google_secops.py index d022349..03f4291 100644 --- a/parsedmarc/google_secops.py +++ b/parsedmarc/google_secops.py @@ -153,6 +153,7 @@ class GoogleSecOpsClient: source_reverse_dns = record["source"].get("reverse_dns") source_base_domain = record["source"].get("base_domain") source_name = record["source"].get("name") + source_type = record["source"].get("type") header_from = record["identifiers"]["header_from"] envelope_from = record["identifiers"]["envelope_from"] @@ -229,6 +230,14 @@ class GoogleSecOpsClient: event["security_result"][0]["detection_fields"].append( {"key": "dmarc.dkim_result", "value": dkim_result} ) + if source_name: + event["security_result"][0]["detection_fields"].append( + {"key": "dmarc.source_service_name", "value": source_name} + ) + if source_type: + event["security_result"][0]["detection_fields"].append( + {"key": "dmarc.source_service_type", "value": source_type} + ) # Add optional context fields (low-value, not for dashboarding) additional_context = [] @@ -238,11 +247,6 @@ class GoogleSecOpsClient: {"key": "source_base_domain", "value": source_base_domain} ) - if source_name: - additional_context.append( - {"key": "source_name", "value": source_name} - ) - if self.static_environment: additional_context.append( {"key": "environment", "value": self.static_environment} @@ -325,6 +329,8 @@ class GoogleSecOpsClient: source_ip = forensic_report["source"]["ip_address"] source_country = forensic_report["source"].get("country") source_reverse_dns = forensic_report["source"].get("reverse_dns") + source_name = forensic_report["source"].get("name") + source_type = forensic_report["source"].get("type") reported_domain = forensic_report["reported_domain"] arrival_date = forensic_report["arrival_date_utc"] @@ -367,6 +373,16 @@ class GoogleSecOpsClient: ], } + # Add optional fields to detection_fields + if source_name: + event["security_result"][0]["detection_fields"].append( + {"key": "dmarc.source_service_name", "value": source_name} + ) + if source_type: + event["security_result"][0]["detection_fields"].append( + {"key": "dmarc.source_service_type", "value": source_type} + ) + # Add optional context fields (low-value, not for dashboarding) additional_context = []