at 23.11-pre 3.9 kB view raw
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 6import ./make-test-python.nix ({ 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 '' 15 leftid=@alice 16 left=fd::a 17 rightid=@bob 18 right=fd::b 19 authby=secret 20 auto=add 21 ''; 22 environment.etc."ipsec.d/tunnel.secrets" = 23 { text = ''@alice @bob : PSK "j1JbIi9WY07rxwcNQ6nbyThKCf9DGxWOyokXIQcAQUnafsNTUJxfsxwk9WYK8fHj"''; 24 mode = "600"; 25 }; 26 }; 27 28 # Common network setup 29 baseNetwork = { 30 # shared hosts file 31 extraHosts = lib.mkVMOverride '' 32 fd::a alice 33 fd::b bob 34 fd::e eve 35 ''; 36 # remove all automatic addresses 37 useDHCP = false; 38 interfaces.eth1.ipv4.addresses = lib.mkVMOverride []; 39 interfaces.eth2.ipv4.addresses = lib.mkVMOverride []; 40 # open a port for testing 41 firewall.allowedUDPPorts = [ 1234 ]; 42 }; 43 44 # Adds an address and route from a to b via Eve 45 addRoute = a: b: { 46 interfaces.eth1.ipv6.addresses = 47 [ { address = a; prefixLength = 64; } ]; 48 interfaces.eth1.ipv6.routes = 49 [ { address = b; prefixLength = 128; via = "fd::e"; } ]; 50 }; 51 52in 53 54{ 55 name = "libreswan"; 56 meta = with lib.maintainers; { 57 maintainers = [ rnhmjoj ]; 58 }; 59 60 # Our protagonist 61 nodes.alice = { ... }: { 62 virtualisation.vlans = [ 1 ]; 63 networking = baseNetwork // addRoute "fd::a" "fd::b"; 64 } // tunnelConfig; 65 66 # Her best friend 67 nodes.bob = { ... }: { 68 virtualisation.vlans = [ 2 ]; 69 networking = baseNetwork // addRoute "fd::b" "fd::a"; 70 } // tunnelConfig; 71 72 # The malicious network operator 73 nodes.eve = { ... }: { 74 virtualisation.vlans = [ 1 2 ]; 75 networking = lib.mkMerge 76 [ baseNetwork 77 { interfaces.br0.ipv6.addresses = 78 [ { address = "fd::e"; prefixLength = 64; } ]; 79 bridges.br0.interfaces = [ "eth1" "eth2" ]; 80 } 81 ]; 82 environment.systemPackages = [ pkgs.tcpdump ]; 83 boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true; 84 }; 85 86 testScript = 87 '' 88 def alice_to_bob(msg: str): 89 """ 90 Sends a message as Alice to Bob 91 """ 92 bob.execute("nc -lu ::0 1234 >/tmp/msg &") 93 alice.sleep(1) 94 alice.succeed(f"echo '{msg}' | nc -uw 0 bob 1234") 95 bob.succeed(f"grep '{msg}' /tmp/msg") 96 97 98 def eavesdrop(): 99 """ 100 Starts eavesdropping on Alice and Bob 101 """ 102 match = "src host alice and dst host bob" 103 eve.execute(f"tcpdump -i br0 -c 1 -Avv {match} >/tmp/log &") 104 105 106 start_all() 107 108 with subtest("Network is up"): 109 alice.wait_until_succeeds("ping -c1 bob") 110 alice.succeed("systemctl restart ipsec") 111 bob.succeed("systemctl restart ipsec") 112 113 with subtest("Eve can eavesdrop cleartext traffic"): 114 eavesdrop() 115 alice_to_bob("I secretly love turnip") 116 eve.sleep(1) 117 eve.succeed("grep turnip /tmp/log") 118 119 with subtest("Libreswan is ready"): 120 alice.wait_for_unit("ipsec") 121 bob.wait_for_unit("ipsec") 122 alice.succeed("ipsec verify 1>&2") 123 124 with subtest("Alice and Bob can start the tunnel"): 125 alice.execute("ipsec auto --start tunnel >&2 &") 126 bob.succeed("ipsec auto --start tunnel") 127 # apparently this is needed to "wake" the tunnel 128 bob.execute("ping -c1 alice") 129 130 with subtest("Eve no longer can eavesdrop"): 131 eavesdrop() 132 alice_to_bob("Just kidding, I actually like rhubarb") 133 eve.sleep(1) 134 eve.fail("grep rhubarb /tmp/log") 135 ''; 136})