nixos/dhcpcd: harden and run as unprivileged user (#336988)
This commit is contained in:
@@ -189,6 +189,11 @@
|
||||
|
||||
- The nvidia driver no longer defaults to the proprietary driver starting with version 560. You will need to manually set `hardware.nvidia.open` to select the proprietary or open driver.
|
||||
|
||||
- The dhcpcd service (`networking.useDHCP`) has been hardened and now runs exclusively as the "dhcpcd" user.
|
||||
Users that were relying on the root privileges in `networking.dhcpcd.runHook` will have to write specific [sudo](security.sudo.extraRules) or [polkit](security.polkit.extraConfig) rules to allow dhcpcd to perform privileged actions.
|
||||
|
||||
As part of these changes, the DHCP lease files directory has also been moved from `/var/db/dhcpcd` to `/var/lib/dhcpcd`. This migration is performed automatically, but users may have to update their backup configuration.
|
||||
|
||||
- `singularity-tools` have the `storeDir` argument removed from its override interface and use `builtins.storeDir` instead.
|
||||
|
||||
- Two build helpers in `singularity-tools`, i.e., `mkLayer` and `shellScript`, are deprecated, as they are no longer involved in image-building. Maintainers will remove them in future releases.
|
||||
|
||||
@@ -132,6 +132,8 @@ in
|
||||
}
|
||||
|
||||
(lib.mkIf cfg.enable {
|
||||
users.groups.resolvconf = {};
|
||||
|
||||
networking.resolvconf.package = pkgs.openresolv;
|
||||
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
@@ -143,12 +145,13 @@ in
|
||||
wants = [ "network-pre.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
restartTriggers = [ config.environment.etc."resolvconf.conf".source ];
|
||||
serviceConfig.RemainAfterExit = true;
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = "${cfg.package}/bin/resolvconf -u";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
${lib.getExe cfg.package} -u
|
||||
chgrp -R resolvconf /etc/resolv.conf /run/resolvconf
|
||||
chmod -R g=u /etc/resolv.conf /run/resolvconf
|
||||
'';
|
||||
};
|
||||
|
||||
})
|
||||
|
||||
@@ -10,7 +10,7 @@ let
|
||||
enableDHCP = config.networking.dhcpcd.enable &&
|
||||
(config.networking.useDHCP || lib.any (i: i.useDHCP == true) interfaces);
|
||||
|
||||
enableNTPService = (config.services.ntp.enable || config.services.ntpd-rs.enable || config.services.openntpd.enable || config.services.chrony.enable);
|
||||
useResolvConf = config.networking.resolvconf.enable;
|
||||
|
||||
# Don't start dhcpcd on explicitly configured interfaces or on
|
||||
# interfaces that are part of a bridge, bond or sit device.
|
||||
@@ -88,23 +88,6 @@ let
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
|
||||
exitHook = pkgs.writeText "dhcpcd.exit-hook" ''
|
||||
${lib.optionalString enableNTPService ''
|
||||
if [ "$reason" = BOUND -o "$reason" = REBOOT ]; then
|
||||
# Restart ntpd. We need to restart it to make sure that it will actually do something:
|
||||
# if ntpd cannot resolve the server hostnames in its config file, then it will never do
|
||||
# anything ever again ("couldn't resolve ..., giving up on it"), so we silently lose
|
||||
# time synchronisation. This also applies to openntpd.
|
||||
${lib.optionalString config.services.ntp.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart ntpd.service || true"}
|
||||
${lib.optionalString config.services.ntpd-rs.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart ntpd-rs.service || true"}
|
||||
${lib.optionalString config.services.openntpd.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart openntpd.service || true"}
|
||||
${lib.optionalString config.services.chrony.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart chronyd.service || true"}
|
||||
fi
|
||||
''}
|
||||
|
||||
${cfg.runHook}
|
||||
'';
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
@@ -181,6 +164,19 @@ in
|
||||
description = ''
|
||||
Shell code that will be run after all other hooks. See
|
||||
`man dhcpcd-run-hooks` for details on what is possible.
|
||||
|
||||
::: {.note}
|
||||
To use sudo or similar tools in your script you may have to set:
|
||||
|
||||
systemd.services.dhcpcd.serviceConfig.NoNewPrivileges = false;
|
||||
|
||||
In addition, as most of the filesystem is inaccessible to dhcpcd
|
||||
by default, you may want to define some exceptions, e.g.
|
||||
|
||||
systemd.services.dhcpcd.serviceConfig.ReadOnlyPaths = [
|
||||
"/run/user/1000/bus" # to send desktop notifications
|
||||
];
|
||||
:::
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -206,22 +202,6 @@ in
|
||||
|
||||
config = lib.mkIf enableDHCP {
|
||||
|
||||
assertions = [ {
|
||||
# dhcpcd doesn't start properly with malloc ∉ [ libc scudo ]
|
||||
# see https://github.com/NixOS/nixpkgs/issues/151696
|
||||
assertion =
|
||||
dhcpcd.enablePrivSep
|
||||
-> lib.elem config.environment.memoryAllocator.provider [ "libc" "scudo" ];
|
||||
message = ''
|
||||
dhcpcd with privilege separation is incompatible with chosen system malloc.
|
||||
Currently only the `libc` and `scudo` allocators are known to work.
|
||||
To disable dhcpcd's privilege separation, overlay Nixpkgs and override dhcpcd
|
||||
to set `enablePrivSep = false`.
|
||||
'';
|
||||
} ];
|
||||
|
||||
environment.etc."dhcpcd.conf".source = dhcpcdConf;
|
||||
|
||||
systemd.services.dhcpcd = let
|
||||
cfgN = config.networking;
|
||||
hasDefaultGatewaySet = (cfgN.defaultGateway != null && cfgN.defaultGateway.address != "")
|
||||
@@ -233,7 +213,7 @@ in
|
||||
wants = [ "network.target" ];
|
||||
before = [ "network-online.target" ];
|
||||
|
||||
restartTriggers = lib.optional (enableNTPService || cfg.runHook != "") [ exitHook ];
|
||||
restartTriggers = [ cfg.runHook ];
|
||||
|
||||
# Stopping dhcpcd during a reconfiguration is undesirable
|
||||
# because it brings down the network interfaces configured by
|
||||
@@ -247,13 +227,62 @@ in
|
||||
serviceConfig =
|
||||
{ Type = "forking";
|
||||
PIDFile = "/run/dhcpcd/pid";
|
||||
SupplementaryGroups = lib.optional useResolvConf "resolvconf";
|
||||
User = "dhcpcd";
|
||||
Group = "dhcpcd";
|
||||
StateDirectory = "dhcpcd";
|
||||
RuntimeDirectory = "dhcpcd";
|
||||
|
||||
ExecStartPre = "+${pkgs.writeShellScript "migrate-dhcpcd" ''
|
||||
# migrate from old database directory
|
||||
if test -f /var/db/dhcpcd/duid; then
|
||||
echo 'migrating DHCP leases from /var/db/dhcpcd to /var/lib/dhcpcd ...'
|
||||
mv /var/db/dhcpcd/* -t /var/lib/dhcpcd
|
||||
chown dhcpcd:dhcpcd /var/lib/dhcpcd/*
|
||||
rmdir /var/db/dhcpcd || true
|
||||
echo done
|
||||
fi
|
||||
''}";
|
||||
|
||||
ExecStart = "@${dhcpcd}/sbin/dhcpcd dhcpcd --quiet ${lib.optionalString cfg.persistent "--persistent"} --config ${dhcpcdConf}";
|
||||
ExecReload = "${dhcpcd}/sbin/dhcpcd --rebind";
|
||||
Restart = "always";
|
||||
AmbientCapabilities = [ "CAP_NET_ADMIN" "CAP_NET_RAW" "CAP_NET_BIND_SERVICE" ];
|
||||
ReadWritePaths = [ "/proc/sys/net/ipv6" ]
|
||||
++ lib.optionals useResolvConf [ "/etc/resolv.conf" "/run/resolvconf" ];
|
||||
DeviceAllow = "";
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
NoNewPrivileges = lib.mkDefault true; # may be disabled for sudo in runHook
|
||||
PrivateDevices = true;
|
||||
PrivateMounts = true;
|
||||
PrivateTmp = true;
|
||||
PrivateUsers = false;
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = "tmpfs"; # allow exceptions to be added to ReadOnlyPaths, etc.
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectSystem = "strict";
|
||||
RemoveIPC = true;
|
||||
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" "AF_PACKET" ];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
"~@aio" "~@chown" "~@keyring" "~@memlock"
|
||||
];
|
||||
SystemCallArchitectures = "native";
|
||||
};
|
||||
};
|
||||
|
||||
# Note: the service could run with `DynamicUser`, however that makes
|
||||
# impossible (for no good reason, see systemd issue #20495) to disable
|
||||
# `NoNewPrivileges` or `ProtectHome`, which users may want to in order
|
||||
# to run certain scripts in `networking.dhcpcd.runHook`.
|
||||
users.users.dhcpcd = {
|
||||
isSystemUser = true;
|
||||
group = "dhcpcd";
|
||||
@@ -262,9 +291,7 @@ in
|
||||
|
||||
environment.systemPackages = [ dhcpcd ];
|
||||
|
||||
environment.etc."dhcpcd.exit-hook" = lib.mkIf (enableNTPService || cfg.runHook != "") {
|
||||
source = exitHook;
|
||||
};
|
||||
environment.etc."dhcpcd.exit-hook".text = cfg.runHook;
|
||||
|
||||
powerManagement.resumeCommands = lib.mkIf config.systemd.services.dhcpcd.enable
|
||||
''
|
||||
|
||||
@@ -13,8 +13,6 @@ import ./make-test-python.nix ({ lib, ... }:
|
||||
specialisation.hardened.configuration = {
|
||||
services.chrony.enableMemoryLocking = true;
|
||||
environment.memoryAllocator.provider = "graphene-hardened";
|
||||
# dhcpcd privsep is incompatible with graphene-hardened
|
||||
networking.useNetworkd = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -11,11 +11,6 @@ import ./make-test-python.nix ({ pkgs, ... } : {
|
||||
imports = [ ../modules/profiles/hardened.nix ];
|
||||
environment.memoryAllocator.provider = "graphene-hardened";
|
||||
nix.settings.sandbox = false;
|
||||
nixpkgs.overlays = [
|
||||
(final: super: {
|
||||
dhcpcd = super.dhcpcd.override { enablePrivSep = false; };
|
||||
})
|
||||
];
|
||||
virtualisation.emptyDiskImages = [ 4096 ];
|
||||
boot.initrd.postDeviceCommands = ''
|
||||
${pkgs.dosfstools}/bin/mkfs.vfat -n EFISYS /dev/vdb
|
||||
|
||||
@@ -132,6 +132,10 @@ let
|
||||
client.wait_until_succeeds("ip addr show dev enp2s0 | grep -q '192.168.2'")
|
||||
client.wait_until_succeeds("ip addr show dev enp2s0 | grep -q 'fd00:1234:5678:2:'")
|
||||
|
||||
with subtest("Wait until we have received the nameservers"):
|
||||
client.wait_until_succeeds("grep -q 2001:db8::1 /etc/resolv.conf")
|
||||
client.wait_until_succeeds("grep -q 192.168.2.1 /etc/resolv.conf")
|
||||
|
||||
with subtest("Test vlan 1"):
|
||||
client.wait_until_succeeds("ping -c 1 192.168.1.1")
|
||||
client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
|
||||
|
||||
@@ -121,6 +121,7 @@ let
|
||||
static.wait_for_unit("NetworkManager.service")
|
||||
|
||||
dynamic.wait_until_succeeds("cat /etc/resolv.conf | grep -q '192.168.1.1'")
|
||||
dynamic.wait_until_succeeds("cat /etc/resolv.conf | grep -q '2001:db8::1'")
|
||||
static.wait_until_succeeds("cat /etc/resolv.conf | grep -q '10.10.10.10'")
|
||||
static.wait_until_fails("cat /etc/resolv.conf | grep -q '192.168.1.1'")
|
||||
'';
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
AdvSendAdvert on;
|
||||
AdvManagedFlag on;
|
||||
AdvOtherConfigFlag on;
|
||||
RDNSS 2001:db8::1 {};
|
||||
|
||||
prefix fd00:1234:5678:${toString n}::/64 {
|
||||
AdvAutonomous off;
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
, runtimeShellPackage
|
||||
, runtimeShell
|
||||
, nixosTests
|
||||
, enablePrivSep ? true
|
||||
}:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
@@ -38,30 +37,21 @@ stdenv.mkDerivation rec {
|
||||
configureFlags = [
|
||||
"--sysconfdir=/etc"
|
||||
"--localstatedir=/var"
|
||||
]
|
||||
++ (
|
||||
if ! enablePrivSep
|
||||
then [ "--disable-privsep" ]
|
||||
else [
|
||||
"--enable-privsep"
|
||||
# dhcpcd disables privsep if it can't find the default user,
|
||||
# so we explicitly specify a user.
|
||||
"--privsepuser=dhcpcd"
|
||||
]
|
||||
);
|
||||
"--disable-privsep"
|
||||
"--dbdir=/var/lib/dhcpcd"
|
||||
];
|
||||
|
||||
makeFlags = [ "PREFIX=${placeholder "out"}" ];
|
||||
|
||||
# Hack to make installation succeed. dhcpcd will still use /var/db
|
||||
# Hack to make installation succeed. dhcpcd will still use /var/lib
|
||||
# at runtime.
|
||||
installFlags = [ "DBDIR=$(TMPDIR)/db" "SYSCONFDIR=${placeholder "out"}/etc" ];
|
||||
|
||||
# Check that the udev plugin got built.
|
||||
postInstall = lib.optionalString (udev != null && stdenv.isLinux) "[ -e ${placeholder "out"}/lib/dhcpcd/dev/udev.so ]";
|
||||
|
||||
passthru = {
|
||||
inherit enablePrivSep;
|
||||
tests = { inherit (nixosTests.networking.scripted) macvlan dhcpSimple dhcpOneIf; };
|
||||
passthru.tests = {
|
||||
inherit (nixosTests.networking.scripted) macvlan dhcpSimple dhcpOneIf;
|
||||
};
|
||||
|
||||
meta = with lib; {
|
||||
|
||||
Reference in New Issue
Block a user