hosts: networking: switch to using nixos NetworkManager/ModemManager/etc, just patched for hardening

This commit is contained in:
Colin 2024-06-02 09:48:17 +00:00
parent 1b85aa0441
commit e4bcbab224
10 changed files with 233 additions and 406 deletions

View File

@ -4,6 +4,8 @@
imports = [
./dns.nix
./hostnames.nix
./modemmanager.nix
./networkmanager.nix
./upnp.nix
./vpn.nix
];
@ -57,7 +59,8 @@
#
# i don't use these, and notably they drag in huge dependency sets and don't cross compile well.
# e.g. openconnect drags in webkitgtk (for SSO)!
networking.networkmanager.plugins = lib.mkForce [];
# networking.networkmanager.plugins = lib.mkForce [];
networking.networkmanager.enableDefaultPlugins = false;
# keyfile.path = where networkmanager should look for connection credentials
networking.networkmanager.settings.keyfile.path = "/var/lib/NetworkManager/system-connections";

View File

@ -0,0 +1,60 @@
{ config, lib, pkgs, ... }:
{
networking.modemmanager.package = pkgs.modemmanager-split.daemon.overrideAttrs (upstream: {
# patch to allow the dbus endpoints to be owned by networkmanager user
postInstall = (upstream.postInstall or "") + ''
substitute $out/share/dbus-1/system.d/org.freedesktop.ModemManager1.conf \
$out/share/dbus-1/system.d/networkmanager-org.freedesktop.ModemManager1.conf \
--replace-fail 'user="root"' 'group="networkmanager"'
'';
});
systemd.services.ModemManager = {
# aliases = [ "dbus-org.freedesktop.ModemManager1.service" ];
# after = [ "polkit.service" ];
# requires = [ "polkit.service" ];
wantedBy = [ "network.target" ]; #< default is `multi-user.target`, somehow it doesn't auto-start with that...
# path = [ "/run/current-system/sw" ]; #< so it can find `sanebox`
# serviceConfig.Type = "dbus";
# serviceConfig.BusName = "org.freedesktop.ModemManager1";
# only if started with `--debug` does mmcli let us issue AT commands like
# `mmcli --modem any --command=<AT_CMD>`
serviceConfig.ExecStart = [
"" # first blank line is to clear the upstream `ExecStart` field.
"${lib.getExe' config.networking.modemmanager.package "ModemManager"} --debug"
];
# --debug sets DEBUG level logging: so reset
serviceConfig.ExecStartPost = "${lib.getExe config.sane.programs.mmcli.package} --set-logging=INFO";
# v this is what upstream ships
# serviceConfig.Restart = "on-abort";
# serviceConfig.StandardError = "null";
# serviceConfig.CapabilityBoundingSet = "CAP_SYS_ADMIN CAP_NET_ADMIN";
# serviceConfig.ProtectSystem = true;
# serviceConfig.ProtectHome = true;
# serviceConfig.PrivateTmp = true;
# serviceConfig.RestrictAddressFamilies = "AF_NETLINK AF_UNIX AF_QIPCRTR";
# serviceConfig.NoNewPrivileges = true;
# TODO: sandbox more aggressively
# - CAP_NET_ADMIN *only*?
# it needs these paths:
# - # "/"
# - "/dev" #v modem-power + net are not enough
# - # "/dev/modem-power"
# - # "/dev/net"
# - "/proc"
# - # /run #v can likely be reduced more
# - "/run/dbus"
# - "/run/NetworkManager"
# - "/run/resolvconf"
# - "/run/systemd"
# - "/run/udev"
# - "/sys"
};
# so that ModemManager can discover when the modem appears
# services.udev.packages = lib.mkIf cfg.enabled [ cfg.package ];
}

View File

@ -0,0 +1,166 @@
{ config, pkgs, ... }:
let
networkmanager = pkgs.networkmanager.overrideAttrs (upstream: {
src = pkgs.fetchFromGitea {
domain = "git.uninsane.org";
owner = "colin";
repo = "NetworkManager";
# patched to fix polkit permissions (with `nmcli`) when NetworkManager runs as user networkmanager
rev = "dev-sane-1.46.0";
hash = "sha256-S5ZiOfCpwtVVVO+DP6OPodJqzSc/LW4waI42DRkT+RA=";
};
# patches = [];
});
# split the package into `daemon` and `nmcli` outputs, because the networkmanager *service*
# doesn't need `nmcli`/`nmtui` tooling
networkmanager-split = pkgs.networkmanager-split.override { inherit networkmanager; };
in {
networking.networkmanager.enable = true;
networking.networkmanager.enableDefaultPlugins = false;
networking.networkmanager.package = networkmanager-split.daemon.overrideAttrs (upstream: {
# postPatch = (upstream.postPatch or "") + ''
# substituteInPlace src/{core/org.freedesktop.NetworkManager,nm-dispatcher/nm-dispatcher}.conf --replace-fail \
# 'user="root"' 'user="networkmanager"'
# '';
postInstall = (upstream.postInstall or "") + ''
# allow the bus to owned by either root or networkmanager users
# use the group here, that way ordinary users can be elevated to control networkmanager
# (via e.g. `nmcli`)
for f in org.freedesktop.NetworkManager.conf nm-dispatcher.conf ; do
substitute $out/share/dbus-1/system.d/$f \
$out/share/dbus-1/system.d/networkmanager-$f \
--replace-fail 'user="root"' 'group="networkmanager"'
done
# remove unused services to prevent any unexpected interactions
rm $out/etc/systemd/system/{nm-cloud-setup.service,nm-cloud-setup.timer,nm-priv-helper.service}
'';
});
# fixup the services to run as `networkmanager` and with less permissions
systemd.services.NetworkManager = {
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" #< this *does* seem to be necessary, though i don't understand why. DHCP?
# "CAP_SYS_MODULE"
"CAP_AUDIT_WRITE" #< allow writing to the audit log
# "CAP_KILL"
];
# TODO: it needs these directories:
# - "/proc/net"
# - "/proc/sys/net"
# - "/run/NetworkManager"
# - "/run/systemd" # for trust-dns-nmhook
# - "/run/udev"
# - # "/run/wg-home.priv"
# - "/sys/class" #< TODO: specify this more precisely
# - "/sys/devices"
# - "/var/lib/NetworkManager"
# - "/var/lib/trust-dns" #< for trust-dns-nmhook
# - "/run/systemd"
};
systemd.services.NetworkManager-wait-online = {
serviceConfig.User = "networkmanager";
serviceConfig.Group = "networkmanager";
};
# fix NetworkManager-dispatcher to actually run as a daemon,
# and sandbox it a bit
systemd.services.NetworkManager-dispatcher = {
after = [ "trust-dns-localhost.service" ]; #< so that /var/lib/trust-dns will exist
# 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.Restart = "always";
# serviceConfig.RestartSec = "1s";
serviceConfig.User = "networkmanager";
serviceConfig.Group = "networkmanager";
# TODO: it needs access only to the above mentioned directories
};
# harden wpa_supplicant (used by NetworkManager)
systemd.services.wpa_supplicant = {
serviceConfig.User = "networkmanager";
serviceConfig.Group = "networkmanager";
serviceConfig.AmbientCapabilities = [
"CAP_NET_ADMIN"
"CAP_NET_RAW"
];
# TODO: it needs only these paths:
# - "/dev/net"
# - "/dev/rfkill"
# - "/proc/sys/net"
# - "/sys/class/net"
# - "/sys/devices"
# - "/run/systemd"
};
networking.networkmanager.settings = {
# wifi.backend = "wpa_supplicant";
# wifi.scan-rand-mac-address = true;
# logging.audit = false;
logging.level = "INFO";
# main.dhcp = "internal";
main.dns = if config.services.resolved.enable then
"systemd-resolved"
else if config.sane.services.trust-dns.enable && config.sane.services.trust-dns.asSystemResolver then
"none"
else
"internal"
;
main.systemd-resolved = false;
};
environment.etc."NetworkManager/system-connections".source = "/var/lib/NetworkManager/system-connections";
# allow networkmanager to control systemd-resolved,
# which it needs to do to apply new DNS settings when using systemd-resolved.
security.polkit.extraConfig = ''
polkit.addRule(function(action, subject) {
if (subject.isInGroup("networkmanager") && action.id.indexOf("org.freedesktop.resolve1.") == 0) {
return polkit.Result.YES;
}
});
'';
users.users.networkmanager = {
isSystemUser = true;
group = "networkmanager";
extraGroups = [ "trust-dns" ];
};
# there is, unfortunately, no proper interface by which to plumb wpa_supplicant into the NixOS service, except by overlay.
nixpkgs.overlays = [(self: super: {
wpa_supplicant = super.wpa_supplicant.overrideAttrs (upstream: {
# postPatch = (upstream.postPatch or "") + ''
# substituteInPlace wpa_supplicant/dbus/dbus-wpa_supplicant.conf --replace-fail \
# 'user="root"' 'user="networkmanager"'
# '';
postInstall = (upstream.postInstall or "") + ''
substitute $out/share/dbus-1/system.d/dbus-wpa_supplicant.conf \
$out/share/dbus-1/system.d/networkmanager-wpa_supplicant.conf \
--replace-fail 'user="root"' 'group="networkmanager"'
'';
postFixup = (upstream.postFixup or "") + ''
# 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>
substituteInPlace $out/share/dbus-1/system-services/* --replace-fail \
"$out$out" "$out"
# remove unused services to avoid unexpected interactions
rm $out/etc/systemd/system/{wpa_supplicant-nl80211@,wpa_supplicant-wired@,wpa_supplicant@}.service
'';
});
})];
}

View File

@ -71,12 +71,14 @@ in
"man-pages"
"man-pages-posix"
# "miniupnpc"
"mmcli"
"nano"
# "ncdu" # ncurses disk usage. doesn't cross compile (zig)
"neovim"
"netcat"
"nethogs"
"nmap"
"nmcli"
"nvme-cli" # nvme
# "openssl"
"parted"

View File

@ -81,13 +81,11 @@
./mepo.nix
./mimeo
./mmcli.nix
./modemmanager.nix
./mopidy.nix
./mpv
./msmtp.nix
./nautilus.nix
./neovim.nix
./networkmanager.nix
./newsflash.nix
./nheko.nix
./nicotine-plus.nix
@ -144,7 +142,6 @@
./wine.nix
./wireplumber.nix
./wireshark.nix
./wpa_supplicant.nix
./wvkbd.nix
./xarchiver.nix
./xdg-desktop-portal.nix

View File

@ -4,7 +4,7 @@ let
in
{
sane.programs.eg25-control = {
suggestedPrograms = [ "modemmanager" ];
suggestedPrograms = [ "mmcli" ];
services.eg25-control-powered = {
description = "eg25-control-powered: power to the Qualcomm eg25 modem used by PinePhone";

View File

@ -1,90 +0,0 @@
{ config, lib, pkgs, ... }:
let
cfg = config.sane.programs.modemmanager;
in
{
sane.programs.modemmanager = {
packageUnwrapped = pkgs.modemmanager-split.daemon.overrideAttrs (upstream: {
postInstall = (upstream.postInstall or "") + ''
substitute $out/share/dbus-1/system.d/org.freedesktop.ModemManager1.conf \
$out/share/dbus-1/system.d/networkmanager-org.freedesktop.ModemManager1.conf \
--replace-fail 'user="root"' 'group="networkmanager"'
'';
meta = upstream.meta // {
mainProgram = "ModemManager";
};
});
# mmcli needs /run/current-system/sw/share/dbus-1 files to function
enableFor.system = lib.mkIf (builtins.any (en: en) (builtins.attrValues cfg.enableFor.user)) true;
suggestedPrograms = [ "mmcli" ];
# bwrap sandboxing works, but requires the real user to be root.
# landlock sandboxing works, and allows the real user to be someone else (like `networkmanager`).
# non-root is very important, because of how many things in e.g. /dev are r/w based on uid=0.
# TODO: have the services run as `networkmanager` user
sandbox.method = "bwrap";
# sandbox.method = "landlock";
sandbox.wrapperType = "inplace"; #< .pc files, GIR files with absolute paths,
sandbox.net = "all"; #< needed for modem bringup
# sandbox.isolatePids = false;
sandbox.capabilities = [
"net_admin"
"net_raw"
];
sandbox.extraPaths = lib.warn "TODO: modemmanager: sandbox more aggressively" [
# "/"
"/dev" #v modem-power + net are not enough
# "/dev/modem-power"
# "/dev/net"
"/proc"
# /run #v can likely be reduced more
"/run/dbus"
"/run/NetworkManager"
"/run/resolvconf"
"/run/systemd"
"/run/udev"
"/sys"
# "/var"
];
};
networking.modemmanager = {
enable = cfg.enabled;
package = cfg.package;
};
systemd.services.ModemManager = lib.mkIf cfg.enabled {
# aliases = [ "dbus-org.freedesktop.ModemManager1.service" ];
# after = [ "polkit.service" ];
# requires = [ "polkit.service" ];
# wantedBy = [ "network.target" ];
path = [ "/run/current-system/sw" ]; #< so it can find `sanebox`
# serviceConfig.Type = "dbus";
# serviceConfig.BusName = "org.freedesktop.ModemManager1";
# only if started with `--debug` does mmcli let us issue AT commands like
# `mmcli --modem any --command=<AT_CMD>`
serviceConfig.ExecStart = [
"" # first blank line is to clear the upstream `ExecStart` field.
"${lib.getExe cfg.package} --debug"
];
# --debug sets DEBUG level logging: so reset
serviceConfig.ExecStartPost = "${lib.getExe config.sane.programs.mmcli.package} --set-logging=INFO";
# v this is what upstream ships
# serviceConfig.Restart = "on-abort";
# serviceConfig.StandardError = "null";
# serviceConfig.CapabilityBoundingSet = "CAP_SYS_ADMIN CAP_NET_ADMIN";
# serviceConfig.ProtectSystem = true;
# serviceConfig.ProtectHome = true;
# serviceConfig.PrivateTmp = true;
# serviceConfig.RestrictAddressFamilies = "AF_NETLINK AF_UNIX AF_QIPCRTR";
# serviceConfig.NoNewPrivileges = true;
};
# so that ModemManager can discover when the modem appears
# services.udev.packages = lib.mkIf cfg.enabled [ cfg.package ];
}

View File

@ -1,230 +0,0 @@
# Network Manager:
# i manage this myself because the nixos service is not flexible enough.
# - it unconditionally puts modemmanager onto the system path, preventing me from patching modemmanager's service file (without an overlay).
#
# XXX: it's normal to see error messages on an ethernet-only host, even when using nixos' official networkmanager service:
# - `Couldn't initialize supplicant interface: Failed to D-Bus activate wpa_supplicant service`
{ config, lib, pkgs, ... }:
let
cfg = config.sane.programs.networkmanager;
networkmanager = pkgs.networkmanager.overrideAttrs (upstream: {
src = pkgs.fetchFromGitea {
domain = "git.uninsane.org";
owner = "colin";
repo = "NetworkManager";
rev = "dev-sane-1.46.0";
hash = "sha256-S5ZiOfCpwtVVVO+DP6OPodJqzSc/LW4waI42DRkT+RA=";
};
# patches = [];
});
networkmanager-split = pkgs.networkmanager-split.override { inherit networkmanager; };
in
{
config = lib.mkMerge [
{
sane.programs.networkmanager = {
packageUnwrapped = networkmanager-split.daemon.overrideAttrs (upstream: {
# postPatch = (upstream.postPatch or "") + ''
# substituteInPlace src/{core/org.freedesktop.NetworkManager,nm-dispatcher/nm-dispatcher}.conf --replace-fail \
# 'user="root"' 'user="networkmanager"'
# '';
postInstall = (upstream.postInstall or "") + ''
# allow the bus to owned by either root or networkmanager users
# use the group here, that way ordinary users can be elevated to control networkmanager
# (via e.g. `nmcli`)
for f in org.freedesktop.NetworkManager.conf nm-dispatcher.conf ; do
substitute $out/share/dbus-1/system.d/$f \
$out/share/dbus-1/system.d/networkmanager-$f \
--replace-fail 'user="root"' 'group="networkmanager"'
done
# remove unused services to prevent any unexpected interactions
rm $out/etc/systemd/system/{nm-cloud-setup.service,nm-cloud-setup.timer,nm-priv-helper.service}
'';
});
suggestedPrograms = [ "nmcli" "wpa_supplicant" ];
enableFor.system = lib.mkIf (builtins.any (en: en) (builtins.attrValues cfg.enableFor.user)) true;
# 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.
sandbox.method = "landlock";
sandbox.capabilities = [
# "dac_override"
"net_admin"
"net_raw"
"net_bind_service" #< TODO: is this needed? why? (DNS?)
# "sys_module"
"audit_write" #< allow writing to the audit log
# "kill"
];
sandbox.extraPaths = [
"/proc/net"
"/proc/sys/net"
"/run/NetworkManager"
"/run/systemd" # for trust-dns-nmhook
"/run/udev"
# "/run/wg-home.priv"
"/sys/class" #< TODO: specify this more precisely
"/sys/devices"
"/var/lib/NetworkManager"
"/var/lib/trust-dns" #< for trust-dns-nmhook
];
sandbox.whitelistDbus = [ "system" ];
};
}
(lib.mkIf cfg.enabled {
# add to systemd.packages so we get the service file it ships, then override what we need to customize (taken from nixpkgs)
# systemd.packages = [ cfg.package ];
networking.networkmanager.enable = true;
networking.networkmanager.enableDefaultPlugins = false;
networking.networkmanager.package = cfg.package;
systemd.services.NetworkManager = {
# wantedBy = [ "network.target" ];
aliases = [ "dbus-org.freedesktop.NetworkManager.service" ];
path = [ "/run/current-system/sw" ]; #< so it can find `sanebox`
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" #< this *does* seem to be necessary, though i don't understand why. DHCP?
# "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.Restart = "always";
serviceConfig.RestartSec = "1s";
serviceConfig.User = "networkmanager";
serviceConfig.Group = "networkmanager";
};
networking.networkmanager.settings = {
# wifi.backend = "wpa_supplicant";
# wifi.scan-rand-mac-address = true;
# logging.audit = false;
logging.level = "INFO";
# main.dhcp = "internal";
main.dns = if config.services.resolved.enable then
"systemd-resolved"
else if config.sane.services.trust-dns.enable && config.sane.services.trust-dns.asSystemResolver then
"none"
else
"internal"
;
main.systemd-resolved = false;
};
environment.etc = {
"NetworkManager/system-connections".source = "/var/lib/NetworkManager/system-connections";
# "NetworkManager/NetworkManager.conf".text = ''
# [device]
# # wifi.backend: wpa_supplicant or iwd
# wifi.backend=wpa_supplicant
# wifi.scan-rand-mac-address=true
# [logging]
# audit=false
# # level: TRACE, DEBUG, INFO, WARN, ERR, OFF
# level=INFO
# # domain=...
# [main]
# # dhcp:
# # - `internal` (default)
# # - `dhclient` (requires dhclient to be installed)
# # - `dhcpcd` (requires dhcpcd to be installed)
# dhcp=internal
# # dns:
# # - `default`: update /etc/resolv.conf with nameservers provided by the active connection
# # - `none`: NM won't update /etc/resolv.conf
# # - `systemd-resolved`: push DNS config to systemd-resolved
# # - `dnsmasq`: run a local caching nameserver
# dns=${if config.services.resolved.enable then
# "systemd-resolved"
# else if config.sane.services.trust-dns.enable && config.sane.services.trust-dns.asSystemResolver then
# "none"
# else
# "internal"
# }
# plugins=keyfile
# # rc-manager: how NM should write to /etc/resolv.conf
# # - regardless of this setting, NM will write /var/lib/NetworkManager/resolv.conf
# rc-manager=unmanaged
# # systemd-resolved: send DNS config to systemd-resolved?
# # this setting has no effect if dns="systemd-resolved"; it's supplementary, not absolute.
# systemd-resolved=false
# # debug=... (see also: NM_DEBUG env var)
# '';
};
# hardware.wirelessRegulatoryDatabase = true;
# networking.useDHCP = false;
# services.udev.packages = [ cfg.package ];
# security.polkit.enable = lib.mkDefault true;
# security.polkit.extraConfig = lib.concatStringsSep "\n" [
# # allow networkmanager unbounded control over modemmanager.
# # i believe this was sourced from the default nixpkgs config.
# ''
# 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;
# }
# });
# ''
# allow networkmanager to control systemd-resolved,
# which it needs to do to apply new DNS settings when using systemd-resolved.
security.polkit.extraConfig = ''
polkit.addRule(function(action, subject) {
if (subject.isInGroup("networkmanager") && action.id.indexOf("org.freedesktop.resolve1.") == 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: NetworkManager-ensure-profiles?
})
];
}

View File

@ -140,7 +140,6 @@ in
"fontconfig"
# "gnome.gnome-bluetooth" # XXX(2023/05/14): broken
# "gnome.gnome-control-center" # XXX(2023/06/28): depends on webkitgtk4_1
"networkmanager"
"pipewire"
"playerctl" # for waybar & particularly to have playerctld running
"rofi" # menu/launcher

View File

@ -1,80 +0,0 @@
{ config, lib, pkgs, ... }:
let
cfg = config.sane.programs.wpa_supplicant;
in
{
config = lib.mkMerge [
{
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"'
# '';
postInstall = (upstream.postInstall or "") + ''
substitute $out/share/dbus-1/system.d/dbus-wpa_supplicant.conf \
$out/share/dbus-1/system.d/networkmanager-wpa_supplicant.conf \
--replace-fail 'user="root"' 'group="networkmanager"'
'';
postFixup = (upstream.postFixup or "") + ''
# 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>
substituteInPlace $out/share/dbus-1/system-services/* --replace-fail \
"$out$out" "$out"
# remove unused services to avoid unexpected interactions
rm $out/etc/systemd/system/{wpa_supplicant-nl80211@,wpa_supplicant-wired@,wpa_supplicant@}.service
'';
});
# bwrap sandboxing works, but requires the real user to be root.
# landlock sandboxing works, and allows the real user to be someone else (like `networkmanager`).
# non-root is very important, because of how many things in e.g. /dev are r/w based on uid=0.
# sandbox.method = "bwrap";
sandbox.method = "landlock";
sandbox.capabilities = [
# see also: <https://github.com/NixOS/nixpkgs/pull/305722>
"net_admin" "net_raw"
];
# sandbox.extraConfig = [ "--sanebox-keep-namespace" "all" ];
sandbox.net = "all";
sandbox.extraPaths = [
"/dev/net"
"/dev/rfkill"
"/proc/sys/net"
"/sys/class/net"
"/sys/devices"
];
sandbox.whitelistDbus = [ "system" ];
};
}
(lib.mkIf cfg.enabled {
#v TODO: networkmanager module enforces that it install this for us...
# if i relly want to avoid that, maybe i implement some `apply` override for `environment.systemPackages`, & others
# or switch to the `iwd` backend, which is more patchable.
# 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`
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" ];
# wantedBy = [ "network.target" ];
# serviceConfig = {
# Type = "dbus";
# BusName = "fi.w1.wpa_supplicant1";
# ExecStart = "${cfg.package}/bin/wpa_supplicant -u";
# Restart = "always";
# };
# };
})
];
}