···239 <link xlink:href="options.html#opt-programs.git.enable">programs.git</link>.
240 </para>
241 </listitem>
242+ <listitem>
243+ <para>
244+ <link xlink:href="https://domainaware.github.io/parsedmarc/">parsedmarc</link>,
245+ a service which parses incoming
246+ <link xlink:href="https://dmarc.org/">DMARC</link> reports and
247+ stores or sends them to a downstream service for further
248+ analysis. Documented in
249+ <link linkend="module-services-parsedmarc">its manual
250+ entry</link>.
251+ </para>
252+ </listitem>
253 </itemizedlist>
254 </section>
255 <section xml:id="sec-release-21.11-incompatibilities">
+5
nixos/doc/manual/release-notes/rl-2111.section.md
···7374- [git](https://git-scm.com), a distributed version control system. Available as [programs.git](options.html#opt-programs.git.enable).
750000076## Backward Incompatibilities {#sec-release-21.11-incompatibilities}
7778
···7374- [git](https://git-scm.com), a distributed version control system. Available as [programs.git](options.html#opt-programs.git.enable).
7576+- [parsedmarc](https://domainaware.github.io/parsedmarc/), a service
77+ which parses incoming [DMARC](https://dmarc.org/) reports and stores
78+ or sends them to a downstream service for further analysis.
79+ Documented in [its manual entry](#module-services-parsedmarc).
80+81## Backward Incompatibilities {#sec-release-21.11-incompatibilities}
8283
···1+# parsedmarc {#module-services-parsedmarc}
2+[parsedmarc](https://domainaware.github.io/parsedmarc/) is a service
3+which parses incoming [DMARC](https://dmarc.org/) reports and stores
4+or sends them to a downstream service for further analysis. In
5+combination with Elasticsearch, Grafana and the included Grafana
6+dashboard, it provides a handy overview of DMARC reports over time.
7+8+## Basic usage {#module-services-parsedmarc-basic-usage}
9+A very minimal setup which reads incoming reports from an external
10+email address and saves them to a local Elasticsearch instance looks
11+like this:
12+13+```nix
14+services.parsedmarc = {
15+ enable = true;
16+ settings.imap = {
17+ host = "imap.example.com";
18+ user = "alice@example.com";
19+ password = "/path/to/imap_password_file";
20+ watch = true;
21+ };
22+ provision.geoIp = false; # Not recommended!
23+};
24+```
25+26+Note that GeoIP provisioning is disabled in the example for
27+simplicity, but should be turned on for fully functional reports.
28+29+## Local mail
30+Instead of watching an external inbox, a local inbox can be
31+automatically provisioned. The recipient's name is by default set to
32+`dmarc`, but can be configured in
33+[services.parsedmarc.provision.localMail.recipientName](options.html#opt-services.parsedmarc.provision.localMail.recipientName). You
34+need to add an MX record pointing to the host. More concretely: for
35+the example to work, an MX record needs to be set up for
36+`monitoring.example.com` and the complete email address that should be
37+configured in the domain's dmarc policy is
38+`dmarc@monitoring.example.com`.
39+40+```nix
41+services.parsedmarc = {
42+ enable = true;
43+ provision = {
44+ localMail = {
45+ enable = true;
46+ hostname = monitoring.example.com;
47+ };
48+ geoIp = false; # Not recommended!
49+ };
50+};
51+```
52+53+## Grafana and GeoIP
54+The reports can be visualized and summarized with parsedmarc's
55+official Grafana dashboard. For all views to work, and for the data to
56+be complete, GeoIP databases are also required. The following example
57+shows a basic deployment where the provisioned Elasticsearch instance
58+is automatically added as a Grafana datasource, and the dashboard is
59+added to Grafana as well.
60+61+```nix
62+services.parsedmarc = {
63+ enable = true;
64+ provision = {
65+ localMail = {
66+ enable = true;
67+ hostname = url;
68+ };
69+ grafana = {
70+ datasource = true;
71+ dashboard = true;
72+ };
73+ };
74+};
75+76+# Not required, but recommended for full functionality
77+services.geoipupdate = {
78+ settings = {
79+ AccountID = 000000;
80+ LicenseKey = "/path/to/license_key_file";
81+ };
82+};
83+84+services.grafana = {
85+ enable = true;
86+ addr = "0.0.0.0";
87+ domain = url;
88+ rootUrl = "https://" + url;
89+ protocol = "socket";
90+ security = {
91+ adminUser = "admin";
92+ adminPasswordFile = "/path/to/admin_password_file";
93+ secretKeyFile = "/path/to/secret_key_file";
94+ };
95+};
96+97+services.nginx = {
98+ enable = true;
99+ recommendedTlsSettings = true;
100+ recommendedOptimisation = true;
101+ recommendedGzipSettings = true;
102+ recommendedProxySettings = true;
103+ upstreams.grafana.servers."unix:/${config.services.grafana.socket}" = {};
104+ virtualHosts.${url} = {
105+ root = config.services.grafana.staticRootPath;
106+ enableACME = true;
107+ forceSSL = true;
108+ locations."/".tryFiles = "$uri @grafana";
109+ locations."@grafana".proxyPass = "http://grafana";
110+ };
111+};
112+users.users.nginx.extraGroups = [ "grafana" ];
113+```
···1+<chapter xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="module-services-parsedmarc">
2+ <title>parsedmarc</title>
3+ <para>
4+ <link xlink:href="https://domainaware.github.io/parsedmarc/">parsedmarc</link>
5+ is a service which parses incoming
6+ <link xlink:href="https://dmarc.org/">DMARC</link> reports and
7+ stores or sends them to a downstream service for further analysis.
8+ In combination with Elasticsearch, Grafana and the included Grafana
9+ dashboard, it provides a handy overview of DMARC reports over time.
10+ </para>
11+ <section xml:id="module-services-parsedmarc-basic-usage">
12+ <title>Basic usage</title>
13+ <para>
14+ A very minimal setup which reads incoming reports from an external
15+ email address and saves them to a local Elasticsearch instance
16+ looks like this:
17+ </para>
18+ <programlisting language="bash">
19+services.parsedmarc = {
20+ enable = true;
21+ settings.imap = {
22+ host = "imap.example.com";
23+ user = "alice@example.com";
24+ password = "/path/to/imap_password_file";
25+ watch = true;
26+ };
27+ provision.geoIp = false; # Not recommended!
28+};
29+</programlisting>
30+ <para>
31+ Note that GeoIP provisioning is disabled in the example for
32+ simplicity, but should be turned on for fully functional reports.
33+ </para>
34+ </section>
35+ <section xml:id="local-mail">
36+ <title>Local mail</title>
37+ <para>
38+ Instead of watching an external inbox, a local inbox can be
39+ automatically provisioned. The recipient’s name is by default set
40+ to <literal>dmarc</literal>, but can be configured in
41+ <link xlink:href="options.html#opt-services.parsedmarc.provision.localMail.recipientName">services.parsedmarc.provision.localMail.recipientName</link>.
42+ You need to add an MX record pointing to the host. More
43+ concretely: for the example to work, an MX record needs to be set
44+ up for <literal>monitoring.example.com</literal> and the complete
45+ email address that should be configured in the domain’s dmarc
46+ policy is <literal>dmarc@monitoring.example.com</literal>.
47+ </para>
48+ <programlisting language="bash">
49+services.parsedmarc = {
50+ enable = true;
51+ provision = {
52+ localMail = {
53+ enable = true;
54+ hostname = monitoring.example.com;
55+ };
56+ geoIp = false; # Not recommended!
57+ };
58+};
59+</programlisting>
60+ </section>
61+ <section xml:id="grafana-and-geoip">
62+ <title>Grafana and GeoIP</title>
63+ <para>
64+ The reports can be visualized and summarized with parsedmarc’s
65+ official Grafana dashboard. For all views to work, and for the
66+ data to be complete, GeoIP databases are also required. The
67+ following example shows a basic deployment where the provisioned
68+ Elasticsearch instance is automatically added as a Grafana
69+ datasource, and the dashboard is added to Grafana as well.
70+ </para>
71+ <programlisting language="bash">
72+services.parsedmarc = {
73+ enable = true;
74+ provision = {
75+ localMail = {
76+ enable = true;
77+ hostname = url;
78+ };
79+ grafana = {
80+ datasource = true;
81+ dashboard = true;
82+ };
83+ };
84+};
85+86+# Not required, but recommended for full functionality
87+services.geoipupdate = {
88+ settings = {
89+ AccountID = 000000;
90+ LicenseKey = "/path/to/license_key_file";
91+ };
92+};
93+94+services.grafana = {
95+ enable = true;
96+ addr = "0.0.0.0";
97+ domain = url;
98+ rootUrl = "https://" + url;
99+ protocol = "socket";
100+ security = {
101+ adminUser = "admin";
102+ adminPasswordFile = "/path/to/admin_password_file";
103+ secretKeyFile = "/path/to/secret_key_file";
104+ };
105+};
106+107+services.nginx = {
108+ enable = true;
109+ recommendedTlsSettings = true;
110+ recommendedOptimisation = true;
111+ recommendedGzipSettings = true;
112+ recommendedProxySettings = true;
113+ upstreams.grafana.servers."unix:/${config.services.grafana.socket}" = {};
114+ virtualHosts.${url} = {
115+ root = config.services.grafana.staticRootPath;
116+ enableACME = true;
117+ forceSSL = true;
118+ locations."/".tryFiles = "$uri @grafana";
119+ locations."@grafana".proxyPass = "http://grafana";
120+ };
121+};
122+users.users.nginx.extraGroups = [ "grafana" ];
123+</programlisting>
124+ </section>
125+</chapter>
+7
nixos/modules/services/search/elasticsearch.nix
···201202 if [ "$(id -u)" = 0 ]; then chown -R elasticsearch:elasticsearch ${cfg.dataDir}; fi
203 '';
0000000204 };
205206 environment.systemPackages = [ cfg.package ];
···201202 if [ "$(id -u)" = 0 ]; then chown -R elasticsearch:elasticsearch ${cfg.dataDir}; fi
203 '';
204+ postStart = ''
205+ # Make sure elasticsearch is up and running before dependents
206+ # are started
207+ while ! ${pkgs.curl}/bin/curl -sS -f http://localhost:${toString cfg.port} 2>/dev/null; do
208+ sleep 1
209+ done
210+ '';
211 };
212213 environment.systemPackages = [ cfg.package ];