sanebox: integrate with pasta (passt) for better net sandboxing

This commit is contained in:
Colin 2024-05-25 09:39:18 +00:00
parent 118ed5f950
commit 7b1bc210fd
5 changed files with 82 additions and 9 deletions

View File

@ -723,6 +723,8 @@ in
# settings (electron app)
obsidian.persist.byStore.plaintext = [ ".config/obsidian" ];
passt.sandbox.enable = false; #< sandbox helper (netns specifically)
parted.sandbox.method = "landlock";
parted.sandbox.extraPaths = [
"/dev"

View File

@ -74,6 +74,10 @@ let
vpn.name
else
sandbox.net;
netGateway = if sandbox.net == "vpn" then
vpn.addrV4
else
null;
dns = if sandbox.net == "vpn" then
vpn.dns
else
@ -429,9 +433,10 @@ let
null
else
wrapPkg name config config.packageUnwrapped
;
suggestedPrograms = lib.optionals (config.sandbox.method == "bwrap") [ "bubblewrap" ]
++ lib.optionals (config.sandbox.method == "firejail") [ "firejail" ];
;
suggestedPrograms = lib.optionals (config.sandbox.method == "bwrap") [
"bubblewrap" "passt"
] ++ lib.optionals (config.sandbox.method == "firejail") [ "firejail" ];
# declare a fs dependency for each secret, but don't specify how to populate it yet.
# can't populate it here because it varies per-user.
# this gets the symlink into the sandbox, but not the actual secret.

View File

@ -7,6 +7,7 @@
, capabilities ? []
, dns ? null
, netDev ? null
, netGateway ? null
, whitelistPwd ? false
, extraConfig ? []
}:
@ -22,6 +23,9 @@ let
netItems = lib.optionals (netDev != null) [
"--sanebox-net-dev"
netDev
] ++ lib.optionals (netGateway != null) [
"--sanebox-net-gateway"
netGateway
] ++ lib.optionals (dns != null) (
lib.flatten (builtins.map
(addr: [ "--sanebox-dns" addr ])

View File

@ -1,6 +1,7 @@
{ lib, stdenv
, bash
, bubblewrap
, passt
, firejail
, landlock-sandboxer
, libcap
@ -24,7 +25,8 @@ stdenv.mkDerivation {
--replace-fail '@bwrap@' '${lib.getExe bubblewrap}' \
--replace-fail '@firejail@' '${lib.getExe' firejail "firejail"}' \
--replace-fail '@landlockSandboxer@' '${lib.getExe landlock-sandboxer}' \
--replace-fail '@capsh@' '${lib.getExe' libcap "capsh"}'
--replace-fail '@capsh@' '${lib.getExe' libcap "capsh"}' \
--replace-fail '@pasta@' '${lib.getExe' passt "pasta"}'
runHook postBuild
'';

View File

@ -6,6 +6,7 @@ FIREJAIL_FALLBACK='@firejail@'
BWRAP_FALLBACK='@bwrap@'
LANDLOCK_SANDBOXER_FALLBACK='@landlockSandboxer@'
CAPSH_FALLBACK='@capsh@'
PASTA_FALLBACK='@pasta@'
## EARLY DEBUG HOOKS
@ -80,7 +81,10 @@ capabilities=()
# - "all": as if all the above were specified
keepNamespace=()
# name of some network device to make available to the sandbox, if any.
# or "all" to keep all devices available
netDev=
# IPv4 address of the default gateway associated with the bridged network device (usually that's just the VPN's IP addr)
netGateway=
# list of IP addresses to use for DNS servers inside the sandbox (firejail only)
dns=()
# list of `VAR=VALUE` environment variables to add to the sandboxed program's environment
@ -121,6 +125,7 @@ usage() {
echo ' --sanebox-firejail-arg <arg>'
echo ' --sanebox-bwrap-arg <arg>'
echo ' --sanebox-net-dev <iface>'
echo ' --sanebox-net-gateway <ip-address>'
echo ' --sanebox-dns <server>'
echo ' --sanebox-keep-namespace <cgroup|ipc|pid|uts|all>'
echo ' do not unshare the provided linux namespace'
@ -516,10 +521,14 @@ parseArgs() {
netDev=$1
shift
;;
(--sanebox-dns)
local dns=$1
(--sanebox-net-gateway)
netGateway=$1
shift
dns+=("$dns")
;;
(--sanebox-dns)
local dnsServer=$1
shift
dns+=("$dnsServer")
;;
(--sanebox-keep-namespace)
local namespace=$1
@ -578,6 +587,9 @@ firejailIngestNetDev() {
# firejail can then spawn a veth from this bridge and namespace it that way.
firejailFlags+=("--net=br-$1")
}
firejailIngestNetGateway() {
debug "firejailIngestNetGateway: noop"
}
firejailIngestDns() {
firejailFlags+=("--dns=$1")
}
@ -604,6 +616,9 @@ bwrapUnshareUts=(--unshare-uts)
bwrapVirtualizeDev=(--dev /dev)
bwrapVirtualizeProc=(--proc /proc)
bwrapVirtualizeTmp=(--tmpfs /tmp)
# args to invoke `pasta` (user-mode network stack) with
bwrapPastaArgs=()
bwrapNetSetup=
bwrapSetup() {
debug "bwrapSetup: noop"
@ -652,8 +667,22 @@ bwrapIngestPath() {
esac
}
bwrapIngestNetDev() {
debug "bwrapIngestNetDev: enabling full net access for '$1' because don't know how to restrict it more narrowly"
local dev=$1
bwrapUnshareNet=()
case $dev in
(all)
;;
(*)
bwrapPastaArgs+=(--outbound-if4 "$dev")
;;
esac
}
bwrapIngestNetGateway() {
bwrapPastaArgs+=(--gateway "$1")
}
bwrapIngestDns() {
# NAT DNS requests to localhost to the VPN's DNS resolver
bwrapNetSetup="ip addr del 127.0.0.1/8 dev lo; iptables -A OUTPUT -t nat -p udp --dport 53 -m iprange --dst-range 127.0.0.1 -j DNAT --to-destination $1:53; $bwrapNetSetup"
}
bwrapIngestKeepNamespace() {
case $1 in
@ -693,6 +722,21 @@ bwrapGetCli() {
"${bwrapFlags[@]}" --
env "${portalEnv[@]}" "${cliArgs[@]}"
)
if [ ${#bwrapPastaArgs} -ne 0 ]; then
# if [ -n "$bwrapNetSetup" ]; then
cliArgs=(
"/bin/sh" "-c"
"$bwrapNetSetup exec"' "$0" "$@"'
"${cliArgs[@]}"
)
# fi
locate _pasta "pasta" "$PASTA_FALLBACK"
cliArgs=(
"$_pasta" --ipv4-only -U none -T none --config-net
"${bwrapPastaArgs[@]}" --
"${cliArgs[@]}"
)
fi
}
@ -741,6 +785,12 @@ landlockIngestPath() {
landlockIngestNetDev() {
debug "landlockIngestNetDev: '$1': stubbed (landlock network is always unrestricted)"
}
landlockIngestNetGateway() {
debug "landlockIngestNetGateway: noop"
}
landlockIngestDns() {
debug "landlockIngestDns: noop"
}
landlockIngestKeepNamespace() {
debug "landlockIngestKeepNamespace: noop"
}
@ -776,6 +826,12 @@ capshonlyIngestPath() {
capshonlyIngestNetDev() {
debug "capshonlyIngestNetDev: '$1': stubbed (capsh network is always unrestricted)"
}
capshonlyIngestNetGateway() {
debug "capshonlyIngestNetGateway: '$1': stubbed (capsh network is always unrestricted)"
}
capshonlyIngestDns() {
debug "capshonlyIngestDns: '$1': stubbed (capsh network is always unrestricted)"
}
capshonlyIngestKeepNamespace() {
debug "capshonlyIngestKeepNamespace: noop"
}
@ -944,7 +1000,11 @@ ingestForBackend() {
done
if [ -n "$netDev" ]; then
"$method"IngestNetDev"$netDev"
"$method"IngestNetDev "$netDev"
fi
if [ -n "$netGateway" ]; then
"$method"IngestNetGateway "$netGateway"
fi
for addr in "${dns[@]}"; do