From d633fc2b389e42ae21e1c1de6ee948580f741ca9 Mon Sep 17 00:00:00 2001 From: Gabriel Arazas Date: Fri, 26 Jul 2024 15:41:49 +0800 Subject: [PATCH] wrapper-manager/sandboxing/bubblewrap: init prototype Not complete, just its options for now. We might even go into the direction of NixPak and nix-bubblewrap creating our own specialized launcher. --- .../sandboxing/bubblewrap/default.nix | 156 ++++++++++++++++++ .../wrapper-manager/sandboxing/default.nix | 3 + 2 files changed, 159 insertions(+) create mode 100644 modules/wrapper-manager/sandboxing/bubblewrap/default.nix diff --git a/modules/wrapper-manager/sandboxing/bubblewrap/default.nix b/modules/wrapper-manager/sandboxing/bubblewrap/default.nix new file mode 100644 index 00000000..5f8c81dc --- /dev/null +++ b/modules/wrapper-manager/sandboxing/bubblewrap/default.nix @@ -0,0 +1,156 @@ +# Bubblewrap integration within wrapper-manager. Several parts were inspired +# from the source code's given examples (at `demo` directory) as well as a few +# Nix projects integrating with it such as `nix-bubblewrap` +# (https://git.sr.ht/~fgaz/nix-bubblewrap) and NixPak +# (https://github.com/nixpak/nixpak). +# +# Similar to most of them, this is basically a builder for the right arguments +# to be passed to `bwrap`. +{ config, lib, pkgs, ... }: + +let + inherit (pkgs) stdenv; + cfg = config.sandboxing.bubblewrap; + + bubblewrapModuleFactory = { isGlobal ? false }: { + package = lib.mkPackageOption pkgs "bubblewrap" { } // lib.optionalAttrs isGlobal { + default = cfg.package; + }; + + extraArgs = lib.mkOption { + type = with lib.types; listOf str; + default = [ ]; + description = + if isGlobal + then '' + Global list of extra arguments to be given to all Bubblewrap-enabled + wrappers. + '' + else '' + List of extra arguments to be given to the Bubblewrap executable. + ''; + }; + + enableSharedNixStore = lib.mkEnableOption "sharing of the Nix store" // { + default = if isGlobal then true else cfg.enableSharedNixStore; + }; + + enableNetwork = lib.mkEnableOption "sharing of the host network" // lib.optionalAttrs isGlobal { + default = if isGlobal then true else cfg.enableNetwork; + }; + + enableIsolation = lib.mkEnableOption "unsharing most of the system" // { + default = if isGlobal then true else cfg.enableIsolation; + }; + + binds = { + ro = lib.mkOption { + type = with lib.types; listOf path; + default = if isGlobal then [ ] else cfg.binds.ro; + description = + if isGlobal + then '' + Global list of read-only mounts to be given to all Bubblewrap-enabled + wrappers. + '' + else '' + List of read-only mounts to the Bubblewrap environment. + ''; + example = [ + "/etc/resolv.conf" + "/etc/ssh" + ]; + }; + + rw = lib.mkOption { + type = with lib.types; listOf path; + default = if isGlobal then [ ] else cfg.binds.rw; + description = + if isGlobal + then '' + Global list of read-write mounts to be given to all + Bubblewrap-enabled wrappers. + '' + else '' + List of read-write mounts to the Bubblewrap environment. + ''; + }; + + dev = lib.mkOption { + type = with lib.types; listOf path; + default = if isGlobal then [ ] else cfg.binds.dev; + description = + if isGlobal + then '' + Global list of devices to be mounted to all Bubblewrap-enabled + wrappers. + '' + else '' + List of devices to be mounted inside of the Bubblewrap environment. + ''; + }; + }; + }; +in +{ + options.sandboxing.bubblewrap = bubblewrapModuleFactory { isGlobal = true; }; + + options.wrappers = + let + bubblewrapModule = { name, config, lib, pkgs, ... }: + let + submoduleCfg = config.sandboxing.bubblewrap; + in + { + options.sandboxing.variant = lib.mkOption { + type = with lib.types; nullOr (enum [ "bubblewrap" ]); + }; + + options.sandboxing.bubblewrap = bubblewrapModuleFactory { isGlobal = false; }; + + config = lib.mkIf (config.sandboxing.variant == "bubblewrap") (lib.mkMerge [ + { + # TODO: All of the Linux-exclusive flags could be handled by the + # launcher instead. ALSO MODULARIZE THIS CRAP! + # Ordering of the arguments here matter(?). + bubblewrap.extraArgs = + cfg.extraArgs + ++ lib.optionals stdenv.isLinux [ + "--proc" "/proc" + "--dev" "/dev" + ] + ++ builtins.map (bind: "--ro-bind-try ${bind}") submoduleCfg.binds.ro + ++ builtins.map (bind: "--bind ${bind}") submoduleCfg.binds.rw + ++ builtins.map (bind: "--dev-bind-try ${bind}") submoduleCfg.binds.dev + ++ builtins.map (var: "--unsetenv ${var}") config.unset + ++ lib.mapAttrsToList (var: value: "--setenv ${var} ${value}") config.env; + + arg0 = lib.getExe' submoduleCfg.package "bwrap"; + prependArgs = lib.mkBefore (submoduleCfg.extraArgs ++ [ "--" submoduleCfg.wraparound.executable ] ++ submoduleCfg.wraparound.extraArgs); + } + + (lib.mkIf submoduleCfg.enableSharedNixStore { + bubblewrap.binds.ro = [ builtins.storeDir ] ++ lib.optionals (builtins.storeDir != "/nix/store") [ "/nix/store" ]; + }) + + (lib.mkIf submoduleCfg.enableNetwork { + # In case isolation is also enabled, we'll have this still + # enabled at least. + bubblewrap.extraArgs = lib.mkAfter [ "--share-net" ]; + bubblewrap.binds.ro = [ + "/etc/ssh" + "/etc/hosts" + "/etc/resolv.conf" + ]; + }) + + (lib.mkIf submoduleCfg.enableIsolation { + bubblewrap.extraArgs = lib.mkBefore [ "--unshare-all" ]; + }) + ]); + }; + in + lib.mkOption { + type = with lib.types; attrsOf (submodule bubblewrapModule); + }; +} diff --git a/modules/wrapper-manager/sandboxing/default.nix b/modules/wrapper-manager/sandboxing/default.nix index a300ec3a..8e47c2cc 100644 --- a/modules/wrapper-manager/sandboxing/default.nix +++ b/modules/wrapper-manager/sandboxing/default.nix @@ -1,6 +1,9 @@ { lib, ... }: { + imports = [ + ./bubblewrap + ]; options.wrappers = let