programs: allow programs to specify "sandbox.method = "bwrap"" for bubblewrap sandboxing
This commit is contained in:
@@ -36,17 +36,19 @@ let
|
|||||||
wrapPkg = pkgName: { fs, net, persist, sandbox, ... }: package: (
|
wrapPkg = pkgName: { fs, net, persist, sandbox, ... }: package: (
|
||||||
if sandbox.method == null then
|
if sandbox.method == null then
|
||||||
package
|
package
|
||||||
else if sandbox.method == "firejail" then
|
else
|
||||||
let
|
let
|
||||||
makeSandboxed = pkgs.callPackage ./make-sandboxed.nix { sane-sandboxed = config.sane.sandboxHelper; };
|
makeSandboxed = pkgs.callPackage ./make-sandboxed.nix { sane-sandboxed = config.sane.sandboxHelper; };
|
||||||
vpn = lib.findSingle (v: v.default) null null (builtins.attrValues config.sane.vpn);
|
vpn = lib.findSingle (v: v.default) null null (builtins.attrValues config.sane.vpn);
|
||||||
in
|
in
|
||||||
makeSandboxed {
|
makeSandboxed {
|
||||||
inherit pkgName package;
|
inherit pkgName package;
|
||||||
inherit (sandbox) binMap;
|
inherit (sandbox) binMap method;
|
||||||
vpn = if net == "vpn" then vpn else null;
|
vpn = if net == "vpn" then vpn else null;
|
||||||
allowedHomePaths = builtins.attrNames fs ++ builtins.attrNames persist.byPath;
|
allowedHomePaths = builtins.attrNames fs ++ builtins.attrNames persist.byPath;
|
||||||
allowedRootPaths = [
|
allowedRootPaths = [
|
||||||
|
"/nix/store"
|
||||||
|
"/etc" #< especially for /etc/profiles/per-user/$USER/bin
|
||||||
"/run/current-system" #< for basics like `ls`, and all this program's `suggestedPrograms` (/run/current-system/sw/bin)
|
"/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
|
"/run/wrappers" #< SUID wrappers, in this case so that firejail can be re-entrant
|
||||||
# "/bin/sh" #< to allow `firejail --join=...` (doesn't work)
|
# "/bin/sh" #< to allow `firejail --join=...` (doesn't work)
|
||||||
@@ -54,11 +56,10 @@ let
|
|||||||
# /run/opengl-driver is a symlink into /nix/store; needed by e.g. mpv
|
# /run/opengl-driver is a symlink into /nix/store; needed by e.g. mpv
|
||||||
"/run/opengl-driver"
|
"/run/opengl-driver"
|
||||||
"/run/opengl-driver-32"
|
"/run/opengl-driver-32"
|
||||||
|
"/run/user" #< particularly /run/user/$id/wayland-1, pulse, etc.
|
||||||
# "/dev/dri" #< fix non-fatal "libEGL warning: wayland-egl: could not open /dev/dri/renderD128" (geary)
|
# "/dev/dri" #< fix non-fatal "libEGL warning: wayland-egl: could not open /dev/dri/renderD128" (geary)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
else
|
|
||||||
throw "unknown sandbox type '${sandbox.method}'"
|
|
||||||
);
|
);
|
||||||
pkgSpec = with lib; types.submodule ({ config, name, ... }: {
|
pkgSpec = with lib; types.submodule ({ config, name, ... }: {
|
||||||
options = {
|
options = {
|
||||||
@@ -207,8 +208,8 @@ let
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
sandbox.method = mkOption {
|
sandbox.method = mkOption {
|
||||||
type = types.nullOr (types.enum [ "firejail" ]);
|
type = types.nullOr (types.enum [ "bwrap" "firejail" ]);
|
||||||
default = null; #< TODO: default to firejail
|
default = null; #< TODO: default to bwrap
|
||||||
description = ''
|
description = ''
|
||||||
how/whether to sandbox all binaries in the package.
|
how/whether to sandbox all binaries in the package.
|
||||||
'';
|
'';
|
||||||
|
@@ -1,12 +1,10 @@
|
|||||||
{ lib
|
{ lib
|
||||||
, buildPackages
|
|
||||||
, firejail
|
|
||||||
, runCommand
|
, runCommand
|
||||||
, runtimeShell
|
, runtimeShell
|
||||||
, sane-sandboxed
|
, sane-sandboxed
|
||||||
, writeTextFile
|
, writeTextFile
|
||||||
}:
|
}:
|
||||||
{ pkgName, package, vpn ? null, allowedHomePaths ? [], allowedRootPaths ? [], binMap ? {} }:
|
{ pkgName, package, method, vpn ? null, allowedHomePaths ? [], allowedRootPaths ? [], binMap ? {} }:
|
||||||
let
|
let
|
||||||
sane-sandboxed' = sane-sandboxed.meta.mainProgram; #< load by bin name to reduce rebuilds
|
sane-sandboxed' = sane-sandboxed.meta.mainProgram; #< load by bin name to reduce rebuilds
|
||||||
|
|
||||||
@@ -29,7 +27,9 @@ let
|
|||||||
addr
|
addr
|
||||||
]) vpn.dns);
|
]) vpn.dns);
|
||||||
|
|
||||||
sandboxFlags = allowPaths allowedRootPaths
|
sandboxFlags = [
|
||||||
|
"--sane-sandbox-method" method
|
||||||
|
] ++ allowPaths allowedRootPaths
|
||||||
++ allowHomePaths allowedHomePaths
|
++ allowHomePaths allowedHomePaths
|
||||||
++ lib.optionals (vpn != null) vpnItems;
|
++ lib.optionals (vpn != null) vpnItems;
|
||||||
|
|
||||||
@@ -96,23 +96,27 @@ let
|
|||||||
for _p in $(ls "$out/bin/"); do
|
for _p in $(ls "$out/bin/"); do
|
||||||
sandboxWrap "$_p"
|
sandboxWrap "$_p"
|
||||||
done
|
done
|
||||||
|
|
||||||
# stamp file which can be consumed to ensure this wrapping code was actually called.
|
|
||||||
mkdir -p $out/nix-support
|
|
||||||
touch $out/nix-support/sandboxed
|
|
||||||
'';
|
'';
|
||||||
|
|
||||||
meta = (unwrapped.meta or {}) // {
|
meta = (unwrapped.meta or {}) // {
|
||||||
# take precedence over non-sandboxed versions of the same binary.
|
# take precedence over non-sandboxed versions of the same binary.
|
||||||
priority = ((unwrapped.meta or {}).priority or 0) - 1;
|
priority = ((unwrapped.meta or {}).priority or 0) - 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
passthru = (unwrapped.passthru or {}) // {
|
passthru = (unwrapped.passthru or {}) // {
|
||||||
checkSandboxed = runCommand "${pkgName}-check-sandboxed" {} ''
|
checkSandboxed = runCommand "${pkgName}-check-sandboxed" {} ''
|
||||||
# this pseudo-package gets "built" as part of toplevel system build.
|
# invoke each binary in a way only the sandbox wrapper will recognize,
|
||||||
# if the build is failing here, that means the program isn't properly sandboxed:
|
# ensuring that every binary has in fact been wrapped.
|
||||||
# make sure that "postFixup" gets called as part of the package's build script
|
_numExec=0
|
||||||
test -f "${packageWrapped}/nix-support/sandboxed" \
|
for b in ${packageWrapped}/bin/*; do
|
||||||
&& touch "$out"
|
PATH="$PATH:${packageWrapped}/bin:${sane-sandboxed}/bin" "$b" --sane-sandbox-method exit0fortest | grep "exiting 0 for test"
|
||||||
|
_numExec=$(( $_numExec + 1 ))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "successfully tested $_numExec binaries"
|
||||||
|
test "$_numExec" -ne 0 && touch "$out"
|
||||||
'';
|
'';
|
||||||
|
|
||||||
sandboxProfiles = writeTextFile {
|
sandboxProfiles = writeTextFile {
|
||||||
name = "${pkgName}-sandbox-profiles";
|
name = "${pkgName}-sandbox-profiles";
|
||||||
destination = "/share/sane-sandboxed/profiles/${pkgName}.profile";
|
destination = "/share/sane-sandboxed/profiles/${pkgName}.profile";
|
||||||
|
@@ -9,7 +9,9 @@ rootPaths=()
|
|||||||
homePaths=()
|
homePaths=()
|
||||||
net=
|
net=
|
||||||
dns=()
|
dns=()
|
||||||
|
method=
|
||||||
firejailFlags=()
|
firejailFlags=()
|
||||||
|
bwrapFlags=()
|
||||||
|
|
||||||
debug() {
|
debug() {
|
||||||
[ -n "$SANE_SANDBOX_DEBUG" ] && printf "[debug] %s" "$1" >&2
|
[ -n "$SANE_SANDBOX_DEBUG" ] && printf "[debug] %s" "$1" >&2
|
||||||
@@ -67,6 +69,10 @@ parseArgs() {
|
|||||||
(--sane-sandbox-disable)
|
(--sane-sandbox-disable)
|
||||||
SANE_SANDBOX_DISABLE=1
|
SANE_SANDBOX_DISABLE=1
|
||||||
;;
|
;;
|
||||||
|
(--sane-sandbox-method)
|
||||||
|
method="$1"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
(--sane-sandbox-dns)
|
(--sane-sandbox-dns)
|
||||||
dns+=("$1")
|
dns+=("$1")
|
||||||
shift
|
shift
|
||||||
@@ -75,6 +81,10 @@ parseArgs() {
|
|||||||
firejailFlags+=("$1")
|
firejailFlags+=("$1")
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
(--sane-sandbox-bwrap-arg)
|
||||||
|
bwrapFlags+=("$1")
|
||||||
|
shift
|
||||||
|
;;
|
||||||
(--sane-sandbox-net)
|
(--sane-sandbox-net)
|
||||||
net="$1"
|
net="$1"
|
||||||
shift
|
shift
|
||||||
@@ -98,36 +108,87 @@ parseArgs() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## FIREJAIL BACKEND
|
||||||
|
|
||||||
|
firejailIngestRootPath() {
|
||||||
|
firejailFlags+=("--noblacklist=$1" "--whitelist=$1")
|
||||||
|
}
|
||||||
|
firejailIngestHomePath() {
|
||||||
|
firejailFlags+=("--noblacklist="'${HOME}/'"$1" "--whitelist="'${HOME}/'"$1")
|
||||||
|
}
|
||||||
|
firejailIngestNet() {
|
||||||
|
firejailFlags+=("--net=$1")
|
||||||
|
}
|
||||||
|
firejailIngestDns() {
|
||||||
|
firejailFlags+=("--dns=$1")
|
||||||
|
}
|
||||||
|
firejailIngestName() {
|
||||||
|
firejailFlags+=("--join-or-start=$1")
|
||||||
|
}
|
||||||
|
|
||||||
|
firejailExec() {
|
||||||
|
if [ -n "$firejailProfile" ]; then
|
||||||
|
firejailFlags+=("--profile=$firejailProfile")
|
||||||
|
fi
|
||||||
|
PATH="$PATH:@firejail@/bin" exec firejail "${firejailFlags[@]}" -- "${cliArgs[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## BUBBLEWRAP BACKEND
|
||||||
|
|
||||||
|
bwrapIngestRootPath() {
|
||||||
|
bwrapFlags+=("--dev-bind" "$1" "$1")
|
||||||
|
}
|
||||||
|
bwrapIngestHomePath() {
|
||||||
|
bwrapFlags+=("--dev-bind" "$HOME/$1" "$HOME/$1")
|
||||||
|
}
|
||||||
|
bwrapIngestName() {
|
||||||
|
echo "bwrap naming/labeling not yet implemented"
|
||||||
|
}
|
||||||
|
# WIP
|
||||||
|
|
||||||
|
bwrapExec() {
|
||||||
|
PATH="$PATH:@bubblewrap@/bin" exec bwrap --dev /dev --proc /proc "${bwrapFlags[@]}" -- "${cliArgs[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## BACKEND HANDOFF
|
||||||
|
|
||||||
parseArgs "$@"
|
parseArgs "$@"
|
||||||
cliArgs+="${parseArgsExtra[@]}"
|
cliArgs+=("${parseArgsExtra[@]}")
|
||||||
|
|
||||||
test -n "$SANE_SANDBOX_DISABLE" && exec "${cliArgs[@]}"
|
test -n "$SANE_SANDBOX_DISABLE" && exec "${cliArgs[@]}"
|
||||||
|
|
||||||
## construct firejail flags from sane-sandbox flags
|
test "$method" = "exit0fortest" && echo "exiting 0 for test" && exit 0
|
||||||
|
|
||||||
|
# drop into an interactive shell to e.g. inspect the mount space
|
||||||
|
test "$method" = "debugshell" && exec sh
|
||||||
|
|
||||||
|
### convert generic args into sandbox-specific args
|
||||||
|
# order matters: for firejail, early args override the later --profile args
|
||||||
|
|
||||||
for _path in "${rootPaths[@]}"; do
|
for _path in "${rootPaths[@]}"; do
|
||||||
firejailFlags+=("--noblacklist=$_path" "--whitelist=$_path")
|
"$method"IngestRootPath "$_path"
|
||||||
done
|
done
|
||||||
|
|
||||||
for _path in "${homePaths[@]}"; do
|
for _path in "${homePaths[@]}"; do
|
||||||
firejailFlags+=("--noblacklist="'${HOME}/'"$_path" "--whitelist="'${HOME}/'"$_path")
|
"$method"IngestHomePath "$_path"
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ -n "$net" ]; then
|
if [ -n "$net" ]; then
|
||||||
firejailFlags+=("--net=$net")
|
"$method"IngestNet "$net"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for _addr in "${dns[@]}"; do
|
for _addr in "${dns[@]}"; do
|
||||||
firejailFlags+=("--dns=$_addr")
|
"$method"IngestDns "$_addr"
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ -n "$name" ]; then
|
if [ -n "$name" ]; then
|
||||||
firejailFlags+=("--join-or-start=$name")
|
"$method"IngestName "$name"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# order matters: customizations (i.e. the above) must be before --profile
|
"$method"Exec
|
||||||
if [ -n "$firejailProfile" ]; then
|
|
||||||
firejailFlags+=("--profile=$firejailProfile")
|
|
||||||
fi
|
|
||||||
|
|
||||||
PATH="$PATH:@firejail@" exec firejail "${firejailFlags[@]}" -- "${cliArgs[@]}"
|
echo "sandbox glue failed for method='$method'"
|
||||||
|
exit 1
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
{ lib, stdenv
|
{ lib, stdenv
|
||||||
|
, bubblewrap
|
||||||
, firejail
|
, firejail
|
||||||
, runtimeShell
|
, runtimeShell
|
||||||
, substituteAll
|
, substituteAll
|
||||||
@@ -8,8 +9,7 @@
|
|||||||
let
|
let
|
||||||
sane-sandboxed = substituteAll {
|
sane-sandboxed = substituteAll {
|
||||||
src = ./sane-sandboxed;
|
src = ./sane-sandboxed;
|
||||||
inherit runtimeShell;
|
inherit bubblewrap firejail runtimeShell;
|
||||||
firejail = "${firejail}/bin/firejail";
|
|
||||||
firejailProfileDirs = "/run/current-system/sw/etc/firejail /etc/firejail ${firejail}/etc/firejail";
|
firejailProfileDirs = "/run/current-system/sw/etc/firejail /etc/firejail ${firejail}/etc/firejail";
|
||||||
# /run might be unavailable inside a container, so to support nested containers
|
# /run might be unavailable inside a container, so to support nested containers
|
||||||
# fallback to a profile dir adjacent to the sane-sandboxed binary
|
# fallback to a profile dir adjacent to the sane-sandboxed binary
|
||||||
|
Reference in New Issue
Block a user