From 75df48d3885c660c3a1d73b13049444f5b43497f Mon Sep 17 00:00:00 2001 From: TNE Date: Sun, 21 Jul 2024 06:43:27 +0200 Subject: [PATCH 1/5] maintainers: add tne --- maintainers/maintainer-list.nix | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/maintainers/maintainer-list.nix b/maintainers/maintainer-list.nix index 51e282bfce87..d63c987446bd 100644 --- a/maintainers/maintainer-list.nix +++ b/maintainers/maintainer-list.nix @@ -22347,6 +22347,12 @@ githubId = 6118602; name = "Viktor"; }; + tne = { + email = "tne@garudalinux.org"; + github = "JustTNE"; + githubId = 38938720; + name = "TNE"; + }; tnias = { email = "phil@grmr.de"; matrix = "@tnias:stratum0.org"; From 6cb4e7d591c65f17c891faa135d267ee8c54b307 Mon Sep 17 00:00:00 2001 From: TNE Date: Sun, 21 Jul 2024 06:58:20 +0200 Subject: [PATCH 2/5] nixos/nat: Only connections made to the nat.externalIP will be port forwarded. --- nixos/doc/manual/release-notes/rl-2505.section.md | 2 ++ nixos/modules/services/networking/nat-iptables.nix | 6 ++++-- nixos/modules/services/networking/nat-nftables.nix | 11 ++++++----- nixos/modules/services/networking/nat.nix | 6 ++++-- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/nixos/doc/manual/release-notes/rl-2505.section.md b/nixos/doc/manual/release-notes/rl-2505.section.md index 83ec8b2de680..c7ad29646cf6 100644 --- a/nixos/doc/manual/release-notes/rl-2505.section.md +++ b/nixos/doc/manual/release-notes/rl-2505.section.md @@ -39,6 +39,8 @@ - `zammad` has had its support for MySQL removed, since it was never working correctly and is now deprecated upstream. Check the [migration guide](https://docs.zammad.org/en/latest/appendix/migrate-to-postgresql.html) for how to convert your database to PostgreSQL. +- The behavior of the `networking.nat.externalIP` and `networking.nat.externalIPv6` options has been changed. `networking.nat.forwardPorts` now only forwards packets destined for the specified IP addresses. + - `kanata` was updated to v1.7.0, which introduces several breaking changes. See the release notes of [v1.7.0](https://github.com/jtroo/kanata/releases/tag/v1.7.0) diff --git a/nixos/modules/services/networking/nat-iptables.nix b/nixos/modules/services/networking/nat-iptables.nix index 351ba9ec3554..4660ff6f0e15 100644 --- a/nixos/modules/services/networking/nat-iptables.nix +++ b/nixos/modules/services/networking/nat-iptables.nix @@ -36,7 +36,7 @@ let ${cfg.extraStopCommands} ''; - mkSetupNat = { iptables, dest, internalIPs, forwardPorts }: '' + mkSetupNat = { iptables, dest, internalIPs, forwardPorts, externalIp }: '' # We can't match on incoming interface in POSTROUTING, so # mark packets coming from the internal interfaces. ${concatMapStrings (iface: '' @@ -60,7 +60,7 @@ let ${concatMapStrings (fwd: '' ${iptables} -w -t nat -A nixos-nat-pre \ -i ${toString cfg.externalInterface} -p ${fwd.proto} \ - --dport ${builtins.toString fwd.sourcePort} \ + ${optionalString (externalIp != null) "-d ${externalIp}"} --dport ${builtins.toString fwd.sourcePort} \ -j DNAT --to-destination ${fwd.destination} ${concatMapStrings (loopbackip: @@ -102,6 +102,7 @@ let inherit dest; inherit (cfg) internalIPs; forwardPorts = filter (x: !(isIPv6 x.destination)) cfg.forwardPorts; + externalIp = cfg.externalIP; }} ${optionalString cfg.enableIPv6 (mkSetupNat { @@ -109,6 +110,7 @@ let dest = destIPv6; internalIPs = cfg.internalIPv6s; forwardPorts = filter (x: isIPv6 x.destination) cfg.forwardPorts; + externalIp = cfg.externalIPv6; })} ${optionalString (cfg.dmzHost != null) '' diff --git a/nixos/modules/services/networking/nat-nftables.nix b/nixos/modules/services/networking/nat-nftables.nix index 7aa93d8a64b1..951b14cec791 100644 --- a/nixos/modules/services/networking/nat-nftables.nix +++ b/nixos/modules/services/networking/nat-nftables.nix @@ -33,14 +33,14 @@ let ports = if m == null then throw "bad ip:ports `${IPPorts}'" else elemAt m 1; }; - mkTable = { ipVer, dest, ipSet, forwardPorts, dmzHost }: + mkTable = { ipVer, dest, ipSet, forwardPorts, dmzHost, externalIP }: let # nftables maps for port forward - # l4proto . dport : addr . port + # [daddr .] l4proto . dport : addr . port fwdMap = toNftSet (map (fwd: with (splitIPPorts fwd.destination); - "${fwd.proto} . ${toNftRange fwd.sourcePort} : ${IP} . ${ports}" + "${optionalString (externalIP != null) "${externalIP} . "}${fwd.proto} . ${toNftRange fwd.sourcePort} : ${IP} . ${ports}" ) forwardPorts); @@ -69,7 +69,7 @@ let type nat hook prerouting priority dstnat; ${optionalString (fwdMap != "") '' - iifname "${cfg.externalInterface}" meta l4proto { tcp, udp } dnat meta l4proto . th dport map { ${fwdMap} } comment "port forward" + iifname "${cfg.externalInterface}" meta l4proto { tcp, udp } dnat ${optionalString (externalIP != null) "${ipVer} daddr . "}meta l4proto . th dport map { ${fwdMap} } comment "port forward" ''} ${optionalString (fwdLoopDnatMap != "") '' @@ -133,7 +133,7 @@ in ipVer = "ip"; inherit dest ipSet; forwardPorts = filter (x: !(isIPv6 x.destination)) cfg.forwardPorts; - inherit (cfg) dmzHost; + inherit (cfg) dmzHost externalIP; }; }; "nixos-nat6" = mkIf cfg.enableIPv6 { @@ -145,6 +145,7 @@ in ipSet = ipv6Set; forwardPorts = filter (x: isIPv6 x.destination) cfg.forwardPorts; dmzHost = null; + externalIP = cfg.externalIPv6; }; }; }; diff --git a/nixos/modules/services/networking/nat.nix b/nixos/modules/services/networking/nat.nix index c2e0cfee9b85..1e8c96d7e887 100644 --- a/nixos/modules/services/networking/nat.nix +++ b/nixos/modules/services/networking/nat.nix @@ -82,7 +82,8 @@ in The public IP address to which packets from the local network are to be rewritten. If this is left empty, the IP address associated with the external interface will be - used. + used. Only connections made to this IP address will be + forwarded to the internal network when using forwardPorts. ''; }; @@ -94,7 +95,8 @@ in The public IPv6 address to which packets from the local network are to be rewritten. If this is left empty, the IP address associated with the external interface will be - used. + used. Only connections made to this IP address will be + forwarded to the internal network when using forwardPorts. ''; }; From 622376ecb09f7cd85b3be3604ea83c4499916f29 Mon Sep 17 00:00:00 2001 From: TNE Date: Sun, 21 Jul 2024 07:20:31 +0200 Subject: [PATCH 3/5] nixos/nat: Prevent NAT reflection on connections not coming from behind the NAT --- .../services/networking/nat-iptables.nix | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/nixos/modules/services/networking/nat-iptables.nix b/nixos/modules/services/networking/nat-iptables.nix index 4660ff6f0e15..1f8d6c24d9b6 100644 --- a/nixos/modules/services/networking/nat-iptables.nix +++ b/nixos/modules/services/networking/nat-iptables.nix @@ -77,15 +77,25 @@ let -j DNAT --to-destination ${fwd.destination} # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from other hosts behind NAT - ${iptables} -w -t nat -A nixos-nat-pre \ - -d ${loopbackip} -p ${fwd.proto} \ - --dport ${builtins.toString fwd.sourcePort} \ - -j DNAT --to-destination ${fwd.destination} - - ${iptables} -w -t nat -A nixos-nat-post \ - -d ${destinationIP} -p ${fwd.proto} \ - --dport ${destinationPorts} \ - -j SNAT --to-source ${loopbackip} + ${concatMapStrings (range: '' + ${iptables} -w -t nat -A nixos-nat-pre \ + -d ${loopbackip} -p ${fwd.proto} -s '${range}' \ + --dport ${builtins.toString fwd.sourcePort} \ + -j DNAT --to-destination ${fwd.destination} + ${iptables} -w -t nat -A nixos-nat-post \ + -d ${destinationIP} -p ${fwd.proto} \ + -s '${range}' --dport ${destinationPorts} \ + -j SNAT --to-source ${loopbackip} + '') internalIPs} + ${concatMapStrings (iface: '' + ${iptables} -w -t nat -A nixos-nat-pre \ + -d ${loopbackip} -p ${fwd.proto} -i '${iface}' \ + --dport ${builtins.toString fwd.sourcePort} \ + -j DNAT --to-destination ${fwd.destination} + ${iptables} -w -t nat -A nixos-nat-post \ + -d ${destinationIP} -p ${fwd.proto} \ + -i '${iface}' --dport ${destinationPorts} \ + -j SNAT --to-source ${loopbackip} '') fwd.loopbackIPs} '') forwardPorts} ''; From 46b2df60a5427342aded9e629795e07b9fcff33a Mon Sep 17 00:00:00 2001 From: TNE Date: Sun, 21 Jul 2024 07:22:52 +0200 Subject: [PATCH 4/5] nixos/nat: Allow NAT to still function when a forward default DROP iptables rule is in effect. This allows feature parity with the nftables "filterForward" firewall option when adding a ip forwarding default drop iptables rule. --- .../services/networking/nat-iptables.nix | 23 +++++++++++++++++++ nixos/modules/services/networking/nat.nix | 5 +++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/nixos/modules/services/networking/nat-iptables.nix b/nixos/modules/services/networking/nat-iptables.nix index 1f8d6c24d9b6..4a4d1f2a258d 100644 --- a/nixos/modules/services/networking/nat-iptables.nix +++ b/nixos/modules/services/networking/nat-iptables.nix @@ -32,6 +32,9 @@ let ip46tables -w -t nat -D OUTPUT -j nixos-nat-out 2>/dev/null || true ip46tables -w -t nat -F nixos-nat-out 2>/dev/null || true ip46tables -w -t nat -X nixos-nat-out 2>/dev/null || true + ip46tables -w -t filter -D FORWARD -j nixos-filter-forward 2>/dev/null || true + ip46tables -w -t filter -F nixos-filter-forward 2>/dev/null || true + ip46tables -w -t filter -X nixos-filter-forward 2>/dev/null || true ${cfg.extraStopCommands} ''; @@ -42,6 +45,8 @@ let ${concatMapStrings (iface: '' ${iptables} -w -t nat -A nixos-nat-pre \ -i '${iface}' -j MARK --set-mark 1 + ${iptables} -w -t filter -A nixos-filter-forward \ + -i '${iface}' ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} -j ACCEPT '') cfg.internalInterfaces} # NAT the marked packets. @@ -54,14 +59,23 @@ let ${concatMapStrings (range: '' ${iptables} -w -t nat -A nixos-nat-post \ -s '${range}' ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest} + ${iptables} -w -t filter -A nixos-filter-forward \ + -s '${range}' ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} -j ACCEPT '') internalIPs} + # Related connections are allowed + ${iptables} -w -t filter -A nixos-filter-forward \ + -m state --state ESTABLISHED,RELATED -j ACCEPT + # NAT from external ports to internal ports. ${concatMapStrings (fwd: '' ${iptables} -w -t nat -A nixos-nat-pre \ -i ${toString cfg.externalInterface} -p ${fwd.proto} \ ${optionalString (externalIp != null) "-d ${externalIp}"} --dport ${builtins.toString fwd.sourcePort} \ -j DNAT --to-destination ${fwd.destination} + ${iptables} -w -t filter -A nixos-filter-forward \ + -i ${toString cfg.externalInterface} -p ${fwd.proto} \ + --dport ${builtins.toString fwd.sourcePort} -j ACCEPT ${concatMapStrings (loopbackip: let @@ -86,6 +100,9 @@ let -d ${destinationIP} -p ${fwd.proto} \ -s '${range}' --dport ${destinationPorts} \ -j SNAT --to-source ${loopbackip} + ${iptables} -w -t filter -A nixos-filter-forward \ + -d ${destinationIP} -p ${fwd.proto} \ + -s '${range}' --dport ${destinationPorts} -j ACCEPT '') internalIPs} ${concatMapStrings (iface: '' ${iptables} -w -t nat -A nixos-nat-pre \ @@ -96,6 +113,10 @@ let -d ${destinationIP} -p ${fwd.proto} \ -i '${iface}' --dport ${destinationPorts} \ -j SNAT --to-source ${loopbackip} + ${iptables} -w -t filter -A nixos-filter-forward \ + -d ${destinationIP} -p ${fwd.proto} \ + -i '${iface}' --dport ${destinationPorts} -j ACCEPT + '') cfg.internalInterfaces} '') fwd.loopbackIPs} '') forwardPorts} ''; @@ -106,6 +127,7 @@ let ip46tables -w -t nat -N nixos-nat-pre ip46tables -w -t nat -N nixos-nat-post ip46tables -w -t nat -N nixos-nat-out + ip46tables -w -t filter -N nixos-filter-forward ${mkSetupNat { iptables = "iptables"; @@ -135,6 +157,7 @@ let ip46tables -w -t nat -A PREROUTING -j nixos-nat-pre ip46tables -w -t nat -A POSTROUTING -j nixos-nat-post ip46tables -w -t nat -A OUTPUT -j nixos-nat-out + ip46tables -w -t filter -A FORWARD -j nixos-filter-forward ''; in diff --git a/nixos/modules/services/networking/nat.nix b/nixos/modules/services/networking/nat.nix index 1e8c96d7e887..8cd2464d3358 100644 --- a/nixos/modules/services/networking/nat.nix +++ b/nixos/modules/services/networking/nat.nix @@ -20,7 +20,10 @@ in type = types.bool; default = false; description = '' - Whether to enable Network Address Translation (NAT). + Whether to enable Network Address Translation (NAT). A + properly configured firewall or a trusted L2 on all network + interfaces is required to prevent unauthorized access to + the internal network. ''; }; From 12f09489008cd92338b75ae950932537cb85917f Mon Sep 17 00:00:00 2001 From: TNE Date: Sun, 21 Jul 2024 07:28:21 +0200 Subject: [PATCH 5/5] nixos/tests/nat: Create more broad and detailed testing conditions This code is mostly from #279629, the uninvoled client checks were removed (since they are the same as the direct connection to the client test) and the tests were adjusted to work as intended as well as bugs fixed. In some cases, some tests are skipped when they do not make sense for the specific configuration that is being tested. --- nixos/tests/nat.nix | 314 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 246 insertions(+), 68 deletions(-) diff --git a/nixos/tests/nat.nix b/nixos/tests/nat.nix index 507e0fd72e2a..a00febf766ae 100644 --- a/nixos/tests/nat.nix +++ b/nixos/tests/nat.nix @@ -1,100 +1,278 @@ -# This is a simple distributed test involving a topology with two -# separate virtual networks - the "inside" and the "outside" - with a -# client on the inside network, a server on the outside network, and a -# router connected to both that performs Network Address Translation -# for the client. -import ./make-test-python.nix ({ pkgs, lib, withFirewall, nftables ? false, ... }: +# This is a distributed test of the Network Address Translation involving a topology +# with a router inbetween three separate virtual networks: +# - "external" -- i.e. the internet, +# - "internal" -- i.e. an office LAN, +# +# This test puts one server on each of those networks and its primary goal is to ensure that: +# - server (named client in the code) in internal network can reach server (named server in the code) on the external network, +# - server in external network can not reach server in internal network (skipped in some cases), +# - when using externalIP, only the specified IP is used for NAT, +# - port forwarding functionality behaves correctly +# +# The client is behind the nat (read: protected by the nat) and the server is on the external network, attempting to access services behind the NAT. + +import ./make-test-python.nix ({ pkgs, lib, withFirewall ? false, nftables ? false, ... }: let unit = if nftables then "nftables" else (if withFirewall then "firewall" else "nat"); + + routerAlternativeExternalIp = "192.168.2.234"; + + makeNginxConfig = hostname: { + enable = true; + virtualHosts."${hostname}" = { + root = "/etc"; + locations."/".index = "hostname"; + listen = [ + { + addr = "0.0.0.0"; + port = 80; + } + { + addr = "0.0.0.0"; + port = 8080; + } + ]; + }; + }; + + makeCommonConfig = hostname: { + services.nginx = makeNginxConfig hostname; + services.vsftpd = { + enable = true; + anonymousUser = true; + localRoot = "/etc/"; + extraConfig = '' + pasv_min_port=51000 + pasv_max_port=51999 + ''; + }; + + # Disable eth0 autoconfiguration + networking.useDHCP = false; + + environment.systemPackages = [ + (pkgs.writeScriptBin "check-connection" + '' + #!/usr/bin/env bash + + set -e + + if [[ "$2" == "" || "$3" == "" || "$1" == "--help" || "$1" == "-h" ]]; + then + echo "check-connection <[expect-success|expect-failure]>" + exit 1 + fi + + ADDRESS="$1" + HOSTNAME="$2" + + function test_icmp() { timeout 3 ping -c 1 $ADDRESS; } + function test_http() { [[ `timeout 3 curl $ADDRESS` == "$HOSTNAME" ]]; } + function test_ftp() { timeout 3 curl ftp://$ADDRESS; } + + if [[ "$3" == "expect-success" ]]; + then + test_icmp; test_http; test_ftp + else + ! test_icmp; ! test_http; ! test_ftp + fi + '' + ) + (pkgs.writeScriptBin "check-last-clients-ip" + '' + #!/usr/bin/env bash + set -e + + [[ `cat /var/log/nginx/access.log | tail -n1 | awk '{print $1}'` == "$1" ]] + '' + ) + ]; + }; + + # VLANS: + # 1 -- simulates the internal network + # 2 -- simulates the external network in { name = "nat" + (lib.optionalString nftables "Nftables") + (if withFirewall then "WithFirewall" else "Standalone"); meta = with pkgs.lib.maintainers; { - maintainers = [ rob ]; + maintainers = [ tne rob ]; }; nodes = - { - client = { lib, nodes, ... }: { - virtualisation.vlans = [ 1 ]; - networking.defaultGateway = - (lib.head nodes.router.networking.interfaces.eth2.ipv4.addresses).address; - networking.nftables.enable = nftables; - }; + { client = + { pkgs, nodes, ... }: + lib.mkMerge [ + ( makeCommonConfig "client" ) + { virtualisation.vlans = [ 1 ]; + networking.defaultGateway = + (pkgs.lib.head nodes.router.networking.interfaces.eth1.ipv4.addresses).address; + networking.nftables.enable = nftables; + networking.firewall.enable = false; + } + ]; - router = { lib, ... }: { - virtualisation.vlans = [ 2 1 ]; - networking.firewall.enable = withFirewall; - networking.firewall.filterForward = nftables; - networking.nftables.enable = nftables; - networking.nat.enable = true; - networking.nat.internalIPs = [ "192.168.1.0/24" ]; - networking.nat.externalInterface = "eth1"; + router = + { nodes, ... }: lib.mkMerge [ + ( makeCommonConfig "router" ) + { virtualisation.vlans = [ 1 2 ]; + networking.firewall = { + enable = withFirewall; + filterForward = nftables; + allowedTCPPorts = [ 21 80 8080 ]; + # For FTP passive mode + allowedTCPPortRanges = [ { from = 51000; to = 51999; } ]; + }; + networking.nftables.enable = nftables; + networking.nat = + let + clientIp = (pkgs.lib.head nodes.client.networking.interfaces.eth1.ipv4.addresses).address; + serverIp = (pkgs.lib.head nodes.router.networking.interfaces.eth2.ipv4.addresses).address; + in + { + enable = true; + internalIPs = [ "${clientIp}/24" ]; + # internalInterfaces = [ "eth1" ]; + externalInterface = "eth2"; + externalIP = serverIp; - specialisation.no-nat.configuration = { - networking.nat.enable = lib.mkForce false; - }; - }; + forwardPorts = [ + { + destination = "${clientIp}:8080"; + proto = "tcp"; + sourcePort = 8080; + + loopbackIPs = [ serverIp ]; + } + ]; + }; + + networking.interfaces.eth2.ipv4.addresses = + lib.mkOrder 10000 [ { address = routerAlternativeExternalIp; prefixLength = 24; } ]; + + services.nginx.virtualHosts.router.listen = lib.mkOrder (-1) [ { + addr = routerAlternativeExternalIp; + port = 8080; + } ]; + + specialisation.no-nat.configuration = { + networking.nat.enable = lib.mkForce false; + }; + } + ]; server = - { ... }: - { virtualisation.vlans = [ 2 ]; - networking.firewall.enable = false; - services.httpd.enable = true; - services.httpd.adminAddr = "foo@example.org"; - services.vsftpd.enable = true; - services.vsftpd.anonymousUser = true; - }; + { nodes, ... }: lib.mkMerge [ + ( makeCommonConfig "server" ) + { virtualisation.vlans = [ 2 ]; + networking.firewall.enable = false; + + networking.defaultGateway = + (pkgs.lib.head nodes.router.networking.interfaces.eth2.ipv4.addresses).address; + } + ]; }; - testScript = '' + testScript = + { nodes, ... }: let + clientIp = (pkgs.lib.head nodes.client.networking.interfaces.eth1.ipv4.addresses).address; + serverIp = (pkgs.lib.head nodes.server.networking.interfaces.eth1.ipv4.addresses).address; + routerIp = (pkgs.lib.head nodes.router.networking.interfaces.eth2.ipv4.addresses).address; + in '' + def wait_for_machine(m): + m.wait_for_unit("network.target") + m.wait_for_unit("nginx.service") + client.start() router.start() server.start() - # The router should have access to the server. - server.wait_for_unit("network.target") - server.wait_for_unit("httpd") - router.wait_for_unit("network.target") - router.succeed("curl -4 --fail http://server/ >&2") + wait_for_machine(router) + wait_for_machine(client) + wait_for_machine(server) - # The client should be also able to connect via the NAT router. - router.wait_for_unit("${unit}") - client.wait_for_unit("network.target") - client.succeed("curl --fail http://server/ >&2") - client.succeed("ping -4 -c 1 server >&2") + # We assume we are isolated from layer 2 attacks or are securely configured (like disabling forwarding by default) + # Relevant moby issue describing the problem allowing bypassing of NAT: https://github.com/moby/moby/issues/14041 + ${lib.optionalString (!nftables) '' + router.succeed("iptables -P FORWARD DROP") + ''} - # Test whether passive FTP works. - server.wait_for_unit("vsftpd") - server.succeed("echo Hello World > /home/ftp/foo.txt") - client.succeed("curl -v ftp://server/foo.txt >&2") + # Sanity checks. + ## The router should have direct access to the server + router.succeed("check-connection ${serverIp} server expect-success") + ## The server should have direct access to the router + server.succeed("check-connection ${routerIp} router expect-success") - # Test whether active FTP works. - client.fail("curl -v -P - ftp://server/foo.txt >&2") + # The client should be also able to connect via the NAT router... + client.succeed("check-connection ${serverIp} server expect-success") + # ... but its IP should be rewritten to be that of the router. + server.succeed("check-last-clients-ip ${routerIp}") - # Test ICMP. - client.succeed("ping -4 -c 1 router >&2") - router.succeed("ping -4 -c 1 client >&2") + # Active FTP (where the FTP server connects back to us via a random port) should work directly... + router.succeed("timeout 3 curl -P eth2:51000-51999 ftp://${serverIp}") + # ... but not from behind NAT. + client.fail("timeout 3 curl -P eth1:51000-51999 ftp://${serverIp};") - # If we turn off NAT, the client shouldn't be able to reach the server. + # If using nftables without firewall, filterForward can't be used and L2 security can't easily be simulated like with iptables, skipping. + # See moby github issue mentioned above. + ${lib.optionalString (nftables && withFirewall) '' + # The server should not be able to reach the client directly... + server.succeed("check-connection ${clientIp} client expect-failure") + ''} + # ... but the server should be able to reach a port forwarded address of the client + server.succeed('[[ `timeout 3 curl http://${routerIp}:8080` == "client" ]]') + # The IP address the client sees should not be rewritten to be that of the router (#277016) + client.succeed("check-last-clients-ip ${serverIp}") + + # But this forwarded port shouldn't intercept communication with + # other IPs than externalIp. + server.succeed('[[ `timeout 3 curl http://${routerAlternativeExternalIp}:8080` == "router" ]]') + + # The loopback should allow the router itself to access the forwarded port + # Note: The reason we use routerIp here is because only routerIp is listed for reflection in networking.nat.forwardPorts.loopbackIPs + # The purpose of loopbackIPs is to allow things inside of the NAT to for example access their own public domain when a service has to make a request + # to itself/another service on the same NAT through a public address + router.succeed('[[ `timeout 3 curl http://${routerIp}:8080` == "client" ]]') + # The loopback should also allow the client to access its own forwarded port + client.succeed('[[ `timeout 3 curl http://${routerIp}:8080` == "client" ]]') + + # If we turn off NAT, nothing should work + router.succeed( + "systemctl stop ${unit}.service" + ) + + # If using nftables and firewall, this makes no sense. We deactivated the firewall after all, + # so we are once again affected by the same issue as the moby github issue mentioned above. + # If using nftables without firewall, filterForward can't be used and L2 security can't easily be simulated like with iptables, skipping. + # See moby github issue mentioned above. + ${lib.optionalString (!nftables) '' + client.succeed("check-connection ${serverIp} server expect-failure") + server.succeed("check-connection ${clientIp} client expect-failure") + ''} + # These should revert to their pre-NATed versions + server.succeed('[[ `timeout 3 curl http://${routerIp}:8080` == "router" ]]') + router.succeed('[[ `timeout 3 curl http://${routerIp}:8080` == "router" ]]') + + # Reverse the effect of nat stop + router.succeed( + "systemctl start ${unit}.service" + ) + + # Switch to a config without NAT at all, again nothing should work router.succeed( "/run/booted-system/specialisation/no-nat/bin/switch-to-configuration test 2>&1" ) - client.fail("curl -4 --fail --connect-timeout 5 http://server/ >&2") - client.fail("ping -4 -c 1 server >&2") - # And make sure that reloading the NAT job works. - router.succeed( - "/run/booted-system/bin/switch-to-configuration test 2>&1" - ) - # FIXME: this should not be necessary, but nat.service is not started because - # network.target is not triggered - # (https://github.com/NixOS/nixpkgs/issues/16230#issuecomment-226408359) - ${lib.optionalString (!withFirewall && !nftables) '' - router.succeed("systemctl start nat.service") + # If using nftables without firewall, filterForward can't be used and L2 security can't easily be simulated like with iptables, skipping. + # See moby github issue mentioned above. + ${lib.optionalString (nftables && withFirewall) '' + client.succeed("check-connection ${serverIp} server expect-failure") + server.succeed("check-connection ${clientIp} client expect-failure") ''} - client.succeed("curl -4 --fail http://server/ >&2") - client.succeed("ping -4 -c 1 server >&2") + + # These should revert to their pre-NATed versions + server.succeed('[[ `timeout 3 curl http://${routerIp}:8080` == "router" ]]') + router.succeed('[[ `timeout 3 curl http://${routerIp}:8080` == "router" ]]') ''; })