mirror of
https://github.com/foo-dogsquared/nixos-config.git
synced 2025-01-31 16:57:55 +00:00
Gabriel Arazas
7524d87b49
It could be done by removing the string context but it is more tedious to maintain in the long run so it would be best to have them separate.
258 lines
8.5 KiB
Nix
258 lines
8.5 KiB
Nix
# Essentially a poor man's version of NixOS filesystem module except that is
|
|
# made for Bubblewrap environment. Everything here should only make use of
|
|
# Bubblewrap's filesystem options from the command-line application.
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
let
|
|
cfg = config.sandboxing.bubblewrap;
|
|
|
|
fileOperationsWithPerms = [
|
|
"file" "dir" "remount-ro"
|
|
"bind-data" "ro-bind-data"
|
|
];
|
|
fileOperationsWithoutPerms = [
|
|
"symlink"
|
|
"bind" "bind-try"
|
|
"dev-bind" "dev-bind-try"
|
|
"ro-bind" "ro-bind-try"
|
|
];
|
|
|
|
bubblewrapModuleFactory = { isGlobal ? false }: let
|
|
filesystemSubmodule = { config, lib, name, ... }: {
|
|
options = {
|
|
source = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = ''
|
|
The source of the path to be copied from.
|
|
'';
|
|
example = lib.literalExpression "./files/example.file";
|
|
};
|
|
|
|
destination = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = ''
|
|
The source of the path to be copied from.
|
|
'';
|
|
default = name;
|
|
example = lib.literalExpression "./files/example.file";
|
|
};
|
|
|
|
permissions = lib.mkOption {
|
|
type = with lib.types; nullOr (strMatching "[0-7]{0,4}");
|
|
description = ''
|
|
The permissions of the node in octal. If the value is `null`, it
|
|
will be handled by Bubblewrap executable. For more details for each
|
|
operation, see {manpage}`bwrap(1)`.
|
|
'';
|
|
default = null;
|
|
example = "0755";
|
|
};
|
|
|
|
operation = lib.mkOption {
|
|
type = lib.types.enum (fileOperationsWithPerms ++ fileOperationsWithoutPerms);
|
|
description = ''
|
|
Specify what filesystem-related operations to be done for the given
|
|
filesystem object. Only certain operations accept permissions given
|
|
from {option}`sandboxing.bubblewrap.filesystem.<name>.permissions`.
|
|
'';
|
|
default = "ro-bind-try";
|
|
example = "bind";
|
|
};
|
|
|
|
lock = lib.mkEnableOption "locking the file";
|
|
};
|
|
};
|
|
|
|
bindsType = with lib.types; listOf (oneOf [ str package ]);
|
|
in {
|
|
enableSharedNixStore = lib.mkEnableOption null // {
|
|
default = if isGlobal then true else cfg.enableSharedNixStore;
|
|
description = ''
|
|
Whether to share the entire Nix store directory.
|
|
'';
|
|
};
|
|
|
|
sharedNixPaths = lib.mkOption {
|
|
type = with lib.types; listOf package;
|
|
default = [ ];
|
|
description = if isGlobal then ''
|
|
A global list of store paths (including its dependencies) to be shared
|
|
per-Bubblewrap-enabled-wrappers.
|
|
'' else ''
|
|
A list of store paths to be mounted (as read-only bind-mounts). Note
|
|
that this also includes the listed store objects' dependencies.
|
|
'';
|
|
example = lib.literalExpression ''
|
|
with pkgs; [
|
|
gtk3
|
|
]
|
|
'';
|
|
};
|
|
|
|
binds = {
|
|
ro = lib.mkOption {
|
|
type = bindsType;
|
|
default = [ ];
|
|
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 = bindsType;
|
|
default = [ ];
|
|
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 = bindsType;
|
|
default = [ ];
|
|
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.
|
|
'';
|
|
};
|
|
};
|
|
|
|
filesystem = lib.mkOption {
|
|
type = with lib.types; attrsOf (submodule filesystemSubmodule);
|
|
description =
|
|
if isGlobal then ''
|
|
Set of filesystem configurations to be copied to per-wrapper.
|
|
'' else ''
|
|
Set of wrapper-specific filesystem configurations in the Bubblewrap
|
|
environment.
|
|
'';
|
|
default = { };
|
|
example = lib.literalExpression ''
|
|
{
|
|
"/etc/hello" = {
|
|
source = ./files/hello;
|
|
permissions = "0700";
|
|
operation = "file";
|
|
};
|
|
|
|
"/etc/xdg" = {
|
|
permissions = "0700";
|
|
operation = "dir";
|
|
};
|
|
|
|
"/srv/data" = {
|
|
source = "/srv/data";
|
|
operation = "symlink";
|
|
};
|
|
|
|
"/srv/logs".operation = "dir";
|
|
}
|
|
'';
|
|
};
|
|
};
|
|
|
|
# TODO: There has to be a better way to get this info without relying on
|
|
# pkgs.closureInfo builder, right?
|
|
getClosurePaths = rootPaths:
|
|
let
|
|
sharedNixPathsClosureInfo = pkgs.closureInfo { inherit rootPaths; };
|
|
closurePaths = lib.readFile "${sharedNixPathsClosureInfo}/store-paths";
|
|
in
|
|
lib.lists.filter (p: p != "") (lib.splitString "\n" closurePaths);
|
|
in
|
|
{
|
|
options.sandboxing.bubblewrap = bubblewrapModuleFactory { isGlobal = true; };
|
|
|
|
options.wrappers =
|
|
let
|
|
bubblewrapModule = { config, lib, name, ... }: let
|
|
submoduleCfg = config.sandboxing.bubblewrap;
|
|
in {
|
|
options.sandboxing.bubblewrap = bubblewrapModuleFactory { isGlobal = false; };
|
|
|
|
config = lib.mkIf (config.sandboxing.variant == "bubblewrap") (lib.mkMerge [
|
|
{
|
|
sandboxing.bubblewrap.binds = cfg.binds;
|
|
sandboxing.bubblewrap.sharedNixPaths = cfg.sharedNixPaths;
|
|
sandboxing.bubblewrap.filesystem = cfg.filesystem;
|
|
}
|
|
|
|
{
|
|
sandboxing.bubblewrap.filesystem =
|
|
let
|
|
renameNixStorePaths = path:
|
|
if lib.isDerivation path then path.pname else path;
|
|
makeFilesystemMapping = operation: bind:
|
|
lib.nameValuePair (renameNixStorePaths bind) {
|
|
inherit operation;
|
|
source = builtins.toString bind;
|
|
destination = builtins.toString bind;
|
|
};
|
|
filesystemMappings =
|
|
lib.lists.map (makeFilesystemMapping "ro-bind-try") submoduleCfg.binds.ro
|
|
++ lib.lists.map (makeFilesystemMapping "bind-try") submoduleCfg.binds.rw
|
|
++ lib.lists.map (makeFilesystemMapping "dev-bind-try") submoduleCfg.binds.dev;
|
|
in
|
|
builtins.listToAttrs filesystemMappings;
|
|
|
|
sandboxing.bubblewrap.extraArgs =
|
|
let
|
|
makeFilesystemArgs = _: metadata:
|
|
let
|
|
src = lib.escapeShellArg metadata.source;
|
|
dst = lib.escapeShellArg metadata.destination;
|
|
hasPermissions = metadata.permissions != null;
|
|
isValidOperationWithPerms = lib.elem metadata.operation fileOperationsWithPerms;
|
|
in
|
|
# Take note of the ordering here such as `--perms` requiring
|
|
# to be before the file operation flags.
|
|
lib.optionals (hasPermissions && isValidOperationWithPerms) [ "--perms ${metadata.permissions}" ]
|
|
++ (
|
|
if lib.elem metadata.operation [ "dir" "remount-ro" ]
|
|
then [ "--${metadata.operation} ${dst}" ]
|
|
else [ "--${metadata.operation} ${src} ${dst}" ]
|
|
)
|
|
++ lib.optionals metadata.lock [ "--lock-file ${dst}" ];
|
|
in
|
|
lib.lists.flatten (lib.mapAttrsToList makeFilesystemArgs submoduleCfg.filesystem);
|
|
}
|
|
|
|
(lib.mkIf submoduleCfg.enableSharedNixStore {
|
|
sandboxing.bubblewrap.binds.ro = [ builtins.storeDir ] ++ lib.optionals (builtins.storeDir != "/nix/store") [ "/nix/store" ];
|
|
})
|
|
|
|
(lib.mkIf (submoduleCfg.sharedNixPaths != [ ]) {
|
|
sandboxing.bubblewrap.extraArgs =
|
|
let
|
|
closurePaths = getClosurePaths submoduleCfg.sharedNixPaths;
|
|
in
|
|
builtins.map (p: "--ro-bind ${lib.escapeShellArg p} ${lib.escapeShellArg p}") closurePaths;
|
|
})
|
|
]);
|
|
};
|
|
in
|
|
lib.mkOption {
|
|
type = with lib.types; attrsOf (submodule bubblewrapModule);
|
|
};
|
|
}
|