sane-sandboxed: add more autodetect options, and a "withEmbeddedSandboxer" package output (for dev)

This commit is contained in:
Colin 2024-02-03 00:17:24 +00:00
parent 24e6e6cacc
commit 3439ca34b8
3 changed files with 109 additions and 77 deletions

View File

@ -43,7 +43,17 @@ let
in
makeSandboxed {
inherit pkgName package;
inherit (sandbox) autodetectCliPaths binMap capabilities embedProfile embedSandboxer extraConfig method whitelistPwd wrapperType;
inherit (sandbox)
autodetectCliPaths
binMap
capabilities
embedProfile
embedSandboxer
extraConfig
method
whitelistPwd
wrapperType
;
vpn = if net == "vpn" then vpn else null;
allowedHomePaths = builtins.attrNames fs ++ builtins.attrNames persist.byPath ++ sandbox.extraHomePaths ++ [
".config/mimeo" #< TODO: required, until i fully integrate xdg-open into sandboxing. else, `xdg-open https://...` inifinite-loops.
@ -259,10 +269,15 @@ let
'';
};
sandbox.autodetectCliPaths = mkOption {
type = types.bool;
default = false;
type = types.coercedTo types.bool
(b: if b then "existing" else null)
(types.nullOr (types.enum [ "existing" "existingFileOrParent" ]));
default = null;
description = ''
if a CLI argument looks like a PATH, should we add it to the sandbox?
- null => never
- "existing" => only if the file exists
- "existingFileOrParent" => add the file if it exists; if not, add its parent if that exists. useful for programs which create files.
'';
};
sandbox.whitelistPwd = mkOption {

View File

@ -199,87 +199,89 @@ let
};
});
in
{ pkgName, package, method, wrapperType, vpn ? null, allowedHomePaths ? [], allowedRootPaths ? [], autodetectCliPaths ? false, binMap ? {}, capabilities ? [], embedProfile ? false, embedSandboxer ? false, extraConfig ? [], whitelistPwd ? false }:
let
sane-sandboxed' = if embedSandboxer then
# optionally hard-code the sandboxer. this forces rebuilds, but allows deep iteration w/o deploys.
lib.getExe sane-sandboxed
else
#v prefer to load by bin name to reduce rebuilds
sane-sandboxed.meta.mainProgram
;
make-sandboxed = { pkgName, package, method, wrapperType, vpn ? null, allowedHomePaths ? [], allowedRootPaths ? [], autodetectCliPaths ? null, binMap ? {}, capabilities ? [], embedProfile ? false, embedSandboxer ? false, extraConfig ? [], whitelistPwd ? false }@args:
let
sane-sandboxed' = if embedSandboxer then
# optionally hard-code the sandboxer. this forces rebuilds, but allows deep iteration w/o deploys.
lib.getExe sane-sandboxed
else
#v prefer to load by bin name to reduce rebuilds
sane-sandboxed.meta.mainProgram
;
allowPath = p: [
"--sane-sandbox-path"
p
];
allowHomePath = p: [
"--sane-sandbox-home-path"
p
];
allowPaths = paths: lib.flatten (builtins.map allowPath paths);
allowHomePaths = paths: lib.flatten (builtins.map allowHomePath paths);
allowPath = p: [
"--sane-sandbox-path"
p
];
allowHomePath = p: [
"--sane-sandbox-home-path"
p
];
allowPaths = paths: lib.flatten (builtins.map allowPath paths);
allowHomePaths = paths: lib.flatten (builtins.map allowHomePath paths);
capabilityFlags = lib.flatten (builtins.map (c: [ "--sane-sandbox-cap" c ]) capabilities);
capabilityFlags = lib.flatten (builtins.map (c: [ "--sane-sandbox-cap" c ]) capabilities);
vpnItems = [
"--sane-sandbox-net"
vpn.bridgeDevice
] ++ lib.flatten (builtins.map (addr: [
"--sane-sandbox-dns"
addr
]) vpn.dns);
vpnItems = [
"--sane-sandbox-net"
vpn.bridgeDevice
] ++ lib.flatten (builtins.map (addr: [
"--sane-sandbox-dns"
addr
]) vpn.dns);
sandboxFlags = [
"--sane-sandbox-method" method
] ++ allowPaths allowedRootPaths
++ allowHomePaths allowedHomePaths
++ capabilityFlags
++ lib.optionals autodetectCliPaths [ "--sane-sandbox-autodetect" ]
++ lib.optionals whitelistPwd [ "--sane-sandbox-add-pwd" ]
++ lib.optionals (vpn != null) vpnItems
++ extraConfig;
sandboxFlags = [
"--sane-sandbox-method" method
] ++ allowPaths allowedRootPaths
++ allowHomePaths allowedHomePaths
++ capabilityFlags
++ lib.optionals (autodetectCliPaths != null) [ "--sane-sandbox-autodetect" autodetectCliPaths ]
++ lib.optionals whitelistPwd [ "--sane-sandbox-add-pwd" ]
++ lib.optionals (vpn != null) vpnItems
++ extraConfig;
sandboxProfilesPkg = writeTextFile {
name = "${pkgName}-sandbox-profiles";
destination = "/share/sane-sandboxed/profiles/${pkgName}.profile";
text = builtins.concatStringsSep "\n" sandboxFlags;
};
sandboxProfileDir = "${sandboxProfilesPkg}/share/sane-sandboxed/profiles";
sandboxProfilesPkg = writeTextFile {
name = "${pkgName}-sandbox-profiles";
destination = "/share/sane-sandboxed/profiles/${pkgName}.profile";
text = builtins.concatStringsSep "\n" sandboxFlags;
};
sandboxProfileDir = "${sandboxProfilesPkg}/share/sane-sandboxed/profiles";
maybeEmbedProfilesDir = lib.optionalString embedProfile ''"--sane-sandbox-profile-dir" "${sandboxProfileDir}"'';
maybeEmbedProfilesDir = lib.optionalString embedProfile ''"--sane-sandbox-profile-dir" "${sandboxProfileDir}"'';
# two ways i could wrap a package in a sandbox:
# 1. package.overrideAttrs, with `postFixup`.
# 2. pkgs.symlinkJoin, creating an entirely new package which calls into the inner binaries.
#
# here we switch between the options.
# regardless of which one is chosen here, all other options are exposed via `passthru`.
sandboxedBy = {
inplace = sandboxBinariesInPlace
binMap
sane-sandboxed'
maybeEmbedProfilesDir
pkgName
(makeHookable package);
wrappedDerivation = let
binaries = sandboxBinariesInPlace
# two ways i could wrap a package in a sandbox:
# 1. package.overrideAttrs, with `postFixup`.
# 2. pkgs.symlinkJoin, creating an entirely new package which calls into the inner binaries.
#
# here we switch between the options.
# regardless of which one is chosen here, all other options are exposed via `passthru`.
sandboxedBy = {
inplace = sandboxBinariesInPlace
binMap
sane-sandboxed'
maybeEmbedProfilesDir
pkgName
(symlinkBinaries pkgName package);
nonBinaries = copyNonBinaries pkgName package binaries;
in symlinkJoin {
name = "${pkgName}-sandboxed-all";
paths = [ binaries nonBinaries ];
passthru = { inherit binaries nonBinaries; };
(makeHookable package);
wrappedDerivation = let
binaries = sandboxBinariesInPlace
binMap
sane-sandboxed'
maybeEmbedProfilesDir
pkgName
(symlinkBinaries pkgName package);
nonBinaries = copyNonBinaries pkgName package binaries;
in symlinkJoin {
name = "${pkgName}-sandboxed-all";
paths = [ binaries nonBinaries ];
passthru = { inherit binaries nonBinaries; };
};
};
};
packageWrapped = sandboxedBy."${wrapperType}";
in
fixupMetaAndPassthru pkgName packageWrapped sandboxProfilesPkg {
inherit sandboxedBy;
}
packageWrapped = sandboxedBy."${wrapperType}";
in
fixupMetaAndPassthru pkgName packageWrapped sandboxProfilesPkg {
inherit sandboxedBy;
withEmbeddedSandboxer = make-sandboxed (args // { embedSandboxer = true; });
}
;
in make-sandboxed

View File

@ -63,11 +63,17 @@ urldecode() {
echo -e "${_//%/\\x}"
}
# return the path to this file or directory's parent, even if the input doesn't exist.
parent() {
realpath --logical --no-symlinks --canonicalize-missing "$1/.."
}
# if the argument looks path-like, then add it to cliPathArgs.
# this function ingests absolute, relative, or file:///-type URIs.
# but it converts any such path into an absolute path before adding it to cliPathArgs.
tryArgAsPath() {
_arg="$1"
_how="$2"
_path=
if [ "${_arg:0:1}" = "/" ]; then
# absolute path
@ -84,6 +90,14 @@ tryArgAsPath() {
if [ -e "$_path" ]; then
cliPathArgs+=("$_path")
elif [ "$_how" = "existingFileOrParent" ]; then
# the path doesn't exist, but that's because the program might create it.
if [ "${_path:0:1}" = "-" ]; then
# 99% chance it's a CLI argument. if not, use `./-<...>`
return
elif [ -e "$(parent "$_path/..")" ]; then
cliPathArgs+=("$_path/..")
fi
fi
}
@ -148,7 +162,8 @@ parseArgs() {
# this is handy for e.g. media players or document viewers.
# it's best combined with some two-tiered thing.
# e.g. first drop to the broadest path set of interest (Music,Videos,tmp, ...), then drop via autodetect.
autodetect=1
autodetect="$1"
shift
;;
(--sane-sandbox-cap)
_cap="$1"
@ -381,7 +396,7 @@ capshonlyExec() {
maybeAutodetectPaths() {
if [ -n "$autodetect" ]; then
for _arg in "${cliArgs[@]:1}"; do
tryArgAsPath "$_arg"
tryArgAsPath "$_arg" "$autodetect"
done
for _path in "${cliPathArgs[@]}"; do
# TODO: might want to also mount the directory *above* this file,