# 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`. # # As already mentioned from the Bubblewrap README, we'll have to be careful for # handling D-Bus so we'll use xdg-dbus-proxy for that. { 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 { imports = [ ./dbus-filter.nix ]; 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); }; }