1# This test sets up a host-to-host IPsec VPN between Alice and Bob, each on its
2# own network and with Eve as the only route between each other. We check that
3# Eve can eavesdrop the plaintext traffic between Alice and Bob, but once they
4# enable the secure tunnel Eve's spying becomes ineffective.
5
6{ lib, pkgs, ... }:
7
8let
9
10 # IPsec tunnel between Alice and Bob
11 tunnelConfig = {
12 services.libreswan.enable = true;
13 services.libreswan.connections.tunnel = ''
14 leftid=@alice
15 left=fd::a
16 rightid=@bob
17 right=fd::b
18 authby=secret
19 auto=add
20 '';
21 environment.etc."ipsec.d/tunnel.secrets" = {
22 text = ''@alice @bob : PSK "j1JbIi9WY07rxwcNQ6nbyThKCf9DGxWOyokXIQcAQUnafsNTUJxfsxwk9WYK8fHj"'';
23 mode = "600";
24 };
25 };
26
27 # Common network setup
28 baseNetwork = {
29 # shared hosts file
30 extraHosts = lib.mkVMOverride ''
31 fd::a alice
32 fd::b bob
33 fd::e eve
34 '';
35 # remove all automatic addresses
36 useDHCP = false;
37 interfaces.eth1.ipv4.addresses = lib.mkVMOverride [ ];
38 interfaces.eth2.ipv4.addresses = lib.mkVMOverride [ ];
39 interfaces.eth1.ipv6.addresses = lib.mkVMOverride [ ];
40 interfaces.eth2.ipv6.addresses = lib.mkVMOverride [ ];
41 # open a port for testing
42 firewall.allowedUDPPorts = [ 1234 ];
43 };
44
45 # Adds an address and route from a to b via Eve
46 addRoute = a: b: {
47 interfaces.eth1.ipv6.addresses = [
48 {
49 address = a;
50 prefixLength = 64;
51 }
52 ];
53 interfaces.eth1.ipv6.routes = [
54 {
55 address = b;
56 prefixLength = 128;
57 via = "fd::e";
58 }
59 ];
60 };
61
62in
63
64{
65 name = "libreswan";
66 meta = with lib.maintainers; {
67 maintainers = [ rnhmjoj ];
68 };
69
70 # Our protagonist
71 nodes.alice =
72 { ... }:
73 {
74 virtualisation.vlans = [ 1 ];
75 networking = baseNetwork // addRoute "fd::a" "fd::b";
76 }
77 // tunnelConfig;
78
79 # Her best friend
80 nodes.bob =
81 { ... }:
82 {
83 virtualisation.vlans = [ 2 ];
84 networking = baseNetwork // addRoute "fd::b" "fd::a";
85 }
86 // tunnelConfig;
87
88 # The malicious network operator
89 nodes.eve =
90 { ... }:
91 {
92 virtualisation.vlans = [
93 1
94 2
95 ];
96 networking = lib.mkMerge [
97 baseNetwork
98 {
99 interfaces.br0.ipv6.addresses = [
100 {
101 address = "fd::e";
102 prefixLength = 64;
103 }
104 ];
105 bridges.br0.interfaces = [
106 "eth1"
107 "eth2"
108 ];
109 }
110 ];
111 environment.systemPackages = [ pkgs.tcpdump ];
112 boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
113 };
114
115 testScript = ''
116 def alice_to_bob(msg: str):
117 """
118 Sends a message as Alice to Bob
119 """
120 bob.execute("nc -lu ::0 1234 >/tmp/msg &")
121 alice.sleep(1)
122 alice.succeed(f"echo '{msg}' | nc -uw 0 bob 1234")
123 bob.succeed(f"grep '{msg}' /tmp/msg")
124
125
126 def eavesdrop():
127 """
128 Starts eavesdropping on Alice and Bob
129 """
130 match = "src host alice and dst host bob"
131 eve.execute(f"tcpdump -i br0 -c 1 -Avv {match} >/tmp/log &")
132
133
134 start_all()
135
136 with subtest("Network is up"):
137 alice.wait_until_succeeds("ping -c1 bob")
138 alice.succeed("systemctl restart ipsec")
139 bob.succeed("systemctl restart ipsec")
140
141 with subtest("Eve can eavesdrop cleartext traffic"):
142 eavesdrop()
143 alice_to_bob("I secretly love turnip")
144 eve.sleep(1)
145 eve.succeed("grep turnip /tmp/log")
146
147 with subtest("Libreswan is ready"):
148 alice.wait_for_unit("ipsec")
149 bob.wait_for_unit("ipsec")
150 alice.succeed("ipsec checkconfig")
151
152 with subtest("Alice and Bob can start the tunnel"):
153 alice.execute("ipsec start tunnel >&2 &")
154 bob.succeed("ipsec start tunnel")
155 # apparently this is needed to "wake" the tunnel
156 bob.execute("ping -c1 alice")
157
158 with subtest("Eve no longer can eavesdrop"):
159 eavesdrop()
160 alice_to_bob("Just kidding, I actually like rhubarb")
161 eve.sleep(1)
162 eve.fail("grep rhubarb /tmp/log")
163 '';
164}