2024-06-02 09:48:17 +00:00
{ config , pkgs , . . . }:
let
2024-06-03 13:04:48 +00:00
# networkmanager = pkgs.networkmanager;
networkmanager = pkgs . networkmanager . overrideAttrs ( upstream : {
src = pkgs . fetchFromGitea {
domain = " g i t . u n i n s a n e . o r g " ;
owner = " c o l i n " ;
repo = " N e t w o r k M a n a g e r " ;
# patched to fix polkit permissions (with `nmcli`) when NetworkManager runs as user networkmanager
rev = " d e v - s a n e - 1 . 4 8 . 0 " ;
hash = " s h a 2 5 6 - v G m O K t w V I t x j Y i o Z J l b 1 o g 3 K 6 u 9 s 4 r c m D n j A P L B C 3 a o = " ;
} ;
# patches = [];
} ) ;
2024-06-02 09:48:17 +00:00
# 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 ;
2024-06-03 11:24:38 +00:00
# plugins mostly add support for establishing different VPN connections.
# the default plugin set includes mostly proprietary VPNs:
# - fortisslvpn (Fortinet)
# - iodine (DNS tunnels)
# - l2tp
# - openconnect (Cisco Anyconnect / Juniper / ocserv)
# - openvpn
# - vpnc (Cisco VPN)
# - sstp
#
# 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 [];
2024-06-02 09:48:17 +00:00
networking . networkmanager . enableDefaultPlugins = false ;
2024-06-03 11:24:38 +00:00
2024-06-02 09:48:17 +00:00
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 = " r o o t " ' ' group = " n e t w o r k m a n a g e r " '
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 = " N e t w o r k M a n a g e r " ; #< tells systemd to create /run/NetworkManager
# serviceConfig.StateDirectory = "NetworkManager"; #< tells systemd to create /var/lib/NetworkManager
serviceConfig . User = " n e t w o r k m a n a g e r " ;
serviceConfig . Group = " n e t w o r k m a n a g e r " ;
serviceConfig . AmbientCapabilities = [
# "CAP_DAC_OVERRIDE"
" C A P _ N E T _ A D M I N "
2024-06-03 13:05:51 +00:00
" C A P _ N E T _ R A W " #< required, else `libndp: ndp_sock_open: Failed to create ICMP6 socket.`
2024-06-02 09:48:17 +00:00
" C A P _ N E T _ B I N D _ S E R V I C E " #< this *does* seem to be necessary, though i don't understand why. DHCP?
# "CAP_SYS_MODULE"
2024-06-03 13:05:51 +00:00
# "CAP_AUDIT_WRITE" #< allow writing to the audit log (optional)
2024-06-02 09:48:17 +00:00
# "CAP_KILL"
] ;
2024-06-03 13:05:51 +00:00
serviceConfig . LockPersonality = true ;
2024-06-03 16:23:22 +00:00
serviceConfig . NoNewPrivileges = true ;
2024-06-03 13:05:51 +00:00
serviceConfig . PrivateDevices = true ; # remount /dev with just the basics, syscall filter to block @raw-io
serviceConfig . PrivateIPC = true ;
2024-06-03 16:23:22 +00:00
serviceConfig . PrivateTmp = true ;
2024-06-03 13:05:51 +00:00
# serviceConfig.PrivateUsers = true; #< BREAKS NetworkManager (presumably, it causes a new user namespace, breaking CAP_NET_ADMIN & others). "platform-linux: do-change-link[3]: failure 1 (Operation not permitted)"
serviceConfig . ProtectClock = true ; # syscall filter to prevent changing the RTC
serviceConfig . ProtectControlGroups = true ;
serviceConfig . ProtectHome = true ; # makes empty: /home, /root, /run/user
serviceConfig . ProtectHostname = true ; # probably not upstreamable: prevents changing hostname
serviceConfig . ProtectKernelLogs = true ; # disable /proc/kmsg, /dev/kmsg
serviceConfig . ProtectKernelModules = true ; # syscall filter to prevent module calls (probably not upstreamable: NM will want to load modules like `ppp`)
serviceConfig . ProtectKernelTunables = true ; # but NM might need to write /proc/sys/net/...
2024-06-03 15:09:53 +00:00
serviceConfig . ProtectSystem = " s t r i c t " ; # makes read-only: all but /dev, /proc, /sys.
2024-06-03 13:05:51 +00:00
serviceConfig . RestrictAddressFamilies = [
" A F _ I N E T "
" A F _ I N E T 6 "
" A F _ N E T L I N K " # breaks near DHCP without this
" A F _ P A C K E T " # for DHCP
" A F _ U N I X "
# AF_ALG ?
# AF_BLUETOOTH ?
# AF_BRIDGE ?
] ;
serviceConfig . RestrictSUIDSGID = true ;
serviceConfig . SystemCallArchitectures = " n a t i v e " ; # prevents e.g. aarch64 syscalls in the event that the kernel is multi-architecture.
2024-06-03 15:09:53 +00:00
# from earlier `landlock` sandboxing, i know it needs these directories:
2024-06-02 09:48:17 +00:00
# - "/proc/net"
# - "/proc/sys/net"
# - "/run/NetworkManager"
# - "/run/systemd" # for trust-dns-nmhook
# - "/run/udev"
# - # "/run/wg-home.priv"
2024-06-03 15:09:53 +00:00
# - "/sys/class"
2024-06-02 09:48:17 +00:00
# - "/sys/devices"
# - "/var/lib/NetworkManager"
# - "/var/lib/trust-dns" #< for trust-dns-nmhook
# - "/run/systemd"
} ;
systemd . services . NetworkManager-wait-online = {
serviceConfig . User = " n e t w o r k m a n a g e r " ;
serviceConfig . Group = " n e t w o r k m a n a g e r " ;
} ;
# fix NetworkManager-dispatcher to actually run as a daemon,
# and sandbox it a bit
systemd . services . NetworkManager-dispatcher = {
after = [ " t r u s t - d n s - l o c a l h o s t . s e r v i c e " ] ; #< 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";
2024-06-03 15:31:50 +00:00
2024-06-03 15:57:41 +00:00
# serviceConfig.DynamicUser = true; #< not possible, else we lose group perms (so can't write to `trust-dns`'s files in the nm hook)
2024-06-03 15:31:50 +00:00
serviceConfig . User = " n e t w o r k m a n a g e r " ; # TODO: should arguably use `DynamicUser`
2024-06-02 09:48:17 +00:00
serviceConfig . Group = " n e t w o r k m a n a g e r " ;
2024-06-03 15:31:50 +00:00
serviceConfig . LockPersonality = true ;
2024-06-03 16:23:22 +00:00
serviceConfig . NoNewPrivileges = true ;
2024-06-03 15:31:50 +00:00
serviceConfig . PrivateDevices = true ; # remount /dev with just the basics, syscall filter to block @raw-io
serviceConfig . PrivateIPC = true ;
2024-06-03 16:23:22 +00:00
serviceConfig . PrivateTmp = true ;
2024-06-03 15:31:50 +00:00
serviceConfig . PrivateUsers = true ;
serviceConfig . ProtectClock = true ; # syscall filter to prevent changing the RTC
serviceConfig . ProtectControlGroups = true ;
serviceConfig . ProtectHome = true ; # makes empty: /home, /root, /run/user
serviceConfig . ProtectHostname = true ; # probably not upstreamable: prevents changing hostname
serviceConfig . ProtectKernelLogs = true ; # disable /proc/kmsg, /dev/kmsg
serviceConfig . ProtectKernelModules = true ; # syscall filter to prevent module calls
serviceConfig . ProtectKernelTunables = true ;
serviceConfig . ProtectSystem = " f u l l " ; # makes read-only: /boot, /etc/, /usr. `strict` isn't possible due to trust-dns hook
serviceConfig . RestrictAddressFamilies = [
" A F _ U N I X " # required, probably for dbus or systemd connectivity
] ;
serviceConfig . RestrictSUIDSGID = true ;
serviceConfig . SystemCallArchitectures = " n a t i v e " ; # prevents e.g. aarch64 syscalls in the event that the kernel is multi-architecture.
2024-06-02 09:48:17 +00:00
} ;
# harden wpa_supplicant (used by NetworkManager)
systemd . services . wpa_supplicant = {
serviceConfig . User = " n e t w o r k m a n a g e r " ;
serviceConfig . Group = " n e t w o r k m a n a g e r " ;
serviceConfig . AmbientCapabilities = [
" C A P _ N E T _ A D M I N "
" C A P _ N E T _ R A W "
] ;
2024-06-03 14:40:53 +00:00
serviceConfig . LockPersonality = true ;
2024-06-03 16:23:22 +00:00
serviceConfig . NoNewPrivileges = true ;
2024-06-03 14:40:53 +00:00
# serviceConfig.PrivateDevices = true; # untried, not likely to work. remount /dev with just the basics, syscall filter to block @raw-io
serviceConfig . PrivateIPC = true ;
2024-06-03 16:23:22 +00:00
serviceConfig . PrivateTmp = true ;
2024-06-03 14:40:53 +00:00
# serviceConfig.PrivateUsers = true; #< untried, not likely to work
serviceConfig . ProtectClock = true ; # syscall filter to prevent changing the RTC
serviceConfig . ProtectControlGroups = true ;
serviceConfig . ProtectHome = true ; # makes empty: /home, /root, /run/user
serviceConfig . ProtectHostname = true ; # prevents changing hostname
serviceConfig . ProtectKernelLogs = true ; # disable /proc/kmsg, /dev/kmsg
serviceConfig . ProtectKernelModules = true ; # syscall filter to prevent module calls
serviceConfig . ProtectKernelTunables = true ; #< N.B.: i think this makes certain /proc writes fail
serviceConfig . ProtectSystem = " s t r i c t " ; # makes read-only: all but /dev, /proc, /sys.
serviceConfig . RestrictAddressFamilies = [
" A F _ I N E T " #< required
" A F _ I N E T 6 "
" A F _ N E T L I N K " #< required
" A F _ P A C K E T " #< required
" A F _ U N I X " #< required (wpa_supplicant wants to use dbus)
] ;
serviceConfig . RestrictSUIDSGID = true ;
serviceConfig . SystemCallArchitectures = " n a t i v e " ; # prevents e.g. aarch64 syscalls in the event that the kernel is multi-architecture.
# from earlier `landlock` sandboxing, i know it needs only these paths:
2024-06-02 09:48:17 +00:00
# - "/dev/net"
# - "/dev/rfkill"
# - "/proc/sys/net"
# - "/sys/class/net"
# - "/sys/devices"
# - "/run/systemd"
} ;
networking . networkmanager . settings = {
2024-06-03 11:24:38 +00:00
# keyfile.path = where networkmanager should look for connection credentials
keyfile . path = " / v a r / l i b / N e t w o r k M a n a g e r / s y s t e m - c o n n e c t i o n s " ;
# wifi.backend = "wpa_supplicant"; #< default
# wifi.scan-rand-mac-address = true; #< default
2024-06-02 09:48:17 +00:00
2024-06-03 11:24:38 +00:00
# logging.audit = false; #< default
2024-06-02 09:48:17 +00:00
logging . level = " I N F O " ;
2024-06-03 11:24:38 +00:00
# main.dhcp = "internal"; #< default
2024-06-02 09:48:17 +00:00
main . dns = if config . services . resolved . enable then
" s y s t e m d - r e s o l v e d "
else if config . sane . services . trust-dns . enable && config . sane . services . trust-dns . asSystemResolver then
" n o n e "
else
" i n t e r n a l "
;
main . systemd-resolved = false ;
} ;
environment . etc . " N e t w o r k M a n a g e r / s y s t e m - c o n n e c t i o n s " . source = " / v a r / l i b / N e t w o r k M a n a g e r / s y s t e m - c o n n e c t i o n s " ;
2024-06-03 11:24:38 +00:00
# the default backend is "wpa_supplicant".
# wpa_supplicant reliably picks weak APs to connect to.
# see: <https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/issues/474>
# iwd is an alternative that shouldn't have this problem
# docs:
# - <https://nixos.wiki/wiki/Iwd>
# - <https://iwd.wiki.kernel.org/networkmanager>
# - `man iwd.config` for global config
# - `man iwd.network` for per-SSID config
# use `iwctl` to control
# networking.networkmanager.wifi.backend = "iwd";
# networking.wireless.iwd.enable = true;
# networking.wireless.iwd.settings = {
# # auto-connect to a stronger network if signal drops below this value
# # bedroom -> bedroom connection is -35 to -40 dBm
# # bedroom -> living room connection is -60 dBm
# General.RoamThreshold = "-52"; # default -70
# General.RoamThreshold5G = "-52"; # default -76
# };
2024-06-02 09:48:17 +00:00
# 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 ( " n e t w o r k m a n a g e r " ) && action . id . indexOf ( " o r g . f r e e d e s k t o p . r e s o l v e 1 . " ) == 0 ) {
return polkit . Result . YES ;
}
} ) ;
'' ;
users . users . networkmanager = {
isSystemUser = true ;
group = " n e t w o r k m a n a g e r " ;
extraGroups = [ " t r u s t - d n s " ] ;
} ;
# 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 = " r o o t " ' ' group = " n e t w o r k m a n a g e r " '
'' ;
postFixup = ( upstream . postFixup or " " ) + ''
# remove unused services to avoid unexpected interactions
rm $ out/etc/systemd/system / { wpa_supplicant-nl80211 @ , wpa_supplicant-wired @ , wpa_supplicant @ } . service
'' ;
} ) ;
} ) ] ;
}