1let
2 certs = import ./common/acme/server/snakeoil-certs.nix;
3 domain = certs.domain;
4in
5{
6 name = "schleuder";
7 nodes.machine =
8 { pkgs, ... }:
9 {
10 imports = [ ./common/user-account.nix ];
11 services.postfix = {
12 enable = true;
13 enableSubmission = true;
14 settings.main = {
15 mydomain = domain;
16 destination = domain;
17 smtp_tls_CAfile = "${certs.ca.cert}";
18 smtpd_tls_chain_files = [
19 "${certs.${domain}.key}"
20 "${certs.${domain}.cert}"
21 ];
22 };
23 localRecipients = [
24 "root"
25 "alice"
26 "bob"
27 ];
28 };
29 services.schleuder = {
30 enable = true;
31 # Don't do it like this in production! The point of this setting
32 # is to allow loading secrets from _outside_ the world-readable
33 # Nix store.
34 extraSettingsFile = pkgs.writeText "schleuder-api-keys.yml" ''
35 api:
36 valid_api_keys:
37 - fnord
38 '';
39 lists = [ "security@${domain}" ];
40 settings.api = {
41 tls_cert_file = "${certs.${domain}.cert}";
42 tls_key_file = "${certs.${domain}.key}";
43 };
44 };
45
46 environment.systemPackages = [
47 pkgs.gnupg
48 pkgs.msmtp
49 (pkgs.writeScriptBin "do-test" ''
50 #!${pkgs.runtimeShell}
51 set -exuo pipefail
52
53 # Generate a GPG key with no passphrase and export it
54 sudo -u alice gpg --passphrase-fd 0 --batch --yes --quick-generate-key 'alice@${domain}' rsa4096 sign,encr < <(echo)
55 sudo -u alice gpg --armor --export alice@${domain} > alice.asc
56 # Create a new mailing list with alice as the owner, and alice's key
57 schleuder-cli list new security@${domain} alice@${domain} alice.asc
58
59 # Send an email from a non-member of the list. Use --auto-from so we don't have to specify who it's from twice.
60 msmtp --auto-from security@${domain} --host=${domain} --port=25 --tls --tls-starttls <<EOF
61 Subject: really big security issue!!
62 From: root@${domain}
63
64 I found a big security problem!
65 EOF
66
67 # Wait for delivery
68 (set +o pipefail; journalctl -f -n 1000 -u postfix | grep -m 1 'delivered to maildir')
69
70 # There should be exactly one email
71 mail=(/var/spool/mail/alice/new/*)
72 [[ "''${#mail[@]}" = 1 ]]
73
74 # Find the fingerprint of the mailing list key
75 read list_key_fp address < <(schleuder-cli keys list security@${domain} | grep security@)
76 schleuder-cli keys export security@${domain} $list_key_fp > list.asc
77
78 # Import the key into alice's keyring, so we can verify it as well as decrypting
79 sudo -u alice gpg --import <list.asc
80 # And perform the decryption.
81 sudo -u alice gpg -d $mail >decrypted
82 # And check that the text matches.
83 grep "big security problem" decrypted
84 '')
85
86 # For debugging:
87 # pkgs.vim pkgs.openssl pkgs.sqliteinteractive
88 ];
89
90 security.pki.certificateFiles = [ certs.ca.cert ];
91
92 # Since we don't have internet here, use dnsmasq to provide MX records from /etc/hosts
93 services.dnsmasq = {
94 enable = true;
95 settings.selfmx = true;
96 };
97
98 networking.extraHosts = ''
99 127.0.0.1 ${domain}
100 '';
101
102 # schleuder-cli's config is not quite optimal in several ways:
103 # - A fingerprint _must_ be pinned, it doesn't even have an option
104 # to trust the PKI
105 # - It compares certificate fingerprints rather than key
106 # fingerprints, so renewals break the pin (though that's not
107 # relevant for this test)
108 # - It compares them as strings, which means we need to match the
109 # expected format exactly. This means removing the :s and
110 # lowercasing it.
111 # Refs:
112 # https://0xacab.org/schleuder/schleuder-cli/-/issues/16
113 # https://0xacab.org/schleuder/schleuder-cli/-/blob/f8895b9f47083d8c7b99a2797c93f170f3c6a3c0/lib/schleuder-cli/helper.rb#L230-238
114 systemd.tmpfiles.rules =
115 let
116 cliconfig =
117 pkgs.runCommand "schleuder-cli.yml"
118 {
119 nativeBuildInputs = [
120 pkgs.jq
121 pkgs.openssl
122 ];
123 }
124 ''
125 fp=$(openssl x509 -in ${certs.${domain}.cert} -noout -fingerprint -sha256 | cut -d = -f 2 | tr -d : | tr 'A-Z' 'a-z')
126 cat > $out <<EOF
127 host: localhost
128 port: 4443
129 tls_fingerprint: "$fp"
130 api_key: fnord
131 EOF
132 '';
133 in
134 [
135 "L+ /root/.schleuder-cli/schleuder-cli.yml - - - - ${cliconfig}"
136 ];
137 };
138
139 testScript = ''
140 machine.wait_for_unit("multi-user.target")
141 machine.wait_until_succeeds("nc -z localhost 4443")
142 machine.succeed("do-test")
143 '';
144}