NetworkManager: run as user instead of root

This commit is contained in:
Colin 2024-05-29 07:24:12 +00:00
parent fb7bcbb5f5
commit 1ee21c4795
5 changed files with 86 additions and 27 deletions

View File

@ -4,6 +4,9 @@
{ ... }:
{
# partially supported in nixpkgs <repo:nixos/nixpkgs:nixos/modules/misc/ids.nix>
sane.ids.networkmanager.uid = 57; #< nixpkgs unofficially reserves this, to match networkmanager's gid
# legacy servo users, some are inconvenient to migrate
sane.ids.dhcpcd.gid = 991;
sane.ids.dhcpcd.uid = 992;

View File

@ -508,9 +508,8 @@ in
ethtool.sandbox.capabilities = [ "net_admin" ];
# eza `ls` replacement
# landlock is OK, only `whitelistPwd` doesn't make the intermediate symlinks traversable, so it breaks on e.g. ~/Videos/servo/Shows/foo
# eza.sandbox.method = "landlock";
eza.sandbox.method = "bwrap";
eza.sandbox.method = "bwrap"; #< note that bwrap causes `/proc` files to be listed differently (e.g. `eza /proc/sys/net/ipv6/conf/`)
eza.sandbox.autodetectCliPaths = "existing";
eza.sandbox.whitelistPwd = true;
eza.sandbox.extraHomePaths = [

View File

@ -12,15 +12,26 @@ in
config = lib.mkMerge [
{
sane.programs.networkmanager = {
packageUnwrapped = pkgs.networkmanager.overrideAttrs (upstream: {
postPatch = (upstream.postPatch or "") + ''
substituteInPlace src/{core/org.freedesktop.NetworkManager,nm-dispatcher/nm-dispatcher}.conf --replace-fail \
'user="root"' 'user="networkmanager"'
'';
# remove unused services to prevent any unexpected interactions
postFixup = (upstream.postFixup or "") + ''
rm $out/etc/systemd/system/{nm-cloud-setup.service,nm-cloud-setup.timer,nm-priv-helper.service}
'';
});
suggestedPrograms = [ "wpa_supplicant" ];
enableFor.system = lib.mkIf (builtins.any (en: en) (builtins.attrValues cfg.enableFor.user)) true;
# TODO: this contains both the NetworkManager service and the NetworkManager-dispatcher service
# this contains both the NetworkManager service and the NetworkManager-dispatcher service
# the latter of which calls a lot of user code.
# as a result, this needs all the perms which my hook in modules/services/trust-dns/trust-dns-nmhook needs
# as a result, this needs all the perms which my hook in modules/services/trust-dns/trust-dns-nmhook needs.
sandbox.method = "landlock";
sandbox.capabilities = [
"dac_override" #< TODO: remove this! it's needed so that trust-dns-nmhook can write to trust-dns's state; instead i should add networkmanager to the trust-dns group.
# "dac_override"
"net_admin"
"net_raw"
"net_bind_service" #< TODO: is this needed? why? (DNS?)
@ -41,7 +52,7 @@ in
"/var/lib/trust-dns" #< for trust-dns-nmhook
];
sandbox.whitelistDbus = [ "system" ]; #< apparently not actually needed?
sandbox.whitelistDbus = [ "system" ];
};
}
@ -53,34 +64,41 @@ in
aliases = [ "dbus-org.freedesktop.NetworkManager.service" ];
path = [ "/run/current-system/sw" ]; #< so it can find `sanebox`
serviceConfig = {
ExecStartPre = [
"${pkgs.coreutils}/bin/mkdir -p /run/NetworkManager"
];
StateDirectory = "NetworkManager";
StateDirectoryMode = 755; # not sure if this really needs to be 755
};
serviceConfig.RuntimeDirectory = "NetworkManager"; #< tells systemd to create /run/NetworkManager
serviceConfig.StateDirectory = "NetworkManager"; #< tells systemd to create /var/lib/NetworkManager
serviceConfig.User = "networkmanager";
serviceConfig.Group = "networkmanager";
serviceConfig.AmbientCapabilities = [
# "CAP_DAC_OVERRIDE"
"CAP_NET_ADMIN"
"CAP_NET_RAW"
"CAP_NET_BIND_SERVICE" #< TODO: is this needed? why? (DNS?)
# "CAP_SYS_MODULE"
"CAP_AUDIT_WRITE" #< allow writing to the audit log
# "CAP_KILL"
];
};
systemd.services.NetworkManager-wait-online = {
path = [ "/run/current-system/sw" ]; #< so `nm-online` can find `sanebox`
wantedBy = [ "network-online.target" ];
serviceConfig.User = "networkmanager";
serviceConfig.Group = "networkmanager";
};
systemd.services.NetworkManager-dispatcher = {
wantedBy = [ "NetworkManager.service" ];
after = [ "trust-dns-localhost.service" ]; #< so that /var/lib/trust-dns will exist
path = [ "/run/current-system/sw" ]; #< so it can find `sanebox`
# to debug, add NM_DISPATCHER_DEBUG_LOG=1
serviceConfig.ExecStart = [
"" # first blank line is to clear the upstream `ExecStart` field.
"${cfg.package}/libexec/nm-dispatcher --persist" # --persist is needed for it to actually run as a daemon
];
serviceConfig.ExecStartPre = [
# TODO: establish the trust-dns dependency more idiomatically than this
"${pkgs.coreutils}/bin/mkdir -p /var/lib/trust-dns && chown trust-dns:trust-dns /var/lib/trust-dns"
];
serviceConfig.Restart = "always";
serviceConfig.RestartSec = "1s";
serviceConfig.User = "networkmanager";
serviceConfig.Group = "networkmanager";
};
environment.etc = {
@ -125,24 +143,34 @@ in
# debug=... (see also: NM_DEBUG env var)
'';
};
hardware.wirelessRegulatoryDatabase = true;
networking.useDHCP = false;
users.groups.networkmanager.gid = config.ids.gids.networkmanager;
services.udev.packages = [ cfg.package ];
security.polkit.enable = true;
security.polkit.enable = lib.mkDefault true;
# allow networkmanager unbounded control over modemmanager.
# i believe this was sourced from the default nixpkgs config.
security.polkit.extraConfig = ''
polkit.addRule(function(action, subject) {
if (
subject.isInGroup("networkmanager")
&& (action.id.indexOf("org.freedesktop.NetworkManager.") == 0
|| action.id.indexOf("org.freedesktop.ModemManager") == 0
))
{ return polkit.Result.YES; }
if (subject.isInGroup("networkmanager")
&& (
action.id.indexOf("org.freedesktop.NetworkManager.") == 0
|| action.id.indexOf("org.freedesktop.ModemManager") == 0
)
) {
return polkit.Result.YES;
}
});
'';
users.groups.networkmanager.gid = config.ids.gids.networkmanager;
users.users.networkmanager = {
isSystemUser = true;
group = "networkmanager";
extraGroups = [ "trust-dns" ];
};
boot.kernelModules = [ "ctr" ]; #< TODO: needed (what even is this)?
# TODO: polkit?
# TODO: NetworkManager-ensure-profiles?
})
];

View File

@ -7,6 +7,10 @@ in
{
sane.programs.wpa_supplicant = {
packageUnwrapped = pkgs.wpa_supplicant.overrideAttrs (upstream: {
postPatch = (upstream.postPatch or "") + ''
substituteInPlace wpa_supplicant/dbus/dbus-wpa_supplicant.conf --replace-fail \
'user="root"' 'user="networkmanager"'
'';
# nixpkgs wpa_supplicant generates a dbus file which has a path like
# /nix/store/abc-wpa_supplicant/nix/store/abc-wpa_supplicant/sbin/...
# upstreaming status: <https://github.com/NixOS/nixpkgs/pull/315346>
@ -15,6 +19,7 @@ in
"$out$out" "$out"
'';
});
# sandbox.enable = false; #< TODO: re-enable
sandbox.method = "landlock"; #< 'bwrap' (likely) can't work, because it needs to manipulate net interfaces in the root namespace
sandbox.capabilities = [
# see also: <https://github.com/NixOS/nixpkgs/pull/305722>
@ -33,7 +38,15 @@ in
(lib.mkIf cfg.enabled {
services.udev.packages = [ cfg.package ];
systemd.packages = [ cfg.package ]; #< needs to be on systemd.packages so we get its service file
systemd.services.wpa_supplicant.path = [ "/run/current-system/sw" ]; #< so it can find `sanebox`
systemd.services.wpa_supplicant = {
path = [ "/run/current-system/sw" ]; #< so it can find `sanebox`
serviceConfig.User = "networkmanager";
serviceConfig.Group = "networkmanager";
serviceConfig.AmbientCapabilities = [
"CAP_NET_ADMIN"
"CAP_NET_RAW"
];
};
# systemd.services.wpa_supplicant = {
# aliases = [ "dbus-fi.w1.wpa_supplicant1.service" ];
# before = [ "network.target" ];

View File

@ -132,6 +132,9 @@ let
# this might not exist on other systems,
# so just bind the deepest path which is guaranteed to exist.
ReadOnlyPaths = [ "/var/lib" ];
} // lib.optionalAttrs cfg.asSystemResolver {
# allow the group to write trust-dns state (needed by NetworkManager hook)
StateDirectoryMode = "775";
};
};
in
@ -214,10 +217,23 @@ in
)
];
# run a hook whenever networking details change, so the DNS zone can be updated to reflect this
environment.etc."NetworkManager/dispatcher.d/60-trust-dns-nmhook" = lib.mkIf cfg.asSystemResolver {
source = "${trust-dns-nmhook}/bin/trust-dns-nmhook";
};
# allow NetworkManager (via trust-dns-nmhook) to restart trust-dns when necessary
# - source: <https://stackoverflow.com/questions/61480914/using-policykit-to-allow-non-root-users-to-start-and-stop-a-service>
security.polkit.extraConfig = lib.mkIf cfg.asSystemResolver ''
polkit.addRule(function(action, subject) {
if (subject.isInGroup("trust-dns") &&
action.id == "org.freedesktop.systemd1.manage-units" &&
action.lookup("unit") == "trust-dns-localhost.service") {
return polkit.Result.YES;
}
});
'';
sane.services.trust-dns.instances.localhost = lib.mkIf cfg.asSystemResolver {
listenAddrsIpv4 = [ "127.0.0.1" ];
listenAddrsIpv6 = [ "::1" ];