diff --git a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml index 57cd98db5894..6eaba9111a2b 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml @@ -239,6 +239,17 @@ programs.git. + + + parsedmarc, + a service which parses incoming + DMARC reports and + stores or sends them to a downstream service for further + analysis. Documented in + its manual + entry. + +
diff --git a/nixos/doc/manual/release-notes/rl-2111.section.md b/nixos/doc/manual/release-notes/rl-2111.section.md index 440d063abf28..b77bd30ea17c 100644 --- a/nixos/doc/manual/release-notes/rl-2111.section.md +++ b/nixos/doc/manual/release-notes/rl-2111.section.md @@ -73,6 +73,11 @@ subsonic-compatible api. Available as [navidrome](#opt-services.navidrome.enable - [git](https://git-scm.com), a distributed version control system. Available as [programs.git](options.html#opt-programs.git.enable). +- [parsedmarc](https://domainaware.github.io/parsedmarc/), a service + which parses incoming [DMARC](https://dmarc.org/) reports and stores + or sends them to a downstream service for further analysis. + Documented in [its manual entry](#module-services-parsedmarc). + ## Backward Incompatibilities {#sec-release-21.11-incompatibilities} diff --git a/nixos/modules/services/monitoring/parsedmarc.md b/nixos/modules/services/monitoring/parsedmarc.md new file mode 100644 index 000000000000..d93134a4cc76 --- /dev/null +++ b/nixos/modules/services/monitoring/parsedmarc.md @@ -0,0 +1,113 @@ +# parsedmarc {#module-services-parsedmarc} +[parsedmarc](https://domainaware.github.io/parsedmarc/) is a service +which parses incoming [DMARC](https://dmarc.org/) reports and stores +or sends them to a downstream service for further analysis. In +combination with Elasticsearch, Grafana and the included Grafana +dashboard, it provides a handy overview of DMARC reports over time. + +## Basic usage {#module-services-parsedmarc-basic-usage} +A very minimal setup which reads incoming reports from an external +email address and saves them to a local Elasticsearch instance looks +like this: + +```nix +services.parsedmarc = { + enable = true; + settings.imap = { + host = "imap.example.com"; + user = "alice@example.com"; + password = "/path/to/imap_password_file"; + watch = true; + }; + provision.geoIp = false; # Not recommended! +}; +``` + +Note that GeoIP provisioning is disabled in the example for +simplicity, but should be turned on for fully functional reports. + +## Local mail +Instead of watching an external inbox, a local inbox can be +automatically provisioned. The recipient's name is by default set to +`dmarc`, but can be configured in +[services.parsedmarc.provision.localMail.recipientName](options.html#opt-services.parsedmarc.provision.localMail.recipientName). You +need to add an MX record pointing to the host. More concretely: for +the example to work, an MX record needs to be set up for +`monitoring.example.com` and the complete email address that should be +configured in the domain's dmarc policy is +`dmarc@monitoring.example.com`. + +```nix +services.parsedmarc = { + enable = true; + provision = { + localMail = { + enable = true; + hostname = monitoring.example.com; + }; + geoIp = false; # Not recommended! + }; +}; +``` + +## Grafana and GeoIP +The reports can be visualized and summarized with parsedmarc's +official Grafana dashboard. For all views to work, and for the data to +be complete, GeoIP databases are also required. The following example +shows a basic deployment where the provisioned Elasticsearch instance +is automatically added as a Grafana datasource, and the dashboard is +added to Grafana as well. + +```nix +services.parsedmarc = { + enable = true; + provision = { + localMail = { + enable = true; + hostname = url; + }; + grafana = { + datasource = true; + dashboard = true; + }; + }; +}; + +# Not required, but recommended for full functionality +services.geoipupdate = { + settings = { + AccountID = 000000; + LicenseKey = "/path/to/license_key_file"; + }; +}; + +services.grafana = { + enable = true; + addr = "0.0.0.0"; + domain = url; + rootUrl = "https://" + url; + protocol = "socket"; + security = { + adminUser = "admin"; + adminPasswordFile = "/path/to/admin_password_file"; + secretKeyFile = "/path/to/secret_key_file"; + }; +}; + +services.nginx = { + enable = true; + recommendedTlsSettings = true; + recommendedOptimisation = true; + recommendedGzipSettings = true; + recommendedProxySettings = true; + upstreams.grafana.servers."unix:/${config.services.grafana.socket}" = {}; + virtualHosts.${url} = { + root = config.services.grafana.staticRootPath; + enableACME = true; + forceSSL = true; + locations."/".tryFiles = "$uri @grafana"; + locations."@grafana".proxyPass = "http://grafana"; + }; +}; +users.users.nginx.extraGroups = [ "grafana" ]; +``` diff --git a/nixos/modules/services/monitoring/parsedmarc.nix b/nixos/modules/services/monitoring/parsedmarc.nix index 58137a74d395..e6a72dea0260 100644 --- a/nixos/modules/services/monitoring/parsedmarc.nix +++ b/nixos/modules/services/monitoring/parsedmarc.nix @@ -529,4 +529,9 @@ in description = "DMARC mail recipient"; }; }; + + # Don't edit the docbook xml directly, edit the md and generate it: + # `pandoc parsedmarc.md -t docbook --top-level-division=chapter --extract-media=media -f markdown+smart > parsedmarc.xml` + meta.doc = ./parsedmarc.xml; + meta.maintainers = [ lib.maintainers.talyz ]; } diff --git a/nixos/modules/services/monitoring/parsedmarc.xml b/nixos/modules/services/monitoring/parsedmarc.xml new file mode 100644 index 000000000000..7167b52d0357 --- /dev/null +++ b/nixos/modules/services/monitoring/parsedmarc.xml @@ -0,0 +1,125 @@ + + parsedmarc + + parsedmarc + is a service which parses incoming + DMARC reports and + stores or sends them to a downstream service for further analysis. + In combination with Elasticsearch, Grafana and the included Grafana + dashboard, it provides a handy overview of DMARC reports over time. + +
+ Basic usage + + A very minimal setup which reads incoming reports from an external + email address and saves them to a local Elasticsearch instance + looks like this: + + +services.parsedmarc = { + enable = true; + settings.imap = { + host = "imap.example.com"; + user = "alice@example.com"; + password = "/path/to/imap_password_file"; + watch = true; + }; + provision.geoIp = false; # Not recommended! +}; + + + Note that GeoIP provisioning is disabled in the example for + simplicity, but should be turned on for fully functional reports. + +
+
+ Local mail + + Instead of watching an external inbox, a local inbox can be + automatically provisioned. The recipient’s name is by default set + to dmarc, but can be configured in + services.parsedmarc.provision.localMail.recipientName. + You need to add an MX record pointing to the host. More + concretely: for the example to work, an MX record needs to be set + up for monitoring.example.com and the complete + email address that should be configured in the domain’s dmarc + policy is dmarc@monitoring.example.com. + + +services.parsedmarc = { + enable = true; + provision = { + localMail = { + enable = true; + hostname = monitoring.example.com; + }; + geoIp = false; # Not recommended! + }; +}; + +
+
+ Grafana and GeoIP + + The reports can be visualized and summarized with parsedmarc’s + official Grafana dashboard. For all views to work, and for the + data to be complete, GeoIP databases are also required. The + following example shows a basic deployment where the provisioned + Elasticsearch instance is automatically added as a Grafana + datasource, and the dashboard is added to Grafana as well. + + +services.parsedmarc = { + enable = true; + provision = { + localMail = { + enable = true; + hostname = url; + }; + grafana = { + datasource = true; + dashboard = true; + }; + }; +}; + +# Not required, but recommended for full functionality +services.geoipupdate = { + settings = { + AccountID = 000000; + LicenseKey = "/path/to/license_key_file"; + }; +}; + +services.grafana = { + enable = true; + addr = "0.0.0.0"; + domain = url; + rootUrl = "https://" + url; + protocol = "socket"; + security = { + adminUser = "admin"; + adminPasswordFile = "/path/to/admin_password_file"; + secretKeyFile = "/path/to/secret_key_file"; + }; +}; + +services.nginx = { + enable = true; + recommendedTlsSettings = true; + recommendedOptimisation = true; + recommendedGzipSettings = true; + recommendedProxySettings = true; + upstreams.grafana.servers."unix:/${config.services.grafana.socket}" = {}; + virtualHosts.${url} = { + root = config.services.grafana.staticRootPath; + enableACME = true; + forceSSL = true; + locations."/".tryFiles = "$uri @grafana"; + locations."@grafana".proxyPass = "http://grafana"; + }; +}; +users.users.nginx.extraGroups = [ "grafana" ]; + +
+