diff --git a/AGENTS.md b/AGENTS.md index 1a17b3e1..bfba2ef1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -179,9 +179,14 @@ Before rewriting a tracked list/data file from freshly-generated content (anythi A review that only verifies functional/numeric correctness (queries return the right values, files import cleanly, types check) will sail past exactly the defects a text-first reviewer catches. On PR #834, four such misses survived a thorough functional review: two long-standing typos inside the OSD ndjson ("SMPT TLS", "filed DMARC"), a typo on an *unchanged* line adjacent to a docs edit, and hand-written bootstrap glue that duplicated the script's existing `wait_for()` helper. Rules drawn from that: - **Whole-file canonical exports put every line in the diff — review them as text, too.** Re-exporting `dashboards/opensearch/opensearch_dashboards.ndjson` or a Grafana JSON from a running instance rewrites the entire file, so pre-existing user-facing strings (saved-object titles, markdown panels, column labels) are formally part of the change. A semantic before/after comparison ("attributes identical") proves no unintended changes but deliberately looks through pre-existing content problems; add one text-level pass over titles and markdown before committing. -- **Proofread the whole hunk around prose edits, not just the `+`/`-` lines.** Typos one line away from an edit are in the reviewer's context window and fair game; they should be in yours. +- **Proofread the whole hunk around prose edits, not just the `+`/`-` lines.** Typos one line away from an edit are in the reviewer's context window and fair game; they should be in yours. Proofread the *rendered* text, not just the wording: on PR #839 a comment wrapped so `#169` landed right after the `#` comment marker, making the raw source read `# #169;` — no line was ever wrong, but the wrap point was. Watch how wraps interact with markers and punctuation (`#` before an issue number, a trailing `-`, a code span split across lines) and reflow rather than argue the text is technically correct. - **Code written mid-incident gets the same review bar as planned code.** Before writing new shell/infra glue while firefighting, check the file for an existing helper that already does it (e.g. `wait_for()` in `dashboard-dev-bootstrap.sh`), and give your own inline code the same scrutiny you'd give a subagent's. +Two more rules, drawn from the PR #839 review (Copilot caught both after a thorough Fable pass missed them): + +- **Docstrings and comments are prose surface too — and beware dual-use terms.** A regression-test docstring described DKIM/SPF results as "stored as nested object arrays"; in Elasticsearch/OpenSearch "nested" is a specific mapping type, and the fix under review hinged on the fields being dynamic-mapped as plain `object`, *not* `nested`. The reviewer had held both facts all session, so the blended sentence pattern-matched as true — author's-context blindness that a fresh reader doesn't share. Give docstrings/comments the same text-level pass as docs and dashboard labels, with extra suspicion for words that are both colloquial English and load-bearing technical terms near the code in question ("nested", "index", "keyword" in anything Elasticsearch-adjacent). +- **Clean inert config inside hunks the diff already rewrites.** Stale entries (e.g. orphaned `renameByName` keys in a Grafana panel) sitting inside a block the PR is editing anyway cost nothing to remove and confuse every later reader if kept; "minimize the diff" is the wrong tiebreaker there. It remains the right tiebreaker for untouched panels/files — don't expand a PR's blast radius to chase pre-existing cruft elsewhere. + ## Releases A release isn't done until built artifacts are attached to the GitHub release page. Full sequence: diff --git a/CHANGELOG.md b/CHANGELOG.md index 41160e7d..b4774fa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,11 @@ - **The Elasticsearch/OpenSearch aggregate dashboards' over-time charts (and the Grafana ES dashboard's summary pies and time series) bucketed on the multi-valued `date_range` field**; a date histogram counts a report once per value, double-counting any report whose begin and end dates fall in different buckets. All date histograms and time-range filters now use the single-valued `date_begin`, matching the report-begin semantics of the PostgreSQL (`begin_date`) and Splunk (`_time` = interval begin) dashboards. - **Aggregate-report policy and authentication result words are now normalized to lowercase** ([#288](https://github.com/domainaware/parsedmarc/issues/288)): reporters that emit mixed-case values such as `Pass` no longer create duplicate result categories in outputs and dashboards. - **The results email (SMTP and Microsoft Graph) is no longer sent when no reports were parsed** ([#200](https://github.com/domainaware/parsedmarc/issues/200)): previously an empty run — an empty inbox, or one where every message was invalid — still emailed a zip of headers-only CSVs. The email step is now skipped with an INFO log when the run produced no aggregate, failure, or SMTP TLS reports. +- **The DKIM/SPF alignment-detail tables on the Kibana/OpenSearch Dashboards, Grafana (Elasticsearch), and Splunk aggregate dashboards no longer show a selector × domain × result cross-product** ([#169](https://github.com/domainaware/parsedmarc/issues/169)). Elasticsearch/OpenSearch flatten the `dkim_results`/`spf_results` object arrays (they are dynamic-mapped as `object`, not `nested`), so stacking terms aggregations on their subfields produced every combination of selector/domain/result across a report's signatures, each repeating the full message count. Aggregate documents now also carry `dkim_results_combined` and `spf_results_combined` — one `"selector / domain / result"` (`"scope / domain / result"`) string per auth result — and the dashboards aggregate those instead; the Splunk detail panels now pair the values with `mvzip`/`mvexpand`. Documents saved by older versions are now backfilled automatically at startup (a non-blocking, idempotent background task); the documented `_update_by_query` command (see the Elasticsearch docs page) remains available for running the backfill manually. The Grafana "DKIM Alignment Details" panel's dmarcian.com DKIM-checker data link was removed because it required the separate domain/selector columns. The SMTP TLS visualizations had the same class of defect; that fix is described in its own entry below. +- **The Kibana/OpenSearch Dashboards aggregate dashboard now includes an "Auth result filters" control panel** above the SPF/DKIM details tables, with dropdowns for DKIM selector/domain/result and SPF scope/domain/result, so results can still be filtered by individual components alongside the combined per-signature columns. +- **SMTP TLS visualizations had the same cross-product defect as the DKIM/SPF alignment tables** ([#169](https://github.com/domainaware/parsedmarc/issues/169)): `policies` is an object array, and each policy's `failure_details` is itself an object array inside it, so stacking terms aggregations on their subfields cross-products the same way. SMTP TLS documents now also carry `policies_combined` — one `"policy_domain / policy_type"` string per policy — and `failure_details_combined` — one `"policy_domain / policy_type / result_type / sending_mta_ip / receiving_ip / receiving_mx_hostname"` string per failure detail — and documents saved by older versions are backfilled automatically at startup the same non-blocking, idempotent way as the DKIM/SPF backfill; the equivalent manual `_update_by_query` command is documented on the Elasticsearch docs page. Also fixed two adjacent dead fields found while making this change: `_SMTPTLSFailureDetailsDoc` declared `additional_information_uri`, but `add_failure_details` passed it to the constructor as `additional_information`, so it was never actually populated on the declared field; and `receiving_mx_hostname`, which `add_failure_details` has always stored, had no field declaration at all. Both are now correctly wired in the Elasticsearch and OpenSearch outputs. +- **The Grafana "Map of Message Source Countries" panel's markers now scale with message volume and use a higher-contrast style.** Previously the markers were fixed-size 5 px dots at 50% opacity in dark green, which were nearly invisible on the dark basemap. +- **Corrected the dead `_SPFResult.results` (plural) field declaration to `result`**, matching what was always written to it. ## 10.2.4 diff --git a/dashboards/grafana/Grafana-DMARC_Reports.json b/dashboards/grafana/Grafana-DMARC_Reports.json index 189f6955..d48f381b 100644 --- a/dashboards/grafana/Grafana-DMARC_Reports.json +++ b/dashboards/grafana/Grafana-DMARC_Reports.json @@ -2132,12 +2132,13 @@ "showLegend": true, "style": { "color": { - "fixed": "dark-green" + "fixed": "green" }, - "opacity": 0.5, + "opacity": 0.8, "size": { + "field": "Sum", "fixed": 5, - "min": 2, + "min": 4, "max": 30 }, "symbol": { @@ -2426,24 +2427,24 @@ { "matcher": { "id": "byName", - "options": "SPF Auth Result" + "options": "SPF Scope / Domain / Result" }, "properties": [ { "id": "custom.width", - "value": 115 + "value": 200 } ] }, { "matcher": { "id": "byName", - "options": "DKIM Auth Result" + "options": "DKIM Selector / Domain / Result" }, "properties": [ { "id": "custom.width", - "value": 123 + "value": 260 } ] }, @@ -2598,7 +2599,7 @@ { "$$hashKey": "object:412", "fake": true, - "field": "spf_results.result.keyword", + "field": "spf_results_combined.keyword", "id": "16", "settings": { "min_doc_count": "1", @@ -2626,7 +2627,7 @@ { "$$hashKey": "object:461", "fake": true, - "field": "dkim_results.result.keyword", + "field": "dkim_results_combined.keyword", "id": "10", "settings": { "min_doc_count": "1", @@ -2666,7 +2667,7 @@ "Sum": 11, "disposition.keyword": 4, "dkim_aligned": 6, - "dkim_results.result.keyword": 9, + "dkim_results_combined.keyword": 9, "header_from.keyword": 10, "org_name.keyword": 7, "source_base_domain.keyword": 0, @@ -2674,16 +2675,14 @@ "source_ip_address.keyword": 2, "source_reverse_dns.keyword": 1, "spf_aligned": 5, - "spf_results.result.keyword": 8 + "spf_results_combined.keyword": 8 }, "renameByName": { "Sum": "Messages", "date_range": "Date", "disposition.keyword": "Disposition", "dkim_aligned": "DKIM", - "dkim_results.domain.keyword": "DKIM Domain", - "dkim_results.result.keyword": "DKIM Auth Result", - "dkim_results.selector.keyword": "DKIM Selector", + "dkim_results_combined.keyword": "DKIM Selector / Domain / Result", "envelope_from.keyword": "Envelope From", "header_from.keyword": "Email Domain", "org_name.keyword": "Reporter", @@ -2692,7 +2691,7 @@ "source_ip_address.keyword": "Source IP", "source_reverse_dns.keyword": "PTR", "spf_aligned": "SPF", - "spf_results.result.keyword": "SPF Auth Result" + "spf_results_combined.keyword": "SPF Scope / Domain / Result" } } } @@ -3431,7 +3430,7 @@ { "$$hashKey": "object:459", "fake": true, - "field": "spf_results.result.keyword", + "field": "spf_results_combined.keyword", "id": "8", "settings": { "min_doc_count": 1, @@ -3502,7 +3501,7 @@ "header_from.keyword": "Header From", "source_base_domain.keyword": "Reverse DNS Base", "spf_aligned": "SPF Aligned", - "spf_results.result.keyword": "SPF Result" + "spf_results_combined.keyword": "SPF Scope / Domain / Result" } } } @@ -3590,22 +3589,12 @@ { "matcher": { "id": "byName", - "options": "DKIM Selector" + "options": "DKIM Selector / Domain / Result" }, "properties": [ { "id": "custom.width", - "value": 320 - }, - { - "id": "links", - "value": [ - { - "targetBlank": true, - "title": "Open dmarcian.com DKIM Record Checker", - "url": "https://dmarcian.com/dkim-inspector/?domain=${__data.fields[\"dkim_results.domain.keyword\"]}&selector=${__data.fields[\"dkim_results.selector.keyword\"]}" - } - ] + "value": 400 } ] } @@ -3649,7 +3638,7 @@ { "$$hashKey": "object:458", "fake": true, - "field": "dkim_results.selector.keyword", + "field": "dkim_results_combined.keyword", "id": "7", "settings": { "min_doc_count": "1", @@ -3660,34 +3649,6 @@ }, "type": "terms" }, - { - "$$hashKey": "object:459", - "fake": true, - "field": "dkim_results.domain.keyword", - "id": "8", - "settings": { - "min_doc_count": 1, - "missing": "-", - "order": "desc", - "orderBy": "4", - "size": "0" - }, - "type": "terms" - }, - { - "$$hashKey": "object:460", - "fake": true, - "field": "dkim_results.result.keyword", - "id": "9", - "settings": { - "min_doc_count": 1, - "missing": null, - "order": "desc", - "orderBy": "4", - "size": "0" - }, - "type": "terms" - }, { "$$hashKey": "object:798", "fake": true, @@ -3744,14 +3705,11 @@ "renameByName": { "Sum": "Messages", "dkim_aligned": "DKIM Aligned", - "dkim_results.domain.keyword": "DKIM Domain", - "dkim_results.result.keyword": "DKIM Result", - "dkim_results.selector.keyword": "DKIM Selector", + "dkim_results_combined.keyword": "DKIM Selector / Domain / Result", "envelope_from.keyword": "Envelope From", "header_from.keyword": "Header From", "source_base_domain.keyword": "Reverse DNS Base", - "spf_aligned": "SPF Aligned", - "spf_results.result.keyword": "SPF Result" + "spf_aligned": "SPF Aligned" } } } diff --git a/dashboards/opensearch/opensearch_dashboards.ndjson b/dashboards/opensearch/opensearch_dashboards.ndjson index 6db8d3c4..4dcf1d30 100644 --- a/dashboards/opensearch/opensearch_dashboards.ndjson +++ b/dashboards/opensearch/opensearch_dashboards.ndjson @@ -1,27 +1,28 @@ -{"attributes":{"fieldFormatMap":"{\"source_asn\":{\"id\":\"number\",\"params\":{\"parsedUrl\":{\"origin\":\"http://127.0.0.1:5602\",\"pathname\":\"/app/home\",\"basePath\":\"\"},\"pattern\":\"0\"}}}","fields":"[{\"count\":0,\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_score\",\"type\":\"number\",\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_type\",\"type\":\"string\",\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"date_begin\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"date_end\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"date_range\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"discovery_method\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"discovery_method.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"discovery_method\"}}},{\"count\":0,\"name\":\"disposition\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"disposition.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"disposition\"}}},{\"count\":0,\"name\":\"dkim_aligned\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"dkim_results.domain\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"dkim_results.domain.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"dkim_results.domain\"}}},{\"count\":0,\"name\":\"dkim_results.result\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"dkim_results.result.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"dkim_results.result\"}}},{\"count\":0,\"name\":\"dkim_results.selector\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"dkim_results.selector.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"dkim_results.selector\"}}},{\"count\":0,\"name\":\"envelope_from\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"envelope_from.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"envelope_from\"}}},{\"count\":0,\"name\":\"envelope_to\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"envelope_to.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"envelope_to\"}}},{\"count\":0,\"name\":\"errors\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"errors.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"errors\"}}},{\"count\":0,\"name\":\"generator\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"generator.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"generator\"}}},{\"count\":0,\"name\":\"header_from\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"header_from.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"header_from\"}}},{\"count\":0,\"name\":\"message_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"normalized_timespan\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"np\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"np.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"np\"}}},{\"count\":0,\"name\":\"org_email\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"org_email.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"org_email\"}}},{\"count\":0,\"name\":\"org_extra_contact_info\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"org_extra_contact_info.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"org_extra_contact_info\"}}},{\"count\":0,\"name\":\"org_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"org_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"org_name\"}}},{\"count\":0,\"name\":\"passed_dmarc\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"policy_overrides.comment\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"policy_overrides.comment.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"policy_overrides.comment\"}}},{\"count\":0,\"name\":\"policy_overrides.type\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"policy_overrides.type.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"policy_overrides.type\"}}},{\"count\":0,\"name\":\"published_policy.adkim\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"published_policy.adkim.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"published_policy.adkim\"}}},{\"count\":0,\"name\":\"published_policy.aspf\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"published_policy.aspf.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"published_policy.aspf\"}}},{\"count\":0,\"name\":\"published_policy.discovery_method\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"published_policy.discovery_method.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"published_policy.discovery_method\"}}},{\"count\":0,\"name\":\"published_policy.domain\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"published_policy.domain.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"published_policy.domain\"}}},{\"count\":0,\"name\":\"published_policy.fo\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"published_policy.fo.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"published_policy.fo\"}}},{\"count\":0,\"name\":\"published_policy.np\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"published_policy.np.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"published_policy.np\"}}},{\"count\":0,\"name\":\"published_policy.p\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"published_policy.p.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"published_policy.p\"}}},{\"count\":0,\"name\":\"published_policy.pct\",\"type\":\"number\",\"esTypes\":[\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"published_policy.sp\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"published_policy.sp.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"published_policy.sp\"}}},{\"count\":0,\"name\":\"published_policy.testing\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"published_policy.testing.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"published_policy.testing\"}}},{\"count\":0,\"name\":\"report_id\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"report_id.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"report_id\"}}},{\"count\":0,\"name\":\"source_as_domain\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"source_as_domain.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"source_as_domain\"}}},{\"count\":0,\"name\":\"source_as_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"source_as_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"source_as_name\"}}},{\"count\":0,\"name\":\"source_asn\",\"type\":\"number\",\"esTypes\":[\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"source_base_domain\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"source_base_domain.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"source_base_domain\"}}},{\"count\":0,\"name\":\"source_country\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"source_country.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"source_country\"}}},{\"count\":0,\"name\":\"source_ip_address\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"source_ip_address.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"source_ip_address\"}}},{\"count\":0,\"name\":\"source_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"source_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"source_name\"}}},{\"count\":0,\"name\":\"source_reverse_dns\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"source_reverse_dns.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"source_reverse_dns\"}}},{\"count\":0,\"name\":\"source_type\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"source_type.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"source_type\"}}},{\"count\":0,\"name\":\"spf_aligned\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"spf_results.domain\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"spf_results.domain.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"spf_results.domain\"}}},{\"count\":0,\"name\":\"spf_results.result\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"spf_results.result.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"spf_results.result\"}}},{\"count\":0,\"name\":\"spf_results.scope\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"spf_results.scope.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"spf_results.scope\"}}},{\"count\":0,\"name\":\"testing\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"testing.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"testing\"}}},{\"count\":0,\"name\":\"xml_namespace\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"xml_namespace.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"xml_namespace\"}}},{\"count\":0,\"name\":\"xml_schema\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"xml_schema.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"xml_schema\"}}}]","timeFieldName":"date_begin","title":"dmarc_aggregate*"},"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","migrationVersion":{"index-pattern":"7.6.0"},"references":[],"type":"index-pattern","updated_at":"2026-07-21T23:26:45.906Z","version":"WzEzMCwyXQ=="} +{"attributes":{"fieldFormatMap":"{\"source_asn\":{\"id\":\"number\",\"params\":{\"parsedUrl\":{\"origin\":\"http://127.0.0.1:5602\",\"pathname\":\"/app/home\",\"basePath\":\"\"},\"pattern\":\"0\"}}}","fields":"[{\"count\":0,\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_score\",\"type\":\"number\",\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_type\",\"type\":\"string\",\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"date_begin\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"date_end\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"date_range\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"discovery_method\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"discovery_method.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"discovery_method\"}}},{\"count\":0,\"name\":\"disposition\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"disposition.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"disposition\"}}},{\"count\":0,\"name\":\"dkim_aligned\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"dkim_results.domain\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"dkim_results.domain.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"dkim_results.domain\"}}},{\"count\":0,\"name\":\"dkim_results.result\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"dkim_results.result.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"dkim_results.result\"}}},{\"count\":0,\"name\":\"dkim_results.selector\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"dkim_results.selector.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"dkim_results.selector\"}}},{\"count\":0,\"name\":\"dkim_results_combined\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"dkim_results_combined.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"dkim_results_combined\"}}},{\"count\":0,\"name\":\"envelope_from\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"envelope_from.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"envelope_from\"}}},{\"count\":0,\"name\":\"envelope_to\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"envelope_to.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"envelope_to\"}}},{\"count\":0,\"name\":\"errors\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"errors.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"errors\"}}},{\"count\":0,\"name\":\"generator\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"generator.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"generator\"}}},{\"count\":0,\"name\":\"header_from\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"header_from.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"header_from\"}}},{\"count\":0,\"name\":\"message_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"normalized_timespan\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"np\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"np.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"np\"}}},{\"count\":0,\"name\":\"org_email\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"org_email.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"org_email\"}}},{\"count\":0,\"name\":\"org_extra_contact_info\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"org_extra_contact_info.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"org_extra_contact_info\"}}},{\"count\":0,\"name\":\"org_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"org_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"org_name\"}}},{\"count\":0,\"name\":\"passed_dmarc\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"policy_overrides.comment\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"policy_overrides.comment.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"policy_overrides.comment\"}}},{\"count\":0,\"name\":\"policy_overrides.type\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"policy_overrides.type.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"policy_overrides.type\"}}},{\"count\":0,\"name\":\"published_policy.adkim\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"published_policy.adkim.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"published_policy.adkim\"}}},{\"count\":0,\"name\":\"published_policy.aspf\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"published_policy.aspf.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"published_policy.aspf\"}}},{\"count\":0,\"name\":\"published_policy.discovery_method\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"published_policy.discovery_method.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"published_policy.discovery_method\"}}},{\"count\":0,\"name\":\"published_policy.domain\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"published_policy.domain.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"published_policy.domain\"}}},{\"count\":0,\"name\":\"published_policy.fo\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"published_policy.fo.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"published_policy.fo\"}}},{\"count\":0,\"name\":\"published_policy.np\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"published_policy.np.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"published_policy.np\"}}},{\"count\":0,\"name\":\"published_policy.p\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"published_policy.p.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"published_policy.p\"}}},{\"count\":0,\"name\":\"published_policy.pct\",\"type\":\"number\",\"esTypes\":[\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"published_policy.sp\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"published_policy.sp.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"published_policy.sp\"}}},{\"count\":0,\"name\":\"published_policy.testing\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"published_policy.testing.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"published_policy.testing\"}}},{\"count\":0,\"name\":\"report_id\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"report_id.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"report_id\"}}},{\"count\":0,\"name\":\"source_as_domain\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"source_as_domain.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"source_as_domain\"}}},{\"count\":0,\"name\":\"source_as_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"source_as_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"source_as_name\"}}},{\"count\":0,\"name\":\"source_asn\",\"type\":\"number\",\"esTypes\":[\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"source_base_domain\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"source_base_domain.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"source_base_domain\"}}},{\"count\":0,\"name\":\"source_country\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"source_country.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"source_country\"}}},{\"count\":0,\"name\":\"source_ip_address\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"source_ip_address.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"source_ip_address\"}}},{\"count\":0,\"name\":\"source_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"source_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"source_name\"}}},{\"count\":0,\"name\":\"source_reverse_dns\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"source_reverse_dns.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"source_reverse_dns\"}}},{\"count\":0,\"name\":\"source_type\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"source_type.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"source_type\"}}},{\"count\":0,\"name\":\"spf_aligned\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"spf_results.domain\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"spf_results.domain.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"spf_results.domain\"}}},{\"count\":0,\"name\":\"spf_results.result\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"spf_results.result.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"spf_results.result\"}}},{\"count\":0,\"name\":\"spf_results.scope\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"spf_results.scope.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"spf_results.scope\"}}},{\"count\":0,\"name\":\"spf_results_combined\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"spf_results_combined.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"spf_results_combined\"}}},{\"count\":0,\"name\":\"testing\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"testing.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"testing\"}}},{\"count\":0,\"name\":\"xml_namespace\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"xml_namespace.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"xml_namespace\"}}},{\"count\":0,\"name\":\"xml_schema\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"xml_schema.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"xml_schema\"}}}]","timeFieldName":"date_begin","title":"dmarc_aggregate*"},"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","migrationVersion":{"index-pattern":"7.6.0"},"references":[],"type":"index-pattern","updated_at":"2026-07-21T23:26:45.906Z","version":"WzEzMCwyXQ=="} {"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Aggregate DMARC SPF alignment","uiStateJSON":"{\"vis\":{\"colors\":{\"false\":\"#e7664c\",\"true\":\"#54b399\"}}}","version":1,"visState":"{\"title\": \"Aggregate DMARC SPF alignment\", \"type\": \"pie\", \"aggs\": [{\"id\": \"1\", \"enabled\": true, \"type\": \"sum\", \"params\": {\"field\": \"message_count\", \"customLabel\": \"messages\"}, \"schema\": \"metric\"}, {\"id\": \"2\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"spf_aligned\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 5, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": false, \"missingBucketLabel\": \"Missing\", \"customLabel\": \"\"}, \"schema\": \"segment\"}], \"params\": {\"type\": \"pie\", \"addTooltip\": true, \"addLegend\": true, \"legendPosition\": \"right\", \"isDonut\": true, \"labels\": {\"show\": false, \"values\": true, \"last_level\": true, \"truncate\": 100}}}"},"id":"6942d480-262c-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzEzMSwyXQ=="} {"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Aggregate DMARC DKIM alignment","uiStateJSON":"{\"vis\":{\"colors\":{\"false\":\"#e7664c\",\"true\":\"#54b399\"}}}","version":1,"visState":"{\"title\": \"Aggregate DMARC DKIM alignment\", \"type\": \"pie\", \"aggs\": [{\"id\": \"1\", \"enabled\": true, \"type\": \"sum\", \"params\": {\"field\": \"message_count\", \"customLabel\": \"messages\"}, \"schema\": \"metric\"}, {\"id\": \"2\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"dkim_aligned\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 5, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": false, \"missingBucketLabel\": \"Missing\"}, \"schema\": \"segment\"}], \"params\": {\"type\": \"pie\", \"addTooltip\": true, \"addLegend\": true, \"legendPosition\": \"right\", \"isDonut\": true, \"labels\": {\"show\": false, \"values\": true, \"last_level\": true, \"truncate\": 100}}}"},"id":"9e23d140-262c-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzEzMiwyXQ=="} -{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":" Aggregate DMARC passed DMARC","uiStateJSON":"{\"vis\":{\"colors\":{\"false\":\"#e7664c\",\"true\":\"#54b399\"}}}","version":1,"visState":"{\"title\": \" Aggregate DMARC passed DMARC\", \"type\": \"pie\", \"aggs\": [{\"id\": \"1\", \"enabled\": true, \"type\": \"sum\", \"params\": {\"field\": \"message_count\", \"customLabel\": \"messages\"}, \"schema\": \"metric\"}, {\"id\": \"2\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"passed_dmarc\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 5, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": false, \"missingBucketLabel\": \"Missing\"}, \"schema\": \"segment\"}], \"params\": {\"type\": \"pie\", \"addTooltip\": true, \"addLegend\": true, \"legendPosition\": \"right\", \"isDonut\": true, \"labels\": {\"show\": false, \"values\": true, \"last_level\": true, \"truncate\": 100}}}"},"id":"f7789f50-262c-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzEzMywyXQ=="} -{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Aggregate DMARC reporting organizations ","uiStateJSON":"{}","version":1,"visState":"{\"title\": \"Aggregate DMARC reporting organizations \", \"type\": \"table\", \"aggs\": [{\"id\": \"1\", \"enabled\": true, \"type\": \"sum\", \"params\": {\"field\": \"message_count\", \"customLabel\": \"messages\"}, \"schema\": \"metric\"}, {\"id\": \"2\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"org_name.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10000, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": false, \"missingBucketLabel\": \"Missing\", \"customLabel\": \"org_name\"}, \"schema\": \"bucket\"}], \"params\": {\"perPage\": 10, \"showPartialRows\": false, \"showMetricsAtAllLevels\": false, \"showTotal\": false, \"totalFunc\": \"sum\", \"percentageCol\": \"\"}}"},"id":"09053d20-2630-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzEzNCwyXQ=="} +{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Aggregate DMARC passed DMARC","uiStateJSON":"{\"vis\":{\"colors\":{\"false\":\"#e7664c\",\"true\":\"#54b399\"}}}","version":1,"visState":"{\"title\": \"Aggregate DMARC passed DMARC\", \"type\": \"pie\", \"aggs\": [{\"id\": \"1\", \"enabled\": true, \"type\": \"sum\", \"params\": {\"field\": \"message_count\", \"customLabel\": \"messages\"}, \"schema\": \"metric\"}, {\"id\": \"2\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"passed_dmarc\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 5, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": false, \"missingBucketLabel\": \"Missing\"}, \"schema\": \"segment\"}], \"params\": {\"type\": \"pie\", \"addTooltip\": true, \"addLegend\": true, \"legendPosition\": \"right\", \"isDonut\": true, \"labels\": {\"show\": false, \"values\": true, \"last_level\": true, \"truncate\": 100}}}"},"id":"f7789f50-262c-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzEzMywyXQ=="} +{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Aggregate DMARC reporting organizations","uiStateJSON":"{}","version":1,"visState":"{\"title\": \"Aggregate DMARC reporting organizations\", \"type\": \"table\", \"aggs\": [{\"id\": \"1\", \"enabled\": true, \"type\": \"sum\", \"params\": {\"field\": \"message_count\", \"customLabel\": \"messages\"}, \"schema\": \"metric\"}, {\"id\": \"2\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"org_name.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10000, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": false, \"missingBucketLabel\": \"Missing\", \"customLabel\": \"org_name\"}, \"schema\": \"bucket\"}], \"params\": {\"perPage\": 10, \"showPartialRows\": false, \"showMetricsAtAllLevels\": false, \"showTotal\": false, \"totalFunc\": \"sum\", \"percentageCol\": \"\"}}"},"id":"09053d20-2630-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzEzNCwyXQ=="} {"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Aggregate DMARC message sources by reverse DNS","uiStateJSON":"{}","version":1,"visState":"{\"title\": \"Aggregate DMARC message sources by reverse DNS\", \"type\": \"table\", \"aggs\": [{\"id\": \"1\", \"enabled\": true, \"type\": \"sum\", \"params\": {\"field\": \"message_count\", \"customLabel\": \"messages\"}, \"schema\": \"metric\"}, {\"id\": \"2\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"source_base_domain.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10000, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": true, \"missingBucketLabel\": \"none\", \"customLabel\": \"source_base_domain\"}, \"schema\": \"bucket\"}], \"params\": {\"perPage\": 10, \"percentageCol\": \"\", \"showMetricsAtAllLevels\": false, \"showPartialRows\": false, \"showTotal\": false, \"totalFunc\": \"sum\"}}"},"id":"a68cc660-2632-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzEzNSwyXQ=="} {"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\": {\"query\": \"\", \"language\": \"kuery\"}, \"filter\": []}"},"title":"Aggregate DMARC message volume and compliance by from domain","uiStateJSON":"{}","version":1,"visState":"{\"title\": \"Aggregate DMARC message volume and compliance by from domain\", \"type\": \"metrics\", \"aggs\": [], \"params\": {\"id\": \"02551b4f-b5de-4ef1-b55c-a3696ef17d48\", \"type\": \"table\", \"series\": [{\"id\": \"e0b142b4-f3b1-4fd8-8252-ec3b705fddf2\", \"color\": \"#68BC00\", \"split_mode\": \"everything\", \"metrics\": [{\"id\": \"b609fd52-621d-4f93-b62c-e43acdcf5eb1\", \"type\": \"sum\", \"field\": \"message_count\"}], \"separate_axis\": 0, \"axis_position\": \"right\", \"formatter\": \"number\", \"chart_type\": \"line\", \"line_width\": 1, \"point_size\": 1, \"fill\": 0.5, \"stacked\": \"none\", \"label\": \"Messages\"}, {\"id\": \"e4b2f739-c9e7-4f0b-b731-02f37539fd74\", \"color\": \"#68BC00\", \"split_mode\": \"everything\", \"metrics\": [{\"id\": \"9921a5c5-9117-42ee-9a08-91c963019c74\", \"type\": \"filter_ratio\", \"numerator\": {\"query\": \"passed_dmarc: true\", \"language\": \"kuery\"}, \"denominator\": {\"query\": \"*\", \"language\": \"kuery\"}, \"metric_agg\": \"sum\", \"field\": \"message_count\"}], \"separate_axis\": 0, \"axis_position\": \"right\", \"formatter\": \"percent\", \"chart_type\": \"line\", \"line_width\": 1, \"point_size\": 1, \"fill\": 0.5, \"stacked\": \"none\", \"label\": \"% DMARC Compliant\"}], \"time_field\": \"date_begin\", \"index_pattern\": \"dmarc_aggregate*\", \"interval\": \"\", \"axis_position\": \"left\", \"axis_formatter\": \"number\", \"axis_scale\": \"normal\", \"show_legend\": 1, \"show_grid\": 1, \"tooltip_mode\": \"show_all\", \"drop_last_bucket\": 0, \"isModelInvalid\": false, \"pivot_id\": \"header_from.keyword\", \"pivot_label\": \"From Domain\", \"pivot_rows\": \"10000\", \"time_range_mode\": \"entire_time_range\"}}"},"id":"9aa252fc-5cea-4fce-a380-14fbada11e89","migrationVersion":{"visualization":"7.10.0"},"references":[],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzEzNiwyXQ=="} {"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Aggregate DMARC message sources by name and type","uiStateJSON":"{}","version":1,"visState":"{\"title\": \"Aggregate DMARC message sources by name and type\", \"type\": \"table\", \"aggs\": [{\"id\": \"1\", \"enabled\": true, \"type\": \"sum\", \"params\": {\"field\": \"message_count\", \"customLabel\": \"messages\"}, \"schema\": \"metric\"}, {\"id\": \"2\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"source_name.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10000, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": true, \"missingBucketLabel\": \"none\", \"customLabel\": \"source_name\"}, \"schema\": \"bucket\"}, {\"id\": \"3\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"source_type.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 2000, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": true, \"missingBucketLabel\": \"none\", \"customLabel\": \"source_type\"}, \"schema\": \"bucket\"}], \"params\": {\"perPage\": 10, \"showPartialRows\": false, \"showMetricsAtAllLevels\": false, \"showTotal\": false, \"totalFunc\": \"sum\", \"percentageCol\": \"\"}}"},"id":"81380390-2635-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzEzNywyXQ=="} {"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Aggregate DMARC message sources by Autonomous System","uiStateJSON":"{}","version":1,"visState":"{\"title\": \"Aggregate DMARC message sources by Autonomous System\", \"type\": \"table\", \"aggs\": [{\"id\": \"1\", \"enabled\": true, \"type\": \"sum\", \"params\": {\"field\": \"message_count\", \"customLabel\": \"messages\"}, \"schema\": \"metric\"}, {\"id\": \"2\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"source_asn\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10000, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": false, \"missingBucketLabel\": \"Missing\", \"customLabel\": \"asn\"}, \"schema\": \"bucket\"}, {\"id\": \"3\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"source_as_name.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10000, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": true, \"missingBucketLabel\": \"\", \"customLabel\": \"as_name\"}, \"schema\": \"bucket\"}, {\"id\": \"4\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"source_as_domain.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10000, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": true, \"missingBucketLabel\": \"\", \"customLabel\": \"as_domain\"}, \"schema\": \"bucket\"}], \"params\": {\"perPage\": 10, \"showPartialRows\": false, \"showMetricsAtAllLevels\": false, \"showTotal\": false, \"totalFunc\": \"sum\", \"percentageCol\": \"\"}}"},"id":"adec76e0-3f68-11f1-a327-dd68bf273446","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzEzOCwyXQ=="} {"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Aggregate DMARC passage over time","uiStateJSON":"{\"vis\":{\"colors\":{\"false\":\"#e7664c\",\"true\":\"#54b399\"}}}","version":1,"visState":"{\"title\": \"Aggregate DMARC passage over time\", \"type\": \"line\", \"aggs\": [{\"id\": \"1\", \"enabled\": true, \"type\": \"sum\", \"params\": {\"field\": \"message_count\", \"customLabel\": \"messages\"}, \"schema\": \"metric\"}, {\"id\": \"2\", \"enabled\": true, \"type\": \"date_histogram\", \"params\": {\"field\": \"date_begin\", \"timeRange\": {\"from\": \"now-7d\", \"to\": \"now\"}, \"useNormalizedOpenSearchInterval\": true, \"scaleMetricValues\": false, \"interval\": \"d\", \"drop_partials\": false, \"min_doc_count\": 1, \"extended_bounds\": {}}, \"schema\": \"segment\"}, {\"id\": \"3\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"passed_dmarc\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 5, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": false, \"missingBucketLabel\": \"Missing\"}, \"schema\": \"group\"}], \"params\": {\"type\": \"line\", \"grid\": {\"categoryLines\": false}, \"categoryAxes\": [{\"id\": \"CategoryAxis-1\", \"type\": \"category\", \"position\": \"bottom\", \"show\": true, \"style\": {}, \"scale\": {\"type\": \"linear\"}, \"labels\": {\"show\": true, \"filter\": true, \"truncate\": 100}, \"title\": {}}], \"valueAxes\": [{\"id\": \"ValueAxis-1\", \"name\": \"LeftAxis-1\", \"type\": \"value\", \"position\": \"left\", \"show\": true, \"style\": {}, \"scale\": {\"type\": \"linear\", \"mode\": \"normal\"}, \"labels\": {\"show\": true, \"rotate\": 0, \"filter\": false, \"truncate\": 100}, \"title\": {\"text\": \"Sum of message_count\"}}], \"seriesParams\": [{\"show\": true, \"type\": \"line\", \"mode\": \"normal\", \"data\": {\"label\": \"Sum of message_count\", \"id\": \"1\"}, \"valueAxis\": \"ValueAxis-1\", \"drawLinesBetweenPoints\": true, \"lineWidth\": 2, \"interpolate\": \"linear\", \"showCircles\": true}], \"addTooltip\": true, \"addLegend\": true, \"legendPosition\": \"right\", \"times\": [], \"addTimeMarker\": false, \"labels\": {}, \"thresholdLine\": {\"show\": false, \"value\": 10, \"width\": 1, \"style\": \"full\", \"color\": \"#E7664C\"}, \"row\": true}}"},"id":"0b277550-263a-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzEzOSwyXQ=="} {"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Aggregate DMARC message disposition over time","uiStateJSON":"{\"vis\":{\"colors\":{\"false\":\"#e7664c\",\"none\":\"#54b399\",\"quarantine\":\"#d6bf57\",\"reject\":\"#e7664c\",\"true\":\"#54b399\"}}}","version":1,"visState":"{\"title\": \"Aggregate DMARC message disposition over time\", \"type\": \"line\", \"aggs\": [{\"id\": \"1\", \"enabled\": true, \"type\": \"sum\", \"params\": {\"field\": \"message_count\", \"customLabel\": \"messages\"}, \"schema\": \"metric\"}, {\"id\": \"2\", \"enabled\": true, \"type\": \"date_histogram\", \"params\": {\"field\": \"date_begin\", \"timeRange\": {\"from\": \"now-7d\", \"to\": \"now\"}, \"useNormalizedOpenSearchInterval\": true, \"scaleMetricValues\": false, \"interval\": \"d\", \"drop_partials\": false, \"min_doc_count\": 1, \"extended_bounds\": {}}, \"schema\": \"segment\"}, {\"id\": \"3\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"disposition.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": true, \"missingBucketLabel\": \"missing\"}, \"schema\": \"group\"}], \"params\": {\"addLegend\": true, \"addTimeMarker\": false, \"addTooltip\": true, \"categoryAxes\": [{\"id\": \"CategoryAxis-1\", \"labels\": {\"filter\": true, \"show\": true, \"truncate\": 100}, \"position\": \"bottom\", \"scale\": {\"type\": \"linear\"}, \"show\": true, \"style\": {}, \"title\": {}, \"type\": \"category\"}], \"grid\": {\"categoryLines\": false}, \"labels\": {}, \"legendPosition\": \"right\", \"row\": true, \"seriesParams\": [{\"data\": {\"id\": \"1\", \"label\": \"Sum of message_count\"}, \"drawLinesBetweenPoints\": true, \"interpolate\": \"linear\", \"lineWidth\": 2, \"mode\": \"normal\", \"show\": true, \"showCircles\": true, \"type\": \"line\", \"valueAxis\": \"ValueAxis-1\"}], \"thresholdLine\": {\"color\": \"#E7664C\", \"show\": false, \"style\": \"full\", \"value\": 10, \"width\": 1}, \"times\": [], \"type\": \"line\", \"valueAxes\": [{\"id\": \"ValueAxis-1\", \"labels\": {\"filter\": false, \"rotate\": 0, \"show\": true, \"truncate\": 100}, \"name\": \"LeftAxis-1\", \"position\": \"left\", \"scale\": {\"mode\": \"normal\", \"type\": \"linear\"}, \"show\": true, \"style\": {}, \"title\": {\"text\": \"Sum of message_count\"}, \"type\": \"value\"}]}}"},"id":"d4545010-263a-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE0MCwyXQ=="} -{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Aggregate DMARC map of message sources by country","uiStateJSON":"{}","version":1,"visState":"{\"title\": \"Aggregate DMARC map of message sources by country\", \"type\": \"region_map\", \"aggs\": [{\"id\": \"1\", \"enabled\": true, \"type\": \"sum\", \"params\": {\"field\": \"message_count\", \"customLabel\": \"messages\"}, \"schema\": \"metric\"}, {\"id\": \"2\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"source_country.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 500, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": false, \"missingBucketLabel\": \"Missing\"}, \"schema\": \"segment\"}], \"params\": {\"addTooltip\": true, \"colorSchema\": \"Yellow to Red\", \"emsHotLink\": \"?locale=en#file/world_countries\", \"isDisplayWarning\": true, \"layerChosenByUser\": \"default\", \"legendPosition\": \"bottomright\", \"mapCenter\": [0, 0], \"mapZoom\": 2, \"outlineWeight\": 1, \"selectedCustomJoinField\": null, \"selectedJoinField\": {\"description\": \"ISO 3166-1 alpha-2 Code\", \"name\": \"iso2\", \"type\": \"id\"}, \"selectedLayer\": {\"attribution\": \"Made with NaturalEarth\", \"created_at\": \"2017-04-26T17:12:15.978370\", \"fields\": [{\"description\": \"ISO 3166-1 alpha-2 Code\", \"name\": \"iso2\", \"type\": \"id\"}, {\"description\": \"ISO 3166-1 alpha-3 Code\", \"name\": \"iso3\", \"type\": \"id\"}, {\"description\": \"Name\", \"name\": \"name\", \"type\": \"name\"}], \"format\": {\"type\": \"geojson\"}, \"id\": \"world_countries\", \"isEMS\": true, \"layerId\": \"elastic_maps_service.World Countries\", \"name\": \"World Countries\", \"origin\": \"elastic_maps_service\"}, \"showAllShapes\": true, \"wms\": {\"enabled\": false, \"options\": {\"attribution\": \"\", \"format\": \"image/png\", \"layers\": \"\", \"styles\": \"\", \"transparent\": true, \"version\": \"\"}, \"selectedTmsLayer\": {\"attribution\": \"Map data \\u00a9 OpenStreetMap contributors\", \"id\": \"road_map\", \"maxZoom\": 14, \"minZoom\": 0, \"origin\": \"elastic_maps_service\"}, \"url\": \"\"}}}"},"id":"bf2bfba0-263c-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE0MSwyXQ=="} +{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Aggregate DMARC map of message sources by country","uiStateJSON":"{}","version":1,"visState":"{\"title\": \"Aggregate DMARC map of message sources by country\", \"type\": \"region_map\", \"aggs\": [{\"id\": \"1\", \"enabled\": true, \"type\": \"sum\", \"params\": {\"field\": \"message_count\", \"customLabel\": \"messages\"}, \"schema\": \"metric\"}, {\"id\": \"2\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"source_country.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 500, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": false, \"missingBucketLabel\": \"Missing\"}, \"schema\": \"segment\"}], \"params\": {\"addTooltip\": true, \"colorSchema\": \"Yellow to Red\", \"emsHotLink\": \"?locale=en#file/world_countries\", \"isDisplayWarning\": true, \"layerChosenByUser\": \"default\", \"legendPosition\": \"bottomright\", \"mapCenter\": [0, 0], \"mapZoom\": 2, \"outlineWeight\": 1, \"selectedCustomJoinField\": null, \"selectedJoinField\": {\"description\": \"ISO 3166-1 alpha-2 Code\", \"name\": \"iso2\", \"type\": \"id\"}, \"selectedLayer\": {\"attribution\": \"Made with NaturalEarth\", \"created_at\": \"2017-04-26T17:12:15.978370\", \"fields\": [{\"description\": \"ISO 3166-1 alpha-2 Code\", \"name\": \"iso2\", \"type\": \"id\"}, {\"description\": \"ISO 3166-1 alpha-3 Code\", \"name\": \"iso3\", \"type\": \"id\"}, {\"description\": \"Name\", \"name\": \"name\", \"type\": \"name\"}], \"format\": {\"type\": \"geojson\"}, \"id\": \"world_countries\", \"isEMS\": true, \"layerId\": \"elastic_maps_service.World Countries\", \"name\": \"World Countries\", \"origin\": \"elastic_maps_service\"}, \"showAllShapes\": true, \"wms\": {\"enabled\": false, \"options\": {\"attribution\": \"\", \"format\": \"image/png\", \"layers\": \"\", \"styles\": \"\", \"transparent\": true, \"version\": \"\"}, \"selectedTmsLayer\": {\"attribution\": \"Map data \\u00a9 OpenStreetMap contributors\", \"id\": \"road_map\", \"maxZoom\": 14, \"minZoom\": 0, \"origin\": \"elastic_maps_service\"}, \"url\": \"\"}}}"},"id":"bf2bfba0-263c-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE0MSwyXQ=="} {"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Aggregate DMARC message sources by country","uiStateJSON":"{}","version":1,"visState":"{\"title\": \"Aggregate DMARC message sources by country\", \"type\": \"table\", \"aggs\": [{\"id\": \"1\", \"enabled\": true, \"type\": \"sum\", \"params\": {\"field\": \"message_count\", \"customLabel\": \"messages\"}, \"schema\": \"metric\"}, {\"id\": \"2\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"source_country.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 500, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": true, \"missingBucketLabel\": \"unknown\", \"customLabel\": \"source_country\"}, \"schema\": \"bucket\"}], \"params\": {\"perPage\": 10, \"showPartialRows\": false, \"showMetricsAtAllLevels\": false, \"showTotal\": false, \"totalFunc\": \"sum\", \"percentageCol\": \"\"}}"},"id":"0bcd9900-263d-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE0MiwyXQ=="} {"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Aggregate DMARC message sources by IP address","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Aggregate DMARC message sources by IP address\",\"type\":\"table\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"sum\",\"params\":{\"field\":\"message_count\",\"customLabel\":\"messages\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"source_ip_address.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"ip_address\"},\"schema\":\"bucket\"},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"source_reverse_dns.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":true,\"missingBucketLabel\":\"none\",\"customLabel\":\"reverse_dns\"},\"schema\":\"bucket\"},{\"id\":\"4\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"source_base_domain.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":true,\"missingBucketLabel\":\"none\",\"customLabel\":\"reverse_dns_base_domain\"},\"schema\":\"bucket\"},{\"id\":\"5\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"source_country.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":true,\"missingBucketLabel\":\"unknown\",\"customLabel\":\"country\"},\"schema\":\"bucket\"}],\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMetricsAtAllLevels\":false,\"showTotal\":false,\"totalFunc\":\"sum\",\"percentageCol\":\"\"}}"},"id":"a8143340-263e-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE0MywyXQ=="} -{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Aggregate DMARC SPF details","uiStateJSON":"{}","version":1,"visState":"{\"title\": \"Aggregate DMARC SPF details\", \"type\": \"table\", \"aggs\": [{\"id\": \"1\", \"enabled\": true, \"type\": \"sum\", \"params\": {\"field\": \"message_count\", \"customLabel\": \"messages\"}, \"schema\": \"metric\"}, {\"id\": \"2\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"header_from.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10000, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": false, \"missingBucketLabel\": \"Missing\", \"customLabel\": \"header_from\"}, \"schema\": \"bucket\"}, {\"id\": \"3\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"envelope_from.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10000, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": false, \"missingBucketLabel\": \"Missing\", \"customLabel\": \"envelope_from\"}, \"schema\": \"bucket\"}, {\"id\": \"4\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"spf_results.result.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": true, \"missingBucketLabel\": \"none\", \"customLabel\": \"spf_result\"}, \"schema\": \"bucket\"}, {\"id\": \"5\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"source_base_domain.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10000, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": true, \"missingBucketLabel\": \"none\", \"customLabel\": \"base_reverse_dns\"}, \"schema\": \"bucket\"}, {\"id\": \"6\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"spf_aligned\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 2, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": false, \"missingBucketLabel\": \"Missing\", \"customLabel\": \"spf_aligned\"}, \"schema\": \"bucket\"}], \"params\": {\"perPage\": 10, \"showPartialRows\": false, \"showMetricsAtAllLevels\": false, \"showTotal\": false, \"totalFunc\": \"sum\", \"percentageCol\": \"\"}}"},"id":"9be589f0-2640-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE0NCwyXQ=="} -{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Aggregate DMARC DKIM details","uiStateJSON":"{}","version":1,"visState":"{\"title\": \"Aggregate DMARC DKIM details\", \"type\": \"table\", \"aggs\": [{\"id\": \"1\", \"enabled\": true, \"type\": \"sum\", \"params\": {\"field\": \"message_count\", \"customLabel\": \"messages\"}, \"schema\": \"metric\"}, {\"id\": \"2\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"header_from.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10000, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": false, \"missingBucketLabel\": \"Missing\", \"customLabel\": \"header_from\"}, \"schema\": \"bucket\"}, {\"id\": \"3\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"dkim_results.selector.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10000, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": false, \"missingBucketLabel\": \"Missing\", \"customLabel\": \"dkim_selector\"}, \"schema\": \"bucket\"}, {\"id\": \"4\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"dkim_results.domain.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10000, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": false, \"missingBucketLabel\": \"Missing\", \"customLabel\": \"dkim_domain\"}, \"schema\": \"bucket\"}, {\"id\": \"5\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"dkim_results.result.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": false, \"missingBucketLabel\": \"Missing\", \"customLabel\": \"dkim_result\"}, \"schema\": \"bucket\"}, {\"id\": \"6\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"dkim_aligned\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 2, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": false, \"missingBucketLabel\": \"Missing\"}, \"schema\": \"bucket\"}, {\"id\": \"7\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"source_base_domain.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10000, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": true, \"missingBucketLabel\": \"none\", \"customLabel\": \"base_reverse_dns\"}, \"schema\": \"bucket\"}], \"params\": {\"perPage\": 10, \"showPartialRows\": false, \"showMetricsAtAllLevels\": false, \"showTotal\": false, \"totalFunc\": \"sum\", \"percentageCol\": \"\"}}"},"id":"7f743d10-2641-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE0NSwyXQ=="} -{"attributes":{"description":"","hits":0,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[]}"},"optionsJSON":"{\"hidePanelTitles\":false,\"useMargins\":true}","panelsJSON":"[{\"version\": \"3.5.0\", \"gridData\": {\"h\": 14, \"i\": \"04aa12b8-a1d4-4826-9114-c93089a84d83\", \"w\": 17, \"x\": 0, \"y\": 0}, \"panelIndex\": \"04aa12b8-a1d4-4826-9114-c93089a84d83\", \"embeddableConfig\": {\"hidePanelTitles\": false, \"vis\": {\"colors\": {\"false\": \"#e7664c\", \"true\": \"#54b399\"}, \"legendOpen\": false}}, \"title\": \"SPF alignment\", \"panelRefName\": \"panel_0\"}, {\"version\": \"3.5.0\", \"gridData\": {\"h\": 14, \"i\": \"99c4825f-503a-4541-8ace-4a4e899720ca\", \"w\": 15, \"x\": 17, \"y\": 0}, \"panelIndex\": \"99c4825f-503a-4541-8ace-4a4e899720ca\", \"embeddableConfig\": {\"vis\": {\"colors\": {\"false\": \"#e7664c\", \"true\": \"#54b399\"}, \"legendOpen\": false}}, \"panelRefName\": \"panel_1\", \"title\": \"DKIM alignment\"}, {\"version\": \"3.5.0\", \"gridData\": {\"h\": 14, \"i\": \"eb18b561-a461-4346-be47-8b78781a259c\", \"w\": 16, \"x\": 32, \"y\": 0}, \"panelIndex\": \"eb18b561-a461-4346-be47-8b78781a259c\", \"embeddableConfig\": {\"hidePanelTitles\": false, \"vis\": {\"colors\": {\"false\": \"#e7664c\", \"true\": \"#54b399\"}, \"legendOpen\": false}}, \"title\": \"Passed DMARC\", \"panelRefName\": \"panel_2\"}, {\"version\": \"3.5.0\", \"gridData\": {\"h\": 20, \"i\": \"4d681551-865b-41ce-9886-a23f5c0b83df\", \"w\": 17, \"x\": 0, \"y\": 14}, \"panelIndex\": \"4d681551-865b-41ce-9886-a23f5c0b83df\", \"embeddableConfig\": {\"hidePanelTitles\": false, \"vis\": {\"columnsWidth\": [{\"colIndex\": 1, \"width\": 279.5}], \"sortColumn\": {\"colIndex\": 1, \"direction\": \"desc\"}}}, \"title\": \"Reporting organizations \", \"panelRefName\": \"panel_3\"}, {\"version\": \"3.5.0\", \"gridData\": {\"h\": 20, \"i\": \"b78ed029-a1d6-43a6-bc59-8edc2757da11\", \"w\": 15, \"x\": 17, \"y\": 14}, \"panelIndex\": \"b78ed029-a1d6-43a6-bc59-8edc2757da11\", \"embeddableConfig\": {\"hidePanelTitles\": false, \"vis\": {\"sortColumn\": {\"colIndex\": 1, \"direction\": \"desc\"}}}, \"title\": \"Message sources by reverse DNS\", \"panelRefName\": \"panel_4\"}, {\"version\": \"3.5.0\", \"gridData\": {\"h\": 20, \"i\": \"ec9c2421-85be-4e0b-91c1-c0c90a19871e\", \"w\": 16, \"x\": 32, \"y\": 14}, \"panelIndex\": \"ec9c2421-85be-4e0b-91c1-c0c90a19871e\", \"embeddableConfig\": {\"hidePanelTitles\": false}, \"title\": \"Message volume and DMARC compliance by from domain\", \"panelRefName\": \"panel_5\"}, {\"version\": \"3.5.0\", \"gridData\": {\"h\": 21, \"i\": \"b0c4d0ec-4e34-4094-8e3e-f180bffafc78\", \"w\": 48, \"x\": 0, \"y\": 34}, \"panelIndex\": \"b0c4d0ec-4e34-4094-8e3e-f180bffafc78\", \"embeddableConfig\": {\"hidePanelTitles\": false, \"vis\": {\"sortColumn\": {\"colIndex\": 2, \"direction\": \"desc\"}}}, \"title\": \"Message sources by name and type\", \"panelRefName\": \"panel_6\"}, {\"version\": \"3.5.0\", \"gridData\": {\"h\": 17, \"i\": \"54f61a15-0c6d-47b4-89c1-02027997a72e\", \"w\": 48, \"x\": 0, \"y\": 55}, \"panelIndex\": \"54f61a15-0c6d-47b4-89c1-02027997a72e\", \"embeddableConfig\": {\"hidePanelTitles\": false}, \"title\": \"Message sources by Autonomous System\", \"panelRefName\": \"panel_7\"}, {\"version\": \"3.5.0\", \"gridData\": {\"h\": 19, \"i\": \"26903ac4-8896-4104-9616-4d52a407163f\", \"w\": 48, \"x\": 0, \"y\": 72}, \"panelIndex\": \"26903ac4-8896-4104-9616-4d52a407163f\", \"embeddableConfig\": {\"hidePanelTitles\": false}, \"title\": \"DMARC passage over time\", \"panelRefName\": \"panel_8\"}, {\"version\": \"3.5.0\", \"gridData\": {\"h\": 18, \"i\": \"4b75365f-31c9-47c7-b9dd-5d6fd232dc70\", \"w\": 48, \"x\": 0, \"y\": 91}, \"panelIndex\": \"4b75365f-31c9-47c7-b9dd-5d6fd232dc70\", \"embeddableConfig\": {\"hidePanelTitles\": false}, \"title\": \"Message disposition over time\", \"panelRefName\": \"panel_9\"}, {\"version\": \"3.5.0\", \"gridData\": {\"h\": 26, \"i\": \"972bdc59-a9c2-4c6c-8d1a-fbac426c114a\", \"w\": 32, \"x\": 0, \"y\": 109}, \"panelIndex\": \"972bdc59-a9c2-4c6c-8d1a-fbac426c114a\", \"embeddableConfig\": {\"hidePanelTitles\": false}, \"title\": \"Map of message sources by country\", \"panelRefName\": \"panel_10\"}, {\"version\": \"3.5.0\", \"gridData\": {\"h\": 26, \"i\": \"16f2ee38-e678-43ee-a531-304112cb5ba6\", \"w\": 16, \"x\": 32, \"y\": 109}, \"panelIndex\": \"16f2ee38-e678-43ee-a531-304112cb5ba6\", \"embeddableConfig\": {\"hidePanelTitles\": false, \"vis\": {\"sortColumn\": {\"colIndex\": 1, \"direction\": \"desc\"}}}, \"title\": \"Message sources by country\", \"panelRefName\": \"panel_11\"}, {\"version\": \"3.5.0\", \"gridData\": {\"h\": 18, \"i\": \"035b5c90-70a1-4844-b824-1cca531d5984\", \"w\": 48, \"x\": 0, \"y\": 135}, \"panelIndex\": \"035b5c90-70a1-4844-b824-1cca531d5984\", \"embeddableConfig\": {\"hidePanelTitles\": false, \"vis\": {\"sortColumn\": {\"colIndex\": 4, \"direction\": \"desc\"}}}, \"title\": \"Message sources by IP address\", \"panelRefName\": \"panel_12\"}, {\"version\": \"3.5.0\", \"gridData\": {\"h\": 15, \"i\": \"d3bafb75-ddef-4ad3-b71a-e78ba2ff92c4\", \"w\": 48, \"x\": 0, \"y\": 153}, \"panelIndex\": \"d3bafb75-ddef-4ad3-b71a-e78ba2ff92c4\", \"embeddableConfig\": {\"hidePanelTitles\": false, \"vis\": {\"sortColumn\": {\"colIndex\": 4, \"direction\": \"desc\"}}}, \"title\": \"SPF details\", \"panelRefName\": \"panel_13\"}, {\"version\": \"3.5.0\", \"gridData\": {\"h\": 11, \"i\": \"b22eb937-6456-486f-a183-8920f6d09f01\", \"w\": 48, \"x\": 0, \"y\": 168}, \"panelIndex\": \"b22eb937-6456-486f-a183-8920f6d09f01\", \"embeddableConfig\": {\"vis\": {\"sortColumn\": {\"colIndex\": 6, \"direction\": \"desc\"}}, \"hidePanelTitles\": false}, \"title\": \"DKIM details\", \"panelRefName\": \"panel_14\"}]","refreshInterval":{"pause":true,"value":0},"timeFrom":"now-7d","timeRestore":true,"timeTo":"now","title":"DMARC aggregate reports","version":1},"id":"50c317b0-262e-11f1-96a6-fb3734bd0b21","migrationVersion":{"dashboard":"7.9.3"},"references":[{"id":"6942d480-262c-11f1-96a6-fb3734bd0b21","name":"panel_0","type":"visualization"},{"id":"9e23d140-262c-11f1-96a6-fb3734bd0b21","name":"panel_1","type":"visualization"},{"id":"f7789f50-262c-11f1-96a6-fb3734bd0b21","name":"panel_2","type":"visualization"},{"id":"09053d20-2630-11f1-96a6-fb3734bd0b21","name":"panel_3","type":"visualization"},{"id":"a68cc660-2632-11f1-96a6-fb3734bd0b21","name":"panel_4","type":"visualization"},{"id":"9aa252fc-5cea-4fce-a380-14fbada11e89","name":"panel_5","type":"visualization"},{"id":"81380390-2635-11f1-96a6-fb3734bd0b21","name":"panel_6","type":"visualization"},{"id":"adec76e0-3f68-11f1-a327-dd68bf273446","name":"panel_7","type":"visualization"},{"id":"0b277550-263a-11f1-96a6-fb3734bd0b21","name":"panel_8","type":"visualization"},{"id":"d4545010-263a-11f1-96a6-fb3734bd0b21","name":"panel_9","type":"visualization"},{"id":"bf2bfba0-263c-11f1-96a6-fb3734bd0b21","name":"panel_10","type":"visualization"},{"id":"0bcd9900-263d-11f1-96a6-fb3734bd0b21","name":"panel_11","type":"visualization"},{"id":"a8143340-263e-11f1-96a6-fb3734bd0b21","name":"panel_12","type":"visualization"},{"id":"9be589f0-2640-11f1-96a6-fb3734bd0b21","name":"panel_13","type":"visualization"},{"id":"7f743d10-2641-11f1-96a6-fb3734bd0b21","name":"panel_14","type":"visualization"}],"type":"dashboard","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE0NiwyXQ=="} +{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Aggregate DMARC SPF details","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Aggregate DMARC SPF details\",\"type\":\"table\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"sum\",\"params\":{\"field\":\"message_count\",\"customLabel\":\"messages\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"header_from.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"header_from\"},\"schema\":\"bucket\"},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"envelope_from.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"envelope_from\"},\"schema\":\"bucket\"},{\"id\":\"4\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"spf_results_combined.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":true,\"missingBucketLabel\":\"none\",\"customLabel\":\"spf (scope / domain / result)\"},\"schema\":\"bucket\"},{\"id\":\"5\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"source_base_domain.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":true,\"missingBucketLabel\":\"none\",\"customLabel\":\"base_reverse_dns\"},\"schema\":\"bucket\"},{\"id\":\"6\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"spf_aligned\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":2,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"spf_aligned\"},\"schema\":\"bucket\"}],\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMetricsAtAllLevels\":false,\"showTotal\":false,\"totalFunc\":\"sum\",\"percentageCol\":\"\"}}"},"id":"9be589f0-2640-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE0NCwyXQ=="} +{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Aggregate DMARC DKIM details","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Aggregate DMARC DKIM details\",\"type\":\"table\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"sum\",\"params\":{\"field\":\"message_count\",\"customLabel\":\"messages\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"header_from.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"header_from\"},\"schema\":\"bucket\"},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"dkim_results_combined.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"dkim (selector / domain / result)\"},\"schema\":\"bucket\"},{\"id\":\"6\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"dkim_aligned\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":2,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"},\"schema\":\"bucket\"},{\"id\":\"7\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"source_base_domain.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":true,\"missingBucketLabel\":\"none\",\"customLabel\":\"base_reverse_dns\"},\"schema\":\"bucket\"}],\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMetricsAtAllLevels\":false,\"showTotal\":false,\"totalFunc\":\"sum\",\"percentageCol\":\"\"}}"},"id":"7f743d10-2641-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE0NSwyXQ=="} +{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"title":"Aggregate DMARC auth result filters","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Aggregate DMARC auth result filters\",\"type\":\"input_control_vis\",\"aggs\":[],\"params\":{\"controls\":[{\"id\":\"1\",\"indexPatternRefName\":\"control_0_index_pattern\",\"fieldName\":\"dkim_results.selector.keyword\",\"parent\":\"\",\"label\":\"DKIM selector\",\"type\":\"list\",\"options\":{\"type\":\"terms\",\"multiselect\":true,\"dynamicOptions\":true,\"size\":5,\"order\":\"desc\"}},{\"id\":\"2\",\"indexPatternRefName\":\"control_1_index_pattern\",\"fieldName\":\"dkim_results.domain.keyword\",\"parent\":\"\",\"label\":\"DKIM domain\",\"type\":\"list\",\"options\":{\"type\":\"terms\",\"multiselect\":true,\"dynamicOptions\":true,\"size\":5,\"order\":\"desc\"}},{\"id\":\"3\",\"indexPatternRefName\":\"control_2_index_pattern\",\"fieldName\":\"dkim_results.result.keyword\",\"parent\":\"\",\"label\":\"DKIM result\",\"type\":\"list\",\"options\":{\"type\":\"terms\",\"multiselect\":true,\"dynamicOptions\":true,\"size\":5,\"order\":\"desc\"}},{\"id\":\"4\",\"indexPatternRefName\":\"control_3_index_pattern\",\"fieldName\":\"spf_results.scope.keyword\",\"parent\":\"\",\"label\":\"SPF scope\",\"type\":\"list\",\"options\":{\"type\":\"terms\",\"multiselect\":true,\"dynamicOptions\":true,\"size\":5,\"order\":\"desc\"}},{\"id\":\"5\",\"indexPatternRefName\":\"control_4_index_pattern\",\"fieldName\":\"spf_results.domain.keyword\",\"parent\":\"\",\"label\":\"SPF domain\",\"type\":\"list\",\"options\":{\"type\":\"terms\",\"multiselect\":true,\"dynamicOptions\":true,\"size\":5,\"order\":\"desc\"}},{\"id\":\"6\",\"indexPatternRefName\":\"control_5_index_pattern\",\"fieldName\":\"spf_results.result.keyword\",\"parent\":\"\",\"label\":\"SPF result\",\"type\":\"list\",\"options\":{\"type\":\"terms\",\"multiselect\":true,\"dynamicOptions\":true,\"size\":5,\"order\":\"desc\"}}],\"updateFiltersOnChange\":false,\"useTimeFilter\":false,\"pinFilters\":false}}"},"id":"8c2a7d40-2a11-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"control_0_index_pattern","type":"index-pattern"},{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"control_1_index_pattern","type":"index-pattern"},{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"control_2_index_pattern","type":"index-pattern"},{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"control_3_index_pattern","type":"index-pattern"},{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"control_4_index_pattern","type":"index-pattern"},{"id":"e1143020-2628-11f1-96a6-fb3734bd0b21","name":"control_5_index_pattern","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE0NSwyXQ=="} +{"attributes":{"description":"","hits":0,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[]}"},"optionsJSON":"{\"hidePanelTitles\":false,\"useMargins\":true}","panelsJSON":"[{\"version\":\"3.5.0\",\"gridData\":{\"h\":14,\"i\":\"04aa12b8-a1d4-4826-9114-c93089a84d83\",\"w\":17,\"x\":0,\"y\":0},\"panelIndex\":\"04aa12b8-a1d4-4826-9114-c93089a84d83\",\"embeddableConfig\":{\"hidePanelTitles\":false,\"vis\":{\"colors\":{\"false\":\"#e7664c\",\"true\":\"#54b399\"},\"legendOpen\":false}},\"title\":\"SPF alignment\",\"panelRefName\":\"panel_0\"},{\"version\":\"3.5.0\",\"gridData\":{\"h\":14,\"i\":\"99c4825f-503a-4541-8ace-4a4e899720ca\",\"w\":15,\"x\":17,\"y\":0},\"panelIndex\":\"99c4825f-503a-4541-8ace-4a4e899720ca\",\"embeddableConfig\":{\"vis\":{\"colors\":{\"false\":\"#e7664c\",\"true\":\"#54b399\"},\"legendOpen\":false}},\"panelRefName\":\"panel_1\",\"title\":\"DKIM alignment\"},{\"version\":\"3.5.0\",\"gridData\":{\"h\":14,\"i\":\"eb18b561-a461-4346-be47-8b78781a259c\",\"w\":16,\"x\":32,\"y\":0},\"panelIndex\":\"eb18b561-a461-4346-be47-8b78781a259c\",\"embeddableConfig\":{\"hidePanelTitles\":false,\"vis\":{\"colors\":{\"false\":\"#e7664c\",\"true\":\"#54b399\"},\"legendOpen\":false}},\"title\":\"Passed DMARC\",\"panelRefName\":\"panel_2\"},{\"version\":\"3.5.0\",\"gridData\":{\"h\":20,\"i\":\"4d681551-865b-41ce-9886-a23f5c0b83df\",\"w\":17,\"x\":0,\"y\":14},\"panelIndex\":\"4d681551-865b-41ce-9886-a23f5c0b83df\",\"embeddableConfig\":{\"hidePanelTitles\":false,\"vis\":{\"columnsWidth\":[{\"colIndex\":1,\"width\":279.5}],\"sortColumn\":{\"colIndex\":1,\"direction\":\"desc\"}}},\"title\":\"Reporting organizations\",\"panelRefName\":\"panel_3\"},{\"version\":\"3.5.0\",\"gridData\":{\"h\":20,\"i\":\"b78ed029-a1d6-43a6-bc59-8edc2757da11\",\"w\":15,\"x\":17,\"y\":14},\"panelIndex\":\"b78ed029-a1d6-43a6-bc59-8edc2757da11\",\"embeddableConfig\":{\"hidePanelTitles\":false,\"vis\":{\"sortColumn\":{\"colIndex\":1,\"direction\":\"desc\"}}},\"title\":\"Message sources by reverse DNS\",\"panelRefName\":\"panel_4\"},{\"version\":\"3.5.0\",\"gridData\":{\"h\":20,\"i\":\"ec9c2421-85be-4e0b-91c1-c0c90a19871e\",\"w\":16,\"x\":32,\"y\":14},\"panelIndex\":\"ec9c2421-85be-4e0b-91c1-c0c90a19871e\",\"embeddableConfig\":{\"hidePanelTitles\":false},\"title\":\"Message volume and DMARC compliance by from domain\",\"panelRefName\":\"panel_5\"},{\"version\":\"3.5.0\",\"gridData\":{\"h\":21,\"i\":\"b0c4d0ec-4e34-4094-8e3e-f180bffafc78\",\"w\":48,\"x\":0,\"y\":34},\"panelIndex\":\"b0c4d0ec-4e34-4094-8e3e-f180bffafc78\",\"embeddableConfig\":{\"hidePanelTitles\":false,\"vis\":{\"sortColumn\":{\"colIndex\":2,\"direction\":\"desc\"}}},\"title\":\"Message sources by name and type\",\"panelRefName\":\"panel_6\"},{\"version\":\"3.5.0\",\"gridData\":{\"h\":17,\"i\":\"54f61a15-0c6d-47b4-89c1-02027997a72e\",\"w\":48,\"x\":0,\"y\":55},\"panelIndex\":\"54f61a15-0c6d-47b4-89c1-02027997a72e\",\"embeddableConfig\":{\"hidePanelTitles\":false},\"title\":\"Message sources by Autonomous System\",\"panelRefName\":\"panel_7\"},{\"version\":\"3.5.0\",\"gridData\":{\"h\":19,\"i\":\"26903ac4-8896-4104-9616-4d52a407163f\",\"w\":48,\"x\":0,\"y\":72},\"panelIndex\":\"26903ac4-8896-4104-9616-4d52a407163f\",\"embeddableConfig\":{\"hidePanelTitles\":false},\"title\":\"DMARC passage over time\",\"panelRefName\":\"panel_8\"},{\"version\":\"3.5.0\",\"gridData\":{\"h\":18,\"i\":\"4b75365f-31c9-47c7-b9dd-5d6fd232dc70\",\"w\":48,\"x\":0,\"y\":91},\"panelIndex\":\"4b75365f-31c9-47c7-b9dd-5d6fd232dc70\",\"embeddableConfig\":{\"hidePanelTitles\":false},\"title\":\"Message disposition over time\",\"panelRefName\":\"panel_9\"},{\"version\":\"3.5.0\",\"gridData\":{\"h\":26,\"i\":\"972bdc59-a9c2-4c6c-8d1a-fbac426c114a\",\"w\":32,\"x\":0,\"y\":109},\"panelIndex\":\"972bdc59-a9c2-4c6c-8d1a-fbac426c114a\",\"embeddableConfig\":{\"hidePanelTitles\":false},\"title\":\"Map of message sources by country\",\"panelRefName\":\"panel_10\"},{\"version\":\"3.5.0\",\"gridData\":{\"h\":26,\"i\":\"16f2ee38-e678-43ee-a531-304112cb5ba6\",\"w\":16,\"x\":32,\"y\":109},\"panelIndex\":\"16f2ee38-e678-43ee-a531-304112cb5ba6\",\"embeddableConfig\":{\"hidePanelTitles\":false,\"vis\":{\"sortColumn\":{\"colIndex\":1,\"direction\":\"desc\"}}},\"title\":\"Message sources by country\",\"panelRefName\":\"panel_11\"},{\"version\":\"3.5.0\",\"gridData\":{\"h\":18,\"i\":\"035b5c90-70a1-4844-b824-1cca531d5984\",\"w\":48,\"x\":0,\"y\":135},\"panelIndex\":\"035b5c90-70a1-4844-b824-1cca531d5984\",\"embeddableConfig\":{\"hidePanelTitles\":false,\"vis\":{\"sortColumn\":{\"colIndex\":4,\"direction\":\"desc\"}}},\"title\":\"Message sources by IP address\",\"panelRefName\":\"panel_12\"},{\"embeddableConfig\":{},\"gridData\":{\"h\":8,\"i\":\"a41c68f2-3d0e-4c3f-9a51-6b1d2f8e7c05\",\"w\":48,\"x\":0,\"y\":153},\"panelIndex\":\"a41c68f2-3d0e-4c3f-9a51-6b1d2f8e7c05\",\"panelRefName\":\"panel_15\",\"title\":\"Auth result filters\",\"version\":\"3.5.0\"},{\"version\":\"3.5.0\",\"gridData\":{\"h\":15,\"i\":\"d3bafb75-ddef-4ad3-b71a-e78ba2ff92c4\",\"w\":48,\"x\":0,\"y\":161},\"panelIndex\":\"d3bafb75-ddef-4ad3-b71a-e78ba2ff92c4\",\"embeddableConfig\":{\"hidePanelTitles\":false,\"vis\":{\"sortColumn\":{\"colIndex\":4,\"direction\":\"desc\"}}},\"title\":\"SPF details\",\"panelRefName\":\"panel_13\"},{\"version\":\"3.5.0\",\"gridData\":{\"h\":11,\"i\":\"b22eb937-6456-486f-a183-8920f6d09f01\",\"w\":48,\"x\":0,\"y\":176},\"panelIndex\":\"b22eb937-6456-486f-a183-8920f6d09f01\",\"embeddableConfig\":{\"vis\":{\"sortColumn\":{\"colIndex\":6,\"direction\":\"desc\"}},\"hidePanelTitles\":false},\"title\":\"DKIM details\",\"panelRefName\":\"panel_14\"}]","refreshInterval":{"pause":true,"value":0},"timeFrom":"now-7d","timeRestore":true,"timeTo":"now","title":"DMARC aggregate reports","version":1},"id":"50c317b0-262e-11f1-96a6-fb3734bd0b21","migrationVersion":{"dashboard":"7.9.3"},"references":[{"id":"6942d480-262c-11f1-96a6-fb3734bd0b21","name":"panel_0","type":"visualization"},{"id":"9e23d140-262c-11f1-96a6-fb3734bd0b21","name":"panel_1","type":"visualization"},{"id":"f7789f50-262c-11f1-96a6-fb3734bd0b21","name":"panel_2","type":"visualization"},{"id":"09053d20-2630-11f1-96a6-fb3734bd0b21","name":"panel_3","type":"visualization"},{"id":"a68cc660-2632-11f1-96a6-fb3734bd0b21","name":"panel_4","type":"visualization"},{"id":"9aa252fc-5cea-4fce-a380-14fbada11e89","name":"panel_5","type":"visualization"},{"id":"81380390-2635-11f1-96a6-fb3734bd0b21","name":"panel_6","type":"visualization"},{"id":"adec76e0-3f68-11f1-a327-dd68bf273446","name":"panel_7","type":"visualization"},{"id":"0b277550-263a-11f1-96a6-fb3734bd0b21","name":"panel_8","type":"visualization"},{"id":"d4545010-263a-11f1-96a6-fb3734bd0b21","name":"panel_9","type":"visualization"},{"id":"bf2bfba0-263c-11f1-96a6-fb3734bd0b21","name":"panel_10","type":"visualization"},{"id":"0bcd9900-263d-11f1-96a6-fb3734bd0b21","name":"panel_11","type":"visualization"},{"id":"a8143340-263e-11f1-96a6-fb3734bd0b21","name":"panel_12","type":"visualization"},{"id":"9be589f0-2640-11f1-96a6-fb3734bd0b21","name":"panel_13","type":"visualization"},{"id":"7f743d10-2641-11f1-96a6-fb3734bd0b21","name":"panel_14","type":"visualization"},{"id":"8c2a7d40-2a11-11f1-96a6-fb3734bd0b21","name":"panel_15","type":"visualization"}],"type":"dashboard","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE0NiwyXQ=="} {"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"title":"About DMARC failure reports (RUF)","uiStateJSON":"{}","version":1,"visState":"{\"title\": \"About DMARC failure reports (RUF)\", \"type\": \"markdown\", \"aggs\": [], \"params\": {\"fontSize\": 12, \"openLinksInNewTab\": false, \"markdown\": \"## About DMARC failure reports (RUF)\\n\\nDMARC failure reports (RUF) contain an email sample that failed DMARC. These can be very useful for DMARC troubleshooting and phishing investigations. However, **most email providers** do not send failure reports, or may only supply the message headers for privacy reasons.\\n\\nIf you want to ensure that email samples are not saved here, **do not** set a `ruf` address in your domain's DMARC record.\\n\\n\\n\"}}"},"id":"ddc4da10-2654-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE0NywyXQ=="} {"attributes":{"fields":"[{\"count\":0,\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_score\",\"type\":\"number\",\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_type\",\"type\":\"string\",\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"arrival_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"auth_failure\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"auth_failure.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"auth_failure\"}}},{\"count\":0,\"name\":\"authentication_results\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"authentication_results.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"authentication_results\"}}},{\"count\":0,\"name\":\"delivery_results\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"delivery_results.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"delivery_results\"}}},{\"count\":0,\"name\":\"domain\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"domain.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"domain\"}}},{\"count\":0,\"name\":\"feedback_type\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"feedback_type.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"feedback_type\"}}},{\"count\":0,\"name\":\"original_mail_from\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"original_mail_from.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"original_mail_from\"}}},{\"count\":0,\"name\":\"original_rcpt_to\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"original_rcpt_to.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"original_rcpt_to\"}}},{\"count\":0,\"name\":\"sample.bcc.address\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.bcc.address.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.bcc.address\"}}},{\"count\":0,\"name\":\"sample.body\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.body.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.body\"}}},{\"count\":0,\"name\":\"sample.cc.address\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.cc.address.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.cc.address\"}}},{\"count\":0,\"name\":\"sample.date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"sample.filename_safe_subject\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.filename_safe_subject.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.filename_safe_subject\"}}},{\"count\":0,\"name\":\"sample.headers.authentication-results\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.authentication-results.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.authentication-results\"}}},{\"count\":0,\"name\":\"sample.headers.auto-submitted\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.auto-submitted.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.auto-submitted\"}}},{\"count\":0,\"name\":\"sample.headers.content-transfer-encoding\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.content-transfer-encoding.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.content-transfer-encoding\"}}},{\"count\":0,\"name\":\"sample.headers.content-type\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.content-type.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.content-type\"}}},{\"count\":0,\"name\":\"sample.headers.date\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.date.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.date\"}}},{\"count\":0,\"name\":\"sample.headers.from\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.from.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.from\"}}},{\"count\":0,\"name\":\"sample.headers.in-reply-to\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.in-reply-to.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.in-reply-to\"}}},{\"count\":0,\"name\":\"sample.headers.reply-to\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.reply-to.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.reply-to\"}}},{\"count\":0,\"name\":\"sample.headers.message-id\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.message-id.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.message-id\"}}},{\"count\":0,\"name\":\"sample.headers.mime-version\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.mime-version.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.mime-version\"}}},{\"count\":0,\"name\":\"sample.headers.received\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.received.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.received\"}}},{\"count\":0,\"name\":\"sample.headers.references\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.references.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.references\"}}},{\"count\":0,\"name\":\"sample.headers.return-path\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.return-path.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.return-path\"}}},{\"count\":0,\"name\":\"sample.headers.subject\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.subject.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.subject\"}}},{\"count\":0,\"name\":\"sample.headers.thread-index\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.thread-index.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.thread-index\"}}},{\"count\":0,\"name\":\"sample.headers.thread-topic\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.thread-topic.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.thread-topic\"}}},{\"count\":0,\"name\":\"sample.headers.to\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.to.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.to\"}}},{\"count\":0,\"name\":\"sample.headers.x-auto-response-suppress\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.x-auto-response-suppress.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.x-auto-response-suppress\"}}},{\"count\":0,\"name\":\"sample.headers.x-exclaimer-md-config\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.x-exclaimer-md-config.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.x-exclaimer-md-config\"}}},{\"count\":0,\"name\":\"sample.headers.x-linkedin-fe\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.x-linkedin-fe.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.x-linkedin-fe\"}}},{\"count\":0,\"name\":\"sample.headers.x-mailer\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.x-mailer.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.x-mailer\"}}},{\"count\":0,\"name\":\"sample.headers.x-ms-exchange-generated-message-source\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.x-ms-exchange-generated-message-source.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.x-ms-exchange-generated-message-source\"}}},{\"count\":0,\"name\":\"sample.headers.x-ms-exchange-inbox-rules-loop\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.x-ms-exchange-inbox-rules-loop.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.x-ms-exchange-inbox-rules-loop\"}}},{\"count\":0,\"name\":\"sample.headers.x-ms-exchange-parent-message-id\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.x-ms-exchange-parent-message-id.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.x-ms-exchange-parent-message-id\"}}},{\"count\":0,\"name\":\"sample.headers.x-ms-exchange-transport-fromentityheader\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.x-ms-exchange-transport-fromentityheader.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.x-ms-exchange-transport-fromentityheader\"}}},{\"count\":0,\"name\":\"sample.headers.x-ms-has-attach\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.x-ms-has-attach.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.x-ms-has-attach\"}}},{\"count\":0,\"name\":\"sample.headers.x-ms-tnef-correlator\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.x-ms-tnef-correlator.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.x-ms-tnef-correlator\"}}},{\"count\":0,\"name\":\"sample.headers.x-onpremexternalip\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.headers.x-onpremexternalip.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.headers.x-onpremexternalip\"}}},{\"count\":0,\"name\":\"sample.headers_only\",\"type\":\"boolean\",\"esTypes\":[\"boolean\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"sample.raw\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.raw.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.raw\"}}},{\"count\":0,\"name\":\"sample.subject\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.subject.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.subject\"}}},{\"count\":0,\"name\":\"sample.to.address\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.to.address.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.to.address\"}}},{\"count\":0,\"name\":\"sample.to.display_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"sample.to.display_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"sample.to.display_name\"}}},{\"count\":0,\"name\":\"source_ip_address\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"source_ip_address.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"source_ip_address\"}}},{\"count\":0,\"name\":\"user_agent\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"user_agent.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"user_agent\"}}},{\"count\":0,\"name\":\"version\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"version.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"version\"}}}]","timeFieldName":"arrival_date","title":"dmarc_f*"},"id":"5ff7dc70-2629-11f1-96a6-fb3734bd0b21","migrationVersion":{"index-pattern":"7.6.0"},"references":[],"type":"index-pattern","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE0OCwyXQ=="} -{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"DMARC failure email samples","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"DMARC failure email samples\",\"type\":\"table\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"arrival_date\",\"orderBy\":\"_key\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"arrival_date\"},\"schema\":\"bucket\"},{\"id\":\"7\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"source_ip_address.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":true,\"missingBucketLabel\":\"\",\"customLabel\":\"source_ip_address\"},\"schema\":\"bucket\"},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"sample.headers.from.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":true,\"missingBucketLabel\":\"\",\"customLabel\":\"from\"},\"schema\":\"bucket\"},{\"id\":\"4\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"sample.headers.subject.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":true,\"missingBucketLabel\":\"\",\"customLabel\":\"subject\"},\"schema\":\"bucket\"},{\"id\":\"5\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"sample.headers.reply-to.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":true,\"missingBucketLabel\":\"\",\"customLabel\":\"reply_to\"},\"schema\":\"bucket\"},{\"id\":\"6\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"authentication_results.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":true,\"missingBucketLabel\":\"\",\"customLabel\":\"autentication_results\"},\"schema\":\"bucket\"}],\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMetricsAtAllLevels\":false,\"showTotal\":false,\"totalFunc\":\"sum\",\"percentageCol\":\"\"}}"},"id":"cabf2640-2650-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"5ff7dc70-2629-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE0OSwyXQ=="} +{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"DMARC failure email samples","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"DMARC failure email samples\",\"type\":\"table\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"arrival_date\",\"orderBy\":\"_key\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"arrival_date\"},\"schema\":\"bucket\"},{\"id\":\"7\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"source_ip_address.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":true,\"missingBucketLabel\":\"\",\"customLabel\":\"source_ip_address\"},\"schema\":\"bucket\"},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"sample.headers.from.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":true,\"missingBucketLabel\":\"\",\"customLabel\":\"from\"},\"schema\":\"bucket\"},{\"id\":\"4\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"sample.headers.subject.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":true,\"missingBucketLabel\":\"\",\"customLabel\":\"subject\"},\"schema\":\"bucket\"},{\"id\":\"5\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"sample.headers.reply-to.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":true,\"missingBucketLabel\":\"\",\"customLabel\":\"reply_to\"},\"schema\":\"bucket\"},{\"id\":\"6\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"authentication_results.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":true,\"missingBucketLabel\":\"\",\"customLabel\":\"authentication_results\"},\"schema\":\"bucket\"}],\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMetricsAtAllLevels\":false,\"showTotal\":false,\"totalFunc\":\"sum\",\"percentageCol\":\"\"}}"},"id":"cabf2640-2650-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"5ff7dc70-2629-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE0OSwyXQ=="} {"attributes":{"description":"","hits":0,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[]}"},"optionsJSON":"{\"hidePanelTitles\":false,\"useMargins\":true}","panelsJSON":"[{\"version\":\"2.19.5\",\"gridData\":{\"x\":0,\"y\":0,\"w\":48,\"h\":8,\"i\":\"c64ada22-522a-4403-b58f-969857dc1181\"},\"panelIndex\":\"c64ada22-522a-4403-b58f-969857dc1181\",\"embeddableConfig\":{},\"panelRefName\":\"panel_0\"},{\"version\":\"2.19.5\",\"gridData\":{\"x\":0,\"y\":8,\"w\":48,\"h\":14,\"i\":\"9a35f16d-1c28-45b2-876d-66661d1f5c43\"},\"panelIndex\":\"9a35f16d-1c28-45b2-876d-66661d1f5c43\",\"embeddableConfig\":{},\"panelRefName\":\"panel_1\"}]","refreshInterval":{"pause":true,"value":0},"timeFrom":"now-90d","timeRestore":true,"timeTo":"now","title":"DMARC failure reports","version":1},"id":"100ed840-2655-11f1-96a6-fb3734bd0b21","migrationVersion":{"dashboard":"7.9.3"},"references":[{"id":"ddc4da10-2654-11f1-96a6-fb3734bd0b21","name":"panel_0","type":"visualization"},{"id":"cabf2640-2650-11f1-96a6-fb3734bd0b21","name":"panel_1","type":"visualization"}],"type":"dashboard","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE1MCwyXQ=="} -{"attributes":{"fields":"[{\"count\":0,\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_score\",\"type\":\"number\",\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_type\",\"type\":\"string\",\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"contact_info\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"contact_info.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"contact_info\"}}},{\"count\":0,\"name\":\"date_begin\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"date_end\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"date_range\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"org_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"org_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"org_name\"}}},{\"count\":0,\"name\":\"policies.failed_session_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":1,\"name\":\"policies.failure_details.failed_session_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"policies.failure_details.failure_reason_code\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"policies.failure_details.failure_reason_code.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"policies.failure_details.failure_reason_code\"}}},{\"count\":0,\"name\":\"policies.failure_details.receiving_ip\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"policies.failure_details.receiving_ip.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"policies.failure_details.receiving_ip\"}}},{\"count\":0,\"name\":\"policies.failure_details.receiving_mx_hostname\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"policies.failure_details.receiving_mx_hostname.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"policies.failure_details.receiving_mx_hostname\"}}},{\"count\":0,\"name\":\"policies.failure_details.result_type\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"policies.failure_details.result_type.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"policies.failure_details.result_type\"}}},{\"count\":0,\"name\":\"policies.failure_details.sending_mta_ip\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"policies.failure_details.sending_mta_ip.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"policies.failure_details.sending_mta_ip\"}}},{\"count\":0,\"name\":\"policies.policy_domain\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"policies.policy_domain.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"policies.policy_domain\"}}},{\"count\":0,\"name\":\"policies.policy_string\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"policies.policy_string.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"policies.policy_string\"}}},{\"count\":0,\"name\":\"policies.policy_type\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"policies.policy_type.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"policies.policy_type\"}}},{\"count\":1,\"name\":\"policies.successful_session_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"report_id\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"report_id.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"report_id\"}}}]","timeFieldName":"date_begin","title":"smtp_tls*"},"id":"307589c0-2629-11f1-96a6-fb3734bd0b21","migrationVersion":{"index-pattern":"7.6.0"},"references":[],"type":"index-pattern","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE1MSwyXQ=="} +{"attributes":{"fields":"[{\"count\":0,\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_score\",\"type\":\"number\",\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_type\",\"type\":\"string\",\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"contact_info\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"contact_info.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"contact_info\"}}},{\"count\":0,\"name\":\"date_begin\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"date_end\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"date_range\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"failure_details_combined\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"failure_details_combined.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"failure_details_combined\"}}},{\"count\":0,\"name\":\"org_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"org_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"org_name\"}}},{\"count\":0,\"name\":\"policies.failed_session_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":1,\"name\":\"policies.failure_details.failed_session_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"policies.failure_details.failure_reason_code\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"policies.failure_details.failure_reason_code.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"policies.failure_details.failure_reason_code\"}}},{\"count\":0,\"name\":\"policies.failure_details.receiving_ip\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"policies.failure_details.receiving_ip.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"policies.failure_details.receiving_ip\"}}},{\"count\":0,\"name\":\"policies.failure_details.receiving_mx_hostname\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"policies.failure_details.receiving_mx_hostname.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"policies.failure_details.receiving_mx_hostname\"}}},{\"count\":0,\"name\":\"policies.failure_details.result_type\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"policies.failure_details.result_type.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"policies.failure_details.result_type\"}}},{\"count\":0,\"name\":\"policies.failure_details.sending_mta_ip\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"policies.failure_details.sending_mta_ip.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"policies.failure_details.sending_mta_ip\"}}},{\"count\":0,\"name\":\"policies.policy_domain\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"policies.policy_domain.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"policies.policy_domain\"}}},{\"count\":0,\"name\":\"policies.policy_string\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"policies.policy_string.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"policies.policy_string\"}}},{\"count\":0,\"name\":\"policies.policy_type\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"policies.policy_type.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"policies.policy_type\"}}},{\"count\":1,\"name\":\"policies.successful_session_count\",\"type\":\"number\",\"esTypes\":[\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"policies_combined\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"policies_combined.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"policies_combined\"}}},{\"count\":0,\"name\":\"report_id\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"report_id.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"report_id\"}}}]","timeFieldName":"date_begin","title":"smtp_tls*"},"id":"307589c0-2629-11f1-96a6-fb3734bd0b21","migrationVersion":{"index-pattern":"7.6.0"},"references":[],"type":"index-pattern","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE1MSwyXQ=="} {"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"SMTP TLS reporting organizations","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"SMTP TLS reporting organizations\",\"type\":\"table\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"sum\",\"params\":{\"field\":\"policies.successful_session_count\",\"customLabel\":\"successful_sessions\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"sum\",\"params\":{\"field\":\"policies.failed_session_count\",\"customLabel\":\"failed_sessions\"},\"schema\":\"metric\"},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"org_name.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"reporting_organization\"},\"schema\":\"bucket\"}],\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMetricsAtAllLevels\":false,\"showTotal\":false,\"totalFunc\":\"sum\",\"percentageCol\":\"\"}}"},"id":"4f3b4cb0-26d2-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"307589c0-2629-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE1MiwyXQ=="} -{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"SMTP TLS domains","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"SMTP TLS domains\",\"type\":\"table\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"sum\",\"params\":{\"field\":\"policies.successful_session_count\",\"customLabel\":\"successful_sessions\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"sum\",\"params\":{\"field\":\"policies.failed_session_count\",\"customLabel\":\"failed_sessions\"},\"schema\":\"metric\"},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"policies.policy_domain.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"policy_domain\"},\"schema\":\"bucket\"},{\"id\":\"4\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"policies.policy_type.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":5,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":true,\"missingBucketLabel\":\"\",\"customLabel\":\"policy_type\"},\"schema\":\"bucket\"}],\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMetricsAtAllLevels\":false,\"showTotal\":false,\"totalFunc\":\"sum\",\"percentageCol\":\"\"}}"},"id":"eeb47eb0-26d2-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"307589c0-2629-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE1MywyXQ=="} -{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"policies.failure_details.failed_session_count > 0\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"SMTP TLS failure details","uiStateJSON":"{}","version":1,"visState":"{\"title\": \"SMTP TLS failure details\", \"type\": \"table\", \"aggs\": [{\"id\": \"1\", \"enabled\": true, \"type\": \"sum\", \"params\": {\"field\": \"policies.failure_details.failed_session_count\", \"customLabel\": \"failed_sessions\"}, \"schema\": \"metric\"}, {\"id\": \"2\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"org_name.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10000, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": false, \"missingBucketLabel\": \"Missing\", \"customLabel\": \"reporting_organization\"}, \"schema\": \"bucket\"}, {\"id\": \"3\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"policies.policy_domain.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10000, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": false, \"missingBucketLabel\": \"Missing\", \"customLabel\": \"policy_domain\"}, \"schema\": \"bucket\"}, {\"id\": \"4\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"policies.policy_type.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10000, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": false, \"missingBucketLabel\": \"Missing\", \"customLabel\": \"policy_type\"}, \"schema\": \"bucket\"}, {\"id\": \"5\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"policies.failure_details.result_type.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10000, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": false, \"missingBucketLabel\": \"Missing\", \"customLabel\": \"failure_type\"}, \"schema\": \"bucket\"}, {\"id\": \"6\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"policies.failure_details.sending_mta_ip.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10000, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": true, \"missingBucketLabel\": \"\", \"customLabel\": \"sending_mta_ip\"}, \"schema\": \"bucket\"}, {\"id\": \"7\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"policies.failure_details.receiving_ip.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10000, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": true, \"missingBucketLabel\": \"\", \"customLabel\": \"receiving_ip\"}, \"schema\": \"bucket\"}, {\"id\": \"8\", \"enabled\": true, \"type\": \"terms\", \"params\": {\"field\": \"policies.failure_details.receiving_mx_hostname.keyword\", \"orderBy\": \"1\", \"order\": \"desc\", \"size\": 10000, \"otherBucket\": false, \"otherBucketLabel\": \"Other\", \"missingBucket\": true, \"missingBucketLabel\": \"\", \"customLabel\": \"receiving_mx\"}, \"schema\": \"bucket\"}], \"params\": {\"perPage\": 10, \"showPartialRows\": false, \"showMetricsAtAllLevels\": false, \"showTotal\": false, \"totalFunc\": \"sum\", \"percentageCol\": \"\"}}"},"id":"5cbcd040-26da-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"307589c0-2629-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE1NCwyXQ=="} +{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"SMTP TLS domains","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"SMTP TLS domains\",\"type\":\"table\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"sum\",\"params\":{\"field\":\"policies.successful_session_count\",\"customLabel\":\"successful_sessions\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"sum\",\"params\":{\"field\":\"policies.failed_session_count\",\"customLabel\":\"failed_sessions\"},\"schema\":\"metric\"},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"policies_combined.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"policy (domain / type)\"},\"schema\":\"bucket\"}],\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMetricsAtAllLevels\":false,\"showTotal\":false,\"totalFunc\":\"sum\",\"percentageCol\":\"\"}}"},"id":"eeb47eb0-26d2-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"307589c0-2629-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE1MywyXQ=="} +{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"policies.failure_details.failed_session_count > 0\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"SMTP TLS failure details","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"SMTP TLS failure details\",\"type\":\"table\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"sum\",\"params\":{\"field\":\"policies.failure_details.failed_session_count\",\"customLabel\":\"failed_sessions\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"org_name.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"reporting_organization\"},\"schema\":\"bucket\"},{\"id\":\"3\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"failure_details_combined.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10000,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"failure detail (domain / type / result / sending mta / receiving ip / mx)\"},\"schema\":\"bucket\"}],\"params\":{\"perPage\":10,\"showPartialRows\":false,\"showMetricsAtAllLevels\":false,\"showTotal\":false,\"totalFunc\":\"sum\",\"percentageCol\":\"\"}}"},"id":"5cbcd040-26da-11f1-96a6-fb3734bd0b21","migrationVersion":{"visualization":"7.10.0"},"references":[{"id":"307589c0-2629-11f1-96a6-fb3734bd0b21","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE1NCwyXQ=="} {"attributes":{"description":"","hits":0,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[]}"},"optionsJSON":"{\"hidePanelTitles\":false,\"useMargins\":true}","panelsJSON":"[{\"embeddableConfig\":{\"hidePanelTitles\":false},\"gridData\":{\"h\":15,\"i\":\"panel-orgs\",\"w\":24,\"x\":0,\"y\":0},\"panelIndex\":\"panel-orgs\",\"title\":\"Reporting organizations\",\"version\":\"3.5.0\",\"panelRefName\":\"panel_0\"},{\"embeddableConfig\":{\"hidePanelTitles\":false},\"gridData\":{\"h\":15,\"i\":\"panel-domains\",\"w\":24,\"x\":24,\"y\":0},\"panelIndex\":\"panel-domains\",\"title\":\"Domains\",\"version\":\"3.5.0\",\"panelRefName\":\"panel_1\"},{\"embeddableConfig\":{\"hidePanelTitles\":false},\"gridData\":{\"h\":20,\"i\":\"panel-failures\",\"w\":48,\"x\":0,\"y\":15},\"panelIndex\":\"panel-failures\",\"title\":\"Failure details\",\"version\":\"3.5.0\",\"panelRefName\":\"panel_2\"}]","refreshInterval":{"pause":true,"value":0},"timeFrom":"now-7d","timeRestore":true,"timeTo":"now","title":"SMTP TLS reporting","version":1},"id":"b2bf75d0-26c9-11f1-96a6-fb3734bd0b21","migrationVersion":{"dashboard":"7.9.3"},"references":[{"id":"4f3b4cb0-26d2-11f1-96a6-fb3734bd0b21","name":"panel_0","type":"visualization"},{"id":"eeb47eb0-26d2-11f1-96a6-fb3734bd0b21","name":"panel_1","type":"visualization"},{"id":"5cbcd040-26da-11f1-96a6-fb3734bd0b21","name":"panel_2","type":"visualization"}],"type":"dashboard","updated_at":"2026-07-21T23:26:45.906Z","version":"WzE1NSwyXQ=="} -{"exportedCount":26,"missingRefCount":0,"missingReferences":[]} +{"exportedCount":27,"missingRefCount":0,"missingReferences":[]} diff --git a/dashboards/splunk/dmarc_aggregate_dashboard.xml b/dashboards/splunk/dmarc_aggregate_dashboard.xml index 32c5f175..f630bc06 100644 --- a/dashboards/splunk/dmarc_aggregate_dashboard.xml +++ b/dashboards/splunk/dmarc_aggregate_dashboard.xml @@ -323,7 +323,12 @@ SPF details - | fillnull value="none" source_base_domain | stats sum(message_count) as message_count by header_from,envelope_from,spf_result,source_base_domain,spf_aligned + | fillnull value="none" source_base_domain envelope_domain spf_scope spf_result +| eval spf_signature=mvzip(mvzip(spf_scope, envelope_domain, " / "), spf_result, " / ") +| mvexpand spf_signature +| stats sum(message_count) as message_count by header_from, envelope_from, spf_signature, source_base_domain, spf_aligned +| eval parts=split(spf_signature, " / "), spf_scope=mvindex(parts, 0), spf_domain=mvindex(parts, 1), spf_result=mvindex(parts, 2) +| table header_from, envelope_from, spf_scope, spf_domain, spf_result, spf_aligned, source_base_domain, message_count | sort -message_count @@ -339,7 +344,13 @@ DKIM details
- | fillnull value="none" source_base_domain | stats sum(message_count) as message_count by header_from,dkim_selector,dkim_domain,dkim_result,dkim_aligned,source_base_domain + | fillnull value="none" source_base_domain +| eval dkim_signature=mvzip(mvzip(dkim_selector, dkim_domain, " / "), dkim_result, " / ") +| mvexpand dkim_signature +| stats sum(message_count) as message_count by header_from, dkim_signature, dkim_aligned, source_base_domain +| eval parts=split(dkim_signature, " / "), dkim_selector=mvindex(parts, 0), dkim_domain=mvindex(parts, 1), dkim_result=mvindex(parts, 2) +| eval dkim_selector=if(dkim_selector=="null", "none", dkim_selector), dkim_domain=if(dkim_domain=="null", "none", dkim_domain), dkim_result=if(dkim_result=="null", "none", dkim_result) +| table header_from, dkim_selector, dkim_domain, dkim_result, dkim_aligned, source_base_domain, message_count | sort -message_count diff --git a/dashboards/splunk/smtp_tls_dashboard.xml b/dashboards/splunk/smtp_tls_dashboard.xml index 40b37362..1b52de99 100644 --- a/dashboards/splunk/smtp_tls_dashboard.xml +++ b/dashboards/splunk/smtp_tls_dashboard.xml @@ -2,18 +2,14 @@ -index=email sourcetype=smtp:tls organization_name=$organization_name$ policies{}.policy_domain=$policy_domain$ policies{}.policy_type=$policy_type$ -| rename policies{}.policy_domain as policy_domain -| rename policies{}.policy_type as policy_type -| rename policies{}.failed_session_count as failed_sessions -| rename policies{}.successful_session_count as successful_sessions -| rename policies{}.failure_details{}.receiving_mx_hostname as receiving_mx_hostname -| rename policies{}.failure_details{}.result_type as failure_type -| rename policies{}.failure_details{}.sending_mta_ip as sending_mta_ip -| rename policies{}.failure_details{}.receiving_ip as receiving_mta_ip +index=email sourcetype=smtp:tls organization_name=$organization_name$ +| spath policies{} output=policy +| mvexpand policy +| spath input=policy +| search policy_domain=$policy_domain$ policy_type=$policy_type$ +| rename successful_session_count as successful_sessions +| rename failed_session_count as failed_sessions | fillnull value=0 failed_sessions successful_sessions -| table * - | table * $time_range.earliest$ $time_range.latest$ @@ -78,8 +74,14 @@ index=email sourcetype=smtp:tls organization_name=$organization_name$ policies{}
-where failed_sessions > 0 -| stats sum(failed_sessions) as failed_sessions by organization_name, policy_domain, policy_type, failure_type, sending_mta_ip, receiving_mta_ip, receiving_mx_hostname +| spath input=policy path=failure_details{} output=detail +| mvexpand detail +| spath input=detail +| where failed_session_count > 0 +| rename result_type as failure_type +| rename receiving_ip as receiving_mta_ip +| fillnull value="none" failure_type sending_mta_ip receiving_mta_ip receiving_mx_hostname +| stats sum(failed_session_count) as failed_sessions by organization_name, policy_domain, policy_type, failure_type, sending_mta_ip, receiving_mta_ip, receiving_mx_hostname diff --git a/docs/source/elasticsearch.md b/docs/source/elasticsearch.md index ffdb972e..2d0a1a43 100644 --- a/docs/source/elasticsearch.md +++ b/docs/source/elasticsearch.md @@ -226,6 +226,158 @@ Kibana index patterns with versions that match the upgraded indexes: 7. Import `export.ndjson` by clicking Import from the Kibana Saved Objects page +## Backfilling the combined DKIM/SPF result fields + +As of the version fixing [#169](https://github.com/domainaware/parsedmarc/issues/169), +aggregate documents include `dkim_results_combined` and `spf_results_combined` — +scalar string arrays that keep each auth result's selector/scope, domain, and +result paired, which the dashboards' alignment-detail tables aggregate on. +Reports saved by older versions lack these fields and will not appear in +those tables. + +parsedmarc now backfills this automatically. On startup, it runs a cheap +count query against each configured aggregate index pattern to check for +documents that have DKIM or SPF results but are missing the corresponding +combined field. If any are found, it submits the backfill as a background +`_update_by_query` task (`wait_for_completion=false`), so startup is never +blocked on it; progress is logged, including the task ID. The check itself +is idempotent — once an index is fully backfilled, later startups see a +count of 0 and log nothing further — and it works the same way on +OpenSearch. Any error talking to the cluster (for example, no indexes yet +on a fresh install) is logged as a warning and retried on the next startup, +rather than aborting parsedmarc. + +If you upgrade the dashboards without pointing the new parsedmarc version +at the cluster, or you'd rather control when the write load happens, you +can still run the backfill manually. It is idempotent (documents that +already have the fields are skipped), so it is safe to re-run. It works +identically on OpenSearch; just adjust the URL and credentials. The query +matches only documents that have at least one DKIM or SPF auth result and +lack the corresponding combined field; documents with no auth results are +skipped, because an `exists` query cannot see an empty array, and for +search purposes an empty `dkim_results_combined` is identical to an +absent one. Each result is matched on either its `domain` or its `result` +subfield as defense in depth: an empty string indexes no text tokens and +is invisible to `exists`, and the storage shape of every historical +parsedmarc version can't be audited, so matching either subfield ensures +no backfillable document is skipped. + +```bash +curl -X POST "http://localhost:9200/dmarc_aggregate*/_update_by_query?conflicts=proceed&wait_for_completion=false" \ + -H "Content-Type: application/json" -d ' +{ + "query": { + "bool": { + "minimum_should_match": 1, + "should": [ + { + "bool": { + "must": [ + { + "bool": { + "minimum_should_match": 1, + "should": [ + {"exists": {"field": "dkim_results.domain"}}, + {"exists": {"field": "dkim_results.result"}} + ] + } + } + ], + "must_not": [{"exists": {"field": "dkim_results_combined"}}] + } + }, + { + "bool": { + "must": [ + { + "bool": { + "minimum_should_match": 1, + "should": [ + {"exists": {"field": "spf_results.domain"}}, + {"exists": {"field": "spf_results.result"}} + ] + } + } + ], + "must_not": [{"exists": {"field": "spf_results_combined"}}] + } + } + ] + } + }, + "script": { + "lang": "painless", + "source": "List dk = new ArrayList(); def dr = ctx._source.dkim_results; if (dr != null) { if (!(dr instanceof List)) { dr = [dr]; } for (e in dr) { if (e == null) { continue; } def sel = e.selector != null ? e.selector : \"none\"; def dom = e.domain != null ? e.domain : \"none\"; def res = e.result != null ? e.result : \"none\"; dk.add(sel + \" / \" + dom + \" / \" + res); } } ctx._source.dkim_results_combined = dk; List sp = new ArrayList(); def sr = ctx._source.spf_results; if (sr != null) { if (!(sr instanceof List)) { sr = [sr]; } for (e in sr) { if (e == null) { continue; } def sc = e.scope != null ? e.scope : \"mfrom\"; def dom = e.domain != null ? e.domain : \"none\"; def res = e.result != null ? e.result : (e.results != null ? e.results : \"none\"); sp.add(sc + \" / \" + dom + \" / \" + res); } } ctx._source.spf_results_combined = sp;" + } +}' +``` + +`wait_for_completion=false` returns a task ID — check progress with +`GET _tasks/`. Adjust the index pattern if you use a custom +`index_prefix`/`index_suffix`. After backfilling, re-import the updated +dashboards ndjson (the index pattern saved object changed too) per the +import instructions above. + +SMTP TLS documents have the same class of defect one level deeper: +`policies` is an object array, and each policy's `failure_details` is an +object array inside it. SMTP TLS documents now also carry +`policies_combined` and `failure_details_combined`, backfilled +automatically at startup the same way, and the equivalent manual command +is: + +```bash +curl -X POST "http://localhost:9200/smtp_tls*/_update_by_query?conflicts=proceed&wait_for_completion=false" \ + -H "Content-Type: application/json" -d ' +{ + "query": { + "bool": { + "minimum_should_match": 1, + "should": [ + { + "bool": { + "must": [ + { + "bool": { + "minimum_should_match": 1, + "should": [ + {"exists": {"field": "policies.policy_domain"}}, + {"exists": {"field": "policies.policy_type"}} + ] + } + } + ], + "must_not": [{"exists": {"field": "policies_combined"}}] + } + }, + { + "bool": { + "must": [ + { + "bool": { + "minimum_should_match": 1, + "should": [ + {"exists": {"field": "policies.failure_details.result_type"}}, + {"exists": {"field": "policies.failure_details.sending_mta_ip"}} + ] + } + } + ], + "must_not": [{"exists": {"field": "failure_details_combined"}}] + } + } + ] + } + }, + "script": { + "lang": "painless", + "source": "List pols = new ArrayList(); List dets = new ArrayList(); def ps = ctx._source.policies; if (ps != null) { if (!(ps instanceof List)) { ps = [ps]; } for (p in ps) { if (p == null) { continue; } def dom = p.policy_domain != null ? p.policy_domain : \"none\"; def typ = p.policy_type != null ? p.policy_type : \"none\"; pols.add(dom + \" / \" + typ); def fds = p.failure_details; if (fds != null) { if (!(fds instanceof List)) { fds = [fds]; } for (f in fds) { if (f == null) { continue; } def rt = f.result_type != null ? f.result_type : \"none\"; def smi = f.sending_mta_ip != null ? f.sending_mta_ip : \"none\"; def ri = f.receiving_ip != null ? f.receiving_ip : \"none\"; def rmh = f.receiving_mx_hostname != null ? f.receiving_mx_hostname : \"none\"; dets.add(dom + \" / \" + typ + \" / \" + rt + \" / \" + smi + \" / \" + ri + \" / \" + rmh); } } } } ctx._source.policies_combined = pols; ctx._source.failure_details_combined = dets;" + } +}' +``` + +It works identically on OpenSearch; just adjust the URL and credentials, same +as the aggregate command above. + ## Records retention Starting in version 5.0.0, `parsedmarc` stores data in a separate diff --git a/docs/source/kibana.md b/docs/source/kibana.md index 33f4dd4d..8a2be4b7 100644 --- a/docs/source/kibana.md +++ b/docs/source/kibana.md @@ -82,7 +82,23 @@ Further down the dashboard, you can filter by source country or source IP address. Tables showing SPF and DKIM alignment details are located under the IP address -table. +table. Each row of the DKIM details table is one real DKIM signature, shown +as a combined `selector / domain / result` value; the SPF details table +shows `scope / domain / result` the same way. Combining the values into one +column keeps each signature's selector, domain, and result paired together, +rather than aggregating them as separate columns. Because a message that +carries multiple DKIM signatures appears once per signature, summing the +messages column across rows can exceed the total number of messages. + +The "Auth result filters" panel above the details tables +provides dropdowns for the individual auth-result components — DKIM +selector, DKIM domain, DKIM result, SPF scope, SPF domain, and SPF +result — and filters the whole dashboard by them. Because components from +different signatures of the same message are indexed together, combining +two of these component filters matches documents where any signature +satisfies each condition individually, not necessarily the same signature; +the combined `selector / domain / result` (`scope / domain / result`) +column remains the per-signature source of truth. :::{note} The alignment tables (SPF details, DKIM details) and the per-IP source @@ -112,3 +128,15 @@ reporting organizations, the policy domains they report on, and the specific failure types — certificate expiry, STARTTLS not supported, STS policy fetch errors, validation failures, and similar — together with the sending and receiving MTA addresses involved. + +Like the DKIM and SPF details tables above, the "SMTP TLS domains" and +"SMTP TLS failure details" tables show one row per policy and one row per +failure detail, respectively, using combined `policy (domain / type)` and +`failure detail (domain / type / result / sending mta / receiving ip / mx)` +columns so that each policy's or failure detail's fields stay paired +together, rather than aggregating them as separate columns. The +`successful_sessions` and `failed_sessions` columns are summed per report +document, though, not per policy: when a single report carries multiple +policies, a row's session sums include the sibling policies from that +report as well as its own. Fully attributing session counts to a single +policy would require restructuring the stored documents. diff --git a/parsedmarc/cli.py b/parsedmarc/cli.py index 73ee41ac..4d3521bd 100644 --- a/parsedmarc/cli.py +++ b/parsedmarc/cli.py @@ -1472,6 +1472,7 @@ def _init_output_clients(opts): elastic.migrate_indexes( aggregate_indexes=[es_aggregate_index], failure_indexes=[es_failure_index], + smtp_tls_indexes=[es_smtp_tls_index], ) clients["elasticsearch"] = _ElasticsearchHandle() except Exception as e: @@ -1518,6 +1519,7 @@ def _init_output_clients(opts): opensearch.migrate_indexes( aggregate_indexes=[os_aggregate_index], failure_indexes=[os_failure_index], + smtp_tls_indexes=[os_smtp_tls_index], ) clients["opensearch"] = _OpenSearchHandle() except Exception as e: diff --git a/parsedmarc/elastic.py b/parsedmarc/elastic.py index 1af3c9a3..758c11fd 100644 --- a/parsedmarc/elastic.py +++ b/parsedmarc/elastic.py @@ -41,6 +41,181 @@ _SERVERLESS = False # settings (e.g. ``refresh_interval``) are accepted and pass through. _SERVERLESS_REJECTED_SETTINGS = frozenset({"number_of_shards", "number_of_replicas"}) +# Guard query for the dkim_results_combined/spf_results_combined backfill +# (see ``migrate_indexes``). Matches only documents that have at least one +# DKIM or SPF auth result and are missing the corresponding combined field. +# Empty arrays are invisible to ``exists``, so documents with zero +# DKIM/SPF results are correctly skipped (verified against real data; +# this also makes the query idempotent — a backfilled document no longer +# matches). Each result is matched on an OR of its ``domain``/``result`` +# subfields as defense in depth: the parsers we audited never store a +# result without both, but an empty string indexes no text tokens and is +# invisible to ``exists``, and the storage shape of every historical +# parsedmarc version can't be audited — matching either subfield costs +# nothing and cannot skip a document that has something to backfill. +_COMBINED_BACKFILL_QUERY: dict[str, Any] = { + "bool": { + "minimum_should_match": 1, + "should": [ + { + "bool": { + "must": [ + { + "bool": { + "minimum_should_match": 1, + "should": [ + {"exists": {"field": "dkim_results.domain"}}, + {"exists": {"field": "dkim_results.result"}}, + ], + } + } + ], + "must_not": [{"exists": {"field": "dkim_results_combined"}}], + } + }, + { + "bool": { + "must": [ + { + "bool": { + "minimum_should_match": 1, + "should": [ + {"exists": {"field": "spf_results.domain"}}, + {"exists": {"field": "spf_results.result"}}, + ], + } + } + ], + "must_not": [{"exists": {"field": "spf_results_combined"}}], + } + }, + ], + } +} + +# Painless script that (re)derives dkim_results_combined/spf_results_combined +# from dkim_results/spf_results, matching the format written by +# save_aggregate_report_to_elasticsearch(): "{selector} / {domain} / {result}" +# per DKIM result and "{scope} / {domain} / {result}" per SPF result. +_COMBINED_BACKFILL_SCRIPT = ( + "List dk = new ArrayList(); " + "def dr = ctx._source.dkim_results; " + "if (dr != null) { " + "if (!(dr instanceof List)) { dr = [dr]; } " + "for (e in dr) { " + "if (e == null) { continue; } " + 'def sel = e.selector != null ? e.selector : "none"; ' + 'def dom = e.domain != null ? e.domain : "none"; ' + 'def res = e.result != null ? e.result : "none"; ' + 'dk.add(sel + " / " + dom + " / " + res); ' + "} } " + "ctx._source.dkim_results_combined = dk; " + "List sp = new ArrayList(); " + "def sr = ctx._source.spf_results; " + "if (sr != null) { " + "if (!(sr instanceof List)) { sr = [sr]; } " + "for (e in sr) { " + "if (e == null) { continue; } " + 'def sc = e.scope != null ? e.scope : "mfrom"; ' + 'def dom = e.domain != null ? e.domain : "none"; ' + 'def res = e.result != null ? e.result : (e.results != null ? e.results : "none"); ' + 'sp.add(sc + " / " + dom + " / " + res); ' + "} } " + "ctx._source.spf_results_combined = sp;" +) + +# Guard query for the policies_combined/failure_details_combined backfill +# (see ``migrate_indexes``). Matches only SMTP TLS documents that have at +# least one policy or failure detail and are missing the corresponding +# combined field. Empty arrays are invisible to ``exists``, so documents +# with zero policies/failure details are correctly skipped (this also +# makes the query idempotent — a backfilled document no longer matches). +# Each result is matched on an OR of its relevant subfields as defense in +# depth: the parsers we audited never store a policy/failure detail +# without these fields, but an empty string indexes no text tokens and is +# invisible to ``exists``, and the storage shape of every historical +# parsedmarc version can't be audited — matching either subfield costs +# nothing and cannot skip a document that has something to backfill. +_SMTP_TLS_COMBINED_BACKFILL_QUERY: dict[str, Any] = { + "bool": { + "minimum_should_match": 1, + "should": [ + { + "bool": { + "must": [ + { + "bool": { + "minimum_should_match": 1, + "should": [ + {"exists": {"field": "policies.policy_domain"}}, + {"exists": {"field": "policies.policy_type"}}, + ], + } + } + ], + "must_not": [{"exists": {"field": "policies_combined"}}], + } + }, + { + "bool": { + "must": [ + { + "bool": { + "minimum_should_match": 1, + "should": [ + { + "exists": { + "field": "policies.failure_details.result_type" + } + }, + { + "exists": { + "field": "policies.failure_details.sending_mta_ip" + } + }, + ], + } + } + ], + "must_not": [{"exists": {"field": "failure_details_combined"}}], + } + }, + ], + } +} + +# Painless script that (re)derives policies_combined/failure_details_combined +# from policies/policies.failure_details, matching the format written by +# save_smtp_tls_report_to_elasticsearch(): "{policy_domain} / {policy_type}" +# per policy and "{policy_domain} / {policy_type} / {result_type} / +# {sending_mta_ip} / {receiving_ip} / {receiving_mx_hostname}" per failure +# detail. +_SMTP_TLS_COMBINED_BACKFILL_SCRIPT = ( + "List pols = new ArrayList(); " + "List dets = new ArrayList(); " + "def ps = ctx._source.policies; " + "if (ps != null) { " + "if (!(ps instanceof List)) { ps = [ps]; } " + "for (p in ps) { " + "if (p == null) { continue; } " + 'def dom = p.policy_domain != null ? p.policy_domain : "none"; ' + 'def typ = p.policy_type != null ? p.policy_type : "none"; ' + 'pols.add(dom + " / " + typ); ' + "def fds = p.failure_details; " + "if (fds != null) { " + "if (!(fds instanceof List)) { fds = [fds]; } " + "for (f in fds) { " + "if (f == null) { continue; } " + 'def rt = f.result_type != null ? f.result_type : "none"; ' + 'def smi = f.sending_mta_ip != null ? f.sending_mta_ip : "none"; ' + 'def ri = f.receiving_ip != null ? f.receiving_ip : "none"; ' + 'def rmh = f.receiving_mx_hostname != null ? f.receiving_mx_hostname : "none"; ' + 'dets.add(dom + " / " + typ + " / " + rt + " / " + smi + " / " + ri + " / " + rmh); ' + "} } } } " + "ctx._source.policies_combined = pols; " + "ctx._source.failure_details_combined = dets;" +) + class _PolicyOverride(InnerDoc): # The elasticsearch.dsl 8.x type stubs use dataclass_transform and only @@ -99,7 +274,7 @@ class _SPFResult(InnerDoc): domain = Text() scope = Text() - results = Text() + result = Text() human_result = Text() @@ -144,8 +319,25 @@ class _AggregateReportDoc(Document): header_from = Text() envelope_from = Text() envelope_to = Text() + # Nested(...) on the two auth-result fields below is only the DSL's + # in-memory document shape; it is never installed as a mapping. + # create_indexes() deliberately skips Index.document() registration so + # these fields stay dynamic-mapped as plain `object` in the cluster + # (see the comment there and issue #169). dkim_results = Nested(_DKIMResult) spf_results = Nested(_SPFResult) + # One "{selector} / {domain} / {result}" (DKIM) or "{scope} / {domain} / + # {result}" (SPF) string per auth result. Kibana/Grafana tables cannot + # terms-aggregate the subfields of an object array without producing a + # cross-product of values (issue #169), so dashboards aggregate these + # composed keywords instead. Declared to match what dynamic mapping + # produces for a string array (text + .keyword). + dkim_results_combined = Text( + multi=True, fields={"keyword": Keyword(ignore_above=256)} + ) + spf_results_combined = Text( + multi=True, fields={"keyword": Keyword(ignore_above=256)} + ) np = Keyword() testing = Keyword() discovery_method = Keyword() @@ -158,7 +350,7 @@ class _AggregateReportDoc(Document): self, domain: str, selector: str, - result: _DKIMResult, + result: str, human_result: str | None = None, ): self.dkim_results.append( @@ -169,12 +361,15 @@ class _AggregateReportDoc(Document): human_result=human_result, ) ) + self.dkim_results_combined.append( + "{0} / {1} / {2}".format(selector, domain, result) + ) def add_spf_result( self, domain: str, scope: str, - result: _SPFResult, + result: str, human_result: str | None = None, ): self.spf_results.append( @@ -185,6 +380,9 @@ class _AggregateReportDoc(Document): human_result=human_result, ) ) + self.spf_results_combined.append( + "{0} / {1} / {2}".format(scope, domain, result) + ) def save(self, **kwargs): # pyright: ignore[reportIncompatibleMethodOverride] self.passed_dmarc = False @@ -301,6 +499,7 @@ class _SMTPTLSFailureDetailsDoc(InnerDoc): result_type = Text() sending_mta_ip = Ip() receiving_mx_helo = Text() + receiving_mx_hostname = Text() receiving_ip = Ip() failed_session_count = Integer() additional_information_uri = Text() @@ -342,7 +541,7 @@ class _SMTPTLSPolicyDoc(InnerDoc): receiving_mx_helo=receiving_mx_helo, receiving_ip=receiving_ip, failed_session_count=failed_session_count, - additional_information=additional_information_uri, + additional_information_uri=additional_information_uri, failure_reason_code=failure_reason_code, ) self.failure_details.append(_details) @@ -365,6 +564,19 @@ class _SMTPTLSReportDoc(Document): contact_info = Text() report_id = Text() policies = Nested(_SMTPTLSPolicyDoc) + # One "{policy_domain} / {policy_type}" string per policy. Kibana/ + # Grafana tables cannot terms-aggregate the subfields of an object + # array without producing a cross-product of values (issue #169), so + # dashboards aggregate these composed keywords instead. Declared to + # match what dynamic mapping produces for a string array (text + + # .keyword). + policies_combined = Text(multi=True, fields={"keyword": Keyword(ignore_above=256)}) + # One "{policy_domain} / {policy_type} / {result_type} / + # {sending_mta_ip} / {receiving_ip} / {receiving_mx_hostname}" string + # per failure detail, across all policies. + failure_details_combined = Text( + multi=True, fields={"keyword": Keyword(ignore_above=256)} + ) class AlreadySaved(ValueError): @@ -450,6 +662,18 @@ def create_indexes(names: list[str], settings: dict[str, Any] | None = None): for name in names: index = Index(name) try: + # Deliberately no Index.document() registration: the shipped + # dashboards cannot rebuild their detail tables on a `nested` + # mapping — Kibana/OSD visual editors do not support nested + # fields, Vega can run nested aggregations but does not render + # tables, and Grafana's nested bucket aggregation (9.4+) lacks + # reverse_nested for parent-level metrics like message_count — + # so the dynamic `object` mapping produced by a bare create is + # load-bearing for the shipped dashboards. _AggregateReportDoc + # still declares dkim_results/spf_results with Nested(...), but + # that is only the DSL's in-memory shape for building documents + # — it is never installed as a mapping. See issue #169 and the + # *_combined fields on _AggregateReportDoc. if not index.exists(): logger.debug("Creating Elasticsearch index: {0}".format(name)) if effective_settings: @@ -462,25 +686,118 @@ def create_indexes(names: list[str], settings: dict[str, Any] | None = None): def migrate_indexes( aggregate_indexes: list[str] | None = None, failure_indexes: list[str] | None = None, + smtp_tls_indexes: list[str] | None = None, ): """ - Updates index mappings + Backfills the ``dkim_results_combined``/``spf_results_combined`` fields + (added for issue #169) on aggregate report documents, and the + ``policies_combined``/``failure_details_combined`` fields on SMTP TLS + report documents, that were saved before those fields existed. - This is a no-op kept for API compatibility (``cli.py`` calls it on - startup). The only migration this function ever performed was - re-typing ``published_policy.fo`` from ``long`` to ``text``, which - applied exclusively to indices still carrying the legacy - Elasticsearch 6-era ``"doc"`` mapping type. The 8.x client can only - reach servers (Elasticsearch 8.x/9.x) whose indices were created on - Elasticsearch 7.x or later and are therefore typeless, so that - migration path is unreachable and has been removed. + For each name in ``aggregate_indexes``/``smtp_tls_indexes``, this + submits an ``update_by_query`` against the ``f"{name}*"`` index pattern + (the real indexes are date-suffixed) as a non-blocking background task + (``wait_for_completion=False``), so it never delays parsedmarc startup. + Submission is guarded by a cheap ``count`` query that only matches + documents with DKIM/SPF results (or policies/failure details) but no + combined field, so once an index is fully backfilled, later calls are a + fast no-op. Any error talking to the cluster (e.g. no indexes yet on a + fresh install, or a transient connection issue) is caught and logged as + a warning rather than raised; the backfill is simply retried on the + next startup, and the manual ``_update_by_query`` commands documented in + ``docs/source/elasticsearch.md`` remain available in the meantime. Args: aggregate_indexes (list): A list of aggregate index names - (accepted for API compatibility; unused) failure_indexes (list): A list of failure index names (accepted for API compatibility; unused) + smtp_tls_indexes (list): A list of SMTP TLS index names """ + if not aggregate_indexes and not smtp_tls_indexes: + return + + try: + client = connections.get_connection() + except Exception as e: + logger.warning( + "Skipping the dkim_results_combined/spf_results_combined/" + "policies_combined/failure_details_combined backfill: could " + f"not get an Elasticsearch connection: {e}. This will be " + "retried at the next startup." + ) + return + for name in aggregate_indexes or []: + pattern = f"{name}*" + try: + count_response = client.count( + index=pattern, + query=_COMBINED_BACKFILL_QUERY, + ignore_unavailable=True, + allow_no_indices=True, + ) + count = count_response["count"] + if not count: + continue + update_response = client.update_by_query( + index=pattern, + query=_COMBINED_BACKFILL_QUERY, + script={"source": _COMBINED_BACKFILL_SCRIPT, "lang": "painless"}, + conflicts="proceed", + wait_for_completion=False, + ignore_unavailable=True, + allow_no_indices=True, + ) + task_id = update_response.get("task") + logger.info( + "Backfilling dkim_results_combined/spf_results_combined on " + f"{count} existing documents in {pattern} (task {task_id})" + ) + except Exception as e: + logger.warning( + "Failed to check/submit the dkim_results_combined/" + f"spf_results_combined backfill for {pattern}: {e}. This " + "will be retried at the next startup; the manual " + "_update_by_query command in the documentation remains " + "available in the meantime." + ) + + for name in smtp_tls_indexes or []: + pattern = f"{name}*" + try: + count_response = client.count( + index=pattern, + query=_SMTP_TLS_COMBINED_BACKFILL_QUERY, + ignore_unavailable=True, + allow_no_indices=True, + ) + count = count_response["count"] + if not count: + continue + update_response = client.update_by_query( + index=pattern, + query=_SMTP_TLS_COMBINED_BACKFILL_QUERY, + script={ + "source": _SMTP_TLS_COMBINED_BACKFILL_SCRIPT, + "lang": "painless", + }, + conflicts="proceed", + wait_for_completion=False, + ignore_unavailable=True, + allow_no_indices=True, + ) + task_id = update_response.get("task") + logger.info( + "Backfilling policies_combined/failure_details_combined on " + f"{count} existing documents in {pattern} (task {task_id})" + ) + except Exception as e: + logger.warning( + "Failed to check/submit the policies_combined/" + f"failure_details_combined backfill for {pattern}: {e}. " + "This will be retried at the next startup; the manual " + "_update_by_query command in the documentation remains " + "available in the meantime." + ) def save_aggregate_report_to_elasticsearch( @@ -949,6 +1266,16 @@ def save_smtp_tls_report_to_elasticsearch( policy_strings = policy["policy_strings"] if "mx_host_patterns" in policy: mx_host_patterns = policy["mx_host_patterns"] + # policies_combined/failure_details_combined: see the field + # declarations on _SMTPTLSReportDoc and issue #169. policies and + # their failure_details are object arrays with the same + # cross-product problem as dkim_results/spf_results, so dashboards + # aggregate these composed strings instead of the raw subfields. + policy_domain_combined = policy.get("policy_domain") or "none" + policy_type_combined = policy.get("policy_type") or "none" + smtp_tls_doc.policies_combined.append( + "{0} / {1}".format(policy_domain_combined, policy_type_combined) + ) policy_doc = _SMTPTLSPolicyDoc( policy_domain=policy["policy_domain"], policy_type=policy["policy_type"], @@ -969,7 +1296,12 @@ def save_smtp_tls_report_to_elasticsearch( if "receiving_mx_hostname" in failure_detail: receiving_mx_hostname = failure_detail["receiving_mx_hostname"] - if "additional_information_uri" in failure_detail: + # The parser's key is additional_info_uri (see + # SMTPTLSFailureDetailsOptional in types.py); accept the + # long-form key too for dicts built by other callers. + if "additional_info_uri" in failure_detail: + additional_information_uri = failure_detail["additional_info_uri"] + elif "additional_information_uri" in failure_detail: additional_information_uri = failure_detail[ "additional_information_uri" ] @@ -994,6 +1326,16 @@ def save_smtp_tls_report_to_elasticsearch( additional_information_uri=additional_information_uri, failure_reason_code=failure_reason_code, ) + smtp_tls_doc.failure_details_combined.append( + "{0} / {1} / {2} / {3} / {4} / {5}".format( + policy_domain_combined, + policy_type_combined, + failure_detail.get("result_type") or "none", + sending_mta_ip or "none", + receiving_ip or "none", + receiving_mx_hostname or "none", + ) + ) smtp_tls_doc.policies.append(policy_doc) create_indexes([index], index_settings) diff --git a/parsedmarc/opensearch.py b/parsedmarc/opensearch.py index fecefc83..adc3e182 100644 --- a/parsedmarc/opensearch.py +++ b/parsedmarc/opensearch.py @@ -34,6 +34,182 @@ class OpenSearchError(Exception): """Raised when an OpenSearch error occurs""" +# Guard query for the dkim_results_combined/spf_results_combined backfill +# (see ``migrate_indexes``). Matches only documents that have at least one +# DKIM or SPF auth result and are missing the corresponding combined field. +# Empty arrays are invisible to ``exists``, so documents with zero +# DKIM/SPF results are correctly skipped (verified against real data; +# this also makes the query idempotent — a backfilled document no longer +# matches). Each result is matched on an OR of its ``domain``/``result`` +# subfields as defense in depth: the parsers we audited never store a +# result without both, but an empty string indexes no text tokens and is +# invisible to ``exists``, and the storage shape of every historical +# parsedmarc version can't be audited — matching either subfield costs +# nothing and cannot skip a document that has something to backfill. +_COMBINED_BACKFILL_QUERY: dict[str, Any] = { + "bool": { + "minimum_should_match": 1, + "should": [ + { + "bool": { + "must": [ + { + "bool": { + "minimum_should_match": 1, + "should": [ + {"exists": {"field": "dkim_results.domain"}}, + {"exists": {"field": "dkim_results.result"}}, + ], + } + } + ], + "must_not": [{"exists": {"field": "dkim_results_combined"}}], + } + }, + { + "bool": { + "must": [ + { + "bool": { + "minimum_should_match": 1, + "should": [ + {"exists": {"field": "spf_results.domain"}}, + {"exists": {"field": "spf_results.result"}}, + ], + } + } + ], + "must_not": [{"exists": {"field": "spf_results_combined"}}], + } + }, + ], + } +} + +# Painless script that (re)derives dkim_results_combined/spf_results_combined +# from dkim_results/spf_results, matching the format written by +# save_aggregate_report_to_opensearch(): "{selector} / {domain} / {result}" +# per DKIM result and "{scope} / {domain} / {result}" per SPF result. +_COMBINED_BACKFILL_SCRIPT = ( + "List dk = new ArrayList(); " + "def dr = ctx._source.dkim_results; " + "if (dr != null) { " + "if (!(dr instanceof List)) { dr = [dr]; } " + "for (e in dr) { " + "if (e == null) { continue; } " + 'def sel = e.selector != null ? e.selector : "none"; ' + 'def dom = e.domain != null ? e.domain : "none"; ' + 'def res = e.result != null ? e.result : "none"; ' + 'dk.add(sel + " / " + dom + " / " + res); ' + "} } " + "ctx._source.dkim_results_combined = dk; " + "List sp = new ArrayList(); " + "def sr = ctx._source.spf_results; " + "if (sr != null) { " + "if (!(sr instanceof List)) { sr = [sr]; } " + "for (e in sr) { " + "if (e == null) { continue; } " + 'def sc = e.scope != null ? e.scope : "mfrom"; ' + 'def dom = e.domain != null ? e.domain : "none"; ' + 'def res = e.result != null ? e.result : (e.results != null ? e.results : "none"); ' + 'sp.add(sc + " / " + dom + " / " + res); ' + "} } " + "ctx._source.spf_results_combined = sp;" +) + +# Guard query for the policies_combined/failure_details_combined backfill +# (see ``migrate_indexes``). Matches only SMTP TLS documents that have at +# least one policy or failure detail and are missing the corresponding +# combined field. Empty arrays are invisible to ``exists``, so documents +# with zero policies/failure details are correctly skipped (this also +# makes the query idempotent — a backfilled document no longer matches). +# Each result is matched on an OR of its relevant subfields as defense in +# depth: the parsers we audited never store a policy/failure detail +# without these fields, but an empty string indexes no text tokens and is +# invisible to ``exists``, and the storage shape of every historical +# parsedmarc version can't be audited — matching either subfield costs +# nothing and cannot skip a document that has something to backfill. +_SMTP_TLS_COMBINED_BACKFILL_QUERY: dict[str, Any] = { + "bool": { + "minimum_should_match": 1, + "should": [ + { + "bool": { + "must": [ + { + "bool": { + "minimum_should_match": 1, + "should": [ + {"exists": {"field": "policies.policy_domain"}}, + {"exists": {"field": "policies.policy_type"}}, + ], + } + } + ], + "must_not": [{"exists": {"field": "policies_combined"}}], + } + }, + { + "bool": { + "must": [ + { + "bool": { + "minimum_should_match": 1, + "should": [ + { + "exists": { + "field": "policies.failure_details.result_type" + } + }, + { + "exists": { + "field": "policies.failure_details.sending_mta_ip" + } + }, + ], + } + } + ], + "must_not": [{"exists": {"field": "failure_details_combined"}}], + } + }, + ], + } +} + +# Painless script that (re)derives policies_combined/failure_details_combined +# from policies/policies.failure_details, matching the format written by +# save_smtp_tls_report_to_opensearch(): "{policy_domain} / {policy_type}" +# per policy and "{policy_domain} / {policy_type} / {result_type} / +# {sending_mta_ip} / {receiving_ip} / {receiving_mx_hostname}" per failure +# detail. +_SMTP_TLS_COMBINED_BACKFILL_SCRIPT = ( + "List pols = new ArrayList(); " + "List dets = new ArrayList(); " + "def ps = ctx._source.policies; " + "if (ps != null) { " + "if (!(ps instanceof List)) { ps = [ps]; } " + "for (p in ps) { " + "if (p == null) { continue; } " + 'def dom = p.policy_domain != null ? p.policy_domain : "none"; ' + 'def typ = p.policy_type != null ? p.policy_type : "none"; ' + 'pols.add(dom + " / " + typ); ' + "def fds = p.failure_details; " + "if (fds != null) { " + "if (!(fds instanceof List)) { fds = [fds]; } " + "for (f in fds) { " + "if (f == null) { continue; } " + 'def rt = f.result_type != null ? f.result_type : "none"; ' + 'def smi = f.sending_mta_ip != null ? f.sending_mta_ip : "none"; ' + 'def ri = f.receiving_ip != null ? f.receiving_ip : "none"; ' + 'def rmh = f.receiving_mx_hostname != null ? f.receiving_mx_hostname : "none"; ' + 'dets.add(dom + " / " + typ + " / " + rt + " / " + smi + " / " + ri + " / " + rmh); ' + "} } } } " + "ctx._source.policies_combined = pols; " + "ctx._source.failure_details_combined = dets;" +) + + class _PolicyOverride(InnerDoc): type = Text() comment = Text() @@ -62,7 +238,7 @@ class _DKIMResult(InnerDoc): class _SPFResult(InnerDoc): domain = Text() scope = Text() - results = Text() + result = Text() human_result = Text() @@ -101,8 +277,25 @@ class _AggregateReportDoc(Document): header_from = Text() envelope_from = Text() envelope_to = Text() + # Nested(...) on the two auth-result fields below is only the DSL's + # in-memory document shape; it is never installed as a mapping. + # create_indexes() deliberately skips Index.document() registration so + # these fields stay dynamic-mapped as plain `object` in the cluster + # (see the comment there and issue #169). dkim_results = Nested(_DKIMResult) spf_results = Nested(_SPFResult) + # One "{selector} / {domain} / {result}" (DKIM) or "{scope} / {domain} / + # {result}" (SPF) string per auth result. Kibana/Grafana tables cannot + # terms-aggregate the subfields of an object array without producing a + # cross-product of values (issue #169), so dashboards aggregate these + # composed keywords instead. Declared to match what dynamic mapping + # produces for a string array (text + .keyword). + dkim_results_combined = Text( + multi=True, fields={"keyword": Keyword(ignore_above=256)} + ) + spf_results_combined = Text( + multi=True, fields={"keyword": Keyword(ignore_above=256)} + ) np = Keyword() testing = Keyword() discovery_method = Keyword() @@ -115,7 +308,7 @@ class _AggregateReportDoc(Document): self, domain: str, selector: str, - result: _DKIMResult, + result: str, human_result: str | None = None, ): self.dkim_results.append( @@ -126,12 +319,15 @@ class _AggregateReportDoc(Document): human_result=human_result, ) ) + self.dkim_results_combined.append( + "{0} / {1} / {2}".format(selector, domain, result) + ) def add_spf_result( self, domain: str, scope: str, - result: _SPFResult, + result: str, human_result: str | None = None, ): self.spf_results.append( @@ -142,6 +338,9 @@ class _AggregateReportDoc(Document): human_result=human_result, ) ) + self.spf_results_combined.append( + "{0} / {1} / {2}".format(scope, domain, result) + ) def save(self, **kwargs): # pyright: ignore[reportIncompatibleMethodOverride] self.passed_dmarc = False @@ -228,6 +427,7 @@ class _SMTPTLSFailureDetailsDoc(InnerDoc): result_type = Text() sending_mta_ip = Ip() receiving_mx_helo = Text() + receiving_mx_hostname = Text() receiving_ip = Ip() failed_session_count = Integer() additional_information_uri = Text() @@ -263,7 +463,7 @@ class _SMTPTLSPolicyDoc(InnerDoc): receiving_mx_helo=receiving_mx_helo, receiving_ip=receiving_ip, failed_session_count=failed_session_count, - additional_information=additional_information_uri, + additional_information_uri=additional_information_uri, failure_reason_code=failure_reason_code, ) self.failure_details.append(_details) @@ -280,6 +480,19 @@ class _SMTPTLSReportDoc(Document): contact_info = Text() report_id = Text() policies = Nested(_SMTPTLSPolicyDoc) + # One "{policy_domain} / {policy_type}" string per policy. Kibana/ + # Grafana tables cannot terms-aggregate the subfields of an object + # array without producing a cross-product of values (issue #169), so + # dashboards aggregate these composed keywords instead. Declared to + # match what dynamic mapping produces for a string array (text + + # .keyword). + policies_combined = Text(multi=True, fields={"keyword": Keyword(ignore_above=256)}) + # One "{policy_domain} / {policy_type} / {result_type} / + # {sending_mta_ip} / {receiving_ip} / {receiving_mx_hostname}" string + # per failure detail, across all policies. + failure_details_combined = Text( + multi=True, fields={"keyword": Keyword(ignore_above=256)} + ) class AlreadySaved(ValueError): @@ -367,6 +580,18 @@ def create_indexes(names: list[str], settings: dict[str, Any] | None = None): for name in names: index = Index(name) try: + # Deliberately no Index.document() registration: the shipped + # dashboards cannot rebuild their detail tables on a `nested` + # mapping — Kibana/OSD visual editors do not support nested + # fields, Vega can run nested aggregations but does not render + # tables, and Grafana's nested bucket aggregation (9.4+) lacks + # reverse_nested for parent-level metrics like message_count — + # so the dynamic `object` mapping produced by a bare create is + # load-bearing for the shipped dashboards. _AggregateReportDoc + # still declares dkim_results/spf_results with Nested(...), but + # that is only the DSL's in-memory shape for building documents + # — it is never installed as a mapping. See issue #169 and the + # *_combined fields on _AggregateReportDoc. if not index.exists(): logger.debug("Creating OpenSearch index: {0}".format(name)) if settings is None: @@ -381,47 +606,175 @@ def create_indexes(names: list[str], settings: dict[str, Any] | None = None): def migrate_indexes( aggregate_indexes: list[str] | None = None, failure_indexes: list[str] | None = None, + smtp_tls_indexes: list[str] | None = None, ): """ - Updates index mappings + Runs index migrations and backfills. + + First, the legacy ``published_policy.fo`` migration: indexes where that + field was mapped as ``long`` (data indexed by very old parsedmarc + releases under the Elasticsearch 6-era ``doc`` mapping type) are rebuilt + as a ``-v2`` index with the text/keyword shape. + + Second, the ``dkim_results_combined``/``spf_results_combined`` backfill + (added for issue #169) for aggregate report documents that were saved + before those fields existed. For each name in ``aggregate_indexes``, + this submits an ``update_by_query`` against the ``f"{name}*"`` index + pattern (the real indexes are date-suffixed) as a non-blocking + background task (``wait_for_completion=False``), so it never delays + parsedmarc startup. Submission is guarded by a cheap ``count`` query + that only matches documents with DKIM/SPF results but no combined + field, so once an index is fully backfilled, later calls are a fast + no-op. Any error talking to the cluster (e.g. no indexes yet on a + fresh install, or a transient connection issue) is caught and logged + as a warning rather than raised; the backfill is simply retried on the + next startup, and the manual ``_update_by_query`` command documented + in ``docs/source/elasticsearch.md`` remains available in the meantime. + + Third, the same treatment for the ``policies_combined``/ + ``failure_details_combined`` fields (same issue #169) on SMTP TLS + report documents, for each name in ``smtp_tls_indexes``. Args: aggregate_indexes (list): A list of aggregate index names failure_indexes (list): A list of failure index names (accepted for API compatibility; no migrations are currently needed for failure indexes) + smtp_tls_indexes (list): A list of SMTP TLS index names """ - version = 2 - if aggregate_indexes is None: - aggregate_indexes = [] - for aggregate_index_name in aggregate_indexes: - if not Index(aggregate_index_name).exists(): - continue - aggregate_index = Index(aggregate_index_name) - doc = "doc" - fo_field = "published_policy.fo" - fo = "fo" - fo_mapping = aggregate_index.get_field_mapping(fields=[fo_field]) - fo_mapping = fo_mapping[list(fo_mapping.keys())[0]]["mappings"] - if doc not in fo_mapping: - continue + if not aggregate_indexes and not smtp_tls_indexes: + return - fo_mapping = fo_mapping[doc][fo_field]["mapping"][fo] - fo_type = fo_mapping["type"] - if fo_type == "long": - new_index_name = "{0}-v{1}".format(aggregate_index_name, version) - body = { - "properties": { - "published_policy.fo": { - "type": "text", - "fields": {"keyword": {"type": "keyword", "ignore_above": 256}}, + version = 2 + for aggregate_index_name in aggregate_indexes or []: + try: + if not Index(aggregate_index_name).exists(): + continue + aggregate_index = Index(aggregate_index_name) + doc = "doc" + fo_field = "published_policy.fo" + fo = "fo" + fo_mapping = aggregate_index.get_field_mapping(fields=[fo_field]) + fo_mapping = fo_mapping[list(fo_mapping.keys())[0]]["mappings"] + if doc not in fo_mapping: + continue + + fo_mapping = fo_mapping[doc][fo_field]["mapping"][fo] + fo_type = fo_mapping["type"] + if fo_type == "long": + new_index_name = "{0}-v{1}".format(aggregate_index_name, version) + body = { + "properties": { + "published_policy.fo": { + "type": "text", + "fields": { + "keyword": {"type": "keyword", "ignore_above": 256} + }, + } } } - } - Index(new_index_name).create() - Index(new_index_name).put_mapping(doc_type=doc, body=body) - reindex(connections.get_connection(), aggregate_index_name, new_index_name) - Index(aggregate_index_name).delete() + Index(new_index_name).create() + Index(new_index_name).put_mapping(doc_type=doc, body=body) + reindex( + connections.get_connection(), aggregate_index_name, new_index_name + ) + Index(aggregate_index_name).delete() + except Exception as e: + logger.warning( + "Failed the legacy published_policy.fo migration for " + f"{aggregate_index_name}: {e}. This will be retried at the " + "next startup." + ) + + try: + client = connections.get_connection() + except Exception as e: + logger.warning( + "Skipping the dkim_results_combined/spf_results_combined/" + "policies_combined/failure_details_combined backfill: could " + f"not get an OpenSearch connection: {e}. This will be retried " + "at the next startup." + ) + return + for name in aggregate_indexes or []: + pattern = f"{name}*" + try: + count_response = client.count( + index=pattern, + body={"query": _COMBINED_BACKFILL_QUERY}, + ignore_unavailable=True, + allow_no_indices=True, + ) + count = count_response["count"] + if not count: + continue + update_response = client.update_by_query( + index=pattern, + body={ + "query": _COMBINED_BACKFILL_QUERY, + "script": { + "source": _COMBINED_BACKFILL_SCRIPT, + "lang": "painless", + }, + }, + conflicts="proceed", + wait_for_completion=False, + ignore_unavailable=True, + allow_no_indices=True, + ) + task_id = update_response.get("task") + logger.info( + "Backfilling dkim_results_combined/spf_results_combined on " + f"{count} existing documents in {pattern} (task {task_id})" + ) + except Exception as e: + logger.warning( + "Failed to check/submit the dkim_results_combined/" + f"spf_results_combined backfill for {pattern}: {e}. This " + "will be retried at the next startup; the manual " + "_update_by_query command in the documentation remains " + "available in the meantime." + ) + + for name in smtp_tls_indexes or []: + pattern = f"{name}*" + try: + count_response = client.count( + index=pattern, + body={"query": _SMTP_TLS_COMBINED_BACKFILL_QUERY}, + ignore_unavailable=True, + allow_no_indices=True, + ) + count = count_response["count"] + if not count: + continue + update_response = client.update_by_query( + index=pattern, + body={ + "query": _SMTP_TLS_COMBINED_BACKFILL_QUERY, + "script": { + "source": _SMTP_TLS_COMBINED_BACKFILL_SCRIPT, + "lang": "painless", + }, + }, + conflicts="proceed", + wait_for_completion=False, + ignore_unavailable=True, + allow_no_indices=True, + ) + task_id = update_response.get("task") + logger.info( + "Backfilling policies_combined/failure_details_combined on " + f"{count} existing documents in {pattern} (task {task_id})" + ) + except Exception as e: + logger.warning( + "Failed to check/submit the policies_combined/" + f"failure_details_combined backfill for {pattern}: {e}. " + "This will be retried at the next startup; the manual " + "_update_by_query command in the documentation remains " + "available in the meantime." + ) def save_aggregate_report_to_opensearch( @@ -885,6 +1238,16 @@ def save_smtp_tls_report_to_opensearch( policy_strings = policy["policy_strings"] if "mx_host_patterns" in policy: mx_host_patterns = policy["mx_host_patterns"] + # policies_combined/failure_details_combined: see the field + # declarations on _SMTPTLSReportDoc and issue #169. policies and + # their failure_details are object arrays with the same + # cross-product problem as dkim_results/spf_results, so dashboards + # aggregate these composed strings instead of the raw subfields. + policy_domain_combined = policy.get("policy_domain") or "none" + policy_type_combined = policy.get("policy_type") or "none" + smtp_tls_doc.policies_combined.append( + "{0} / {1}".format(policy_domain_combined, policy_type_combined) + ) policy_doc = _SMTPTLSPolicyDoc( policy_domain=policy["policy_domain"], policy_type=policy["policy_type"], @@ -905,7 +1268,12 @@ def save_smtp_tls_report_to_opensearch( if "receiving_mx_hostname" in failure_detail: receiving_mx_hostname = failure_detail["receiving_mx_hostname"] - if "additional_information_uri" in failure_detail: + # The parser's key is additional_info_uri (see + # SMTPTLSFailureDetailsOptional in types.py); accept the + # long-form key too for dicts built by other callers. + if "additional_info_uri" in failure_detail: + additional_information_uri = failure_detail["additional_info_uri"] + elif "additional_information_uri" in failure_detail: additional_information_uri = failure_detail[ "additional_information_uri" ] @@ -930,6 +1298,16 @@ def save_smtp_tls_report_to_opensearch( additional_information_uri=additional_information_uri, failure_reason_code=failure_reason_code, ) + smtp_tls_doc.failure_details_combined.append( + "{0} / {1} / {2} / {3} / {4} / {5}".format( + policy_domain_combined, + policy_type_combined, + failure_detail.get("result_type") or "none", + sending_mta_ip or "none", + receiving_ip or "none", + receiving_mx_hostname or "none", + ) + ) smtp_tls_doc.policies.append(policy_doc) create_indexes([index], index_settings) diff --git a/tests/test_elastic.py b/tests/test_elastic.py index be1c551a..e739b49c 100644 --- a/tests/test_elastic.py +++ b/tests/test_elastic.py @@ -16,6 +16,7 @@ from parsedmarc.elastic import ( AlreadySaved, ElasticsearchError, create_indexes, + migrate_indexes, save_aggregate_report_to_elasticsearch, save_failure_report_to_elasticsearch, save_smtp_tls_report_to_elasticsearch, @@ -395,6 +396,143 @@ class TestCreateIndexesServerless(unittest.TestCase): mock_index.create.assert_called_once() +# --------------------------------------------------------------------------- +# migrate_indexes +# --------------------------------------------------------------------------- + + +class TestMigrateIndexes(unittest.TestCase): + """migrate_indexes backfills dkim_results_combined/spf_results_combined + (issue #169) on pre-existing aggregate documents as a non-blocking + background task. It is guarded by a cheap count() query so repeated + startups against an already-backfilled index are a no-op, and any SDK + error is caught and logged rather than raised, so it never blocks + parsedmarc startup.""" + + def test_backfill_submitted_when_old_docs_exist(self): + with patch("parsedmarc.elastic.connections.get_connection") as mock_get_conn: + mock_client = MagicMock() + mock_client.count.return_value = {"count": 42} + mock_get_conn.return_value = mock_client + migrate_indexes(aggregate_indexes=["dmarc_aggregate"]) + + mock_client.update_by_query.assert_called_once() + kwargs = mock_client.update_by_query.call_args.kwargs + self.assertEqual(kwargs["index"], "dmarc_aggregate*") + self.assertEqual(kwargs["conflicts"], "proceed") + self.assertFalse(kwargs["wait_for_completion"]) + self.assertEqual(kwargs["query"], elastic_module._COMBINED_BACKFILL_QUERY) + script_source = kwargs["script"]["source"] + self.assertIn("ctx._source.dkim_results_combined", script_source) + self.assertIn("ctx._source.spf_results_combined", script_source) + + # The count() guard query also targets the date-suffixed pattern. + count_kwargs = mock_client.count.call_args.kwargs + self.assertEqual(count_kwargs["index"], "dmarc_aggregate*") + self.assertEqual(count_kwargs["query"], elastic_module._COMBINED_BACKFILL_QUERY) + + def test_backfill_skipped_when_no_old_docs(self): + with patch("parsedmarc.elastic.connections.get_connection") as mock_get_conn: + mock_client = MagicMock() + mock_client.count.return_value = {"count": 0} + mock_get_conn.return_value = mock_client + migrate_indexes(aggregate_indexes=["dmarc_aggregate"]) + + mock_client.update_by_query.assert_not_called() + + def test_backfill_skipped_when_no_aggregate_indexes(self): + with patch("parsedmarc.elastic.connections.get_connection") as mock_get_conn: + migrate_indexes() + migrate_indexes(aggregate_indexes=None) + + mock_get_conn.assert_not_called() + + def test_backfill_failure_does_not_raise(self): + with patch("parsedmarc.elastic.connections.get_connection") as mock_get_conn: + mock_client = MagicMock() + mock_client.count.side_effect = RuntimeError("cluster unreachable") + mock_get_conn.return_value = mock_client + with self.assertLogs("parsedmarc.log", level="WARNING") as cm: + migrate_indexes(aggregate_indexes=["dmarc_aggregate"]) + + self.assertTrue(any("cluster unreachable" in msg for msg in cm.output)) + mock_client.update_by_query.assert_not_called() + + def test_get_connection_failure_does_not_raise(self): + """connections.get_connection() itself sits outside the per-index + try/except; if it raises (e.g. no Elasticsearch connection has been + configured yet), migrate_indexes must still not propagate the + exception, per its docstring's promise that any cluster error is + caught and logged.""" + with patch("parsedmarc.elastic.connections.get_connection") as mock_get_conn: + mock_get_conn.side_effect = RuntimeError("no connection") + with self.assertLogs("parsedmarc.log", level="WARNING") as cm: + migrate_indexes(aggregate_indexes=["dmarc_aggregate"]) + + self.assertTrue( + any("Skipping the dkim_results_combined" in msg for msg in cm.output) + ) + self.assertTrue(any("no connection" in msg for msg in cm.output)) + + def test_smtp_tls_backfill_submitted_when_old_docs_exist(self): + """SMTP TLS analogue of test_backfill_submitted_when_old_docs_exist: + policies_combined/failure_details_combined backfill (also issue + #169) is submitted with its own guard query and painless script.""" + with patch("parsedmarc.elastic.connections.get_connection") as mock_get_conn: + mock_client = MagicMock() + mock_client.count.return_value = {"count": 7} + mock_get_conn.return_value = mock_client + migrate_indexes(smtp_tls_indexes=["smtp_tls"]) + + mock_client.update_by_query.assert_called_once() + kwargs = mock_client.update_by_query.call_args.kwargs + self.assertEqual(kwargs["index"], "smtp_tls*") + self.assertEqual(kwargs["conflicts"], "proceed") + self.assertFalse(kwargs["wait_for_completion"]) + self.assertEqual( + kwargs["query"], elastic_module._SMTP_TLS_COMBINED_BACKFILL_QUERY + ) + script_source = kwargs["script"]["source"] + self.assertIn("ctx._source.policies_combined", script_source) + self.assertIn("ctx._source.failure_details_combined", script_source) + + count_kwargs = mock_client.count.call_args.kwargs + self.assertEqual(count_kwargs["index"], "smtp_tls*") + self.assertEqual( + count_kwargs["query"], elastic_module._SMTP_TLS_COMBINED_BACKFILL_QUERY + ) + + def test_smtp_tls_backfill_skipped_when_no_old_docs(self): + with patch("parsedmarc.elastic.connections.get_connection") as mock_get_conn: + mock_client = MagicMock() + mock_client.count.return_value = {"count": 0} + mock_get_conn.return_value = mock_client + migrate_indexes(smtp_tls_indexes=["smtp_tls"]) + + mock_client.update_by_query.assert_not_called() + + def test_smtp_tls_backfill_skipped_when_no_smtp_tls_indexes_or_aggregate(self): + with patch("parsedmarc.elastic.connections.get_connection") as mock_get_conn: + migrate_indexes() + migrate_indexes(smtp_tls_indexes=None) + + mock_get_conn.assert_not_called() + + def test_smtp_tls_backfill_failure_does_not_raise(self): + """SMTP TLS analogue of test_backfill_failure_does_not_raise: an + error from the cluster during the smtp_tls_indexes loop is caught + and logged rather than raised.""" + with patch("parsedmarc.elastic.connections.get_connection") as mock_get_conn: + mock_client = MagicMock() + mock_client.count.side_effect = RuntimeError("cluster unreachable") + mock_get_conn.return_value = mock_client + with self.assertLogs("parsedmarc.log", level="WARNING") as cm: + migrate_indexes(smtp_tls_indexes=["smtp_tls"]) + + self.assertTrue(any("cluster unreachable" in msg for msg in cm.output)) + mock_client.update_by_query.assert_not_called() + + # --------------------------------------------------------------------------- # save_aggregate_report_to_elasticsearch # --------------------------------------------------------------------------- @@ -549,6 +687,54 @@ class TestSaveAggregateReport(unittest.TestCase): mock_doc_cls.call_args.kwargs["date_begin"].timestamp(), 1705276800 ) + def test_save_populates_combined_dkim_and_spf_fields(self): + """Regression guard for issue #169: two DKIM signatures on one + record must yield exactly two combined entries, not a 4-way + cross-product. autospec=True is required on the save patch so + mock_save.call_args captures the doc instance as ``self``.""" + report = _aggregate_report() + report["records"][0]["auth_results"] = { + "dkim": [ + { + "domain": "example.net", + "selector": "net1", + "result": "fail", + "human_result": None, + }, + { + "domain": "example.org", + "selector": "org1", + "result": "pass", + "human_result": None, + }, + ], + "spf": [ + { + "domain": "example.org", + "scope": "mfrom", + "result": "pass", + "human_result": None, + }, + ], + } + with ( + patch("parsedmarc.elastic.Search", return_value=_empty_search()), + patch( + "parsedmarc.elastic.Index", + return_value=MagicMock(exists=MagicMock(return_value=True)), + ), + patch.object( + elastic_module._AggregateReportDoc, "save", autospec=True + ) as mock_save, + ): + save_aggregate_report_to_elasticsearch(report) + doc = mock_save.call_args[0][0] + self.assertEqual( + list(doc.dkim_results_combined), + ["net1 / example.net / fail", "org1 / example.org / pass"], + ) + self.assertEqual(list(doc.spf_results_combined), ["mfrom / example.org / pass"]) + class TestAggregateDocPassedDmarc(unittest.TestCase): """The _AggregateReportDoc.save() override derives passed_dmarc — the @@ -576,6 +762,57 @@ class TestAggregateDocPassedDmarc(unittest.TestCase): self.assertEqual(bool(doc.passed_dmarc), expected) +class TestAggregateDocCombinedResults(unittest.TestCase): + """add_dkim_result/add_spf_result never touch the network, so these + construct _AggregateReportDoc directly rather than going through the + save_* entry point.""" + + def test_add_dkim_result_appends_combined_string(self): + """Regression guard for issue #169: dkim_results/spf_results are + arrays of objects that the engine dynamic-maps as plain ``object`` + (not ``nested``) and flattens, so Kibana/Grafana tables cannot + terms-aggregate their subfields without producing a cross-product + of selector/domain/result values. The composed + "selector / domain / result" string preserves the per-signature + pairing that the flattened array loses.""" + doc = elastic_module._AggregateReportDoc() + doc.add_dkim_result( + domain="example.net", selector="net1", result="fail", human_result=None + ) + doc.add_dkim_result( + domain="example.org", selector="org1", result="pass", human_result=None + ) + expected = ["net1 / example.net / fail", "org1 / example.org / pass"] + # dkim_results_combined is declared as Text(multi=True, ...); the SDK + # stub types the class attribute as Text (no Iterable protocol), + # even though the runtime value is an AttrList once multi=True is + # set. Same category of stub gap as the Q()/meta.index ignores in + # elastic.py. + self.assertEqual(list(doc.dkim_results_combined), expected) # pyright: ignore[reportArgumentType] + self.assertEqual(doc.to_dict()["dkim_results_combined"], expected) + + def test_add_spf_result_appends_combined_string(self): + doc = elastic_module._AggregateReportDoc() + doc.add_spf_result( + domain="example.org", scope="mfrom", result="pass", human_result=None + ) + expected = ["mfrom / example.org / pass"] + self.assertEqual(list(doc.spf_results_combined), expected) # pyright: ignore[reportArgumentType] + self.assertEqual(doc.to_dict()["spf_results_combined"], expected) + + def test_spf_result_serializes_under_singular_result_key(self): + """The _SPFResult class previously declared a dead ``results`` + (plural) field while the save path wrote ``result``; verify the + serialized inner doc actually uses the singular key.""" + doc = elastic_module._AggregateReportDoc() + doc.add_spf_result( + domain="example.org", scope="mfrom", result="pass", human_result=None + ) + d = doc.to_dict()["spf_results"][0] + self.assertEqual(d["result"], "pass") + self.assertNotIn("results", d) + + # --------------------------------------------------------------------------- # save_failure_report_to_elasticsearch # --------------------------------------------------------------------------- @@ -930,6 +1167,82 @@ class TestSaveSmtpTlsReport(unittest.TestCase): save_smtp_tls_report_to_elasticsearch(report) mock_save.assert_called_once() + def test_save_populates_combined_policy_and_failure_detail_fields(self): + """Regression guard for the SMTP TLS analogue of issue #169: + policies and their failure_details are object arrays, so stacked + terms aggregations on their subfields cross-product just like + dkim_results/spf_results did. Two policies (one with two failure + details, one with none) must yield exactly two policies_combined + entries and two failure_details_combined entries, not a + cross-product. autospec=True is required on the save patch so + mock_save.call_args captures the doc instance as ``self``.""" + report = _smtp_tls_report( + policies=[ + { + "policy_domain": "example.com", + "policy_type": "sts", + "successful_session_count": 100, + "failed_session_count": 2, + "failure_details": [ + { + "result_type": "certificate-expired", + "failed_session_count": 1, + "sending_mta_ip": "192.0.2.1", + "receiving_ip": "203.0.113.1", + "receiving_mx_hostname": "mx1.example.com", + "additional_info_uri": ( + "https://reports.example.com/tls-help" + ), + }, + { + "result_type": "starttls-not-supported", + "failed_session_count": 1, + "sending_mta_ip": "192.0.2.2", + "receiving_ip": "203.0.113.2", + "receiving_mx_hostname": "mx2.example.com", + }, + ], + }, + { + "policy_domain": "example.net", + "policy_type": "tlsa", + "successful_session_count": 50, + "failed_session_count": 0, + }, + ] + ) + with ( + patch("parsedmarc.elastic.Search", return_value=_empty_search()), + patch("parsedmarc.elastic.Index"), + patch.object( + elastic_module._SMTPTLSReportDoc, "save", autospec=True + ) as mock_save, + ): + save_smtp_tls_report_to_elasticsearch(report) + doc = mock_save.call_args[0][0] + self.assertEqual( + list(doc.policies_combined), ["example.com / sts", "example.net / tlsa"] + ) + expected_detail_expired = ( + "example.com / sts / certificate-expired / 192.0.2.1 / " + "203.0.113.1 / mx1.example.com" + ) + expected_detail_starttls = ( + "example.com / sts / starttls-not-supported / 192.0.2.2 / " + "203.0.113.2 / mx2.example.com" + ) + self.assertEqual( + list(doc.failure_details_combined), + [expected_detail_expired, expected_detail_starttls], + ) + # The parser emits additional_info_uri (SMTPTLSFailureDetailsOptional + # in types.py); the saver must persist it on the declared + # additional_information_uri field rather than dropping it. + self.assertEqual( + doc.policies[0].failure_details[0].additional_information_uri, + "https://reports.example.com/tls-help", + ) + class TestBackwardCompatAlias(unittest.TestCase): def test_save_forensic_alias_points_to_save_failure(self): diff --git a/tests/test_opensearch.py b/tests/test_opensearch.py index 130013b9..c1748466 100644 --- a/tests/test_opensearch.py +++ b/tests/test_opensearch.py @@ -376,16 +376,193 @@ class TestCreateIndexes(unittest.TestCase): class TestMigrateIndexes(unittest.TestCase): + """migrate_indexes backfills dkim_results_combined/spf_results_combined + (issue #169) on pre-existing aggregate documents as a non-blocking + background task. It is guarded by a cheap count() query so repeated + startups against an already-backfilled index are a no-op, and any SDK + error is caught and logged rather than raised, so it never blocks + parsedmarc startup.""" + + def test_backfill_submitted_when_old_docs_exist(self): + with ( + patch("parsedmarc.opensearch.Index") as mock_index_cls, + patch("parsedmarc.opensearch.connections.get_connection") as mock_get_conn, + ): + # The legacy fo migration that runs first sees no base index. + mock_index_cls.return_value.exists.return_value = False + mock_client = MagicMock() + mock_client.count.return_value = {"count": 42} + mock_get_conn.return_value = mock_client + migrate_indexes(aggregate_indexes=["dmarc_aggregate"]) + + mock_client.update_by_query.assert_called_once() + kwargs = mock_client.update_by_query.call_args.kwargs + self.assertEqual(kwargs["index"], "dmarc_aggregate*") + self.assertEqual(kwargs["conflicts"], "proceed") + self.assertFalse(kwargs["wait_for_completion"]) + self.assertEqual( + kwargs["body"]["query"], opensearch_module._COMBINED_BACKFILL_QUERY + ) + script_source = kwargs["body"]["script"]["source"] + self.assertIn("ctx._source.dkim_results_combined", script_source) + self.assertIn("ctx._source.spf_results_combined", script_source) + + # The count() guard query also targets the date-suffixed pattern. + count_kwargs = mock_client.count.call_args.kwargs + self.assertEqual(count_kwargs["index"], "dmarc_aggregate*") + self.assertEqual( + count_kwargs["body"]["query"], opensearch_module._COMBINED_BACKFILL_QUERY + ) + + def test_backfill_skipped_when_no_old_docs(self): + with ( + patch("parsedmarc.opensearch.Index") as mock_index_cls, + patch("parsedmarc.opensearch.connections.get_connection") as mock_get_conn, + ): + mock_index_cls.return_value.exists.return_value = False + mock_client = MagicMock() + mock_client.count.return_value = {"count": 0} + mock_get_conn.return_value = mock_client + migrate_indexes(aggregate_indexes=["dmarc_aggregate"]) + + mock_client.update_by_query.assert_not_called() + + def test_backfill_skipped_when_no_aggregate_indexes(self): + with patch("parsedmarc.opensearch.connections.get_connection") as mock_get_conn: + migrate_indexes() + migrate_indexes(aggregate_indexes=None) + + mock_get_conn.assert_not_called() + + def test_backfill_failure_does_not_raise(self): + with ( + patch("parsedmarc.opensearch.Index") as mock_index_cls, + patch("parsedmarc.opensearch.connections.get_connection") as mock_get_conn, + ): + mock_index_cls.return_value.exists.return_value = False + mock_client = MagicMock() + mock_client.count.side_effect = RuntimeError("cluster unreachable") + mock_get_conn.return_value = mock_client + with self.assertLogs("parsedmarc.log", level="WARNING") as cm: + migrate_indexes(aggregate_indexes=["dmarc_aggregate"]) + + self.assertTrue(any("cluster unreachable" in msg for msg in cm.output)) + mock_client.update_by_query.assert_not_called() + + def test_get_connection_failure_does_not_raise(self): + """connections.get_connection() itself sits outside the per-index + try/except for the combined-field backfill; if it raises (e.g. no + OpenSearch connection has been configured yet), migrate_indexes must + still not propagate the exception, per its docstring's promise that + any cluster error is caught and logged.""" + with ( + patch("parsedmarc.opensearch.Index") as mock_index_cls, + patch("parsedmarc.opensearch.connections.get_connection") as mock_get_conn, + ): + # The legacy fo migration that runs first sees no base index. + mock_index_cls.return_value.exists.return_value = False + mock_get_conn.side_effect = RuntimeError("no connection") + with self.assertLogs("parsedmarc.log", level="WARNING") as cm: + migrate_indexes(aggregate_indexes=["dmarc_aggregate"]) + + self.assertTrue( + any("Skipping the dkim_results_combined" in msg for msg in cm.output) + ) + self.assertTrue(any("no connection" in msg for msg in cm.output)) + + def test_smtp_tls_backfill_submitted_when_old_docs_exist(self): + """SMTP TLS analogue of test_backfill_submitted_when_old_docs_exist: + policies_combined/failure_details_combined backfill (also issue + #169) is submitted with its own guard query and painless script.""" + with ( + patch("parsedmarc.opensearch.Index") as mock_index_cls, + patch("parsedmarc.opensearch.connections.get_connection") as mock_get_conn, + ): + # The legacy fo migration that runs first sees no base index. + mock_index_cls.return_value.exists.return_value = False + mock_client = MagicMock() + mock_client.count.return_value = {"count": 7} + mock_get_conn.return_value = mock_client + migrate_indexes(smtp_tls_indexes=["smtp_tls"]) + + mock_client.update_by_query.assert_called_once() + kwargs = mock_client.update_by_query.call_args.kwargs + self.assertEqual(kwargs["index"], "smtp_tls*") + self.assertEqual(kwargs["conflicts"], "proceed") + self.assertFalse(kwargs["wait_for_completion"]) + self.assertEqual( + kwargs["body"]["query"], opensearch_module._SMTP_TLS_COMBINED_BACKFILL_QUERY + ) + script_source = kwargs["body"]["script"]["source"] + self.assertIn("ctx._source.policies_combined", script_source) + self.assertIn("ctx._source.failure_details_combined", script_source) + + count_kwargs = mock_client.count.call_args.kwargs + self.assertEqual(count_kwargs["index"], "smtp_tls*") + self.assertEqual( + count_kwargs["body"]["query"], + opensearch_module._SMTP_TLS_COMBINED_BACKFILL_QUERY, + ) + + def test_smtp_tls_backfill_skipped_when_no_old_docs(self): + with ( + patch("parsedmarc.opensearch.Index") as mock_index_cls, + patch("parsedmarc.opensearch.connections.get_connection") as mock_get_conn, + ): + mock_index_cls.return_value.exists.return_value = False + mock_client = MagicMock() + mock_client.count.return_value = {"count": 0} + mock_get_conn.return_value = mock_client + migrate_indexes(smtp_tls_indexes=["smtp_tls"]) + + mock_client.update_by_query.assert_not_called() + + def test_smtp_tls_backfill_skipped_when_no_smtp_tls_indexes_or_aggregate(self): + with patch("parsedmarc.opensearch.connections.get_connection") as mock_get_conn: + migrate_indexes() + migrate_indexes(smtp_tls_indexes=None) + + mock_get_conn.assert_not_called() + + def test_smtp_tls_backfill_failure_does_not_raise(self): + """SMTP TLS analogue of test_backfill_failure_does_not_raise: an + error from the cluster during the smtp_tls_indexes loop is caught + and logged rather than raised.""" + with ( + patch("parsedmarc.opensearch.Index") as mock_index_cls, + patch("parsedmarc.opensearch.connections.get_connection") as mock_get_conn, + ): + mock_index_cls.return_value.exists.return_value = False + mock_client = MagicMock() + mock_client.count.side_effect = RuntimeError("cluster unreachable") + mock_get_conn.return_value = mock_client + with self.assertLogs("parsedmarc.log", level="WARNING") as cm: + migrate_indexes(smtp_tls_indexes=["smtp_tls"]) + + self.assertTrue(any("cluster unreachable" in msg for msg in cm.output)) + mock_client.update_by_query.assert_not_called() + + +class TestMigrateIndexesFoMigration(unittest.TestCase): """The legacy `published_policy.fo` field was mapped as `long` in older indexes. migrate_indexes detects that and rebuilds the index with the text/keyword shape. The branch is gnarly; a regression - would silently leave old data un-migrated.""" + would silently leave old data un-migrated. Each test stubs the + combined-field backfill that now runs afterwards in the same call + (count 0 → no-op).""" - def test_no_indexes_is_noop(self): - migrate_indexes() # Should not raise + @staticmethod + def _noop_backfill_client(): + client = MagicMock() + client.count.return_value = {"count": 0} + return client def test_skips_non_existent_index(self): - with patch("parsedmarc.opensearch.Index") as mock_index_cls: + with ( + patch("parsedmarc.opensearch.Index") as mock_index_cls, + patch("parsedmarc.opensearch.connections.get_connection") as mock_get_conn, + ): + mock_get_conn.return_value = self._noop_backfill_client() mock_index_cls.return_value.exists.return_value = False migrate_indexes(aggregate_indexes=["missing"]) # exists() returned False — no field_mapping fetch. @@ -394,12 +571,16 @@ class TestMigrateIndexes(unittest.TestCase): def test_skips_when_doc_mapping_absent(self): """An index that has 'fo' but not under the 'doc' type (e.g., empty index with default mapping) is left alone.""" - with patch("parsedmarc.opensearch.Index") as mock_index_cls: + with ( + patch("parsedmarc.opensearch.Index") as mock_index_cls, + patch("parsedmarc.opensearch.connections.get_connection") as mock_get_conn, + patch("parsedmarc.opensearch.reindex") as mock_reindex, + ): + mock_get_conn.return_value = self._noop_backfill_client() idx = mock_index_cls.return_value idx.exists.return_value = True idx.get_field_mapping.return_value = {"some_key": {"mappings": {}}} - with patch("parsedmarc.opensearch.reindex") as mock_reindex: - migrate_indexes(aggregate_indexes=["dmarc_aggregate-2023-01-01"]) + migrate_indexes(aggregate_indexes=["dmarc_aggregate-2023-01-01"]) mock_reindex.assert_not_called() def test_migrates_when_fo_is_long(self): @@ -411,6 +592,8 @@ class TestMigrateIndexes(unittest.TestCase): patch("parsedmarc.opensearch.reindex") as mock_reindex, patch("parsedmarc.opensearch.connections.get_connection") as mock_get_conn, ): + mock_client = self._noop_backfill_client() + mock_get_conn.return_value = mock_client idx = mock_index_cls.return_value idx.exists.return_value = True idx.get_field_mapping.return_value = { @@ -423,16 +606,18 @@ class TestMigrateIndexes(unittest.TestCase): } } migrate_indexes(aggregate_indexes=["dmarc_aggregate-2023-01-01"]) - # reindex called from old → new (v2) index. + # reindex called from old → new (v2) index, with the client from + # connections.get_connection(). mock_reindex.assert_called_once() - # connections.get_connection consulted to get the ES client. - mock_get_conn.assert_called_once() + self.assertIs(mock_reindex.call_args.args[0], mock_client) def test_skips_when_fo_already_text(self): with ( patch("parsedmarc.opensearch.Index") as mock_index_cls, + patch("parsedmarc.opensearch.connections.get_connection") as mock_get_conn, patch("parsedmarc.opensearch.reindex") as mock_reindex, ): + mock_get_conn.return_value = self._noop_backfill_client() idx = mock_index_cls.return_value idx.exists.return_value = True idx.get_field_mapping.return_value = { @@ -447,6 +632,35 @@ class TestMigrateIndexes(unittest.TestCase): migrate_indexes(aggregate_indexes=["dmarc_aggregate-2024-01-01"]) mock_reindex.assert_not_called() + def test_index_exists_failure_does_not_raise(self): + """A cluster error inside the per-index fo-migration loop (e.g. + Index(...).exists() raising because the cluster is unreachable) + must not abort startup: it is caught, logged, and the loop moves + on to the combined-field backfill, which is exercised here with + its own connection failure so both warnings are asserted.""" + with ( + patch("parsedmarc.opensearch.Index") as mock_index_cls, + patch("parsedmarc.opensearch.connections.get_connection") as mock_get_conn, + ): + mock_index_cls.return_value.exists.side_effect = ConnectionError( + "cluster unreachable" + ) + mock_get_conn.side_effect = RuntimeError("no connection") + with self.assertLogs("parsedmarc.log", level="WARNING") as cm: + migrate_indexes(aggregate_indexes=["dmarc_aggregate"]) + + self.assertTrue( + any( + "legacy published_policy.fo migration" in msg + and "cluster unreachable" in msg + for msg in cm.output + ) + ) + self.assertTrue( + any("Skipping the dkim_results_combined" in msg for msg in cm.output) + ) + self.assertTrue(any("no connection" in msg for msg in cm.output)) + # --------------------------------------------------------------------------- # save_aggregate_report_to_opensearch @@ -602,6 +816,54 @@ class TestSaveAggregateReport(unittest.TestCase): mock_doc_cls.call_args.kwargs["date_begin"].timestamp(), 1705276800 ) + def test_save_populates_combined_dkim_and_spf_fields(self): + """Regression guard for issue #169: two DKIM signatures on one + record must yield exactly two combined entries, not a 4-way + cross-product. autospec=True is required on the save patch so + mock_save.call_args captures the doc instance as ``self``.""" + report = _aggregate_report() + report["records"][0]["auth_results"] = { + "dkim": [ + { + "domain": "example.net", + "selector": "net1", + "result": "fail", + "human_result": None, + }, + { + "domain": "example.org", + "selector": "org1", + "result": "pass", + "human_result": None, + }, + ], + "spf": [ + { + "domain": "example.org", + "scope": "mfrom", + "result": "pass", + "human_result": None, + }, + ], + } + with ( + patch("parsedmarc.opensearch.Search", return_value=_empty_search()), + patch( + "parsedmarc.opensearch.Index", + return_value=MagicMock(exists=MagicMock(return_value=True)), + ), + patch.object( + opensearch_module._AggregateReportDoc, "save", autospec=True + ) as mock_save, + ): + save_aggregate_report_to_opensearch(report) + doc = mock_save.call_args[0][0] + self.assertEqual( + list(doc.dkim_results_combined), + ["net1 / example.net / fail", "org1 / example.org / pass"], + ) + self.assertEqual(list(doc.spf_results_combined), ["mfrom / example.org / pass"]) + class TestAggregateDocPassedDmarc(unittest.TestCase): """The _AggregateReportDoc.save() override derives passed_dmarc — the @@ -629,6 +891,56 @@ class TestAggregateDocPassedDmarc(unittest.TestCase): self.assertEqual(bool(doc.passed_dmarc), expected) +class TestAggregateDocCombinedResults(unittest.TestCase): + """add_dkim_result/add_spf_result never touch the network, so these + construct _AggregateReportDoc directly rather than going through the + save_* entry point.""" + + def test_add_dkim_result_appends_combined_string(self): + """Regression guard for issue #169: dkim_results/spf_results are + arrays of objects that the engine dynamic-maps as plain ``object`` + (not ``nested``) and flattens, so Kibana/Grafana tables cannot + terms-aggregate their subfields without producing a cross-product + of selector/domain/result values. The composed + "selector / domain / result" string preserves the per-signature + pairing that the flattened array loses.""" + doc = opensearch_module._AggregateReportDoc() + doc.add_dkim_result( + domain="example.net", selector="net1", result="fail", human_result=None + ) + doc.add_dkim_result( + domain="example.org", selector="org1", result="pass", human_result=None + ) + expected = ["net1 / example.net / fail", "org1 / example.org / pass"] + # dkim_results_combined is declared as Text(multi=True, ...); the SDK + # stub types the class attribute as Text (no Iterable protocol), + # even though the runtime value is an AttrList once multi=True is + # set. + self.assertEqual(list(doc.dkim_results_combined), expected) # pyright: ignore[reportArgumentType] + self.assertEqual(doc.to_dict()["dkim_results_combined"], expected) + + def test_add_spf_result_appends_combined_string(self): + doc = opensearch_module._AggregateReportDoc() + doc.add_spf_result( + domain="example.org", scope="mfrom", result="pass", human_result=None + ) + expected = ["mfrom / example.org / pass"] + self.assertEqual(list(doc.spf_results_combined), expected) # pyright: ignore[reportArgumentType] + self.assertEqual(doc.to_dict()["spf_results_combined"], expected) + + def test_spf_result_serializes_under_singular_result_key(self): + """The _SPFResult class previously declared a dead ``results`` + (plural) field while the save path wrote ``result``; verify the + serialized inner doc actually uses the singular key.""" + doc = opensearch_module._AggregateReportDoc() + doc.add_spf_result( + domain="example.org", scope="mfrom", result="pass", human_result=None + ) + d = doc.to_dict()["spf_results"][0] + self.assertEqual(d["result"], "pass") + self.assertNotIn("results", d) + + # --------------------------------------------------------------------------- # save_failure_report_to_opensearch # --------------------------------------------------------------------------- @@ -979,6 +1291,82 @@ class TestSaveSmtpTlsReport(unittest.TestCase): save_smtp_tls_report_to_opensearch(report) mock_save.assert_called_once() + def test_save_populates_combined_policy_and_failure_detail_fields(self): + """Regression guard for the SMTP TLS analogue of issue #169: + policies and their failure_details are object arrays, so stacked + terms aggregations on their subfields cross-product just like + dkim_results/spf_results did. Two policies (one with two failure + details, one with none) must yield exactly two policies_combined + entries and two failure_details_combined entries, not a + cross-product. autospec=True is required on the save patch so + mock_save.call_args captures the doc instance as ``self``.""" + report = _smtp_tls_report( + policies=[ + { + "policy_domain": "example.com", + "policy_type": "sts", + "successful_session_count": 100, + "failed_session_count": 2, + "failure_details": [ + { + "result_type": "certificate-expired", + "failed_session_count": 1, + "sending_mta_ip": "192.0.2.1", + "receiving_ip": "203.0.113.1", + "receiving_mx_hostname": "mx1.example.com", + "additional_info_uri": ( + "https://reports.example.com/tls-help" + ), + }, + { + "result_type": "starttls-not-supported", + "failed_session_count": 1, + "sending_mta_ip": "192.0.2.2", + "receiving_ip": "203.0.113.2", + "receiving_mx_hostname": "mx2.example.com", + }, + ], + }, + { + "policy_domain": "example.net", + "policy_type": "tlsa", + "successful_session_count": 50, + "failed_session_count": 0, + }, + ] + ) + with ( + patch("parsedmarc.opensearch.Search", return_value=_empty_search()), + patch("parsedmarc.opensearch.Index"), + patch.object( + opensearch_module._SMTPTLSReportDoc, "save", autospec=True + ) as mock_save, + ): + save_smtp_tls_report_to_opensearch(report) + doc = mock_save.call_args[0][0] + self.assertEqual( + list(doc.policies_combined), ["example.com / sts", "example.net / tlsa"] + ) + expected_detail_expired = ( + "example.com / sts / certificate-expired / 192.0.2.1 / " + "203.0.113.1 / mx1.example.com" + ) + expected_detail_starttls = ( + "example.com / sts / starttls-not-supported / 192.0.2.2 / " + "203.0.113.2 / mx2.example.com" + ) + self.assertEqual( + list(doc.failure_details_combined), + [expected_detail_expired, expected_detail_starttls], + ) + # The parser emits additional_info_uri (SMTPTLSFailureDetailsOptional + # in types.py); the saver must persist it on the declared + # additional_information_uri field rather than dropping it. + self.assertEqual( + doc.policies[0].failure_details[0].additional_information_uri, + "https://reports.example.com/tls-help", + ) + class TestBackwardCompatAlias(unittest.TestCase): def test_save_forensic_alias_points_to_save_failure(self):