1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
|
{
nixos-lib,
pkgsUnstable,
nixpkgs-unstable,
vhackPackages,
pkgs,
extraModules,
nixLib,
...
}: let
mail_server = import ./nodes/mail_server.nix {inherit extraModules pkgs vhackPackages;};
inherit (mail_server) mkMailServer;
user = import ./nodes/user.nix {inherit pkgs vhackPackages;};
inherit (user) mkUser;
in
nixos-lib.runTest {
hostPkgs = pkgs; # the Nixpkgs package set used outside the VMs
name = "email-dns";
node = {
specialArgs = {inherit pkgsUnstable vhackPackages nixpkgs-unstable nixLib;};
# Use the nixpkgs as constructed by the `nixpkgs.*` options
pkgs = null;
};
nodes = {
acme = {
nodes,
lib,
...
}: {
imports = [./nodes/acme];
networking.nameservers = lib.mkForce [
nodes.name_server.networking.primaryIPAddress
];
};
name_server = import ./nodes/name_server.nix {inherit extraModules;};
mail1_server =
mkMailServer "mail1"
{
class = "individual";
name = "bob";
secret = "bob-password";
email = ["bob@bob.com"];
};
mail2_server =
mkMailServer "mail2"
{
class = "individual";
name = "alice";
secret = "alice-password";
email = ["alice@alice.com"];
};
bob = mkUser "bob" "mail1";
alice = mkUser "alice" "mail2";
};
testScript = {...}: let
checkEmailEmpty = pkgs.writeShellScript "assert-empty-emails" ''
set -xe
# fetchmail returns EXIT_CODE 1 when no new mail
fetchmail --verbose >&2 || [ "$?" -eq 1 ] || {
echo "Mail was not empty" >&2
exit 1
}
'';
checkEmailNotEmpty = pkgs.writeShellScript "assert-empty-emails" ''
set -xe
# fetchmail returns EXIT_CODE 1 when no new mail
fetchmail --verbose >&2 || [ "$?" -ne 1 ] || {
echo "No new mail" >&2
exit 1
}
'';
checkSpamEmailNotEmpty = pkgs.writeShellScript "assert-empty-emails" ''
set -xe
# fetchmail returns EXIT_CODE 1 when no new mail
fetchmail --folder JUNK --verbose >&2 || [ "$?" -ne 1 ] || {
echo "No new mail" >&2
exit 1
}
'';
inherit (pkgs) lib;
in
/*
python
*/
''
from time import sleep
# Start dependencies for the other services
acme.start()
acme.wait_for_unit("pebble.service")
name_server.start()
name_server.wait_for_unit("nsd.service")
# Start the actual testing machines
start_all()
mail1_server.wait_for_unit("stalwart-mail.service")
mail1_server.wait_for_open_port(993) # imap
mail1_server.wait_for_open_port(465) # smtp
mail2_server.wait_for_unit("stalwart-mail.service")
mail2_server.wait_for_open_port(993) # imap
mail2_server.wait_for_open_port(465) # smtp
alice.wait_for_unit("multi-user.target")
bob.wait_for_unit("multi-user.target")
name_server.wait_until_succeeds("stat /var/lib/acme/mta-sts.alice.com/cert.pem")
name_server.wait_until_succeeds("stat /var/lib/acme/mta-sts.bob.com/cert.pem")
with subtest("Add pebble ca key to all services"):
for node in [name_server, mail1_server, mail2_server, alice, bob]:
node.succeed("${pkgs.writeShellScript "fetch-and-set-ca" ''
set -xe
# Fetch the randomly generated ca certificate
curl https://acme.test:15000/roots/0 > /tmp/ca.crt
curl https://acme.test:15000/intermediates/0 >> /tmp/ca.crt
# Append it to the various system stores
# The file paths are from <nixpgks>/modules/security/ca.nix
for cert_path in "ssl/certs/ca-certificates.crt" "ssl/certs/ca-bundle.crt" "pki/tls/certs/ca-bundle.crt"; do
cert_path="/etc/$cert_path"
mv "$cert_path" "$cert_path.old"
cat "$cert_path.old" > "$cert_path"
cat /tmp/ca.crt >> "$cert_path"
done
export NIX_SSL_CERT_FILE=/tmp/ca.crt
export SSL_CERT_FILE=/tmp/ca.crt
# TODO
# # P11-Kit trust source.
# environment.etc."ssl/trust-source".source = "$${cacertPackage.p11kit}/etc/ssl/trust-source";
''}")
with subtest("Both mailserver successfully started all services"):
import json
def all_services_running(host):
(status, output) = host.systemctl("list-units --state=failed --plain --no-pager --output=json")
host_failed = json.loads(output)
assert len(host_failed) == 0, f"Expected zero failing services, but found: {json.dumps(host_failed, indent=4)}"
all_services_running(mail1_server)
all_services_running(mail2_server)
with subtest("Both start without mail"):
alice.succeed("sudo -u alice ${checkEmailEmpty}")
bob.succeed("sudo -u bob ${checkEmailEmpty}")
with subtest("Alice can send an empty email to bob"):
alice.succeed("sudo -u alice ${pkgs.writeShellScript "alice-send" ''
set -xe
echo "" | msmtp --debug --account alice bob@bob.com >&2
''}")
# Give `mail2_server` some time to send the email.
sleep(160)
bob.succeed("sudo -u bob ${checkSpamEmailNotEmpty}")
with subtest("Alice can send an non-empty email to bob"):
alice.succeed("sudo -u alice ${pkgs.writeShellScript "alice-send" ''
set -xe
cat << EOF | msmtp --debug --account alice bob@bob.com >&2
Subject: Hi bob, I'm Alice!
Good day, Bob!
This is an email.
It contains a subject and a body.
I also assert utf8 support by including my last name in this very message.
XOXO
Alice van Dåligen.
.
EOF
''}")
# Give `mail2_server` some time to send the email.
sleep(120)
bob.succeed("sudo -u bob ${checkEmailNotEmpty}")
mail1_server.copy_from_vm("/var/lib/", "server1")
mail2_server.copy_from_vm("/var/lib/", "server2")
bob.copy_from_vm("/home/bob/mail", "bob")
'';
}
|