mirror of
https://github.com/domainaware/parsedmarc.git
synced 2026-08-01 05:02:18 +00:00
* Unify DMARC pass/fail chart naming on "DMARC compliance" The same metric had three names across the dashboards: the pass/fail pie chart was "Passed DMARC" (OpenSearch, Splunk) and "DMARC Passage" (Grafana), while the per-domain table column added in #834 was "% DMARC Compliant". Converge on the compliance wording, which already anchored the "Message volume and DMARC compliance by from domain" panels in every dashboard family: - Pie chart: "DMARC compliance" ("DMARC Compliance" in Grafana, which title-cases panel names; OpenSearch and Splunk use sentence case). - Line chart: "DMARC passage over time" -> "DMARC compliance over time" everywhere, so the pie rename doesn't leave the same inconsistency one panel down. - Splunk filter dropdown label: "Passed DMARC" -> "DMARC compliant". - Grafana Guide panel text and docs/source/kibana.md prose updated to match, plus a pre-existing typo fix ("pie charts. you"). Field names, tokens, and queries (passed_dmarc / dmarc_passed) are untouched; only human-facing titles, labels, and prose changed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix "pie charts. you" typo in the Grafana Guide panel text The period-for-comma typo fixed in docs/source/kibana.md also existed in the Grafana Guide panel markdown, in both duplicated copies of the guide text (content and options.content). Caught by Copilot review on PR #865. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Sync the Grafana Guide panel walkthrough with the current panels The Guide text described a three-column table layout with a from-domain list "on the right" and directed readers to a "Message From Header" table, but the dashboard has two table columns (Reporting Organisations on the left; Top 2000 Message Sources by Reverse DNS on the right with Message volume and DMARC compliance by from domain below it) and no Message From Header table. Point the walkthrough at the real panel titles and mention the per-domain compliance percentage the table shows, mirroring the kibana.md walkthrough updated in #834. Both duplicated copies of the guide markdown (content and options.content) remain identical. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Scope the CHANGELOG rename claim to dashboards that have the charts The bullet said the pass/fail charts were renamed "across the ... Grafana dashboards", but the PostgreSQL Grafana variant has no pass/fail pie or over-time chart, so the claim quantified over a dashboard it doesn't apply to. Name the affected dashboards explicitly and note the PostgreSQL variant is unchanged. Caught by Copilot review on PR #865. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
373 lines
16 KiB
XML
373 lines
16 KiB
XML
<form theme="dark" version="1.1">
|
|
<label>Aggregate DMARC data</label>
|
|
<description>A summary of aggregate DMARC report data</description>
|
|
<search id="base_search">
|
|
<query>
|
|
index="email" sourcetype="dmarc:aggregate" spf_aligned=$spf_aligned$ dkim_aligned=$dkim_aligned$ passed_dmarc=$passed_dmarc$ org_name=$org_name$ source_reverse_dns=$source_reverse_dns$ header_from=$header_from$ envelope_from=$envelope_from$ (published_policy.p=$domain_policy$ OR published_policy.sp=$domain_policy$) disposition=$disposition$ source_ip_address=$source_ip_address$ source_base_domain=$source_base_domain$ source_country=$source_country$
|
|
| rename spf_results{}.domain as envelope_domain spf_results{}.result as spf_result spf_results{}.scope as spf_scope dkim_results{}.selector as dkim_selector dkim_results{}.domain as dkim_domain dkim_results{}.result as dkim_result
|
|
| fillnull value=null source_reverse_dns source_base_domain dkim_selector dkim_domain dkim_result source_type source_name source_as_name
|
|
| search dkim_selector=$dkim_selector$ dkim_domain=$dkim_domain$ source_type="$source_type$" source_name="$source_name$" source_as_name="$source_as_name$"
|
|
| table *
|
|
</query>
|
|
<earliest>$time_range.earliest$</earliest>
|
|
<latest>$time_range.latest$</latest>
|
|
</search>
|
|
<fieldset submitButton="false" autoRun="true">
|
|
<input type="dropdown" token="spf_aligned" searchWhenChanged="true">
|
|
<label>SPF alignment</label>
|
|
<choice value="*">any</choice>
|
|
<choice value="true">true</choice>
|
|
<choice value="false">false</choice>
|
|
<default>*</default>
|
|
</input>
|
|
<input type="dropdown" token="dkim_aligned" searchWhenChanged="true">
|
|
<label>DKIM alignment</label>
|
|
<choice value="*">any</choice>
|
|
<choice value="true">true</choice>
|
|
<choice value="false">false</choice>
|
|
<default>*</default>
|
|
</input>
|
|
<input type="dropdown" token="passed_dmarc" searchWhenChanged="true">
|
|
<label>DMARC compliant</label>
|
|
<choice value="*">any</choice>
|
|
<choice value="true">true</choice>
|
|
<choice value="false">false</choice>
|
|
<default>*</default>
|
|
</input>
|
|
<input type="text" token="org_name" searchWhenChanged="true">
|
|
<label>Reporting organization</label>
|
|
<default>*</default>
|
|
</input>
|
|
<input type="text" token="header_from" searchWhenChanged="true">
|
|
<label>Message header from</label>
|
|
<default>*</default>
|
|
</input>
|
|
<input type="text" token="envelope_from" searchWhenChanged="true">
|
|
<label>Envelope from</label>
|
|
<default>*</default>
|
|
</input>
|
|
<input type="dropdown" token="domain_policy" searchWhenChanged="true">
|
|
<label>Domain policy</label>
|
|
<choice value="*">any</choice>
|
|
<choice value="none">none</choice>
|
|
<choice value="quarantine">quarantine</choice>
|
|
<choice value="reject">reject</choice>
|
|
<default>*</default>
|
|
</input>
|
|
<input type="dropdown" token="disposition" searchWhenChanged="true">
|
|
<label>Message disposition</label>
|
|
<choice value="*">any</choice>
|
|
<choice value="none">none</choice>
|
|
<choice value="quarantine">quarantine</choice>
|
|
<choice value="reject">reject</choice>
|
|
<default>*</default>
|
|
</input>
|
|
<input type="dropdown" token="source_name" searchWhenChanged="true">
|
|
<label>Source name</label>
|
|
<default>*</default>
|
|
<choice value="*">any</choice>
|
|
<initialValue>*</initialValue>
|
|
<fieldForLabel>source_name</fieldForLabel>
|
|
<fieldForValue>source_name</fieldForValue>
|
|
<search>
|
|
<query>index="email" sourcetype="dmarc:aggregate" source_type="$source_type$"
|
|
| stats count by source_name</query>
|
|
</search>
|
|
</input>
|
|
<input type="dropdown" token="source_type" searchWhenChanged="true">
|
|
<label>Source type</label>
|
|
<default>*</default>
|
|
<initialValue>*</initialValue>
|
|
<choice value="*">any</choice>
|
|
<fieldForLabel>source_type</fieldForLabel>
|
|
<fieldForValue>source_type</fieldForValue>
|
|
<search>
|
|
<query>index="email" sourcetype="dmarc:aggregate"
|
|
| stats count by source_type</query>
|
|
</search>
|
|
</input>
|
|
<input type="dropdown" token="source_as_name" searchWhenChanged="true">
|
|
<label>Source AS name</label>
|
|
<choice value="*">any</choice>
|
|
<default>*</default>
|
|
<initialValue>*</initialValue>
|
|
<fieldForLabel>source_as_name</fieldForLabel>
|
|
<fieldForValue>source_as_name</fieldForValue>
|
|
<search>
|
|
<query>index="email" sourcetype="dmarc:aggregate"
|
|
| stats count by source_as_name</query>
|
|
<earliest>0</earliest>
|
|
<latest></latest>
|
|
</search>
|
|
</input>
|
|
<input type="text" token="source_ip_address" searchWhenChanged="true">
|
|
<label>Source IP address</label>
|
|
<default>*</default>
|
|
</input>
|
|
<input type="text" token="source_reverse_dns" searchWhenChanged="true">
|
|
<label>Source reverse DNS</label>
|
|
<default>*</default>
|
|
</input>
|
|
<input type="text" token="source_base_domain" searchWhenChanged="true">
|
|
<label>Source base domain</label>
|
|
<default>*</default>
|
|
</input>
|
|
<input type="text" token="source_country" searchWhenChanged="true">
|
|
<label>Source country ISO code</label>
|
|
<default>*</default>
|
|
</input>
|
|
<input type="text" token="dkim_selector" searchWhenChanged="true">
|
|
<label>DKIM selector</label>
|
|
<default>*</default>
|
|
</input>
|
|
<input type="text" token="dkim_domain" searchWhenChanged="true">
|
|
<label>DKIM domain</label>
|
|
<default>*</default>
|
|
</input>
|
|
<input type="time" token="time_range" searchWhenChanged="true">
|
|
<label>Time range</label>
|
|
<default>
|
|
<earliest>-7d@h</earliest>
|
|
<latest>now</latest>
|
|
</default>
|
|
</input>
|
|
</fieldset>
|
|
<row>
|
|
<panel>
|
|
<title>SPF alignment</title>
|
|
<chart>
|
|
<search base="base_search">
|
|
<query>| stats sum(message_count) as message_count by spf_aligned</query>
|
|
</search>
|
|
<option name="charting.chart">pie</option>
|
|
<option name="charting.drilldown">none</option>
|
|
<option name="charting.fieldColors">{"true":0x65a637,"false":0xd93f3c}</option>
|
|
</chart>
|
|
</panel>
|
|
<panel>
|
|
<title>DKIM alignment</title>
|
|
<chart>
|
|
<search base="base_search">
|
|
<query> | stats sum(message_count) by dkim_aligned</query>
|
|
</search>
|
|
<option name="charting.chart">pie</option>
|
|
<option name="charting.drilldown">none</option>
|
|
<option name="charting.fieldColors">{"true":0x65a637,"false":0xd93f3c}</option>
|
|
<option name="height">250</option>
|
|
</chart>
|
|
</panel>
|
|
<panel>
|
|
<title>DMARC compliance</title>
|
|
<chart>
|
|
<search base="base_search">
|
|
<query>| stats sum(message_count) by passed_dmarc</query>
|
|
</search>
|
|
<option name="charting.chart">pie</option>
|
|
<option name="charting.drilldown">none</option>
|
|
<option name="charting.fieldColors">{"true":0x65a637,"false":0xd93f3c}</option>
|
|
</chart>
|
|
</panel>
|
|
</row>
|
|
<row>
|
|
<panel>
|
|
<title>Reporting organizations</title>
|
|
<table>
|
|
<search base="base_search">
|
|
<query>| stats sum(message_count) as message_count by org_name | sort -message_count</query>
|
|
</search>
|
|
<option name="drilldown">none</option>
|
|
<option name="refresh.display">progressbar</option>
|
|
<format type="number" field="sum(message_count)">
|
|
<option name="precision">0</option>
|
|
</format>
|
|
</table>
|
|
</panel>
|
|
<panel>
|
|
<title>Message sources by reverse DNS</title>
|
|
<table>
|
|
<search base="base_search">
|
|
<query>| fillnull value="none" | stats sum(message_count) as message_count by source_base_domain | sort -message_count</query>
|
|
</search>
|
|
<option name="drilldown">none</option>
|
|
<option name="refresh.display">progressbar</option>
|
|
<format type="number" field="sum(message_count)">
|
|
<option name="precision">0</option>
|
|
</format>
|
|
</table>
|
|
</panel>
|
|
<panel>
|
|
<title>Message volume and DMARC compliance by from domain</title>
|
|
<table>
|
|
<search base="base_search">
|
|
<query>| stats sum(message_count) as Messages, sum(eval(if(passed_dmarc="true", message_count, 0))) as passed by header_from | eval "% DMARC Compliant"=if(Messages>0, round(passed/Messages*100, 1), null()) | fields - passed | rename header_from as "From Domain" | sort -Messages</query>
|
|
</search>
|
|
<option name="drilldown">none</option>
|
|
<option name="refresh.display">progressbar</option>
|
|
<format type="number" field="Messages">
|
|
<option name="precision">0</option>
|
|
</format>
|
|
<format type="number" field="% DMARC Compliant">
|
|
<option name="precision">1</option>
|
|
</format>
|
|
</table>
|
|
</panel>
|
|
</row>
|
|
<row>
|
|
<panel>
|
|
<title>Message sources by name and type</title>
|
|
<table>
|
|
<search base="base_search">
|
|
<query> | stats sum(message_count) as message_count by source_name, source_type | sort -message_count</query>
|
|
</search>
|
|
<option name="drilldown">none</option>
|
|
<option name="refresh.display">progressbar</option>
|
|
<format type="number" field="message_count">
|
|
<option name="precision">0</option>
|
|
</format>
|
|
</table>
|
|
</panel>
|
|
</row>
|
|
<row>
|
|
<panel>
|
|
<title>Message sources by Autonomous System</title>
|
|
<table>
|
|
<search base="base_search">
|
|
<query>| stats sum(message_count) as message_count by source_asn, source_as_name, source_as_domain
|
|
| eval source_asn = if(isnotnull(source_asn) AND source_asn!="", "AS" . source_asn, source_asn)
|
|
| sort -message_count</query>
|
|
</search>
|
|
<option name="drilldown">none</option>
|
|
<option name="refresh.display">progressbar</option>
|
|
<format type="number" field="message_count">
|
|
<option name="precision">0</option>
|
|
</format>
|
|
</table>
|
|
</panel>
|
|
</row>
|
|
<row>
|
|
<panel>
|
|
<title>DMARC compliance over time</title>
|
|
<chart>
|
|
<search base="base_search">
|
|
<query>| timechart span=1d sum(message_count) as message_count by passed_dmarc</query>
|
|
</search>
|
|
<option name="charting.axisTitleX.text">Time</option>
|
|
<option name="charting.axisTitleX.visibility">visible</option>
|
|
<option name="charting.axisTitleY.text">Messages</option>
|
|
<option name="charting.axisTitleY.visibility">visible</option>
|
|
<option name="charting.axisTitleY2.visibility">visible</option>
|
|
<option name="charting.chart">line</option>
|
|
<option name="charting.drilldown">none</option>
|
|
<option name="charting.fieldColors">{"true":0x65a637,"false":0xd93f3c}</option>
|
|
<option name="charting.legend.placement">right</option>
|
|
<option name="height">280</option>
|
|
<option name="refresh.display">progressbar</option>
|
|
</chart>
|
|
</panel>
|
|
</row>
|
|
<row>
|
|
<panel>
|
|
<title>Message disposition over time</title>
|
|
<chart>
|
|
<search base="base_search">
|
|
<query>| timechart span=1d sum(message_count) as message_count by disposition</query>
|
|
</search>
|
|
<option name="charting.axisTitleX.text">Time</option>
|
|
<option name="charting.axisTitleY.text">Messages</option>
|
|
<option name="charting.chart">line</option>
|
|
<option name="charting.chart.nullValueMode">zero</option>
|
|
<option name="charting.chart.showDataLabels">none</option>
|
|
<option name="charting.drilldown">none</option>
|
|
<option name="charting.fieldColors">{"none":0x65a637,"quarantine":0xf2b134,"reject":0xd93f3c}</option>
|
|
<option name="refresh.display">progressbar</option>
|
|
</chart>
|
|
</panel>
|
|
</row>
|
|
<row>
|
|
<panel>
|
|
<title>Map of message sources by country</title>
|
|
<map>
|
|
<search base="base_search">
|
|
<query> | iplocation source_ip_address | stats sum(message_count) as message_count by Country | geom geo_countries featureIdField="Country"</query>
|
|
</search>
|
|
<option name="drilldown">none</option>
|
|
<option name="height">566</option>
|
|
<option name="mapping.type">choropleth</option>
|
|
</map>
|
|
</panel>
|
|
<panel>
|
|
<title>Message sources by country</title>
|
|
<table>
|
|
<search base="base_search">
|
|
<query>| stats sum(message_count) as message_count by source_country | sort -message_count</query>
|
|
</search>
|
|
<option name="count">20</option>
|
|
<option name="drilldown">none</option>
|
|
<option name="percentagesRow">false</option>
|
|
<option name="refresh.display">progressbar</option>
|
|
<format type="number" field="sum(message_count)">
|
|
<option name="precision">0</option>
|
|
</format>
|
|
</table>
|
|
</panel>
|
|
</row>
|
|
<row>
|
|
<panel>
|
|
<title>Message sources by IP address</title>
|
|
<table>
|
|
<search base="base_search">
|
|
<query>| fillnull value="none" source_reverse_dns source_base_domain | fillnull value="unknown" source_country | stats sum(message_count) as message_count by source_ip_address,source_reverse_dns,source_base_domain,source_country | sort -message_count</query>
|
|
</search>
|
|
<option name="drilldown">none</option>
|
|
<option name="refresh.display">progressbar</option>
|
|
<format type="number" field="message_count">
|
|
<option name="precision">0</option>
|
|
</format>
|
|
</table>
|
|
</panel>
|
|
</row>
|
|
<row>
|
|
<panel>
|
|
<title>SPF details</title>
|
|
<table>
|
|
<search base="base_search">
|
|
<query>| 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</query>
|
|
</search>
|
|
<option name="drilldown">none</option>
|
|
<option name="refresh.display">progressbar</option>
|
|
<format type="number" field="message_count">
|
|
<option name="precision">0</option>
|
|
</format>
|
|
</table>
|
|
</panel>
|
|
</row>
|
|
<row>
|
|
<panel>
|
|
<title>DKIM details</title>
|
|
<table>
|
|
<search base="base_search">
|
|
<query>| 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</query>
|
|
</search>
|
|
<option name="drilldown">none</option>
|
|
<option name="refresh.display">progressbar</option>
|
|
<format type="number" field="message_count">
|
|
<option name="precision">0</option>
|
|
</format>
|
|
</table>
|
|
</panel>
|
|
</row>
|
|
</form>
|