From df861a3ef0f9b72af3a6126b40ad921606e104a8 Mon Sep 17 00:00:00 2001 From: Colin Date: Mon, 22 Jan 2024 11:11:08 +0000 Subject: [PATCH] programs: firejail: inject custom firejail config through /etc/firejail this improves rebuild times, and makes it easier for packages to inject their own free-form config --- hosts/common/programs/mpv.nix | 2 - hosts/common/programs/spotify.nix | 4 ++ modules/programs.nix | 61 +++++++++++++++++++++---------- 3 files changed, 45 insertions(+), 22 deletions(-) diff --git a/hosts/common/programs/mpv.nix b/hosts/common/programs/mpv.nix index ae238c6d..6dc7d921 100644 --- a/hosts/common/programs/mpv.nix +++ b/hosts/common/programs/mpv.nix @@ -63,8 +63,6 @@ in }); sandbox.method = "firejail"; - sandbox.binMap."mpv_identify.sh" = "mpv"; - sandbox.binMap."umpv" = "mpv"; persist.byStore.plaintext = [ ".local/state/mpv/watch_later" ]; fs.".config/mpv/input.conf".symlink.text = let diff --git a/hosts/common/programs/spotify.nix b/hosts/common/programs/spotify.nix index 5649c59a..da9cdbe1 100644 --- a/hosts/common/programs/spotify.nix +++ b/hosts/common/programs/spotify.nix @@ -1,6 +1,10 @@ { ... }: { sane.programs.spotify = { + sandbox.method = "firejail"; + sandbox.extraFirejailConfig = '' + keep-dev-shm + ''; persist.byStore.plaintext = [ # probably just songs and such (haven't checked) ".cache/spotify" diff --git a/modules/programs.nix b/modules/programs.nix index e3b9b351..1fb8838d 100644 --- a/modules/programs.nix +++ b/modules/programs.nix @@ -33,7 +33,7 @@ let defaultEnables = solveDefaultEnableFor cfg; # wrap a package so that its binaries (maybe) run in a sandbox - wrapPkg = { fs, net, persist, sandbox, ... }: package: ( + wrapPkg = pkgName: { fs, net, persist, sandbox, ... }: package: ( if sandbox.method == null then package else if sandbox.method == "firejail" then @@ -42,24 +42,25 @@ let firejailBin = "/run/wrappers/bin/firejail"; allowPath = p: [ - "--noblacklist=${p}" - "--whitelist=${p}" + "noblacklist ${p}" + "whitelist ${p}" ]; allowHomePath = p: allowPath ''''${HOME}/${p}''; allowPaths = paths: lib.flatten (builtins.map allowPath paths); allowHomePaths = paths: lib.flatten (builtins.map allowHomePath paths); - fsFlags = allowHomePaths (builtins.attrNames fs); - persistFlags = allowHomePaths (builtins.attrNames persist.byPath); + fsItems = allowHomePaths (builtins.attrNames fs); + persistItems = allowHomePaths (builtins.attrNames persist.byPath); vpn = lib.findSingle (v: v.default) null null (builtins.attrValues config.sane.vpn); - vpnFlags = [ - "--net=${vpn.bridgeDevice}" - ] ++ (builtins.map (addr: "--dns=${addr}") vpn.dns); + vpnItems = [ + "net ${vpn.bridgeDevice}" + ] ++ (builtins.map (addr: "dns ${addr}") vpn.dns); - firejailFlags = [ + firejailItems = [ # "--quiet" #< TODO: enable # "--tracelog" # logs blacklist violations to syslog (but default firejail disallows this) + # "--keep-dev-shm" #< required for spotify ] ++ allowPaths [ "/run/current-system" #< for basics like `ls`, and all this program's `suggestedPrograms` (/run/current-system/sw/bin) "/run/wrappers" #< SUID wrappers, in this case so that firejail can be re-entrant @@ -68,13 +69,10 @@ let # /run/opengl-driver is a symlink into /nix/store; needed by e.g. mpv "/run/opengl-driver" "/run/opengl-driver-32" - ] ++ fsFlags - ++ persistFlags - ++ lib.optionals (net == "vpn") vpnFlags; - - firejailBase = pkgs.writeShellScript - "firejail-${package.pname or package.name or "unknown"}-base" - ''exec ${firejailBin} ${lib.escapeShellArgs firejailFlags} \''; + # "/dev/dri" #< fix non-fatal "libEGL warning: wayland-egl: could not open /dev/dri/renderD128" (geary) + ] ++ fsItems + ++ persistItems + ++ lib.optionals (net == "vpn") vpnItems; # two ways i could wrap a package in a sandbox: # 1. package.overrideAttrs, with `postFixup`. @@ -96,6 +94,7 @@ let else package ; + packageWrapped = package'.overrideAttrs (unwrapped: { postFixup = (unwrapped.postFixup or "") + '' tryFirejailProfile() { @@ -130,18 +129,21 @@ let || tryFirejailProfile "$1" \ || tryFirejailProfile "${unwrapped.pname or ""}" \ || tryFirejailProfile "${unwrapped.name or ""}" \ + || tryFirejailProfile "${pkgName}" \ || (echo "failed to locate firejail profile for $1: aborting!" && false) } firejailWrap() { name="$1" getFirejailProfile "$name" mv "$out/bin/$name" "$out/bin/.$name-firejailed" - cat <> "tmp-firejail-$name" - --profile="$firejailProfilePath" \ + cat <> "$out/bin/$name" + #!${pkgs.runtimeShell} + exec ${firejailBin} \ + --include="${pkgName}.local" \ + --profile="$firejailProfileName" \ --join-or-start="$firejailProfileName" \ -- "$out/bin/.$name-firejailed" "\$@" EOF - cat ${firejailBase} "tmp-firejail-$name" > "$out/bin/$name" chmod +x "$out/bin/$name" } @@ -165,6 +167,7 @@ let test -f "${packageWrapped}/nix-support/sandboxed-${sandbox.method}" \ && touch "$out" ''; + firejailLocalConfig = builtins.concatStringsSep "\n" firejailItems; }; }); in @@ -335,6 +338,18 @@ let then set `sandbox.binMap.umpv = "mpv";` to sandbox `bin/umpv` with the same rules as `bin/mpv` ''; }; + sandbox.extraFirejailConfig = mkOption { + type = types.lines; + default = ""; + description = '' + extra lines to add to this package's /etc/firejail/{pname}.local file, which is included when running any of the package's /bin files if sandbox.method is set to "firejail". + + example: sandbox.extraFirejailConfig = ''' + whitelist ''${HOME}/.ssh + keep-dev-shm + '''; + ''; + }; configOption = mkOption { type = types.raw; default = mkOption { @@ -358,7 +373,7 @@ let package = if config.packageUnwrapped == null then null else - wrapPkg config config.packageUnwrapped + wrapPkg name config config.packageUnwrapped ; }; }); @@ -379,6 +394,11 @@ let p.package.passthru.checkSandboxed ]; + sane.fs = lib.optionalAttrs (p.enabled && p.sandbox.method == "firejail" && p.package != null) { + "/etc/firejail/${name}.local".symlink.text = + p.package.passthru.firejailLocalConfig + p.sandbox.extraFirejailConfig; + }; + # conditionally add to system PATH and env environment = lib.optionalAttrs (p.enabled && p.enableFor.system) { systemPackages = lib.optional (p.package != null) p.package; @@ -450,6 +470,7 @@ in environment.systemPackages = f.environment.systemPackages; environment.variables = f.environment.variables; users.users = f.users.users; + sane.fs = f.sane.fs; sane.users = f.sane.users; sops.secrets = f.sops.secrets; system.checks = f.system.checks;