2024-07-26 07:41:49 +00:00
|
|
|
# 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`.
|
2024-07-26 07:56:16 +00:00
|
|
|
#
|
|
|
|
# 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.
|
2024-07-26 07:41:49 +00:00
|
|
|
{ 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
|
|
|
|
{
|
2024-07-26 07:56:16 +00:00
|
|
|
imports = [
|
|
|
|
./dbus-filter.nix
|
|
|
|
];
|
|
|
|
|
2024-07-26 07:41:49 +00:00
|
|
|
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);
|
|
|
|
};
|
|
|
|
}
|