Merge pull request #118037 from mayflower/privacy-extensions-configurable
nixos/network: allow configuring tempaddr for undeclared interfaces
This commit is contained in:
commit
29e92116d1
@ -7,8 +7,12 @@
|
|||||||
|
|
||||||
<para>
|
<para>
|
||||||
IPv6 is enabled by default. Stateless address autoconfiguration is used to
|
IPv6 is enabled by default. Stateless address autoconfiguration is used to
|
||||||
automatically assign IPv6 addresses to all interfaces. You can disable IPv6
|
automatically assign IPv6 addresses to all interfaces, and Privacy
|
||||||
support globally by setting:
|
Extensions (RFC 4946) are enabled by default. You can adjust the default
|
||||||
|
for this by setting <xref linkend="opt-networking.tempAddresses"/>.
|
||||||
|
This option may be overridden on a per-interface basis by
|
||||||
|
<xref linkend="opt-networking.interfaces._name_.tempAddress"/>.
|
||||||
|
You can disable IPv6 support globally by setting:
|
||||||
<programlisting>
|
<programlisting>
|
||||||
<xref linkend="opt-networking.enableIPv6"/> = false;
|
<xref linkend="opt-networking.enableIPv6"/> = false;
|
||||||
</programlisting>
|
</programlisting>
|
||||||
|
@ -144,33 +144,20 @@ let
|
|||||||
};
|
};
|
||||||
|
|
||||||
tempAddress = mkOption {
|
tempAddress = mkOption {
|
||||||
type = types.enum [ "default" "enabled" "disabled" ];
|
type = types.enum (lib.attrNames tempaddrValues);
|
||||||
default = if cfg.enableIPv6 then "default" else "disabled";
|
default = cfg.tempAddresses;
|
||||||
defaultText = literalExample ''if cfg.enableIPv6 then "default" else "disabled"'';
|
defaultText = literalExample ''config.networking.tempAddresses'';
|
||||||
description = ''
|
description = ''
|
||||||
When IPv6 is enabled with SLAAC, this option controls the use of
|
When IPv6 is enabled with SLAAC, this option controls the use of
|
||||||
temporary address (aka privacy extensions). This is used to reduce tracking.
|
temporary address (aka privacy extensions) on this
|
||||||
The three possible values are:
|
interface. This is used to reduce tracking.
|
||||||
|
|
||||||
<itemizedlist>
|
See also the global option
|
||||||
<listitem>
|
<xref linkend="opt-networking.tempAddresses"/>, which
|
||||||
<para>
|
applies to all interfaces where this is not set.
|
||||||
<literal>"default"</literal> to generate temporary addresses and use
|
|
||||||
them by default;
|
Possible values are:
|
||||||
</para>
|
${tempaddrDoc}
|
||||||
</listitem>
|
|
||||||
<listitem>
|
|
||||||
<para>
|
|
||||||
<literal>"enabled"</literal> to generate temporary addresses but keep
|
|
||||||
using the standard EUI-64 ones by default;
|
|
||||||
</para>
|
|
||||||
</listitem>
|
|
||||||
<listitem>
|
|
||||||
<para>
|
|
||||||
<literal>"disabled"</literal> to completely disable temporary addresses.
|
|
||||||
</para>
|
|
||||||
</listitem>
|
|
||||||
</itemizedlist>
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -366,6 +353,32 @@ let
|
|||||||
|
|
||||||
isHexString = s: all (c: elem c hexChars) (stringToCharacters (toLower s));
|
isHexString = s: all (c: elem c hexChars) (stringToCharacters (toLower s));
|
||||||
|
|
||||||
|
tempaddrValues = {
|
||||||
|
disabled = {
|
||||||
|
sysctl = "0";
|
||||||
|
description = "completely disable IPv6 temporary addresses";
|
||||||
|
};
|
||||||
|
enabled = {
|
||||||
|
sysctl = "1";
|
||||||
|
description = "generate IPv6 temporary addresses but still use EUI-64 addresses as source addresses";
|
||||||
|
};
|
||||||
|
default = {
|
||||||
|
sysctl = "2";
|
||||||
|
description = "generate IPv6 temporary addresses and use these as source addresses in routing";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
tempaddrDoc = ''
|
||||||
|
<itemizedlist>
|
||||||
|
${concatStringsSep "\n" (mapAttrsToList (name: { description, ... }: ''
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<literal>"${name}"</literal> to ${description};
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
'') tempaddrValues)}
|
||||||
|
</itemizedlist>
|
||||||
|
'';
|
||||||
|
|
||||||
in
|
in
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -1039,6 +1052,21 @@ in
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
networking.tempAddresses = mkOption {
|
||||||
|
default = if cfg.enableIPv6 then "default" else "disabled";
|
||||||
|
type = types.enum (lib.attrNames tempaddrValues);
|
||||||
|
description = ''
|
||||||
|
Whether to enable IPv6 Privacy Extensions for interfaces not
|
||||||
|
configured explicitly in
|
||||||
|
<xref linkend="opt-networking.interfaces._name_.tempAddress" />.
|
||||||
|
|
||||||
|
This sets the ipv6.conf.*.use_tempaddr sysctl for all
|
||||||
|
interfaces. Possible values are:
|
||||||
|
|
||||||
|
${tempaddrDoc}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -1098,7 +1126,7 @@ in
|
|||||||
// listToAttrs (forEach interfaces
|
// listToAttrs (forEach interfaces
|
||||||
(i: let
|
(i: let
|
||||||
opt = i.tempAddress;
|
opt = i.tempAddress;
|
||||||
val = { disabled = 0; enabled = 1; default = 2; }.${opt};
|
val = tempaddrValues.${opt}.sysctl;
|
||||||
in nameValuePair "net.ipv6.conf.${replaceChars ["."] ["/"] i.name}.use_tempaddr" val));
|
in nameValuePair "net.ipv6.conf.${replaceChars ["."] ["/"] i.name}.use_tempaddr" val));
|
||||||
|
|
||||||
# Capabilities won't work unless we have at-least a 4.3 Linux
|
# Capabilities won't work unless we have at-least a 4.3 Linux
|
||||||
@ -1188,9 +1216,11 @@ in
|
|||||||
(pkgs.writeTextFile rec {
|
(pkgs.writeTextFile rec {
|
||||||
name = "ipv6-privacy-extensions.rules";
|
name = "ipv6-privacy-extensions.rules";
|
||||||
destination = "/etc/udev/rules.d/98-${name}";
|
destination = "/etc/udev/rules.d/98-${name}";
|
||||||
text = ''
|
text = let
|
||||||
|
sysctl-value = tempaddrValues.${cfg.tempAddresses}.sysctl;
|
||||||
|
in ''
|
||||||
# enable and prefer IPv6 privacy addresses by default
|
# enable and prefer IPv6 privacy addresses by default
|
||||||
ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.bash}/bin/sh -c 'echo 2 > /proc/sys/net/ipv6/conf/%k/use_tempaddr'"
|
ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.bash}/bin/sh -c 'echo ${sysctl-value} > /proc/sys/net/ipv6/conf/%k/use_tempaddr'"
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
(pkgs.writeTextFile rec {
|
(pkgs.writeTextFile rec {
|
||||||
@ -1199,15 +1229,13 @@ in
|
|||||||
text = concatMapStrings (i:
|
text = concatMapStrings (i:
|
||||||
let
|
let
|
||||||
opt = i.tempAddress;
|
opt = i.tempAddress;
|
||||||
val = if opt == "disabled" then 0 else 1;
|
val = tempaddrValues.${opt}.sysctl;
|
||||||
msg = if opt == "disabled"
|
msg = tempaddrValues.${opt}.description;
|
||||||
then "completely disable IPv6 privacy addresses"
|
|
||||||
else "enable IPv6 privacy addresses but prefer EUI-64 addresses";
|
|
||||||
in
|
in
|
||||||
''
|
''
|
||||||
# override to ${msg} for ${i.name}
|
# override to ${msg} for ${i.name}
|
||||||
ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.procps}/bin/sysctl net.ipv6.conf.${replaceChars ["."] ["/"] i.name}.use_tempaddr=${toString val}"
|
ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.procps}/bin/sysctl net.ipv6.conf.${replaceChars ["."] ["/"] i.name}.use_tempaddr=${val}"
|
||||||
'') (filter (i: i.tempAddress != "default") interfaces);
|
'') (filter (i: i.tempAddress != cfg.tempAddresses) interfaces);
|
||||||
})
|
})
|
||||||
] ++ lib.optional (cfg.wlanInterfaces != {})
|
] ++ lib.optional (cfg.wlanInterfaces != {})
|
||||||
(pkgs.writeTextFile {
|
(pkgs.writeTextFile {
|
||||||
|
@ -8,12 +8,34 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
|
|||||||
};
|
};
|
||||||
|
|
||||||
nodes =
|
nodes =
|
||||||
# Remove the interface configuration provided by makeTest so that the
|
{
|
||||||
# interfaces are all configured implicitly
|
# We use lib.mkForce here to remove the interface configuration
|
||||||
{ client = { ... }: { networking.interfaces = lib.mkForce {}; };
|
# provided by makeTest, so that the interfaces are all configured
|
||||||
|
# implicitly.
|
||||||
|
|
||||||
|
# This client should use privacy extensions fully, having a
|
||||||
|
# completely-default network configuration.
|
||||||
|
client_defaults.networking.interfaces = lib.mkForce {};
|
||||||
|
|
||||||
|
# Both of these clients should obtain temporary addresses, but
|
||||||
|
# not use them as the default source IP. We thus run the same
|
||||||
|
# checks against them — but the configuration resulting in this
|
||||||
|
# behaviour is different.
|
||||||
|
|
||||||
|
# Here, by using an altered default value for the global setting...
|
||||||
|
client_global_setting = {
|
||||||
|
networking.interfaces = lib.mkForce {};
|
||||||
|
networking.tempAddresses = "enabled";
|
||||||
|
};
|
||||||
|
# and here, by setting this on the interface explicitly.
|
||||||
|
client_interface_setting = {
|
||||||
|
networking.tempAddresses = "disabled";
|
||||||
|
networking.interfaces = lib.mkForce {
|
||||||
|
eth1.tempAddress = "enabled";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
server =
|
server =
|
||||||
{ ... }:
|
|
||||||
{ services.httpd.enable = true;
|
{ services.httpd.enable = true;
|
||||||
services.httpd.adminAddr = "foo@example.org";
|
services.httpd.adminAddr = "foo@example.org";
|
||||||
networking.firewall.allowedTCPPorts = [ 80 ];
|
networking.firewall.allowedTCPPorts = [ 80 ];
|
||||||
@ -40,9 +62,12 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
|
|||||||
# Start the router first so that it respond to router solicitations.
|
# Start the router first so that it respond to router solicitations.
|
||||||
router.wait_for_unit("radvd")
|
router.wait_for_unit("radvd")
|
||||||
|
|
||||||
|
clients = [client_defaults, client_global_setting, client_interface_setting]
|
||||||
|
|
||||||
start_all()
|
start_all()
|
||||||
|
|
||||||
client.wait_for_unit("network.target")
|
for client in clients:
|
||||||
|
client.wait_for_unit("network.target")
|
||||||
server.wait_for_unit("network.target")
|
server.wait_for_unit("network.target")
|
||||||
server.wait_for_unit("httpd.service")
|
server.wait_for_unit("httpd.service")
|
||||||
|
|
||||||
@ -64,28 +89,42 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
|
|||||||
|
|
||||||
|
|
||||||
with subtest("Loopback address can be pinged"):
|
with subtest("Loopback address can be pinged"):
|
||||||
client.succeed("ping -c 1 ::1 >&2")
|
client_defaults.succeed("ping -c 1 ::1 >&2")
|
||||||
client.fail("ping -c 1 ::2 >&2")
|
client_defaults.fail("ping -c 1 2001:db8:: >&2")
|
||||||
|
|
||||||
with subtest("Local link addresses can be obtained and pinged"):
|
with subtest("Local link addresses can be obtained and pinged"):
|
||||||
client_ip = wait_for_address(client, "eth1", "link")
|
for client in clients:
|
||||||
server_ip = wait_for_address(server, "eth1", "link")
|
client_ip = wait_for_address(client, "eth1", "link")
|
||||||
client.succeed(f"ping -c 1 {client_ip}%eth1 >&2")
|
server_ip = wait_for_address(server, "eth1", "link")
|
||||||
client.succeed(f"ping -c 1 {server_ip}%eth1 >&2")
|
client.succeed(f"ping -c 1 {client_ip}%eth1 >&2")
|
||||||
|
client.succeed(f"ping -c 1 {server_ip}%eth1 >&2")
|
||||||
|
|
||||||
with subtest("Global addresses can be obtained, pinged, and reached via http"):
|
with subtest("Global addresses can be obtained, pinged, and reached via http"):
|
||||||
client_ip = wait_for_address(client, "eth1", "global")
|
for client in clients:
|
||||||
server_ip = wait_for_address(server, "eth1", "global")
|
client_ip = wait_for_address(client, "eth1", "global")
|
||||||
client.succeed(f"ping -c 1 {client_ip} >&2")
|
server_ip = wait_for_address(server, "eth1", "global")
|
||||||
client.succeed(f"ping -c 1 {server_ip} >&2")
|
client.succeed(f"ping -c 1 {client_ip} >&2")
|
||||||
client.succeed(f"curl --fail -g http://[{server_ip}]")
|
client.succeed(f"ping -c 1 {server_ip} >&2")
|
||||||
client.fail(f"curl --fail -g http://[{client_ip}]")
|
client.succeed(f"curl --fail -g http://[{server_ip}]")
|
||||||
|
client.fail(f"curl --fail -g http://[{client_ip}]")
|
||||||
|
|
||||||
with subtest("Privacy extensions: Global temporary address can be obtained and pinged"):
|
with subtest(
|
||||||
ip = wait_for_address(client, "eth1", "global", temporary=True)
|
"Privacy extensions: Global temporary address is used as default source address"
|
||||||
|
):
|
||||||
|
ip = wait_for_address(client_defaults, "eth1", "global", temporary=True)
|
||||||
# Default route should have "src <temporary address>" in it
|
# Default route should have "src <temporary address>" in it
|
||||||
client.succeed(f"ip r g ::2 | grep {ip}")
|
client_defaults.succeed(f"ip route get 2001:db8:: | grep 'src {ip}'")
|
||||||
|
|
||||||
# TODO: test reachability of a machine on another network.
|
for client, setting_desc in (
|
||||||
|
(client_global_setting, "global"),
|
||||||
|
(client_interface_setting, "interface"),
|
||||||
|
):
|
||||||
|
with subtest(f'Privacy extensions: "enabled" through {setting_desc} setting)'):
|
||||||
|
# We should be obtaining both a temporary address and an EUI-64 address...
|
||||||
|
ip = wait_for_address(client, "eth1", "global")
|
||||||
|
assert "ff:fe" in ip
|
||||||
|
ip_temp = wait_for_address(client, "eth1", "global", temporary=True)
|
||||||
|
# But using the EUI-64 one.
|
||||||
|
client.succeed(f"ip route get 2001:db8:: | grep 'src {ip}'")
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user