at master 3.7 kB view raw
1{ 2 config, 3 pkgs, 4 lib, 5 ... 6}: 7 8let 9 cfg = config.programs.nekoray; 10in 11{ 12 options = { 13 programs.nekoray = { 14 enable = lib.mkEnableOption "nekoray, a GUI proxy configuration manager"; 15 16 package = lib.mkPackageOption pkgs "nekoray" { }; 17 18 tunMode = { 19 enable = lib.mkEnableOption "TUN mode of nekoray"; 20 21 setuid = lib.mkEnableOption '' 22 setting suid bit for nekobox_core to run as root, which is less 23 secure than default setcap method but closer to upstream assumptions. 24 Enable this if you find the default setcap method configured in 25 this module doesn't work for you 26 ''; 27 }; 28 }; 29 }; 30 31 config = lib.mkIf cfg.enable { 32 environment.systemPackages = [ cfg.package ]; 33 34 security.wrappers.nekobox_core = lib.mkIf cfg.tunMode.enable { 35 source = "${cfg.package}/share/nekoray/nekobox_core"; 36 owner = "root"; 37 group = "root"; 38 setuid = lib.mkIf cfg.tunMode.setuid true; 39 # Taken from https://github.com/SagerNet/sing-box/blob/dev-next/release/config/sing-box.service 40 capabilities = lib.mkIf ( 41 !cfg.tunMode.setuid 42 ) "cap_net_admin,cap_net_raw,cap_net_bind_service,cap_sys_ptrace,cap_dac_read_search+ep"; 43 }; 44 45 # avoid resolvectl password prompt popping up three times 46 # https://github.com/SagerNet/sing-tun/blob/0686f8c4f210f4e7039c352d42d762252f9d9cf5/tun_linux.go#L1062 47 # We use a hack here to determine whether the requested process is nekobox_core 48 # Detect whether its capabilities contain at least `net_admin` and `net_raw`. 49 # This does not reduce security, as we can already bypass `resolved` with them. 50 # Alternatives to consider: 51 # 1. Use suid to execute as a specific user, and check username with polkit. 52 # However, NixOS module doesn't let us to set setuid and capabilities at the 53 # same time, and it's tricky to make both work together because of some security 54 # considerations in the kernel. 55 # 2. Check cmdline to get executable path. This is insecure because the process can 56 # change its own cmdline. `/proc/<pid>/exe` is reliable but kernel forbids 57 # checking that entry of process from different users, and polkit runs `spawn` 58 # as an unprivileged user. 59 # 3. Put nekobox_core into a systemd service, and let polkit check service name. 60 # This is the most secure and convenient way but requires heavy modification 61 # to nekoray source code. Would be good to let upstream support that eventually. 62 security.polkit.extraConfig = 63 lib.mkIf (cfg.tunMode.enable && (!cfg.tunMode.setuid) && config.services.resolved.enable) 64 '' 65 polkit.addRule(function(action, subject) { 66 const allowedActionIds = [ 67 "org.freedesktop.resolve1.set-domains", 68 "org.freedesktop.resolve1.set-default-route", 69 "org.freedesktop.resolve1.set-dns-servers" 70 ]; 71 72 if (allowedActionIds.indexOf(action.id) !== -1) { 73 try { 74 var parentPid = polkit.spawn(["${lib.getExe' pkgs.procps "ps"}", "-o", "ppid=", subject.pid]).trim(); 75 var parentCap = polkit.spawn(["${lib.getExe' pkgs.libcap "getpcaps"}", parentPid]).trim(); 76 if (parentCap.includes("cap_net_admin") && parentCap.includes("cap_net_raw")) { 77 return polkit.Result.YES; 78 } else { 79 return polkit.Result.NOT_HANDLED; 80 } 81 } catch (e) { 82 return polkit.Result.NOT_HANDLED; 83 } 84 } 85 }) 86 ''; 87 }; 88 89 meta.maintainers = with lib.maintainers; [ aleksana ]; 90}