From 8ae7e255e55f7f0d29f59e664dd33433f53e5d04 Mon Sep 17 00:00:00 2001 From: Colin Date: Tue, 10 Sep 2024 00:02:03 +0000 Subject: [PATCH] gocryptfs: sandbox with bunpen --- hosts/common/programs/gocryptfs.nix | 12 ++++++++---- modules/persist/stores/ephemeral/default.nix | 12 ++++++++---- .../stores/ephemeral/gocryptfs-ephemeral | 5 +++-- modules/persist/stores/private/default.nix | 17 ++++++++++------- .../persist/stores/private/gocryptfs-private | 7 ++++--- .../stores/private/provision-private-key | 1 + 6 files changed, 34 insertions(+), 20 deletions(-) diff --git a/hosts/common/programs/gocryptfs.nix b/hosts/common/programs/gocryptfs.nix index c42863ac9..62d84a2f1 100644 --- a/hosts/common/programs/gocryptfs.nix +++ b/hosts/common/programs/gocryptfs.nix @@ -1,11 +1,13 @@ { ... }: { sane.programs.gocryptfs = { - sandbox.method = "landlock"; + sandbox.method = "bunpen"; sandbox.autodetectCliPaths = "existing"; sandbox.capabilities = [ - # CAP_SYS_ADMIN is only required if directly invoking gocryptfs - # i.e. not leverage a mount helper like `mount.fuse3-sane`. + # CAP_SYS_ADMIN is only required if directly invoking gocryptfs. + # 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" "chown" "dac_override" @@ -16,8 +18,10 @@ "setgid" "setuid" ]; + sandbox.tryKeepUsers = true; + sandbox.keepPids = true; 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? ]; }; } diff --git a/modules/persist/stores/ephemeral/default.nix b/modules/persist/stores/ephemeral/default.nix index c1402aa43..c1d908cea 100644 --- a/modules/persist/stores/ephemeral/default.nix +++ b/modules/persist/stores/ephemeral/default.nix @@ -17,10 +17,12 @@ lib.mkIf config.sane.persist.enable "gocryptfs" ]; }; - sandbox.method = "landlock"; + suggestedPrograms = [ "gocryptfs" ]; + + sandbox.method = "bunpen"; sandbox.autodetectCliPaths = "existing"; 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" "dac_override" "dac_read_search" @@ -30,7 +32,8 @@ lib.mkIf config.sane.persist.enable "setgid" "setuid" ]; - suggestedPrograms = [ "gocryptfs" ]; + sandbox.tryKeepUsers = true; + sandbox.keepPids = true; }; sane.persist.stores."ephemeral" = { @@ -59,6 +62,7 @@ lib.mkIf config.sane.persist.enable mount.depends = [ config.sane.fs."${backing}".unit ]; + # 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"; # 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/... # see `systemd-analyze filesystems` for a full list mount.mountConfig.RestrictFileSystems = "@common-block @basic-api fuse pipefs"; - mount.mountConfig.RestrictNamespaces = true; + # mount.mountConfig.RestrictNamespaces = true; mount.mountConfig.RestrictNetworkInterfaces = ""; mount.mountConfig.RestrictRealtime = true; mount.mountConfig.RestrictSUIDSGID = true; diff --git a/modules/persist/stores/ephemeral/gocryptfs-ephemeral b/modules/persist/stores/ephemeral/gocryptfs-ephemeral index a5a580b7f..5c26e83d4 100755 --- a/modules/persist/stores/ephemeral/gocryptfs-ephemeral +++ b/modules/persist/stores/ephemeral/gocryptfs-ephemeral @@ -7,11 +7,12 @@ backing=$1 # facing=$2 # backing might exist from the last boot, so wipe it: +# TODO: should be `rm $backing/*`?? rm -fr "$backing" mkdir -p "$backing" # the password shows up in /proc/.../env, briefly. # that's inconsequential: we just care that it's not *persisted*. pw=$(dd if=/dev/random bs=128 count=1 | base64 --wrap=0) -echo "$pw" | gocryptfs -quiet -passfile /dev/fd/0 -init "$backing" -echo "$pw" | exec gocryptfs -quiet -passfile /dev/fd/0 "$@" +echo "$pw" | gocryptfs -quiet -nosyslog -passfile /dev/fd/0 -init "$backing" +echo "$pw" | exec gocryptfs -quiet -nosyslog -passfile /dev/fd/0 "$@" diff --git a/modules/persist/stores/private/default.nix b/modules/persist/stores/private/default.nix index a023b33a5..1e2be599a 100644 --- a/modules/persist/stores/private/default.nix +++ b/modules/persist/stores/private/default.nix @@ -20,6 +20,7 @@ lib.mkIf config.sane.persist.enable sandbox.method = "bunpen"; sandbox.autodetectCliPaths = "parent"; 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)... }; sane.programs.gocryptfs-private = { @@ -28,10 +29,10 @@ lib.mkIf config.sane.persist.enable srcRoot = ./.; pkgs = [ "gocryptfs" ]; }; - sandbox.method = "landlock"; + sandbox.method = "bunpen"; sandbox.autodetectCliPaths = "existing"; 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" "dac_override" "dac_read_search" @@ -41,8 +42,10 @@ lib.mkIf config.sane.persist.enable "setgid" "setuid" ]; + sandbox.tryKeepUsers = true; + sandbox.keepPids = true; 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" ]; }; @@ -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. # "quiet" # "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. # see: - "x-systemd.mount-timeout=infinity" + # "x-systemd.mount-timeout=infinity" # "retry=10000" # "fg" "pass_fuse_fd" @@ -96,7 +99,7 @@ lib.mkIf config.sane.persist.enable config.sane.fs."/run/gocryptfs/private.key".unit ]; # unitConfig.DefaultDependencies = "no"; - mount.mountConfig.TimeoutSec = "infinity"; + # mount.mountConfig.TimeoutSec = "infinity"; # 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"; @@ -110,7 +113,7 @@ lib.mkIf config.sane.persist.enable mount.mountConfig.RemoveIPC = true; 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.RestrictNamespaces = true; + # mount.mountConfig.RestrictNamespaces = true; mount.mountConfig.RestrictNetworkInterfaces = ""; mount.mountConfig.RestrictRealtime = true; mount.mountConfig.RestrictSUIDSGID = true; diff --git a/modules/persist/stores/private/gocryptfs-private b/modules/persist/stores/private/gocryptfs-private index 785bb8947..3003e8301 100755 --- a/modules/persist/stores/private/gocryptfs-private +++ b/modules/persist/stores/private/gocryptfs-private @@ -1,6 +1,7 @@ #!/usr/bin/env nix-shell #!nix-shell -i bash -p bash -p gocryptfs -passfile=/run/gocryptfs/private.key -gocryptfs --sanebox-path "$passfile" "$@" -rm "$passfile" +pass_file=/run/gocryptfs/private.key +pass_str=$(cat "$pass_file") +rm "$pass_file" +echo "$pass_str" | exec gocryptfs -nosyslog -passfile /dev/fd/0 "$@" diff --git a/modules/persist/stores/private/provision-private-key b/modules/persist/stores/private/provision-private-key index c6dd660b0..1a2dc1656 100755 --- a/modules/persist/stores/private/provision-private-key +++ b/modules/persist/stores/private/provision-private-key @@ -32,6 +32,7 @@ waitForPassfile() { done } validatePassword() { + echo "validating password ..." if ! cat "$passfile" | gocryptfs-xray -dumpmasterkey "$conffile" > /dev/null; then echo "failed key validation" rm -f "$passfile"