notes: 2.3.0 -> 2.3.1 (#352950)
[NixPkgs.git] / nixos / tests / libreswan-nat.nix
blob973e304f9e3a36969782feb3c4282dd202424b3f
1 # This test sets up an IPsec VPN server that allows a client behind an IPv4 NAT
2 # router to access the IPv6 internet. We check that the client initially can't
3 # ping an IPv6 hosts and its connection to the server can be eavesdropped by
4 # the router, but once the IPsec tunnel is enstablished it can talk to an
5 # IPv6-only host and the connection is secure.
7 # Notes:
8 #   - the VPN is implemented using policy-based routing.
9 #   - the client is assigned an IPv6 address from the same /64 subnet
10 #     of the server, without DHCPv6 or SLAAC.
11 #   - the server acts as NDP proxy for the client, so that the latter
12 #     becomes reachable at its assigned IPv6 via the server.
13 #   - the client falls back to TCP if UDP is blocked
15 { lib, pkgs, ... }:
17 let
19   # Common network setup
20   baseNetwork = {
21     # shared hosts file
22     networking.extraHosts = lib.mkVMOverride ''
23       203.0.113.1 router
24       203.0.113.2 server
25       2001:db8::2 inner
26       192.168.1.1 client
27     '';
28     # open a port for testing
29     networking.firewall.allowedUDPPorts = [ 1234 ];
30   };
32   # Common IPsec configuration
33   baseTunnel = {
34     services.libreswan.enable = true;
35     environment.etc."ipsec.d/tunnel.secrets" =
36       { text = ''@server %any : PSK "j1JbIi9WY07rxwcNQ6nbyThKCf9DGxWOyokXIQcAQUnafsNTUJxfsxwk9WYK8fHj"'';
37         mode = "600";
38       };
39   };
41   # Helpers to add a static IP address on an interface
42   setAddress4 = iface: addr: {
43     networking.interfaces.${iface}.ipv4.addresses =
44       lib.mkVMOverride [ { address = addr; prefixLength = 24; } ];
45   };
46   setAddress6 = iface: addr: {
47     networking.interfaces.${iface}.ipv6.addresses =
48       lib.mkVMOverride [ { address = addr; prefixLength = 64; } ];
49   };
54   name = "libreswan-nat";
55   meta = with lib.maintainers; {
56     maintainers = [ rnhmjoj ];
57   };
59   nodes.router = { pkgs, ... }: lib.mkMerge [
60     baseNetwork
61     (setAddress4 "eth1" "203.0.113.1")
62     (setAddress4 "eth2" "192.168.1.1")
63     {
64       virtualisation.vlans = [ 1 2 ];
65       environment.systemPackages = [ pkgs.tcpdump ];
66       networking.nat = {
67         enable = true;
68         externalInterface = "eth1";
69         internalInterfaces = [ "eth2" ];
70       };
71       networking.firewall.trustedInterfaces = [ "eth2" ];
72     }
73   ];
75   nodes.inner = lib.mkMerge [
76     baseNetwork
77     (setAddress6 "eth1" "2001:db8::2")
78     { virtualisation.vlans = [ 3 ]; }
79   ];
81   nodes.server = lib.mkMerge [
82     baseNetwork
83     baseTunnel
84     (setAddress4 "eth1" "203.0.113.2")
85     (setAddress6 "eth2" "2001:db8::1")
86     {
87       virtualisation.vlans = [ 1 3 ];
88       networking.firewall.allowedUDPPorts = [ 500 4500 ];
89       networking.firewall.allowedTCPPorts = [ 993 ];
91       # see https://github.com/NixOS/nixpkgs/pull/310857
92       networking.firewall.checkReversePath = false;
94       boot.kernel.sysctl = {
95         # enable forwarding packets
96         "net.ipv6.conf.all.forwarding" = 1;
97         "net.ipv4.conf.all.forwarding" = 1;
98         # enable NDP proxy for VPN clients
99         "net.ipv6.conf.all.proxy_ndp" = 1;
100       };
102       services.libreswan.configSetup = "listen-tcp=yes";
103       services.libreswan.connections.tunnel = ''
104         # server
105         left=203.0.113.2
106         leftid=@server
107         leftsubnet=::/0
108         leftupdown=${pkgs.writeScript "updown" ''
109           # act as NDP proxy for VPN clients
110           if test "$PLUTO_VERB" = up-client-v6; then
111             ip neigh add proxy "$PLUTO_PEER_CLIENT_NET" dev eth2
112           fi
113           if test "$PLUTO_VERB" = down-client-v6; then
114             ip neigh del proxy "$PLUTO_PEER_CLIENT_NET" dev eth2
115           fi
116         ''}
118         # clients
119         right=%any
120         rightaddresspool=2001:db8:0:0:c::/97
121         modecfgdns=2001:db8::1
123         # clean up vanished clients
124         dpddelay=30
126         auto=add
127         keyexchange=ikev2
128         rekey=no
129         narrowing=yes
130         fragmentation=yes
131         authby=secret
133         leftikeport=993
134         retransmit-timeout=10s
135       '';
136     }
137   ];
139   nodes.client = lib.mkMerge [
140     baseNetwork
141     baseTunnel
142     (setAddress4 "eth1" "192.168.1.2")
143     {
144       virtualisation.vlans = [ 2 ];
145       networking.defaultGateway = {
146         address = "192.168.1.1";
147         interface = "eth1";
148       };
149       services.libreswan.connections.tunnel = ''
150         # client
151         left=%defaultroute
152         leftid=@client
153         leftmodecfgclient=yes
154         leftsubnet=::/0
156         # server
157         right=203.0.113.2
158         rightid=@server
159         rightsubnet=::/0
161         auto=add
162         narrowing=yes
163         rekey=yes
164         fragmentation=yes
165         authby=secret
167         # fallback when UDP is blocked
168         enable-tcp=fallback
169         tcp-remoteport=993
170         retransmit-timeout=5s
171       '';
172     }
173   ];
175   testScript =
176     ''
177       def client_to_host(machine, msg: str):
178           """
179           Sends a message from client to server
180           """
181           machine.execute("nc -lu :: 1234 >/tmp/msg &")
182           client.sleep(1)
183           client.succeed(f"echo '{msg}' | nc -uw 0 {machine.name} 1234")
184           client.sleep(1)
185           machine.succeed(f"grep '{msg}' /tmp/msg")
188       def eavesdrop():
189           """
190           Starts eavesdropping on the router
191           """
192           match = "udp port 1234"
193           router.execute(f"tcpdump -i eth1 -c 1 -Avv {match} >/tmp/log &")
196       start_all()
198       with subtest("Network is up"):
199           client.wait_until_succeeds("ping -c1 server")
200           client.succeed("systemctl restart ipsec")
201           server.succeed("systemctl restart ipsec")
203       with subtest("Router can eavesdrop cleartext traffic"):
204           eavesdrop()
205           client_to_host(server, "I secretly love turnip")
206           router.sleep(1)
207           router.succeed("grep turnip /tmp/log")
209       with subtest("Libreswan is ready"):
210           client.wait_for_unit("ipsec")
211           server.wait_for_unit("ipsec")
212           client.succeed("ipsec checkconfig")
213           server.succeed("ipsec checkconfig")
215       with subtest("Client can't ping VPN host"):
216           client.fail("ping -c1 inner")
218       with subtest("Client can start the tunnel"):
219           client.succeed("ipsec start tunnel")
220           client.succeed("ip -6 addr show lo | grep -q 2001:db8:0:0:c")
222       with subtest("Client can ping VPN host"):
223           client.wait_until_succeeds("ping -c1 2001:db8::1")
224           client.succeed("ping -c1 inner")
226       with subtest("Eve no longer can eavesdrop"):
227           eavesdrop()
228           client_to_host(inner, "Just kidding, I actually like rhubarb")
229           router.sleep(1)
230           router.fail("grep rhubarb /tmp/log")
232       with subtest("TCP fallback is available"):
233           server.succeed("iptables -I nixos-fw -p udp -j DROP")
234           client.succeed("ipsec restart")
235           client.execute("ipsec start tunnel")
236           client.wait_until_succeeds("ping -c1 inner")
237     '';