about summary refs log tree commit diff
path: root/nixos/tests/schleuder.nix
blob: e57ef66bb8f9da88c06a6a765abc9640ca3d670c (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
let
  certs = import ./common/acme/server/snakeoil-certs.nix;
  domain = certs.domain;
in
import ./make-test-python.nix {
  name = "schleuder";
  nodes.machine = { pkgs, ... }: {
    imports = [ ./common/user-account.nix ];
    services.postfix = {
      enable = true;
      enableSubmission = true;
      tlsTrustedAuthorities = "${certs.ca.cert}";
      sslCert = "${certs.${domain}.cert}";
      sslKey = "${certs.${domain}.key}";
      inherit domain;
      destination = [ domain ];
      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")
  '';
}