about summary refs log tree commit diff stats
path: root/tests/by-name/em/email-dns/test.nix
blob: 32447aea8ab7d6bb60ad349c6389b6e60b134503 (plain) (blame)
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")
      '';
  }