summaryrefslogtreecommitdiffstats
path: root/nixos/tests/schleuder.nix
blob: 13bd079a29e4293c6c73299428911c171239c70b (plain)
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
let
  certs = import ./common/acme/server/snakeoil-certs.nix;
  domain = certs.domain;
in
{
  name = "schleuder";
  nodes.machine =
    { pkgs, ... }:
    {
      imports = [ ./common/user-account.nix ];
      services.postfix = {
        enable = true;
        enableSubmission = true;
        settings.main = {
          mydomain = domain;
          destination = domain;
          smtp_tls_CAfile = "${certs.ca.cert}";
          smtpd_tls_chain_files = [
            "${certs.${domain}.key}"
            "${certs.${domain}.cert}"
          ];
        };
        localRecipients = [
          "root"
          "alice"
          "bob"
        ];
      };
      services.schleuder = {
        enable = true;
        # Don't do it like this in production! The point of this setting
        # is to allow loading secrets from _outside_ the world-readable
        # Nix store.
        extraSettingsFile = pkgs.writeText "schleuder-api-keys.yml" ''
          api:
            valid_api_keys:
              - fnord
        '';
        lists = [ "security@${domain}" ];
        settings.api = {
          tls_cert_file = "${certs.${domain}.cert}";
          tls_key_file = "${certs.${domain}.key}";
        };
      };

      environment.systemPackages = [
        pkgs.gnupg
        pkgs.msmtp
        (pkgs.writeScriptBin "do-test" ''
          #!${pkgs.runtimeShell}
          set -exuo pipefail

          # Generate a GPG key with no passphrase and export it
          sudo -u alice gpg --passphrase-fd 0 --batch --yes --quick-generate-key 'alice@${domain}' rsa4096 sign,encr < <(echo)
          sudo -u alice gpg --armor --export alice@${domain} > alice.asc
          # Create a new mailing list with alice as the owner, and alice's key
          schleuder-cli list new security@${domain} alice@${domain} alice.asc

          # 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.
          msmtp --auto-from security@${domain} --host=${domain} --port=25 --tls --tls-starttls <<EOF
            Subject: really big security issue!!
            From: root@${domain}

            I found a big security problem!
          EOF

          # Wait for delivery
          (set +o pipefail; journalctl -f -n 1000 -u postfix | grep -m 1 'delivered to maildir')

          # There should be exactly one email
          mail=(/var/spool/mail/alice/new/*)
          [[ "''${#mail[@]}" = 1 ]]

          # Find the fingerprint of the mailing list key
          read list_key_fp address < <(schleuder-cli keys list security@${domain} | grep security@)
          schleuder-cli keys export security@${domain} $list_key_fp > list.asc

          # Import the key into alice's keyring, so we can verify it as well as decrypting
          sudo -u alice gpg --import <list.asc
          # And perform the decryption.
          sudo -u alice gpg -d $mail >decrypted
          # And check that the text matches.
          grep "big security problem" decrypted
        '')

        # For debugging:
        # pkgs.vim pkgs.openssl pkgs.sqliteinteractive
      ];

      security.pki.certificateFiles = [ certs.ca.cert ];

      # Since we don't have internet here, use dnsmasq to provide MX records from /etc/hosts
      services.dnsmasq = {
        enable = true;
        settings.selfmx = true;
      };

      networking.extraHosts = ''
        127.0.0.1 ${domain}
      '';

      # schleuder-cli's config is not quite optimal in several ways:
      # - A fingerprint _must_ be pinned, it doesn't even have an option
      #   to trust the PKI
      # - It compares certificate fingerprints rather than key
      #   fingerprints, so renewals break the pin (though that's not
      #   relevant for this test)
      # - It compares them as strings, which means we need to match the
      #   expected format exactly. This means removing the :s and
      #   lowercasing it.
      # Refs:
      # https://0xacab.org/schleuder/schleuder-cli/-/issues/16
      # https://0xacab.org/schleuder/schleuder-cli/-/blob/f8895b9f47083d8c7b99a2797c93f170f3c6a3c0/lib/schleuder-cli/helper.rb#L230-238
      systemd.tmpfiles.rules =
        let
          cliconfig =
            pkgs.runCommand "schleuder-cli.yml"
              {
                nativeBuildInputs = [
                  pkgs.jq
                  pkgs.openssl
                ];
              }
              ''
                fp=$(openssl x509 -in ${certs.${domain}.cert} -noout -fingerprint -sha256 | cut -d = -f 2 | tr -d : | tr 'A-Z' 'a-z')
                cat > $out <<EOF
                host: localhost
                port: 4443
                tls_fingerprint: "$fp"
                api_key: fnord
                EOF
              '';
        in
        [
          "L+ /root/.schleuder-cli/schleuder-cli.yml - - - - ${cliconfig}"
        ];
    };

  testScript = ''
    machine.wait_for_unit("multi-user.target")
    machine.wait_until_succeeds("nc -z localhost 4443")
    machine.succeed("do-test")
  '';
}