Make the output and mailbox integrations optional extras (#888)

* Make the output and mailbox integrations optional extras (#883)

Breaking change for the next major release: pip install parsedmarc now
installs the parsing core plus a working core CLI (file, IMAP, Maildir,
and mbox input; CSV/JSON, Splunk HEC, webhook, and syslog output).
Everything else moves behind an extra: elastic, opensearch, kafka, s3,
gelf, loganalytics, msgraph, and gmail, joining the existing postgresql
extra, with an umbrella [all] that deliberately excludes postgresql
(psycopg's binary wheels do not exist on every platform, so
parsedmarc[all] must never fail to install there).

cli.py imports the six SDK-dependent output modules behind the #884
TYPE_CHECKING/try-except guard; a configured section whose extra is
missing fails fast with a ConfigurationError naming the section and the
exact pip install command — including the msgraph and gmail_api mailbox
sections (detected via parsedmarc.mail's placeholder classes) and
postgresql (checked before the constructor so the startup retry loop
does not retry a missing dependency for a minute). The Azure/kiota Graph
error types fall back to never-raised sentinel classes.

The Docker image installs [all,postgresql], so container users see no
change. CI lint installs [build,all,postgresql]; the unit-test job
installs [build,all], deliberately without postgresql so
test_postgres.py's absent-psycopg arm stays exercised. The
never-imported dateparser dependency is dropped in favor of declaring
python-dateutil, which utils.py actually imports; pytz moves to the
build extra for the one test that uses it.

Verified live: a no-extras wheel install imports, parses samples, and
reports the install hint for each gated section; a [all] install
restores every integration; the Docker image builds with every SDK
importable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Patch psycopg presence in the PostgreSQL CLI wiring tests

CI's unit-test job deliberately installs [build,all] without the
postgresql extra, so parsedmarc.cli.postgres.psycopg is None there and
the new missing-extra presence check correctly made _main exit 1 before
the wiring under test ran. The tests simulate the SDK being available
(PostgreSQLClient is mocked at the SDK boundary), so the module-level
psycopg handle is now patched present in setUp. Verified against a
simulated psycopg-absent environment as well as the local full install.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Address Copilot review: narrow guards to ModuleNotFoundError, fix docs

- The optional-integration and Graph error-type import guards now catch
  ModuleNotFoundError instead of ImportError, so only a genuinely absent
  package reads as a missing extra; a broken-but-present SDK fails
  loudly with its real error instead of masquerading as one. The test
  blocker raises ModuleNotFoundError accordingly — the exact exception a
  missing package produces.
- _missing_extra_hint docstring no longer calls every gated integration
  an output module (it also serves the msgraph/gmail_api mailbox
  sections).
- Fix the pre-existing passsword typo in usage.md's kafka section; the
  INI key the code reads is password (cli.py _parse_config).

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Quote extras specs in copy-paste install commands

From Copilot's second review round: zsh treats an unquoted .[build,all]
as a glob and fails with 'no matches found', so the commands shown in
AGENTS.md, CONTRIBUTING.md, dashboards/README.md, and the bootstrap
script's comment are now quoted. The CI workflows keep the unquoted
form: they run under bash, which passes unmatched globs through
literally. The suggestion to change the 'Choosing what to install'
heading level was rejected — it is a subsection of 'Installing
parsedmarc', matching the file's existing hierarchy.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Fix upgrade command in the changelog

* Documentation review: accuracy, spelling, grammar, and clarity pass

A full prose review of docs/source, README, CONTRIBUTING, and the
dashboards README, with every accuracy claim verified against the code
before changing it. Highlights:

- usage.md: documented six missing [general] options (the CSV/JSON
  filename options, prettify_json, normalize_timespan_threshold_hours),
  the required kafka smtp_tls_topic, [imap] timeout/max_retries, and
  the postgresql env-var prefix; corrected the maildir_path default
  (None, not INBOX — cli.py Namespace defaults), the mailbox
  check_timeout option name, the systemd restart interval (RestartSec
  is 5m), and merged the duplicate silent entry; quoted every
  copy-paste extras spec for zsh safety.
- elasticsearch.md: fixed an invalid openssl command (rsa:4096 -nodes),
  the dashboards filename (opensearch_dashboards.ndjson, matching the
  file the link serves), and assorted grammar.
- davmail.md: the service-enable command now enables davmail.service
  (was parsedmarc.service — a copy-paste error that left DavMail
  unenabled), plus a view typo and DavMail capitalization.
- output.md: the example schema reference is RFC 7489 Appendix C
  (7480 is RDAP). kibana.md: SPF relies on the SMTP envelope, not
  session headers (RFC 7208). dmarc.md: DKM -> DKIM.
- README: the intro now also names the OpenSearch/Grafana stack,
  matching the feature list. CONTRIBUTING: pre-PR checks now include
  ruff format --check and pyright, matching CI's lint job.
- dashboards/README: the service table and seed description now include
  the PostgreSQL backend the compose stack runs.

Sample data blocks, the CLI-help mirror block, and released CHANGELOG
entries were deliberately left untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Docstring review: accuracy, spelling, grammar, and clarity pass

Every docstring in parsedmarc/, parsedmarc/mail/, the maps maintainer
scripts, and the test suite reviewed with each claim verified against
the code it documents. Text-only — no behavior changes. Highlights:

- Copy-paste errors corrected: parsed_smtp_tls_reports_to_csv and
  splunk/loganalytics save functions described aggregate or failure
  reports they do not handle; LogAnalyticsException claimed to be an
  Elasticsearch error.
- Docstring/behavior mismatches: parse_report_email's report_type
  enumeration omitted smtp_tls; parse_failure_report typed msg_date as
  str (it is datetime); strip_attachment_payloads claimed payloads are
  replaced with None (the key is deleted); kafkaclient's failure and
  SMTP TLS savers claimed per-record slicing while sending the whole
  list in one message (docstrings now describe reality — whether
  slicing was intended is flagged for follow-up); the postgres savers
  claimed to take parse_report_file's return value but receive the
  inner report dict; elastic/opensearch save functions' Raises listed
  only AlreadySaved.
- None-as-semantic-state documented where missing (get_base_domain,
  get_ip_address_country), enumeration completeness fixed
  (get_ip_address_info's 9 result keys, maps script outputs, TSV
  columns), and the stale 44-industry-types count corrected to the
  46 the authoritative README list defines.
- Test docstrings aligned with what the tests actually assert,
  including two that overstated coverage of the elastic/opensearch
  address-list tests.
- Two argparse help strings fixed: file_path now names SMTP TLS report
  files alongside aggregate and failure, mirrored into usage.md's
  CLI-help block; --offline's doubled spaces removed (rendered help
  unchanged).
- elasticsearch.md's security claim corrected against Elastic's docs:
  security is enabled and auto-configured on first startup since 8.0
  (not "8.7 secure mode"), so the settings are verified, not
  hand-written.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Sean Whalen
2026-08-28 17:33:18 -04:00
committed by GitHub
co-authored by Claude Fable 5 Copilot
parent 52be8850b2
commit 07bca1ad28
44 changed files with 1131 additions and 283 deletions
+7 -7
View File
@@ -2,12 +2,12 @@
:::{note}
Starting in 8.0.0, parsedmarc supports accessing Microsoft/Office 365
inboxes via the Microsoft Graph API, which is preferred over Davmail.
inboxes via the Microsoft Graph API, which is preferred over DavMail.
:::
Some organizations do not allow IMAP or the Microsoft Graph API,
and only support Exchange Web Services (EWS)/Outlook Web Access (OWA).
In that case, Davmail will need to be set up
In that case, DavMail will need to be set up
as a local EWS/OWA IMAP gateway. It can even work where
[Modern Auth/multi-factor authentication] is required.
@@ -22,7 +22,7 @@ Install Java:
sudo apt-get install default-jre-headless
```
Configure Davmail by creating a `davmail.properties` file
Configure DavMail by creating a `davmail.properties` file
```properties
# DavMail settings, see http://davmail.sourceforge.net/ for documentation
@@ -135,7 +135,7 @@ Then, enable the service
```bash
sudo systemctl daemon-reload
sudo systemctl enable parsedmarc.service
sudo systemctl enable davmail.service
sudo service davmail restart
```
@@ -163,7 +163,7 @@ service davmail status
:::{note}
In the event of a crash, systemd will restart the service after 5
minutes, but the `service davmail status` command will only show the
logs for the current process. To vew the logs for previous runs as
logs for the current process. To view the logs for previous runs as
well as the current process (newest to oldest), run:
```bash
@@ -174,8 +174,8 @@ journalctl -u davmail.service -r
## Configuring parsedmarc for DavMail
Because you are interacting with DavMail server over the loopback
(i.e. `127.0.0.1`), add the following options to `parsedmarc.ini`
Because you are interacting with the DavMail server over the loopback
(i.e. `127.0.0.1`), add the following options to the `parsedmarc.ini`
config file:
```ini
+2 -2
View File
@@ -16,12 +16,12 @@ check out the sister project,
### Lookalike domains
DMARC protects against domain spoofing, not lookalike domains. for open source
DMARC protects against domain spoofing, not lookalike domains. For open source
lookalike domain monitoring, check out [DomainAware](https://github.com/seanthegeek/domainaware).
## DMARC Alignment Guide
DMARC ensures that SPF and DKM authentication mechanisms actually authenticate
DMARC ensures that SPF and DKIM authentication mechanisms actually authenticate
against the same domain that the end user sees.
A message passes a DMARC check by passing DKIM or SPF, **as long as the related
+24 -19
View File
@@ -55,13 +55,17 @@ sudo systemctl start elasticsearch.service
sudo systemctl start kibana.service
```
As of Elasticsearch 8.7, activate secure mode (xpack.security.*.ssl)
Since Elasticsearch 8.0, security is enabled and auto-configured on
first startup: TLS certificates are generated, the `xpack.security.*`
settings below are written to `elasticsearch.yml`, and a password is
generated for the `elastic` user. Verify the settings are present —
and add them only if your install skipped auto-configuration:
```bash
sudo vim /etc/elasticsearch/elasticsearch.yml
```
Add the following configuration
The security configuration looks like this:
```text
# Enable security features
@@ -92,11 +96,12 @@ openssl req -x509 -nodes -days 365 -newkey rsa:4096 -keyout kibana.key -out kiba
Or, to create a Certificate Signing Request (CSR) for a CA, run:
```bash
openssl req -newkey rsa:4096-nodes -keyout kibana.key -out kibana.csr
openssl req -newkey rsa:4096 -nodes -keyout kibana.key -out kibana.csr
```
Fill in the prompts. Watch out for Common Name (e.g. server FQDN or YOUR
domain name), which is the IP address or domain name that you will use to access Kibana. it is the most important field.
domain name), which is the IP address or domain name that you will use to
access Kibana. It is the most important field.
If you generated a CSR, remove the CSR after you have your certs
@@ -129,7 +134,7 @@ server.ssl.key: /etc/kibana/kibana.key
:::{note}
For more security, you can configure Kibana to use a local network connection
to elasticsearch :
to Elasticsearch:
```text
elasticsearch.hosts: ['https://SERVER_IP:9200']
```
@@ -149,14 +154,14 @@ Enroll Kibana in Elasticsearch
sudo /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana
```
Then access to your web server at `https://SERVER_IP:5601`, accept the self-signed
certificate and paste the token in the "Enrollment token" field.
Then access your web server at `https://SERVER_IP:5601`, accept the self-signed
certificate, and paste the token in the "Enrollment token" field.
```bash
sudo /usr/share/kibana/bin/kibana-verification-code
```
Then put the verification code to your web browser.
Then enter the verification code in your web browser.
End Kibana configuration
@@ -182,12 +187,12 @@ sudo systemctl restart elasticsearch
Now that Elasticsearch is up and running, use `parsedmarc` to send data to
it.
Download (right-click the link and click save as) [export.ndjson].
Download (right-click the link and click save as) [opensearch_dashboards.ndjson].
Connect to kibana using the "elastic" user and the password you previously provide
on the console ("End Kibana configuration" part).
Connect to Kibana using the "elastic" user and the password you previously
provided on the console ("End Kibana configuration" part).
Import `export.ndjson` the Saved Objects tab of the Stack management
Import `opensearch_dashboards.ndjson` in the Saved Objects tab of the Stack Management
page of Kibana. (Hamburger menu -> "Management" -> "Stack Management" ->
"Kibana" -> "Saved Objects")
@@ -198,20 +203,20 @@ the commercial [X-Pack].
```{image} _static/screenshots/saved-objects.png
:align: center
:alt: A screenshot of setting the Saved Objects Stack management UI in Kibana
:alt: A screenshot of the Saved Objects Stack Management UI in Kibana
:target: _static/screenshots/saved-objects.png
```
```{image} _static/screenshots/confirm-overwrite.png
:align: center
:alt: A screenshot of the overwrite conformation prompt
:alt: A screenshot of the overwrite confirmation prompt
:target: _static/screenshots/confirm-overwrite.png
```
## Upgrading Kibana index patterns
`parsedmarc` 5.0.0 makes some changes to the way data is indexed in
Elasticsearch. if you are upgrading from a previous release of
Elasticsearch. If you are upgrading from a previous release of
`parsedmarc`, you need to complete the following steps to replace the
Kibana index patterns with versions that match the upgraded indexes:
@@ -220,10 +225,10 @@ Kibana index patterns with versions that match the upgraded indexes:
3. Check the checkboxes for the `dmarc_aggregate` and `dmarc_failure`
index patterns
4. Click Delete
5. Click Delete on the conformation message
5. Click Delete on the confirmation message
6. Download (right-click the link and click save as)
the latest version of [export.ndjson]
7. Import `export.ndjson` by clicking Import from the Kibana
the latest version of [opensearch_dashboards.ndjson]
7. Import `opensearch_dashboards.ndjson` by clicking Import from the Kibana
Saved Objects page
## Backfilling the combined DKIM/SPF result fields
@@ -408,6 +413,6 @@ retention regulations such as GDPR. For more information,
check out the Elastic guide to [managing time-based indexes efficiently](https://www.elastic.co/blog/managing-time-based-indices-efficiently).
[elasticsearch]: https://www.elastic.co/guide/en/elasticsearch/reference/current/rpm.html
[export.ndjson]: https://raw.githubusercontent.com/domainaware/parsedmarc/master/dashboards/opensearch/opensearch_dashboards.ndjson
[opensearch_dashboards.ndjson]: https://raw.githubusercontent.com/domainaware/parsedmarc/master/dashboards/opensearch/opensearch_dashboards.ndjson
[kibana]: https://www.elastic.co/guide/en/kibana/current/rpm.html
[x-pack]: https://www.elastic.co/products/x-pack
+54 -3
View File
@@ -61,7 +61,7 @@ On Debian or Ubuntu systems, run:
sudo apt-get install -y python3-pip python3-venv python3-dev libxml2-dev libxslt-dev
```
On CentOS, RHEL, oR Rocky Linux systems, run:
On CentOS, RHEL, or Rocky Linux systems, run:
```bash
sudo dnf install -y python3 python3-pip python3-devel libxml2-devel libxslt-devel
@@ -89,13 +89,64 @@ any files created later are also owned by `parsedmarc`
```bash
sudo -u parsedmarc python3 -m venv /opt/parsedmarc/venv
sudo -u parsedmarc /opt/parsedmarc/venv/bin/pip install --upgrade pip
sudo -u parsedmarc /opt/parsedmarc/venv/bin/pip install --upgrade parsedmarc
sudo -u parsedmarc /opt/parsedmarc/venv/bin/pip install --upgrade "parsedmarc[all]"
```
To upgrade `parsedmarc` later, re-run the last command above and then
restart the service.
## Optional dependencies
### Choosing what to install
Starting with the next major release, the integrations that talk to
external systems live in optional extras, so an install only carries the
dependencies it actually uses.
`pip install parsedmarc` — the base install — provides:
- the parsing library (aggregate, failure, and SMTP TLS reports),
- the CLI, reading reports from files, an IMAP mailbox, a Maildir, or an
mbox file,
- and CSV/JSON, Splunk HEC, webhook, and syslog output.
Everything else needs an extra:
| Extra | Enables |
|---|---|
| `elastic` | The `[elasticsearch]` output |
| `opensearch` | The `[opensearch]` output |
| `kafka` | The `[kafka]` output |
| `s3` | The `[s3]` output |
| `gelf` | The `[gelf]` output |
| `loganalytics` | The `[log_analytics]` (Azure Monitor) output |
| `msgraph` | The `[msgraph]` mailbox input (Microsoft 365) |
| `gmail` | The `[gmail_api]` mailbox input |
| `postgresql` | The `[postgresql]` output |
Extras can be combined: `pip install "parsedmarc[elastic,msgraph]"`.
`pip install "parsedmarc[all]"` installs every extra in the table
**except** `postgresql`, which stays separate because `psycopg`'s
prebuilt binary wheels are not available for every platform — folding it
into `all` would make `parsedmarc[all]` fail to install there. Add it
explicitly when you need it: `pip install "parsedmarc[all,postgresql]"`.
If a configuration file names a section whose extra is not installed,
`parsedmarc` exits at startup with an error naming the exact
`pip install` command to run.
:::{note}
**Upgrading from 10.x:** every 10.x install carried the Elasticsearch,
OpenSearch, Kafka, AWS, Azure, Gmail, and Microsoft Graph packages,
whether or not it used them. They are no longer installed by
`pip install parsedmarc`, so switch your install command to
`pip install --upgrade "parsedmarc[all]"` (adding `postgresql` if you
use it) to keep every integration available. An in-place upgrade does
not uninstall packages you already have, but a rebuilt virtualenv — or
any fresh install — gets only what the extras name. Users of the
prebuilt Docker image are unaffected: it bundles `[all,postgresql]`.
:::
## Optional system dependencies
If you would like to be able to parse emails saved from Microsoft
Outlook (i.e. OLE .msg files), install `msgconvert`:
+3 -3
View File
@@ -28,7 +28,7 @@ will filter for that value.
:::{note}
Messages should not be considered malicious just because they failed to pass
DMARC; especially if you have just started collecting data. It may be a
DMARC, especially if you have just started collecting data. It may be a
legitimate service that needs SPF and DKIM configured correctly.
:::
@@ -36,7 +36,7 @@ Start by filtering the results to only show failed DKIM alignment. While DMARC
passes if a message passes SPF or DKIM alignment, only DKIM alignment remains
valid when a message is forwarded without changing the from address, which is
often caused by a mailbox forwarding rule. This is because DKIM signatures are
part of the message headers, whereas SPF relies on SMTP session headers.
part of the message headers, whereas SPF relies on the SMTP envelope.
Underneath the pie charts, you can see graphs of DMARC compliance and message
disposition over time.
@@ -74,7 +74,7 @@ your domains coming from consumer email services, such as Google/Gmail and
Yahoo! This occurs when customers have mailbox rules in place that forward
emails from an old account to a new account, which is why DKIM
authentication is so important, as mentioned earlier. Similar patterns may
be observed with businesses who send from reverse DNS addressees of
be observed with businesses who send from reverse DNS addresses of
parent, subsidiary, and outdated brands.
:::
+1 -1
View File
@@ -130,7 +130,7 @@ command line instead, for example:
touch var/templates/lists/list.example.com/en/list:member:regular:footer
```
Where `list.example.com` the list ID, and `en` is the language.
Where `list.example.com` is the list ID, and `en` is the language.
Then restart mailman core.
+1 -1
View File
@@ -5,7 +5,7 @@
Here are the results from parsing the [example](https://dmarc.org/wiki/FAQ#I_need_to_implement_aggregate_reports.2C_what_do_they_look_like.3F)
report from the dmarc.org wiki. It's actually an older draft of
the 1.0 report schema standardized in
[RFC 7480 Appendix C](https://tools.ietf.org/html/rfc7489#appendix-C).
[RFC 7489 Appendix C](https://tools.ietf.org/html/rfc7489#appendix-C).
This draft schema is still in wide use.
`parsedmarc` produces consistent, normalized output, regardless
+2 -2
View File
@@ -1,7 +1,7 @@
# Splunk
Starting in version 4.3.0 `parsedmarc` supports sending aggregate and/or
failure DMARC data to a Splunk [HTTP Event collector (HEC)].
failure DMARC data to a Splunk [HTTP Event Collector (HEC)].
The project repository contains [XML files] for premade Splunk
dashboards for aggregate and failure DMARC reports.
@@ -19,4 +19,4 @@ Kibana dashboards, although the Kibana dashboards have slightly
easier and more flexible filtering options.
[xml files]: https://github.com/domainaware/parsedmarc/tree/master/dashboards/splunk
[http event collector (hec)]: http://docs.splunk.com/Documentation/Splunk/latest/Data/AboutHEC
[http event collector (hec)]: https://docs.splunk.com/Documentation/Splunk/latest/Data/AboutHEC
+106 -51
View File
@@ -14,8 +14,8 @@ usage: parsedmarc [-h] [-c CONFIG_FILE] [-r] [--strip-attachment-payloads] [-o O
Parses DMARC reports
positional arguments:
file_path one or more paths to aggregate or failure report files, emails, mbox files, or directories
containing them
file_path one or more paths to aggregate, failure, or SMTP TLS report files, emails, mbox files, or
directories containing them
options:
-h, --help show this help message and exit
@@ -119,21 +119,23 @@ smtp_tls_url = https://smtp_tls_url.example.com
timeout = 60
```
The full set of configuration options are:
The full set of configuration options is:
- `general`
- `save_aggregate` - bool: Save aggregate report data to
Elasticsearch, Splunk and/or S3
- `save_failure` - bool: Save failure report data to
Elasticsearch, Splunk and/or S3
- `save_smtp_tls` - bool: Save SMTP-STS report data to
- `save_smtp_tls` - bool: Save SMTP TLS report data to
Elasticsearch, Splunk and/or S3
- `index_prefix_domain_map` - str: Path to a YAML file mapping
OpenSearch/Elasticsearch index prefixes to domain names
- `strip_attachment_payloads` - bool: Remove attachment
payloads from results
- `silent` - bool: Set this to `False` to output results to STDOUT
- `output` - str: Directory to place JSON and CSV files in. This is required if you set either of the JSON output file options.
- `silent` - bool: Only print errors; set this to `False` to output
results to STDOUT (Default: `True`)
- `output` - str: Directory to place JSON and CSV files in. The JSON
and CSV filename options below only take effect when this is set.
- `archive_directory` - str: Optional. When set, successfully
processed report files given as local file/directory path
arguments are moved into
@@ -156,6 +158,19 @@ The full set of configuration options are:
JSON output file
- `failure_json_filename` - str: filename for the failure
JSON output file
- `smtp_tls_json_filename` - str: filename for the SMTP TLS
JSON output file
- `aggregate_csv_filename` - str: filename for the aggregate
CSV output file
- `failure_csv_filename` - str: filename for the failure
CSV output file
- `smtp_tls_csv_filename` - str: filename for the SMTP TLS
CSV output file
- `prettify_json` - bool: Set this to `False` to output JSON in a
single line without indentation (Default: `True`)
- `normalize_timespan_threshold_hours` - float: Aggregate reports
covering a longer time span than this many hours have their
records normalized into per-day records (Default: `24`)
- `ip_db_path` - str: An optional custom path to a MMDB file
from IPinfo, MaxMind, or DBIP
- `ipinfo_url` - str: Overrides the default download URL for the
@@ -172,24 +187,25 @@ The full set of configuration options are:
- `offline` - bool: Do not use online queries for geolocation
or DNS. Also disables automatic downloading of the IP-to-country
database and reverse DNS map.
- `always_use_local_files` - Disables the download of the
- `always_use_local_files` - bool: Disables the download of the
IP-to-country database and reverse DNS map
- `local_reverse_dns_map_path` - Overrides the default local file path to use for the reverse DNS map
- `reverse_dns_map_url` - Overrides the default download URL for the reverse DNS map
- `local_psl_overrides_path` - Overrides the default local file path to use for the PSL overrides list
- `psl_overrides_url` - Overrides the default download URL for the PSL overrides list
- `local_reverse_dns_map_path` - str: Overrides the default local file path to use for the reverse DNS map
- `reverse_dns_map_url` - str: Overrides the default download URL for the reverse DNS map
- `local_psl_overrides_path` - str: Overrides the default local file path to use for the PSL overrides list
- `psl_overrides_url` - str: Overrides the default download URL for the PSL overrides list
- `nameservers` - str: A comma separated list of
DNS resolvers (Default: `[Cloudflare's public resolvers]`). Each entry
is an IP address (DNS over UDP/TCP port 53), an `https://` URL
(DNS over HTTPS), or `tls://ip[:port][#hostname]` (DNS over TLS) —
see [Encrypted DNS](#encrypted-dns)
- `dns_test_address` - str: a dummy address used for DNS pre-flight checks
(Default: 1.1.1.1)
- `dns_timeout` - float: DNS timeout period
- `dns_test_address` - str: A dummy address used for the DNS pre-flight
check that runs when `nameservers` is set (Default: `1.1.1.1`)
- `dns_timeout` - float: DNS timeout period in seconds (Default: `2.0`)
- `dns_retries` - int: Number of times to retry a DNS query after a
timeout or other transient error (Default: 0)
timeout or other transient error (Default: `0`)
- `debug` - bool: Print debugging messages
- `silent` - bool: Only print errors (Default: `True`)
- `verbose` - bool: More verbose output
- `warnings` - bool: Print warnings in addition to errors
- `fail_on_output_error` - bool: Exit with a non-zero status code if
any configured output destination fails while saving/publishing
reports (Default: `False`)
@@ -227,8 +243,9 @@ The full set of configuration options are:
(Default: `INBOX`)
- `archive_folder` - str: The mailbox folder (or label for
Gmail) to sort processed emails into (Default: `Archive`)
- `watch` - bool: Use the IMAP `IDLE` command to process
messages as they arrive or poll MS Graph for new messages
- `watch` - bool: Process new messages as they arrive, via the
IMAP `IDLE` command or by polling the other mailbox types
(Microsoft Graph, Gmail API, Maildir)
- `delete` - bool: Delete messages after processing them,
instead of archiving them
- `delete_aggregate` - bool: Delete aggregate report messages
@@ -271,9 +288,9 @@ The full set of configuration options are:
runs. See
[Mailbox messages are only archived once the reports are saved](#mailbox-messages-are-only-archived-once-the-reports-are-saved)
below.
- `since` - str: Search for messages since certain time. (Examples: `5m|3h|2d|1w`)
- `since` - str: Search for messages since a certain time. (Examples: `5m|3h|2d|1w`)
Acceptable units - {"m":"minutes", "h":"hours", "d":"days", "w":"weeks"}.
Defaults to `1d` if incorrect value is provided.
Defaults to `1d` if an incorrect value is provided.
- `imap`
- `host` - str: The IMAP server hostname or IP address
- `port` - int: The IMAP server port (Default: `993`)
@@ -296,9 +313,16 @@ The full set of configuration options are:
(Default: `True`)
- `skip_certificate_verification` - bool: Skip certificate
verification (not recommended)
- `timeout` - int: Number of seconds to wait for an IMAP operation
(Default: `30`)
- `max_retries` - int: Maximum number of retries after an IMAP
timeout (Default: `4`)
- `user` - str: The IMAP user
- `password` - str: The IMAP password
- `msgraph`
Requires the `msgraph` extra: `pip install "parsedmarc[msgraph]"`
- `auth_method` - str: Authentication method, valid types are
`UsernamePassword`, `DeviceCode`, `ClientSecret`, `Certificate`, or
`ClientAssertion` (Default: `UsernamePassword`).
@@ -307,7 +331,8 @@ The full set of configuration options are:
- `password` - str: The user password, required when the auth
method is UsernamePassword
- `client_id` - str: The app registration's client ID
- `client_secret` - str: The app registration's secret
- `client_secret` - str: The app registration's secret. Required when
the auth method is `UsernamePassword` or `ClientSecret`
- `certificate_path` - str: Path to a PEM or PKCS12 certificate
including the private key. Required when the auth method is
`Certificate`
@@ -329,8 +354,9 @@ The full set of configuration options are:
- `mailbox` - str: The mailbox name. This defaults to the
current user if using the UsernamePassword auth method, but
could be a shared mailbox if the user has access to the mailbox
- `graph_url` - str: Microsoft Graph URL. Allows for use of National Clouds (ex Azure Gov)
(Default: https://graph.microsoft.com)
- `graph_url` - str: Microsoft Graph URL. Allows for use of national
clouds (e.g. Azure Gov)
(Default: `https://graph.microsoft.com`)
:::{warning}
Setting `graph_url` alone is **not** sufficient for a national/sovereign
@@ -534,6 +560,9 @@ The full set of configuration options are:
| Invalid/rejected timestamp in the `since`/`receivedDateTime` filter | Historical bug (parsedmarc [#706](https://github.com/domainaware/parsedmarc/pull/706)/[#708](https://github.com/domainaware/parsedmarc/pull/708)): older versions appended a spurious `Z` to an already-UTC-offset ISO timestamp. Fixed since parsedmarc 9.5.1/9.5.5. | Upgrade parsedmarc if you're on a version older than 9.5.5. |
:::
- `elasticsearch`
Requires the `elastic` extra: `pip install "parsedmarc[elastic]"`
- `hosts` - str: A comma separated list of hostnames and ports
or URLs (e.g. `127.0.0.1:9200` or
`https://user:secret@localhost`)
@@ -548,7 +577,7 @@ The full set of configuration options are:
- `ssl` - bool: Use an encrypted SSL/TLS connection
(Default: `True`)
- `timeout` - float: Timeout in seconds (Default: 60)
- `cert_path` - str: Path to a trusted certificates
- `cert_path` - str: Path to a trusted CA certificates file
- `skip_certificate_verification` - bool: Skip certificate
verification (not recommended)
- `index_suffix` - str: A suffix to apply to the index names
@@ -565,6 +594,9 @@ The full set of configuration options are:
settings sent at index creation; any other settings (e.g.
`refresh_interval`) are passed through unchanged (Default: `False`)
- `opensearch`
Requires the `opensearch` extra: `pip install "parsedmarc[opensearch]"`
- `hosts` - str: A comma separated list of hostnames and ports
or URLs (e.g. `127.0.0.1:9200` or
`https://user:secret@localhost`)
@@ -583,7 +615,7 @@ The full set of configuration options are:
- `ssl` - bool: Use an encrypted SSL/TLS connection
(Default: `True`)
- `timeout` - float: Timeout in seconds (Default: 60)
- `cert_path` - str: Path to a trusted certificates
- `cert_path` - str: Path to a trusted CA certificates file
- `skip_certificate_verification` - bool: Skip certificate
verification (not recommended)
- `index_suffix` - str: A suffix to apply to the index names
@@ -600,14 +632,18 @@ The full set of configuration options are:
- `skip_certificate_verification` - bool: Skip certificate
verification (not recommended)
- `kafka`
Requires the `kafka` extra: `pip install "parsedmarc[kafka]"`
- `hosts` - str: A comma separated list of Kafka hosts
- `user` - str: The Kafka user
- `passsword` - str: The Kafka password
- `password` - str: The Kafka password
- `ssl` - bool: Use an encrypted SSL/TLS connection (Default: `True`)
- `skip_certificate_verification` - bool: Skip certificate
verification (not recommended)
- `aggregate_topic` - str: The Kafka topic for aggregate reports
- `failure_topic` - str: The Kafka topic for failure reports
- `smtp_tls_topic` - str: The Kafka topic for SMTP TLS reports
- `smtp`
The results email is only sent when at least one aggregate, failure,
@@ -632,7 +668,7 @@ The full set of configuration options are:
- `to` - list: A list of email addresses to send to
- `subject` - str: The Subject header to use in the email
(Default: `parsedmarc report`)
- `attachment` - str: The ZIP attachment filenames
- `attachment` - str: The ZIP attachment filename
(Default: `DMARC-<YYYY-MM-DD>.zip`)
- `message` - str: The email message
(Default: `Please see the attached DMARC results.`)
@@ -653,13 +689,14 @@ The full set of configuration options are:
all individual parameters above are ignored.
The PostgreSQL backend is an optional extra. Install it with
`pip install parsedmarc[postgresql]` (it pulls in `psycopg`); the
`pip install "parsedmarc[postgresql]"` (it pulls in `psycopg`); the
prebuilt binary wheels are not available for every platform, which is
why it is not a mandatory dependency. The prebuilt Docker image
(`ghcr.io/domainaware/parsedmarc`) already bundles this extra, so the
PostgreSQL backend works out of the box in the container — `psycopg`
ships `amd64` and `arm64` binary wheels, both of which the image
supports.
why it is the one extra that `parsedmarc[all]` deliberately leaves out
— combine the two with `pip install "parsedmarc[all,postgresql]"`. The
prebuilt Docker image (`ghcr.io/domainaware/parsedmarc`) already
bundles `[all,postgresql]`, so the PostgreSQL backend works out of the
box in the container — `psycopg` ships `amd64` and `arm64` binary
wheels, both of which the image supports.
Tables are created automatically on first run using
`CREATE TABLE IF NOT EXISTS`, so no manual schema migration is needed
@@ -692,6 +729,9 @@ The full set of configuration options are:
this section is configured.
- `s3`
Requires the `s3` extra: `pip install "parsedmarc[s3]"`
- `bucket` - str: The S3 bucket name
- `path` - str: The path to upload reports to (Default: `/`)
- `region_name` - str: The region name (Optional)
@@ -755,6 +795,9 @@ The full set of configuration options are:
```
- `gmail_api`
Requires the `gmail` extra: `pip install "parsedmarc[gmail]"`
- `credentials_file` - str: Path to file containing the
credentials, None to disable (Default: `None`)
- `token_file` - str: Path to save the token file
@@ -766,7 +809,9 @@ The full set of configuration options are:
accepted as `delegated_user` for backward compatibility.
:::{note}
credentials_file and token_file can be got with [quickstart](https://developers.google.com/gmail/api/quickstart/python).Please change the scope to `https://www.googleapis.com/auth/gmail.modify`.
`credentials_file` and `token_file` can be obtained by following the
Gmail API [quickstart](https://developers.google.com/gmail/api/quickstart/python).
Please change the scope to `https://www.googleapis.com/auth/gmail.modify`.
:::
:::{note}
When `auth_mode = service_account`, `credentials_file` must point to a
@@ -782,6 +827,9 @@ The full set of configuration options are:
- `paginate_messages` - bool: When `True`, fetch all applicable Gmail messages.
When `False`, only fetch up to 100 new messages per run (Default: `True`)
- `log_analytics`
Requires the `loganalytics` extra: `pip install "parsedmarc[loganalytics]"`
- `client_id` - str: The app registration's client ID
- `client_secret` - str: The app registration's client secret
- `tenant_id` - str: The tenant id where the app registration resides
@@ -792,22 +840,27 @@ The full set of configuration options are:
- `dcr_smtp_tls_stream` - str: The stream name for the SMTP TLS reports in the DCR
:::{note}
Information regarding the setup of the Data Collection Rule can be found [in the Azure documentation](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/tutorial-logs-ingestion-portal).
:::
Information regarding the setup of the Data Collection Rule can be found [in the Azure documentation](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/tutorial-logs-ingestion-portal).
:::
- `gelf`
Requires the `gelf` extra: `pip install "parsedmarc[gelf]"`
- `host` - str: The GELF server name or IP address
- `port` - int: The port to use
- `mode` - str: The GELF transport type to use. Valid modes: `tcp`, `udp`, `tls`
- `maildir`
- `maildir_path` - str: Full path for mailbox maildir location (Default: `INBOX`)
- `maildir_create` - bool: Create maildir if not present (Default: False)
- `maildir_path` - str: Full path to the maildir location (the key
`path` is accepted as an alias). Required to read from a maildir.
- `maildir_create` - bool: Create the maildir if not present
(Default: `False`; the key `create` is accepted as an alias)
- `webhook` - Post the individual reports to a webhook url with the report as the JSON body
- `webhook` - Post the individual reports to a webhook URL with the report as the JSON body
- `aggregate_url` - str: URL of the webhook which should receive the aggregate reports
- `failure_url` - str: URL of the webhook which should receive the failure reports
- `smtp_tls_url` - str: URL of the webhook which should receive the smtp_tls reports
- `timeout` - int: Interval in which the webhook call should timeout
- `smtp_tls_url` - str: URL of the webhook which should receive the SMTP TLS reports
- `timeout` - int: Timeout in seconds for webhook requests (Default: `60`)
:::{warning}
It is **strongly recommended** to **not** use the `nameservers`
@@ -847,8 +900,8 @@ known samples you want to save to that folder
:::
:::{warning}
Elasticsearch 8 change limits policy for shards, restricting by
default to 1000. parsedmarc use a shard per analyzed day. If you
Elasticsearch 8 changed the limits policy for shards, restricting them
by default to 1000. parsedmarc uses a shard per analyzed day. If you
have more than ~3 years of data, you will need to update this
limit.
Check current usage (from Management -> Dev Tools -> Console):
@@ -863,7 +916,7 @@ GET /_cluster/health?pretty
}
```
Update the limit to 2k per example:
For example, update the limit to 2000:
```text
PUT _cluster/settings
@@ -1123,6 +1176,7 @@ For sections with underscores in the name, the full section name is used:
| `kafka` | `PARSEDMARC_KAFKA_` |
| `smtp` | `PARSEDMARC_SMTP_` |
| `s3` | `PARSEDMARC_S3_` |
| `postgresql` | `PARSEDMARC_POSTGRESQL_` |
| `syslog` | `PARSEDMARC_SYSLOG_` |
| `gmail_api` | `PARSEDMARC_GMAIL_API_` |
| `maildir` | `PARSEDMARC_MAILDIR_` |
@@ -1207,7 +1261,7 @@ high-volume mailbox processing.
## Multi-tenant support
Starting in `8.19.0`, ParseDMARC provides multi-tenant support by placing data into separate OpenSearch or Elasticsearch index prefixes. To set this up, create a YAML file that is formatted where each key is a tenant name, and the value is a list of domains related to that tenant, not including subdomains, like this:
Starting in `8.19.0`, ParseDMARC provides multi-tenant support by placing data into separate OpenSearch or Elasticsearch index prefixes. To set this up, create a YAML file where each key is a tenant name, and the value is a list of domains related to that tenant, not including subdomains, like this:
```yaml
example:
@@ -1223,8 +1277,8 @@ Save it to disk where the user running ParseDMARC can read it, then set `index_p
When configured correctly, if ParseDMARC finds that a report is related to a domain in the mapping, the report will be saved in an index name that has the tenant name prefixed to it with a trailing underscore. Then, you can use the security features of OpenSearch or the ELK stack to only grant users access to the indexes that they need.
:::{note}
A domain cannot be used in multiple tenant lists. Only the first prefix list that contains the matching domain is used.
:::{note}
A domain cannot be used in multiple tenant lists. Only the first prefix list that contains the matching domain is used.
:::
Each key must be a tenant name and each value a *list* of domain names, all strings; a file of any other shape is rejected at startup.
@@ -1301,9 +1355,9 @@ sudo service parsedmarc restart
On `systemctl stop`/`restart` (or Ctrl-C) `parsedmarc` finishes the
current batch, flushes its outputs, and exits cleanly. Shutdown is
observed at batch boundaries, so the worst-case delay is roughly
`mailbox_check_timeout` (default 30s) plus the batch's processing and
`[mailbox] check_timeout` (default 30s) plus the batch's processing and
flush time. Keep `TimeoutStopSec` comfortably above
`mailbox_check_timeout` (≈2×, and raise both together) or systemd will
`check_timeout` (≈2×, and raise both together) or systemd will
`SIGKILL` mid-batch. In the foreground, a second Ctrl-C force-quits
immediately, skipping the output flush.
:::
@@ -1360,8 +1414,9 @@ service parsedmarc status
```
:::{note}
In the event of a crash, systemd will restart the service after 10
minutes, but the `service parsedmarc status` command will only show
In the event of a crash, systemd will restart the service after 5
minutes (per the `RestartSec` setting above), but the
`service parsedmarc status` command will only show
the logs for the current process. To view the logs for previous runs
as well as the current process (newest to oldest), run: