gocryptfs: sandbox with bunpen

This commit is contained in:
2024-09-10 00:02:03 +00:00
parent 6f72453f5d
commit 8ae7e255e5
6 changed files with 34 additions and 20 deletions

View File

@@ -1,11 +1,13 @@
{ ... }: { ... }:
{ {
sane.programs.gocryptfs = { sane.programs.gocryptfs = {
sandbox.method = "landlock"; sandbox.method = "bunpen";
sandbox.autodetectCliPaths = "existing"; sandbox.autodetectCliPaths = "existing";
sandbox.capabilities = [ sandbox.capabilities = [
# CAP_SYS_ADMIN is only required if directly invoking gocryptfs # CAP_SYS_ADMIN is only required if directly invoking gocryptfs.
# i.e. not leverage a mount helper like `mount.fuse3-sane`. # it's not *necessarily* required if using a mount helper like `mount.fuse3-sane`
# however if using a namespace-based sandbox method (bunpen, bwrap), and you wish
# to preserve user mappings, it's still required.
"sys_admin" "sys_admin"
"chown" "chown"
"dac_override" "dac_override"
@@ -16,8 +18,10 @@
"setgid" "setgid"
"setuid" "setuid"
]; ];
sandbox.tryKeepUsers = true;
sandbox.keepPids = true;
suggestedPrograms = [ suggestedPrograms = [
"util-linux" #< gocryptfs complains that it can't exec `logger`, otherwise "util-linux" #< gocryptfs complains that it can't exec `logger`, otherwise. TODO(2024-09-09): is this still needed?
]; ];
}; };
} }

View File

@@ -17,10 +17,12 @@ lib.mkIf config.sane.persist.enable
"gocryptfs" "gocryptfs"
]; ];
}; };
sandbox.method = "landlock"; suggestedPrograms = [ "gocryptfs" ];
sandbox.method = "bunpen";
sandbox.autodetectCliPaths = "existing"; sandbox.autodetectCliPaths = "existing";
sandbox.capabilities = [ sandbox.capabilities = [
# "sys_admin" #< omitted: not required if using fuse3-sane with -o pass_fuse_fd "sys_admin" #< XXX: this is required to keep user mappings; for single-user it's actually not necessary if using fuse3-sane with -o pass_fuse_fd
"chown" "chown"
"dac_override" "dac_override"
"dac_read_search" "dac_read_search"
@@ -30,7 +32,8 @@ lib.mkIf config.sane.persist.enable
"setgid" "setgid"
"setuid" "setuid"
]; ];
suggestedPrograms = [ "gocryptfs" ]; sandbox.tryKeepUsers = true;
sandbox.keepPids = true;
}; };
sane.persist.stores."ephemeral" = { sane.persist.stores."ephemeral" = {
@@ -59,6 +62,7 @@ lib.mkIf config.sane.persist.enable
mount.depends = [ mount.depends = [
config.sane.fs."${backing}".unit config.sane.fs."${backing}".unit
]; ];
# hardening (systemd-analyze security mnt-persist-ephemeral.mount) # hardening (systemd-analyze security mnt-persist-ephemeral.mount)
mount.mountConfig.AmbientCapabilities = "CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_CHOWN CAP_MKNOD CAP_LEASE CAP_SETGID CAP_SETUID CAP_FOWNER"; mount.mountConfig.AmbientCapabilities = "CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_CHOWN CAP_MKNOD CAP_LEASE CAP_SETGID CAP_SETUID CAP_FOWNER";
# CAP_LEASE is probably not necessary -- does any fs user use leases? # CAP_LEASE is probably not necessary -- does any fs user use leases?
@@ -73,7 +77,7 @@ lib.mkIf config.sane.persist.enable
#VVV this includes anything it reads from, e.g. /bin/sh; /nix/store/... #VVV this includes anything it reads from, e.g. /bin/sh; /nix/store/...
# see `systemd-analyze filesystems` for a full list # see `systemd-analyze filesystems` for a full list
mount.mountConfig.RestrictFileSystems = "@common-block @basic-api fuse pipefs"; mount.mountConfig.RestrictFileSystems = "@common-block @basic-api fuse pipefs";
mount.mountConfig.RestrictNamespaces = true; # mount.mountConfig.RestrictNamespaces = true;
mount.mountConfig.RestrictNetworkInterfaces = ""; mount.mountConfig.RestrictNetworkInterfaces = "";
mount.mountConfig.RestrictRealtime = true; mount.mountConfig.RestrictRealtime = true;
mount.mountConfig.RestrictSUIDSGID = true; mount.mountConfig.RestrictSUIDSGID = true;

View File

@@ -7,11 +7,12 @@ backing=$1
# facing=$2 # facing=$2
# backing might exist from the last boot, so wipe it: # backing might exist from the last boot, so wipe it:
# TODO: should be `rm $backing/*`??
rm -fr "$backing" rm -fr "$backing"
mkdir -p "$backing" mkdir -p "$backing"
# the password shows up in /proc/.../env, briefly. # the password shows up in /proc/.../env, briefly.
# that's inconsequential: we just care that it's not *persisted*. # that's inconsequential: we just care that it's not *persisted*.
pw=$(dd if=/dev/random bs=128 count=1 | base64 --wrap=0) pw=$(dd if=/dev/random bs=128 count=1 | base64 --wrap=0)
echo "$pw" | gocryptfs -quiet -passfile /dev/fd/0 -init "$backing" echo "$pw" | gocryptfs -quiet -nosyslog -passfile /dev/fd/0 -init "$backing"
echo "$pw" | exec gocryptfs -quiet -passfile /dev/fd/0 "$@" echo "$pw" | exec gocryptfs -quiet -nosyslog -passfile /dev/fd/0 "$@"

View File

@@ -20,6 +20,7 @@ lib.mkIf config.sane.persist.enable
sandbox.method = "bunpen"; sandbox.method = "bunpen";
sandbox.autodetectCliPaths = "parent"; sandbox.autodetectCliPaths = "parent";
sandbox.tryKeepUsers = true; sandbox.tryKeepUsers = true;
sandbox.keepPids = true;
sandbox.capabilities = [ "dac_read_search" ]; #< TODO: this and `tryKeepUsers` shouldn't be needed (it wasn't needed with bwrap)... sandbox.capabilities = [ "dac_read_search" ]; #< TODO: this and `tryKeepUsers` shouldn't be needed (it wasn't needed with bwrap)...
}; };
sane.programs.gocryptfs-private = { sane.programs.gocryptfs-private = {
@@ -28,10 +29,10 @@ lib.mkIf config.sane.persist.enable
srcRoot = ./.; srcRoot = ./.;
pkgs = [ "gocryptfs" ]; pkgs = [ "gocryptfs" ];
}; };
sandbox.method = "landlock"; sandbox.method = "bunpen";
sandbox.autodetectCliPaths = "existing"; sandbox.autodetectCliPaths = "existing";
sandbox.capabilities = [ sandbox.capabilities = [
# "sys_admin" #< omitted: not required if using fuse3-sane with -o pass_fuse_fd "sys_admin" #< XXX: this is required to keep user mappings; for single-user it's actually not necessary if using fuse3-sane with -o pass_fuse_fd
"chown" "chown"
"dac_override" "dac_override"
"dac_read_search" "dac_read_search"
@@ -41,8 +42,10 @@ lib.mkIf config.sane.persist.enable
"setgid" "setgid"
"setuid" "setuid"
]; ];
sandbox.tryKeepUsers = true;
sandbox.keepPids = true;
sandbox.extraPaths = [ sandbox.extraPaths = [
"/run/gocryptfs" #< TODO: teach sanebox about `-o FLAG1=VALUE1,FLAG2=VALUE2` style of argument passing, then use `existingOrParent` autodetect, and remove this "/run/gocryptfs/private.key" #< TODO: teach sanebox about `-o FLAG1=VALUE1,FLAG2=VALUE2` style of argument passing, then use `existing` autodetect, and remove this
]; ];
suggestedPrograms = [ "gocryptfs" ]; suggestedPrograms = [ "gocryptfs" ];
}; };
@@ -77,10 +80,10 @@ lib.mkIf config.sane.persist.enable
"allow_other" # root ends up being the user that mounts this, so need to make it visible to other users. "allow_other" # root ends up being the user that mounts this, so need to make it visible to other users.
# "quiet" # "quiet"
# "defaults" # "unknown flag: --defaults. Try 'gocryptfs -help'" # "defaults" # "unknown flag: --defaults. Try 'gocryptfs -help'"
"passfile=/run/gocryptfs/private.key" # "passfile=/run/gocryptfs/private.key"
# options so that we can block for the password file *without* systemd killing us. # options so that we can block for the password file *without* systemd killing us.
# see: <https://bbs.archlinux.org/viewtopic.php?pid=1906174#p1906174> # see: <https://bbs.archlinux.org/viewtopic.php?pid=1906174#p1906174>
"x-systemd.mount-timeout=infinity" # "x-systemd.mount-timeout=infinity"
# "retry=10000" # "retry=10000"
# "fg" # "fg"
"pass_fuse_fd" "pass_fuse_fd"
@@ -96,7 +99,7 @@ lib.mkIf config.sane.persist.enable
config.sane.fs."/run/gocryptfs/private.key".unit config.sane.fs."/run/gocryptfs/private.key".unit
]; ];
# unitConfig.DefaultDependencies = "no"; # unitConfig.DefaultDependencies = "no";
mount.mountConfig.TimeoutSec = "infinity"; # mount.mountConfig.TimeoutSec = "infinity";
# hardening (systemd-analyze security mnt-persist-private.mount) # hardening (systemd-analyze security mnt-persist-private.mount)
mount.mountConfig.AmbientCapabilities = "CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_CHOWN CAP_MKNOD CAP_LEASE CAP_SETGID CAP_SETUID CAP_FOWNER"; mount.mountConfig.AmbientCapabilities = "CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_CHOWN CAP_MKNOD CAP_LEASE CAP_SETGID CAP_SETUID CAP_FOWNER";
@@ -110,7 +113,7 @@ lib.mkIf config.sane.persist.enable
mount.mountConfig.RemoveIPC = true; mount.mountConfig.RemoveIPC = true;
mount.mountConfig.RestrictAddressFamilies = "AF_UNIX"; # "none" works, but then it can't connect to the logger mount.mountConfig.RestrictAddressFamilies = "AF_UNIX"; # "none" works, but then it can't connect to the logger
mount.mountConfig.RestrictFileSystems = "@common-block @basic-api fuse pipefs"; mount.mountConfig.RestrictFileSystems = "@common-block @basic-api fuse pipefs";
mount.mountConfig.RestrictNamespaces = true; # mount.mountConfig.RestrictNamespaces = true;
mount.mountConfig.RestrictNetworkInterfaces = ""; mount.mountConfig.RestrictNetworkInterfaces = "";
mount.mountConfig.RestrictRealtime = true; mount.mountConfig.RestrictRealtime = true;
mount.mountConfig.RestrictSUIDSGID = true; mount.mountConfig.RestrictSUIDSGID = true;

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env nix-shell #!/usr/bin/env nix-shell
#!nix-shell -i bash -p bash -p gocryptfs #!nix-shell -i bash -p bash -p gocryptfs
passfile=/run/gocryptfs/private.key pass_file=/run/gocryptfs/private.key
gocryptfs --sanebox-path "$passfile" "$@" pass_str=$(cat "$pass_file")
rm "$passfile" rm "$pass_file"
echo "$pass_str" | exec gocryptfs -nosyslog -passfile /dev/fd/0 "$@"

View File

@@ -32,6 +32,7 @@ waitForPassfile() {
done done
} }
validatePassword() { validatePassword() {
echo "validating password ..."
if ! cat "$passfile" | gocryptfs-xray -dumpmasterkey "$conffile" > /dev/null; then if ! cat "$passfile" | gocryptfs-xray -dumpmasterkey "$conffile" > /dev/null; then
echo "failed key validation" echo "failed key validation"
rm -f "$passfile" rm -f "$passfile"