1import ./make-test-python.nix (
2 { pkgs, lib, ... }:
3 let
4 orga = "example";
5 domain = "${orga}.localdomain";
6
7 tls-cert = pkgs.runCommand "selfSignedCert" { buildInputs = [ pkgs.openssl ]; } ''
8 openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -days 36500 \
9 -subj '/CN=machine.${domain}'
10 install -D -t $out key.pem cert.pem
11 '';
12
13 gitRepositories = [
14 "repo1"
15 "repo2"
16 ];
17 in
18 {
19 name = "public-inbox";
20
21 meta.maintainers = with pkgs.lib.maintainers; [ julm ];
22
23 nodes.machine =
24 {
25 config,
26 pkgs,
27 nodes,
28 ...
29 }:
30 let
31 inherit (config.services) public-inbox;
32 in
33 {
34 virtualisation.diskSize = 1 * 1024;
35 virtualisation.memorySize = 1 * 1024;
36 networking.domain = domain;
37
38 security.pki.certificateFiles = [ "${tls-cert}/cert.pem" ];
39 # If using security.acme:
40 #security.acme.certs."${domain}".postRun = ''
41 # systemctl try-restart public-inbox-nntpd public-inbox-imapd
42 #'';
43
44 services.public-inbox = {
45 enable = true;
46 postfix.enable = true;
47 openFirewall = true;
48 settings.publicinbox = {
49 css = [ "href=https://machine.${domain}/style/light.css" ];
50 nntpserver = [ "nntps://machine.${domain}" ];
51 wwwlisting = "match=domain";
52 };
53 mda = {
54 enable = true;
55 args = [ "--no-precheck" ]; # Allow Bcc:
56 };
57 http = {
58 enable = true;
59 port = "/run/public-inbox-http.sock";
60 #port = 8080;
61 args = [ "-W0" ];
62 mounts = [
63 "https://machine.${domain}/inbox"
64 ];
65 };
66 nntp = {
67 enable = true;
68 #port = 563;
69 args = [ "-W0" ];
70 cert = "${tls-cert}/cert.pem";
71 key = "${tls-cert}/key.pem";
72 };
73 imap = {
74 enable = true;
75 #port = 993;
76 args = [ "-W0" ];
77 cert = "${tls-cert}/cert.pem";
78 key = "${tls-cert}/key.pem";
79 };
80 inboxes =
81 lib.recursiveUpdate
82 (lib.genAttrs gitRepositories (repo: {
83 address = [
84 # Routed to the "public-inbox:" transport in services.postfix.transport
85 "${repo}@${domain}"
86 ];
87 description = ''
88 ${repo}@${domain} :
89 discussions about ${repo}.
90 '';
91 url = "https://machine.${domain}/inbox/${repo}";
92 newsgroup = "inbox.comp.${orga}.${repo}";
93 coderepo = [ repo ];
94 }))
95 {
96 repo2 = {
97 hide = [
98 "imap" # FIXME: doesn't work for IMAP as of public-inbox 1.6.1
99 "manifest"
100 "www"
101 ];
102 };
103 };
104 settings.coderepo = lib.listToAttrs (
105 map (
106 repositoryName:
107 lib.nameValuePair repositoryName {
108 dir = "/var/lib/public-inbox/repositories/${repositoryName}.git";
109 cgitUrl = "https://git.${domain}/${repositoryName}.git";
110 }
111 ) gitRepositories
112 );
113 };
114
115 # Use nginx as a reverse proxy for public-inbox-httpd
116 services.nginx = {
117 enable = true;
118 recommendedGzipSettings = true;
119 recommendedOptimisation = true;
120 recommendedTlsSettings = true;
121 recommendedProxySettings = true;
122 virtualHosts."machine.${domain}" = {
123 forceSSL = true;
124 sslCertificate = "${tls-cert}/cert.pem";
125 sslCertificateKey = "${tls-cert}/key.pem";
126 locations."/".return = "302 /inbox";
127 locations."= /inbox".return = "302 /inbox/";
128 locations."/inbox".proxyPass = "http://unix:${public-inbox.http.port}:/inbox";
129 # If using TCP instead of a Unix socket:
130 #locations."/inbox".proxyPass = "http://127.0.0.1:${toString public-inbox.http.port}/inbox";
131 # Referred to by settings.publicinbox.css
132 # See http://public-inbox.org/meta/_/text/color/
133 locations."= /style/light.css".alias = pkgs.writeText "light.css" ''
134 * { background:#fff; color:#000 }
135
136 a { color:#00f; text-decoration:none }
137 a:visited { color:#808 }
138
139 *.q { color:#008 }
140
141 *.add { color:#060 }
142 *.del {color:#900 }
143 *.head { color:#000 }
144 *.hunk { color:#960 }
145
146 .hl.num { color:#f30 } /* number */
147 .hl.esc { color:#f0f } /* escape character */
148 .hl.str { color:#f30 } /* string */
149 .hl.ppc { color:#c3c } /* preprocessor */
150 .hl.pps { color:#f30 } /* preprocessor string */
151 .hl.slc { color:#099 } /* single-line comment */
152 .hl.com { color:#099 } /* multi-line comment */
153 /* .hl.opt { color:#ccc } */ /* operator */
154 /* .hl.ipl { color:#ccc } */ /* interpolation */
155
156 /* keyword groups kw[a-z] */
157 .hl.kwa { color:#f90 }
158 .hl.kwb { color:#060 }
159 .hl.kwc { color:#f90 }
160 /* .hl.kwd { color:#ccc } */
161 '';
162 };
163 };
164
165 services.postfix = {
166 enable = true;
167 setSendmail = true;
168 #sslCert = "${tls-cert}/cert.pem";
169 #sslKey = "${tls-cert}/key.pem";
170 recipientDelimiter = "+";
171 };
172
173 environment.systemPackages = [
174 pkgs.gitMinimal
175 pkgs.mailutils
176 pkgs.openssl
177 ];
178
179 };
180
181 testScript = ''
182 start_all()
183
184 # The threshold and/or hardening may have to be changed with new features/checks
185 with subtest("systemd hardening thresholds"):
186 print(machine.succeed("systemd-analyze security public-inbox-httpd.service --threshold=5 --no-pager"))
187 print(machine.succeed("systemd-analyze security public-inbox-imapd.service --threshold=5 --no-pager"))
188 print(machine.succeed("systemd-analyze security public-inbox-nntpd.service --threshold=4 --no-pager"))
189
190 machine.wait_for_unit("multi-user.target")
191 machine.wait_for_unit("public-inbox-init.service")
192
193 machine.succeed(
194 ${lib.concatMapStrings (repositoryName: ''
195 "sudo -u public-inbox git init --bare -b main /var/lib/public-inbox/repositories/${repositoryName}.git",
196 '') gitRepositories}
197 )
198
199 # List inboxes through public-inbox-httpd
200 machine.wait_for_unit("public-inbox-httpd.socket")
201 machine.wait_for_unit("nginx.service")
202 machine.succeed("curl -L https://machine.${domain} | grep repo1@${domain}")
203 # The repo2 inbox is hidden
204 machine.fail("curl -L https://machine.${domain} | grep repo2@${domain}")
205
206 # Send a mail and read it through public-inbox-httpd
207 # Must work too when using a recipientDelimiter.
208 machine.wait_for_unit("postfix.service")
209 machine.succeed("mail -t <${pkgs.writeText "mail" ''
210 Subject: Testing mail
211 From: root@localhost
212 To: repo1+extension@${domain}
213 Message-ID: <repo1@root-1>
214 Content-Type: text/plain; charset=utf-8
215 Content-Disposition: inline
216
217 This is a testing mail.
218 ''}")
219 machine.sleep(10)
220 machine.succeed("curl -L 'https://machine.${domain}/inbox/repo1/repo1@root-1/T/#u' | grep 'This is a testing mail.'")
221
222 # Read a mail through public-inbox-imapd
223 machine.wait_for_unit("public-inbox-imapd.socket")
224 machine.succeed("openssl s_client -ign_eof -crlf -connect machine.${domain}:993 <${pkgs.writeText "imap-commands" ''
225 tag login anonymous@${domain} anonymous
226 tag SELECT INBOX.comp.${orga}.repo1.0
227 tag FETCH 1 (BODY[HEADER])
228 tag LOGOUT
229 ''} | grep '^Message-ID: <repo1@root-1>'")
230
231 # TODO: Read a mail through public-inbox-nntpd
232 #machine.wait_for_unit("public-inbox-nntpd.socket")
233
234 # Delete a mail.
235 # Note that the use of an extension not listed in the addresses
236 # require to use --all
237 machine.succeed("curl -L https://machine.${domain}/inbox/repo1/repo1@root-1/raw | sudo -u public-inbox public-inbox-learn rm --all")
238 machine.fail("curl -L https://machine.${domain}/inbox/repo1/repo1@root-1/T/#u | grep 'This is a testing mail.'")
239
240 # Compact the database
241 machine.succeed("sudo -u public-inbox public-inbox-compact --all")
242 '';
243 }
244)