/mnt/persist/private: split waiting on the keyfile out of the mount process
This commit is contained in:
@@ -1,13 +1,3 @@
|
||||
# TODO: this can be simplified.
|
||||
# mounting /mnt/persist/private consists of two steps:
|
||||
# - way for a password to appear at /run/gocryptfs/private.key
|
||||
# - try mounting the filesystem, using that password
|
||||
# and retry if that fails.
|
||||
# because fs mounts lack a retry capability, that whole thing is encapsulated inside one step,
|
||||
# and then there's dancing to ensure that `test -L /mnt/persist/private` doesn't hang before the store has been mounted.
|
||||
# if i can break these apart, the implementation should look a lot less confusing.
|
||||
#
|
||||
# i can probably use `echo $passwd | gocryptfs-xray -dumpmasterkey /nix/persist/private/gocryptfs.conf` for the above
|
||||
{ config, lib, pkgs, sane-lib, utils, ... }:
|
||||
|
||||
let
|
||||
@@ -17,34 +7,18 @@ let
|
||||
in
|
||||
lib.mkIf config.sane.persist.enable
|
||||
{
|
||||
sane.programs."mount.fuse3.gocryptfs-private" = {
|
||||
sane.programs."provision-private-key" = {
|
||||
packageUnwrapped = pkgs.static-nix-shell.mkBash {
|
||||
pname = "mount.fuse3.gocryptfs-private";
|
||||
pname = "provision-private-key";
|
||||
srcRoot = ./.;
|
||||
pkgs = [
|
||||
"coreutils-full"
|
||||
"gocryptfs"
|
||||
"inotify-tools"
|
||||
"libfuse-sane"
|
||||
];
|
||||
};
|
||||
# sandbox.method = "landlock"; #< trickier than it looks to enable sandboxing of any kind here.
|
||||
sandbox.autodetectCliPaths = "existing";
|
||||
sandbox.capabilities = [
|
||||
"sys_admin"
|
||||
"chown"
|
||||
"dac_override"
|
||||
"dac_read_search"
|
||||
"fowner"
|
||||
"lease"
|
||||
"mknod"
|
||||
"setgid"
|
||||
"setuid"
|
||||
];
|
||||
sandbox.extraPaths = [
|
||||
"/dev/fuse"
|
||||
"/run/gocryptfs"
|
||||
];
|
||||
suggestedPrograms = [ "gocryptfs-private" ];
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.autodetectCliPaths = "parent";
|
||||
};
|
||||
sane.programs.gocryptfs-private = {
|
||||
packageUnwrapped = pkgs.static-nix-shell.mkBash {
|
||||
@@ -91,7 +65,7 @@ lib.mkIf config.sane.persist.enable
|
||||
|
||||
fileSystems."${origin}" = {
|
||||
device = "gocryptfs-private#${backing}";
|
||||
fsType = "fuse3.gocryptfs-private";
|
||||
fsType = "fuse3.sane";
|
||||
options = [
|
||||
# "auto"
|
||||
"nofail"
|
||||
@@ -117,7 +91,7 @@ lib.mkIf config.sane.persist.enable
|
||||
wantedBy = [ "local-fs.target" ];
|
||||
mount.depends = [
|
||||
config.sane.fs."${backing}".unit
|
||||
config.sane.fs."/run/gocryptfs".unit
|
||||
config.sane.fs."/run/gocryptfs/private.key".unit
|
||||
];
|
||||
# unitConfig.DefaultDependencies = "no";
|
||||
mount.mountConfig.TimeoutSec = "infinity";
|
||||
@@ -154,9 +128,15 @@ lib.mkIf config.sane.persist.enable
|
||||
user = config.sane.defaultUser; #< must be user-writable so i can unlock it.
|
||||
mode = "0770";
|
||||
};
|
||||
sane.fs."/run/gocryptfs/private.key".generated.command = [
|
||||
"${lib.getExe config.sane.programs.provision-private-key.package}"
|
||||
"/run/gocryptfs/private.key"
|
||||
"${backing}/gocryptfs.conf"
|
||||
];
|
||||
|
||||
sane.programs."mount.fuse3.gocryptfs-private".enableFor.system = true;
|
||||
system.fsPackages = [ pkgs.libfuse-sane config.sane.programs."mount.fuse3.gocryptfs-private".package ];
|
||||
sane.programs."gocryptfs-private".enableFor.system = true;
|
||||
sane.programs."provision-private-key".enableFor.system = true;
|
||||
system.fsPackages = [ pkgs.libfuse-sane ];
|
||||
|
||||
sane.user.services.gocryptfs-private = {
|
||||
description = "wait for /mnt/persist/private to be mounted";
|
||||
|
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash -p bash -p gocryptfs
|
||||
|
||||
exec gocryptfs --sanebox-path /run/gocryptfs/private.key "$@"
|
||||
passfile=/run/gocryptfs/private.key
|
||||
gocryptfs --sanebox-path "$passfile" "$@"
|
||||
rm "$passfile"
|
||||
|
@@ -1,66 +0,0 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash -p bash -p coreutils-full -p inotify-tools -p libfuse-sane
|
||||
|
||||
# backing=$1
|
||||
# facing=$2
|
||||
mountArgs=("$@")
|
||||
passdir=/run/gocryptfs
|
||||
passfile="$passdir/private.key"
|
||||
|
||||
waitForPassfileOnce() {
|
||||
local timeout=$1
|
||||
if [ -f "$passfile" ]; then
|
||||
return 0
|
||||
else
|
||||
# wait for some file to be created inside the directory.
|
||||
# inotifywait returns 0 if the file was created. 1 or 2 if timeout was hit or it was interrupted by a different event.
|
||||
inotifywait --timeout "$timeout" --event create "$passdir"
|
||||
return 1 #< maybe it was created; we'll pick that up immediately, on next check
|
||||
fi
|
||||
}
|
||||
waitForPassfile() {
|
||||
# there's a race condition between testing the path and starting `inotifywait`.
|
||||
# therefore, use a retry loop. exponential backoff to decrease the impact of the race condition,
|
||||
# especially near the start of boot to allow for quick reboots even if/when i hit the race.
|
||||
for timeout in 4 4 8 8 8 8 16 16 16 16 16 16 16 16; do
|
||||
if waitForPassfileOnce "$timeout"; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
while true; do
|
||||
if waitForPassfileOnce 30; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
}
|
||||
tryOpenStore() {
|
||||
# try to open the store (blocking), if it fails, then delete the passfile because the user probably entered the wrong password
|
||||
echo "mounting with ${mountArgs[*]}"
|
||||
# gocryptfs will unlock the store, and *then* fork into the background.
|
||||
# so when it returns, the files are either immediately accessible, or the mount failed (likely due to a bad password
|
||||
# if ! SANEBOX_APPEND="--sanebox-path $passfile" gocryptfs "${mountArgs[@]}"; then
|
||||
# echo "failed mount (transient failure)"
|
||||
# rm -f "$passfile"
|
||||
# return 1
|
||||
# fi
|
||||
#
|
||||
# the actual `mount` operation blocks fs operations until it succeeds or fails.
|
||||
# hence, we don't want to be inside `mount` while waiting for the password, else we block the world.
|
||||
# instead, do this funky recursive thing, where we call ourself after the password is provided.
|
||||
# TODO: better would be to move the password procurement out of the mount altogether, into some .unit file,
|
||||
# but that's tricky because the unit file wouldn't have a great way of validating the password.
|
||||
if ! mount.fuse3.sane "${mountArgs[@]}"; then
|
||||
echo "failed mount (transient failure)"
|
||||
rm -f "$passfile"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
waitForPassfile
|
||||
while ! tryOpenStore; do
|
||||
waitForPassfile
|
||||
done
|
||||
echo "mounted"
|
||||
# mount is complete (successful), and backgrounded.
|
||||
# remove the passfile even on successful mount, for vague safety reasons (particularly if the user were to explicitly unmount the private store).
|
||||
rm -f "$passfile"
|
46
modules/persist/stores/private/provision-private-key
Executable file
46
modules/persist/stores/private/provision-private-key
Executable file
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash -p bash -p coreutils-full -p gocryptfs -p inotify-tools
|
||||
|
||||
passfile="$1" # e.g. /run/gocryptfs/private.key
|
||||
conffile="$2" # e.g. /nix/persist/private/gocryptfs.conf
|
||||
passdir=$(dirname "$passfile")
|
||||
|
||||
waitForPassfileOnce() {
|
||||
local timeout=$1
|
||||
if [ -f "$passfile" ]; then
|
||||
return 0
|
||||
else
|
||||
# wait for some file to be created inside the directory.
|
||||
# inotifywait returns 0 if the file was created. 1 or 2 if timeout was hit or it was interrupted by a different event.
|
||||
inotifywait --timeout "$timeout" --event create "$passdir"
|
||||
return 1 #< maybe it was created; we'll pick that up immediately, on next check
|
||||
fi
|
||||
}
|
||||
waitForPassfile() {
|
||||
# there's a race condition between testing the path and starting `inotifywait`.
|
||||
# therefore, use a retry loop. exponential backoff to decrease the impact of the race condition,
|
||||
# especially near the start of boot to allow for quick reboots even if/when i hit the race.
|
||||
for timeout in 4 4 8 8 8 8 16 16 16 16 16 16 16 16; do
|
||||
if waitForPassfileOnce "$timeout"; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
while true; do
|
||||
if waitForPassfileOnce 30; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
}
|
||||
validatePassword() {
|
||||
if ! cat "$passfile" | gocryptfs-xray -dumpmasterkey "$conffile" > /dev/null; then
|
||||
echo "failed key validation"
|
||||
rm -f "$passfile"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
waitForPassfile
|
||||
while ! validatePassword; do
|
||||
waitForPassfile
|
||||
done
|
||||
echo "key provisioned"
|
Reference in New Issue
Block a user