mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-06-13 21:25:30 +03:00
nixos/nat: Match iptables behavior with nftables, add externalIP check (#277016)
This commit is contained in:
commit
dd9a2e26ac
6 changed files with 314 additions and 87 deletions
|
@ -22366,6 +22366,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";
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -32,16 +32,21 @@ 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}
|
||||
'';
|
||||
|
||||
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: ''
|
||||
${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} \
|
||||
--dport ${builtins.toString fwd.sourcePort} \
|
||||
${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
|
||||
|
@ -77,15 +91,32 @@ 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}
|
||||
${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 \
|
||||
-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}
|
||||
${iptables} -w -t filter -A nixos-filter-forward \
|
||||
-d ${destinationIP} -p ${fwd.proto} \
|
||||
-i '${iface}' --dport ${destinationPorts} -j ACCEPT
|
||||
'') cfg.internalInterfaces}
|
||||
'') fwd.loopbackIPs}
|
||||
'') forwardPorts}
|
||||
'';
|
||||
|
@ -96,12 +127,14 @@ 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";
|
||||
inherit dest;
|
||||
inherit (cfg) internalIPs;
|
||||
forwardPorts = filter (x: !(isIPv6 x.destination)) cfg.forwardPorts;
|
||||
externalIp = cfg.externalIP;
|
||||
}}
|
||||
|
||||
${optionalString cfg.enableIPv6 (mkSetupNat {
|
||||
|
@ -109,6 +142,7 @@ let
|
|||
dest = destIPv6;
|
||||
internalIPs = cfg.internalIPv6s;
|
||||
forwardPorts = filter (x: isIPv6 x.destination) cfg.forwardPorts;
|
||||
externalIp = cfg.externalIPv6;
|
||||
})}
|
||||
|
||||
${optionalString (cfg.dmzHost != null) ''
|
||||
|
@ -123,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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
'';
|
||||
};
|
||||
|
||||
|
@ -82,7 +85,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 +98,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.
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
|
@ -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 <target-address> <target-hostname> <[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" ]]')
|
||||
'';
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue