1# This tests parsedmarc by sending a report to its monitored email
2# address and reading the results out of Elasticsearch.
3
4{ pkgs, ... }@args:
5let
6 inherit (import ../../lib/testing-python.nix args) makeTest;
7 inherit (pkgs) lib;
8
9 dmarcTestReport = builtins.fetchurl {
10 name = "dmarc-test-report";
11 url = "https://github.com/domainaware/parsedmarc/raw/f45ab94e0608088e0433557608d9f4e9517d3afe/samples/aggregate/estadocuenta1.infonacot.gob.mx!example.com!1536853302!1536939702!2940.xml.zip";
12 sha256 = "0dq64cj49711kbja27pjl2hy0d3azrjxg91kqrh40x46fkn1dwkx";
13 };
14
15 sendEmail =
16 address:
17 pkgs.writeScriptBin "send-email" ''
18 #!${pkgs.python3.interpreter}
19 import smtplib
20 from email import encoders
21 from email.mime.base import MIMEBase
22 from email.mime.multipart import MIMEMultipart
23 from email.mime.text import MIMEText
24
25 sender_email = "dmarc_tester@fake.domain"
26 receiver_email = "${address}"
27
28 message = MIMEMultipart()
29 message["From"] = sender_email
30 message["To"] = receiver_email
31 message["Subject"] = "DMARC test"
32
33 message.attach(MIMEText("Testing parsedmarc", "plain"))
34
35 attachment = MIMEBase("application", "zip")
36
37 with open("${dmarcTestReport}", "rb") as report:
38 attachment.set_payload(report.read())
39
40 encoders.encode_base64(attachment)
41
42 attachment.add_header(
43 "Content-Disposition",
44 "attachment; filename= estadocuenta1.infonacot.gob.mx!example.com!1536853302!1536939702!2940.xml.zip",
45 )
46
47 message.attach(attachment)
48 text = message.as_string()
49
50 with smtplib.SMTP('localhost') as server:
51 server.sendmail(sender_email, receiver_email, text)
52 server.quit()
53 '';
54in
55{
56 localMail = makeTest {
57 name = "parsedmarc-local-mail";
58 meta = with lib.maintainers; {
59 maintainers = [ talyz ];
60 };
61
62 nodes.parsedmarc =
63 { nodes, ... }:
64 {
65 virtualisation.memorySize = 2048;
66
67 services.postfix = {
68 enableSubmission = true;
69 enableSubmissions = true;
70 submissionsOptions = {
71 smtpd_sasl_auth_enable = "yes";
72 smtpd_client_restrictions = "permit";
73 };
74 };
75
76 services.parsedmarc = {
77 enable = true;
78 provision = {
79 geoIp = false;
80 localMail = {
81 enable = true;
82 hostname = "localhost";
83 };
84 };
85 };
86
87 environment.systemPackages = [
88 (sendEmail "dmarc@localhost")
89 pkgs.jq
90 ];
91 };
92
93 testScript =
94 { nodes }:
95 let
96 esPort = toString nodes.parsedmarc.config.services.elasticsearch.port;
97 valueObject = lib.optionalString (lib.versionAtLeast nodes.parsedmarc.config.services.elasticsearch.package.version "7") ".value";
98 in
99 ''
100 parsedmarc.start()
101 parsedmarc.wait_for_unit("postfix.service")
102 parsedmarc.wait_for_unit("dovecot2.service")
103 parsedmarc.wait_for_unit("parsedmarc.service")
104 parsedmarc.wait_until_succeeds(
105 "curl -sS -f http://localhost:${esPort}"
106 )
107
108 parsedmarc.fail(
109 "curl -sS -f http://localhost:${esPort}/_search?q=report_id:2940"
110 + " | tee /dev/console"
111 + " | jq -es 'if . == [] then null else .[] | .hits.total${valueObject} > 0 end'"
112 )
113 parsedmarc.succeed("send-email")
114 parsedmarc.wait_until_succeeds(
115 "curl -sS -f http://localhost:${esPort}/_search?q=report_id:2940"
116 + " | tee /dev/console"
117 + " | jq -es 'if . == [] then null else .[] | .hits.total${valueObject} > 0 end'"
118 )
119 '';
120 };
121
122 externalMail =
123 let
124 certs = import ../common/acme/server/snakeoil-certs.nix;
125 mailDomain = certs.domain;
126 parsedmarcDomain = "parsedmarc.fake.domain";
127 in
128 makeTest {
129 name = "parsedmarc-external-mail";
130 meta = with lib.maintainers; {
131 maintainers = [ talyz ];
132 };
133
134 nodes = {
135 parsedmarc =
136 { nodes, ... }:
137 {
138 virtualisation.memorySize = 2048;
139
140 security.pki.certificateFiles = [
141 certs.ca.cert
142 ];
143
144 networking.extraHosts = ''
145 127.0.0.1 ${parsedmarcDomain}
146 ${nodes.mail.config.networking.primaryIPAddress} ${mailDomain}
147 '';
148
149 services.parsedmarc = {
150 enable = true;
151 provision.geoIp = false;
152 settings.imap = {
153 host = mailDomain;
154 port = 993;
155 ssl = true;
156 user = "alice";
157 password = "${pkgs.writeText "imap-password" "foobar"}";
158 };
159 };
160
161 environment.systemPackages = [
162 pkgs.jq
163 ];
164 };
165
166 mail =
167 { nodes, ... }:
168 {
169 imports = [ ../common/user-account.nix ];
170
171 networking.extraHosts = ''
172 127.0.0.1 ${mailDomain}
173 ${nodes.parsedmarc.config.networking.primaryIPAddress} ${parsedmarcDomain}
174 '';
175
176 services.dovecot2 = {
177 enable = true;
178 protocols = [ "imap" ];
179 sslCACert = "${certs.ca.cert}";
180 sslServerCert = "${certs.${mailDomain}.cert}";
181 sslServerKey = "${certs.${mailDomain}.key}";
182 };
183
184 services.postfix = {
185 enable = true;
186 origin = mailDomain;
187 settings.main = {
188 myhostname = mailDomain;
189 mydestination = mailDomain;
190 };
191 enableSubmission = true;
192 enableSubmissions = true;
193 submissionsOptions = {
194 smtpd_sasl_auth_enable = "yes";
195 smtpd_client_restrictions = "permit";
196 };
197 };
198 environment.systemPackages = [ (sendEmail "alice@${mailDomain}") ];
199
200 networking.firewall.allowedTCPPorts = [ 993 ];
201 };
202 };
203
204 testScript =
205 { nodes }:
206 let
207 esPort = toString nodes.parsedmarc.config.services.elasticsearch.port;
208 valueObject = lib.optionalString (lib.versionAtLeast nodes.parsedmarc.config.services.elasticsearch.package.version "7") ".value";
209 in
210 ''
211 mail.start()
212 mail.wait_for_unit("postfix.service")
213 mail.wait_for_unit("dovecot2.service")
214
215 parsedmarc.start()
216 parsedmarc.wait_for_unit("parsedmarc.service")
217 parsedmarc.wait_until_succeeds(
218 "curl -sS -f http://localhost:${esPort}"
219 )
220
221 parsedmarc.fail(
222 "curl -sS -f http://localhost:${esPort}/_search?q=report_id:2940"
223 + " | tee /dev/console"
224 + " | jq -es 'if . == [] then null else .[] | .hits.total${valueObject} > 0 end'"
225 )
226 mail.succeed("send-email")
227 parsedmarc.wait_until_succeeds(
228 "curl -sS -f http://localhost:${esPort}/_search?q=report_id:2940"
229 + " | tee /dev/console"
230 + " | jq -es 'if . == [] then null else .[] | .hits.total${valueObject} > 0 end'"
231 )
232 '';
233 };
234}