summary refs log tree commit diff
path: root/nixos/tests/libreswan.nix
blob: ff3d2344a6799beaa3a92815f88ed332ca7e34fe (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
# This test sets up a host-to-host IPsec VPN between Alice and Bob, each on its
# own network and with Eve as the only route between each other. We check that
# Eve can eavesdrop the plaintext traffic between Alice and Bob, but once they
# enable the secure tunnel Eve's spying becomes ineffective.

import ./make-test-python.nix ({ lib, pkgs, ... }:

let

  # IPsec tunnel between Alice and Bob
  tunnelConfig = {
    services.libreswan.enable = true;
    services.libreswan.connections.tunnel =
      ''
        leftid=@alice
        left=fd::a
        rightid=@bob
        right=fd::b
        authby=secret
        auto=add
      '';
    environment.etc."ipsec.d/tunnel.secrets" =
      { text = ''@alice @bob : PSK "j1JbIi9WY07rxwcNQ6nbyThKCf9DGxWOyokXIQcAQUnafsNTUJxfsxwk9WYK8fHj"'';
        mode = "600";
      };
  };

  # Common network setup
  baseNetwork = {
    # shared hosts file
    extraHosts = lib.mkVMOverride ''
      fd::a alice
      fd::b bob
      fd::e eve
    '';
    # remove all automatic addresses
    useDHCP = false;
    interfaces.eth1.ipv4.addresses = lib.mkVMOverride [];
    interfaces.eth2.ipv4.addresses = lib.mkVMOverride [];
    # open a port for testing
    firewall.allowedUDPPorts = [ 1234 ];
  };

  # Adds an address and route from a to b via Eve
  addRoute = a: b: {
    interfaces.eth1.ipv6.addresses =
      [ { address = a; prefixLength = 64; } ];
    interfaces.eth1.ipv6.routes =
      [ { address = b; prefixLength = 128; via = "fd::e"; } ];
  };

in

{
  name = "libreswan";
  meta = with lib.maintainers; {
    maintainers = [ rnhmjoj ];
  };

  # Our protagonist
  nodes.alice = { ... }: {
    virtualisation.vlans = [ 1 ];
    networking = baseNetwork // addRoute "fd::a" "fd::b";
  } // tunnelConfig;

  # Her best friend
  nodes.bob = { ... }: {
    virtualisation.vlans = [ 2 ];
    networking = baseNetwork // addRoute "fd::b" "fd::a";
  } // tunnelConfig;

  # The malicious network operator
  nodes.eve = { ... }: {
    virtualisation.vlans = [ 1 2 ];
    networking = lib.mkMerge
      [ baseNetwork
        { interfaces.br0.ipv6.addresses =
            [ { address = "fd::e"; prefixLength = 64; } ];
          bridges.br0.interfaces = [ "eth1" "eth2" ];
        }
      ];
    environment.systemPackages = [ pkgs.tcpdump ];
    boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
  };

  testScript =
    ''
      def alice_to_bob(msg: str):
          """
          Sends a message as Alice to Bob
          """
          bob.execute("nc -lu ::0 1234 >/tmp/msg &")
          alice.sleep(1)
          alice.succeed(f"echo '{msg}' | nc -uw 0 bob 1234")
          bob.succeed(f"grep '{msg}' /tmp/msg")


      def eavesdrop():
          """
          Starts eavesdropping on Alice and Bob
          """
          match = "src host alice and dst host bob"
          eve.execute(f"tcpdump -i br0 -c 1 -Avv {match} >/tmp/log &")


      start_all()

      with subtest("Network is up"):
          alice.wait_until_succeeds("ping -c1 bob")

      with subtest("Eve can eavesdrop cleartext traffic"):
          eavesdrop()
          alice_to_bob("I secretly love turnip")
          eve.sleep(1)
          eve.succeed("grep turnip /tmp/log")

      with subtest("Libreswan is ready"):
          alice.wait_for_unit("ipsec")
          bob.wait_for_unit("ipsec")
          alice.succeed("ipsec verify 1>&2")

      with subtest("Alice and Bob can start the tunnel"):
          alice.execute("ipsec auto --start tunnel >&2 &")
          bob.succeed("ipsec auto --start tunnel")
          # apparently this is needed to "wake" the tunnel
          bob.execute("ping -c1 alice")

      with subtest("Eve no longer can eavesdrop"):
          eavesdrop()
          alice_to_bob("Just kidding, I actually like rhubarb")
          eve.sleep(1)
          eve.fail("grep rhubarb /tmp/log")
    '';
})