From 5c9de39abaa4842aacc933bc665f6898e916b9fd Mon Sep 17 00:00:00 2001 From: rnhmjoj Date: Thu, 20 Jun 2024 15:13:58 +0200 Subject: [PATCH 1/3] linux/common-config: enable IPsec over TCP This enable kernel support for TCP encapsulation of IPsec (RFC8229) This is required for networks where the standard mode is not available due to firewall rules blocking UDP traffic. --- pkgs/os-specific/linux/kernel/common-config.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkgs/os-specific/linux/kernel/common-config.nix b/pkgs/os-specific/linux/kernel/common-config.nix index c9bf29616062..fbbcbdd77930 100644 --- a/pkgs/os-specific/linux/kernel/common-config.nix +++ b/pkgs/os-specific/linux/kernel/common-config.nix @@ -327,6 +327,10 @@ let INET_RAW_DIAG = mkDefault module; INET_DIAG_DESTROY = mkDefault yes; + # IPsec over TCP + INET_ESPINTCP = whenAtLeast "5.8" yes; + INET6_ESPINTCP = whenAtLeast "5.8" yes; + # enable multipath-tcp MPTCP = whenAtLeast "5.6" yes; MPTCP_IPV6 = whenAtLeast "5.6" yes; From 818afd9d6d910444257d4571744717ba6e32ec90 Mon Sep 17 00:00:00 2001 From: rnhmjoj Date: Thu, 9 May 2024 20:22:57 +0200 Subject: [PATCH 2/3] nixos/tests/libreswan: use runTest --- nixos/tests/all-tests.nix | 2 +- nixos/tests/libreswan.nix | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 8d5b865891e4..eca040ea85fb 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -500,7 +500,7 @@ in { libreddit = handleTest ./libreddit.nix {}; librenms = handleTest ./librenms.nix {}; libresprite = handleTest ./libresprite.nix {}; - libreswan = handleTest ./libreswan.nix {}; + libreswan = runTest ./libreswan.nix; librewolf = handleTest ./firefox.nix { firefoxPackage = pkgs.librewolf; }; libuiohook = handleTest ./libuiohook.nix {}; libvirtd = handleTest ./libvirtd.nix {}; diff --git a/nixos/tests/libreswan.nix b/nixos/tests/libreswan.nix index c798a04645bc..6dd9a845d19a 100644 --- a/nixos/tests/libreswan.nix +++ b/nixos/tests/libreswan.nix @@ -3,7 +3,7 @@ # 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, ... }: +{ lib, pkgs, ... }: let @@ -133,4 +133,4 @@ in eve.sleep(1) eve.fail("grep rhubarb /tmp/log") ''; -}) +} From 7c021fdfcd602c0364c97a658d8748663389e3b1 Mon Sep 17 00:00:00 2001 From: rnhmjoj Date: Thu, 9 May 2024 20:24:56 +0200 Subject: [PATCH 3/3] nixos/tests/libreswan-nat: add test --- nixos/tests/all-tests.nix | 1 + nixos/tests/libreswan-nat.nix | 238 ++++++++++++++++++++ pkgs/tools/networking/libreswan/default.nix | 2 +- 3 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 nixos/tests/libreswan-nat.nix diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index eca040ea85fb..ba772bcd986f 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -501,6 +501,7 @@ in { librenms = handleTest ./librenms.nix {}; libresprite = handleTest ./libresprite.nix {}; libreswan = runTest ./libreswan.nix; + libreswan-nat = runTest ./libreswan-nat.nix; librewolf = handleTest ./firefox.nix { firefoxPackage = pkgs.librewolf; }; libuiohook = handleTest ./libuiohook.nix {}; libvirtd = handleTest ./libvirtd.nix {}; diff --git a/nixos/tests/libreswan-nat.nix b/nixos/tests/libreswan-nat.nix new file mode 100644 index 000000000000..973e304f9e3a --- /dev/null +++ b/nixos/tests/libreswan-nat.nix @@ -0,0 +1,238 @@ +# This test sets up an IPsec VPN server that allows a client behind an IPv4 NAT +# router to access the IPv6 internet. We check that the client initially can't +# ping an IPv6 hosts and its connection to the server can be eavesdropped by +# the router, but once the IPsec tunnel is enstablished it can talk to an +# IPv6-only host and the connection is secure. +# +# Notes: +# - the VPN is implemented using policy-based routing. +# - the client is assigned an IPv6 address from the same /64 subnet +# of the server, without DHCPv6 or SLAAC. +# - the server acts as NDP proxy for the client, so that the latter +# becomes reachable at its assigned IPv6 via the server. +# - the client falls back to TCP if UDP is blocked + +{ lib, pkgs, ... }: + +let + + # Common network setup + baseNetwork = { + # shared hosts file + networking.extraHosts = lib.mkVMOverride '' + 203.0.113.1 router + 203.0.113.2 server + 2001:db8::2 inner + 192.168.1.1 client + ''; + # open a port for testing + networking.firewall.allowedUDPPorts = [ 1234 ]; + }; + + # Common IPsec configuration + baseTunnel = { + services.libreswan.enable = true; + environment.etc."ipsec.d/tunnel.secrets" = + { text = ''@server %any : PSK "j1JbIi9WY07rxwcNQ6nbyThKCf9DGxWOyokXIQcAQUnafsNTUJxfsxwk9WYK8fHj"''; + mode = "600"; + }; + }; + + # Helpers to add a static IP address on an interface + setAddress4 = iface: addr: { + networking.interfaces.${iface}.ipv4.addresses = + lib.mkVMOverride [ { address = addr; prefixLength = 24; } ]; + }; + setAddress6 = iface: addr: { + networking.interfaces.${iface}.ipv6.addresses = + lib.mkVMOverride [ { address = addr; prefixLength = 64; } ]; + }; + +in + +{ + name = "libreswan-nat"; + meta = with lib.maintainers; { + maintainers = [ rnhmjoj ]; + }; + + nodes.router = { pkgs, ... }: lib.mkMerge [ + baseNetwork + (setAddress4 "eth1" "203.0.113.1") + (setAddress4 "eth2" "192.168.1.1") + { + virtualisation.vlans = [ 1 2 ]; + environment.systemPackages = [ pkgs.tcpdump ]; + networking.nat = { + enable = true; + externalInterface = "eth1"; + internalInterfaces = [ "eth2" ]; + }; + networking.firewall.trustedInterfaces = [ "eth2" ]; + } + ]; + + nodes.inner = lib.mkMerge [ + baseNetwork + (setAddress6 "eth1" "2001:db8::2") + { virtualisation.vlans = [ 3 ]; } + ]; + + nodes.server = lib.mkMerge [ + baseNetwork + baseTunnel + (setAddress4 "eth1" "203.0.113.2") + (setAddress6 "eth2" "2001:db8::1") + { + virtualisation.vlans = [ 1 3 ]; + networking.firewall.allowedUDPPorts = [ 500 4500 ]; + networking.firewall.allowedTCPPorts = [ 993 ]; + + # see https://github.com/NixOS/nixpkgs/pull/310857 + networking.firewall.checkReversePath = false; + + boot.kernel.sysctl = { + # enable forwarding packets + "net.ipv6.conf.all.forwarding" = 1; + "net.ipv4.conf.all.forwarding" = 1; + # enable NDP proxy for VPN clients + "net.ipv6.conf.all.proxy_ndp" = 1; + }; + + services.libreswan.configSetup = "listen-tcp=yes"; + services.libreswan.connections.tunnel = '' + # server + left=203.0.113.2 + leftid=@server + leftsubnet=::/0 + leftupdown=${pkgs.writeScript "updown" '' + # act as NDP proxy for VPN clients + if test "$PLUTO_VERB" = up-client-v6; then + ip neigh add proxy "$PLUTO_PEER_CLIENT_NET" dev eth2 + fi + if test "$PLUTO_VERB" = down-client-v6; then + ip neigh del proxy "$PLUTO_PEER_CLIENT_NET" dev eth2 + fi + ''} + + # clients + right=%any + rightaddresspool=2001:db8:0:0:c::/97 + modecfgdns=2001:db8::1 + + # clean up vanished clients + dpddelay=30 + + auto=add + keyexchange=ikev2 + rekey=no + narrowing=yes + fragmentation=yes + authby=secret + + leftikeport=993 + retransmit-timeout=10s + ''; + } + ]; + + nodes.client = lib.mkMerge [ + baseNetwork + baseTunnel + (setAddress4 "eth1" "192.168.1.2") + { + virtualisation.vlans = [ 2 ]; + networking.defaultGateway = { + address = "192.168.1.1"; + interface = "eth1"; + }; + services.libreswan.connections.tunnel = '' + # client + left=%defaultroute + leftid=@client + leftmodecfgclient=yes + leftsubnet=::/0 + + # server + right=203.0.113.2 + rightid=@server + rightsubnet=::/0 + + auto=add + narrowing=yes + rekey=yes + fragmentation=yes + authby=secret + + # fallback when UDP is blocked + enable-tcp=fallback + tcp-remoteport=993 + retransmit-timeout=5s + ''; + } + ]; + + testScript = + '' + def client_to_host(machine, msg: str): + """ + Sends a message from client to server + """ + machine.execute("nc -lu :: 1234 >/tmp/msg &") + client.sleep(1) + client.succeed(f"echo '{msg}' | nc -uw 0 {machine.name} 1234") + client.sleep(1) + machine.succeed(f"grep '{msg}' /tmp/msg") + + + def eavesdrop(): + """ + Starts eavesdropping on the router + """ + match = "udp port 1234" + router.execute(f"tcpdump -i eth1 -c 1 -Avv {match} >/tmp/log &") + + + start_all() + + with subtest("Network is up"): + client.wait_until_succeeds("ping -c1 server") + client.succeed("systemctl restart ipsec") + server.succeed("systemctl restart ipsec") + + with subtest("Router can eavesdrop cleartext traffic"): + eavesdrop() + client_to_host(server, "I secretly love turnip") + router.sleep(1) + router.succeed("grep turnip /tmp/log") + + with subtest("Libreswan is ready"): + client.wait_for_unit("ipsec") + server.wait_for_unit("ipsec") + client.succeed("ipsec checkconfig") + server.succeed("ipsec checkconfig") + + with subtest("Client can't ping VPN host"): + client.fail("ping -c1 inner") + + with subtest("Client can start the tunnel"): + client.succeed("ipsec start tunnel") + client.succeed("ip -6 addr show lo | grep -q 2001:db8:0:0:c") + + with subtest("Client can ping VPN host"): + client.wait_until_succeeds("ping -c1 2001:db8::1") + client.succeed("ping -c1 inner") + + with subtest("Eve no longer can eavesdrop"): + eavesdrop() + client_to_host(inner, "Just kidding, I actually like rhubarb") + router.sleep(1) + router.fail("grep rhubarb /tmp/log") + + with subtest("TCP fallback is available"): + server.succeed("iptables -I nixos-fw -p udp -j DROP") + client.succeed("ipsec restart") + client.execute("ipsec start tunnel") + client.wait_until_succeeds("ping -c1 inner") + ''; +} diff --git a/pkgs/tools/networking/libreswan/default.nix b/pkgs/tools/networking/libreswan/default.nix index 07f35663752b..7dac682407bd 100644 --- a/pkgs/tools/networking/libreswan/default.nix +++ b/pkgs/tools/networking/libreswan/default.nix @@ -104,7 +104,7 @@ stdenv.mkDerivation rec { -i $out/bin/ipsec ''; - passthru.tests.libreswan = nixosTests.libreswan; + passthru.tests = { inherit (nixosTests) libreswan libreswan-nat; }; meta = with lib; { homepage = "https://libreswan.org";