/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, ... }:
|
{ config, lib, pkgs, sane-lib, utils, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
@@ -17,34 +7,18 @@ let
|
|||||||
in
|
in
|
||||||
lib.mkIf config.sane.persist.enable
|
lib.mkIf config.sane.persist.enable
|
||||||
{
|
{
|
||||||
sane.programs."mount.fuse3.gocryptfs-private" = {
|
sane.programs."provision-private-key" = {
|
||||||
packageUnwrapped = pkgs.static-nix-shell.mkBash {
|
packageUnwrapped = pkgs.static-nix-shell.mkBash {
|
||||||
pname = "mount.fuse3.gocryptfs-private";
|
pname = "provision-private-key";
|
||||||
srcRoot = ./.;
|
srcRoot = ./.;
|
||||||
pkgs = [
|
pkgs = [
|
||||||
"coreutils-full"
|
"coreutils-full"
|
||||||
|
"gocryptfs"
|
||||||
"inotify-tools"
|
"inotify-tools"
|
||||||
"libfuse-sane"
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
# sandbox.method = "landlock"; #< trickier than it looks to enable sandboxing of any kind here.
|
sandbox.method = "bwrap";
|
||||||
sandbox.autodetectCliPaths = "existing";
|
sandbox.autodetectCliPaths = "parent";
|
||||||
sandbox.capabilities = [
|
|
||||||
"sys_admin"
|
|
||||||
"chown"
|
|
||||||
"dac_override"
|
|
||||||
"dac_read_search"
|
|
||||||
"fowner"
|
|
||||||
"lease"
|
|
||||||
"mknod"
|
|
||||||
"setgid"
|
|
||||||
"setuid"
|
|
||||||
];
|
|
||||||
sandbox.extraPaths = [
|
|
||||||
"/dev/fuse"
|
|
||||||
"/run/gocryptfs"
|
|
||||||
];
|
|
||||||
suggestedPrograms = [ "gocryptfs-private" ];
|
|
||||||
};
|
};
|
||||||
sane.programs.gocryptfs-private = {
|
sane.programs.gocryptfs-private = {
|
||||||
packageUnwrapped = pkgs.static-nix-shell.mkBash {
|
packageUnwrapped = pkgs.static-nix-shell.mkBash {
|
||||||
@@ -91,7 +65,7 @@ lib.mkIf config.sane.persist.enable
|
|||||||
|
|
||||||
fileSystems."${origin}" = {
|
fileSystems."${origin}" = {
|
||||||
device = "gocryptfs-private#${backing}";
|
device = "gocryptfs-private#${backing}";
|
||||||
fsType = "fuse3.gocryptfs-private";
|
fsType = "fuse3.sane";
|
||||||
options = [
|
options = [
|
||||||
# "auto"
|
# "auto"
|
||||||
"nofail"
|
"nofail"
|
||||||
@@ -117,7 +91,7 @@ lib.mkIf config.sane.persist.enable
|
|||||||
wantedBy = [ "local-fs.target" ];
|
wantedBy = [ "local-fs.target" ];
|
||||||
mount.depends = [
|
mount.depends = [
|
||||||
config.sane.fs."${backing}".unit
|
config.sane.fs."${backing}".unit
|
||||||
config.sane.fs."/run/gocryptfs".unit
|
config.sane.fs."/run/gocryptfs/private.key".unit
|
||||||
];
|
];
|
||||||
# unitConfig.DefaultDependencies = "no";
|
# unitConfig.DefaultDependencies = "no";
|
||||||
mount.mountConfig.TimeoutSec = "infinity";
|
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.
|
user = config.sane.defaultUser; #< must be user-writable so i can unlock it.
|
||||||
mode = "0770";
|
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;
|
sane.programs."gocryptfs-private".enableFor.system = true;
|
||||||
system.fsPackages = [ pkgs.libfuse-sane config.sane.programs."mount.fuse3.gocryptfs-private".package ];
|
sane.programs."provision-private-key".enableFor.system = true;
|
||||||
|
system.fsPackages = [ pkgs.libfuse-sane ];
|
||||||
|
|
||||||
sane.user.services.gocryptfs-private = {
|
sane.user.services.gocryptfs-private = {
|
||||||
description = "wait for /mnt/persist/private to be mounted";
|
description = "wait for /mnt/persist/private to be mounted";
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
#!/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
|
||||||
|
|
||||||
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