diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 04c6cd103a49..e620fb2a9c56 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -261,6 +261,7 @@ ./programs/nano.nix ./programs/nautilus-open-any-terminal.nix ./programs/nbd.nix + ./programs/nekoray.nix ./programs/neovim.nix ./programs/nethoscope.nix ./programs/nexttrace.nix diff --git a/nixos/modules/programs/nekoray.nix b/nixos/modules/programs/nekoray.nix new file mode 100644 index 000000000000..e5ad81b950aa --- /dev/null +++ b/nixos/modules/programs/nekoray.nix @@ -0,0 +1,90 @@ +{ + config, + pkgs, + lib, + ... +}: + +let + cfg = config.programs.nekoray; +in +{ + options = { + programs.nekoray = { + enable = lib.mkEnableOption "nekoray, a GUI proxy configuration manager"; + + package = lib.mkPackageOption pkgs "nekoray" { }; + + tunMode = { + enable = lib.mkEnableOption "TUN mode of nekoray"; + + setuid = lib.mkEnableOption '' + setting suid bit for nekobox_core to run as root, which is less + secure than default setcap method but closer to upstream assumptions. + Enable this if you find the default setcap method configured in + this module doesn't work for you + ''; + }; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + + security.wrappers.nekobox_core = lib.mkIf cfg.tunMode.enable { + source = "${cfg.package}/share/nekoray/nekobox_core"; + owner = "root"; + group = "root"; + setuid = lib.mkIf cfg.tunMode.setuid true; + # Taken from https://github.com/SagerNet/sing-box/blob/dev-next/release/config/sing-box.service + capabilities = lib.mkIf ( + !cfg.tunMode.setuid + ) "cap_net_admin,cap_net_raw,cap_net_bind_service,cap_sys_ptrace,cap_dac_read_search+ep"; + }; + + # avoid resolvectl password prompt popping up three times + # https://github.com/SagerNet/sing-tun/blob/0686f8c4f210f4e7039c352d42d762252f9d9cf5/tun_linux.go#L1062 + # We use a hack here to determine whether the requested process is nekobox_core + # Detect whether its capabilities contain at least `net_admin` and `net_raw`. + # This does not reduce security, as we can already bypass `resolved` with them. + # Alternatives to consider: + # 1. Use suid to execute as a specific user, and check username with polkit. + # However, NixOS module doesn't let us to set setuid and capabilities at the + # same time, and it's tricky to make both work together because of some security + # considerations in the kernel. + # 2. Check cmdline to get executable path. This is insecure because the process can + # change its own cmdline. `/proc//exe` is reliable but kernel forbids + # checking that entry of process from different users, and polkit runs `spawn` + # as an unprivileged user. + # 3. Put nekobox_core into a systemd service, and let polkit check service name. + # This is the most secure and convenient way but requires heavy modification + # to nekoray source code. Would be good to let upstream support that eventually. + security.polkit.extraConfig = + lib.mkIf (cfg.tunMode.enable && (!cfg.tunMode.setuid) && config.services.resolved.enable) + '' + polkit.addRule(function(action, subject) { + const allowedActionIds = [ + "org.freedesktop.resolve1.set-domains", + "org.freedesktop.resolve1.set-default-route", + "org.freedesktop.resolve1.set-dns-servers" + ]; + + if (allowedActionIds.indexOf(action.id) !== -1) { + try { + var parentPid = polkit.spawn(["${lib.getExe' pkgs.procps "ps"}", "-o", "ppid=", subject.pid]).trim(); + var parentCap = polkit.spawn(["${lib.getExe' pkgs.libcap "getpcaps"}", parentPid]).trim(); + if (parentCap.includes("cap_net_admin") && parentCap.includes("cap_net_raw")) { + return polkit.Result.YES; + } else { + return polkit.Result.NOT_HANDLED; + } + } catch (e) { + return polkit.Result.NOT_HANDLED; + } + } + }) + ''; + }; + + meta.maintainers = with lib.maintainers; [ aleksana ]; +}