unl0kr: split the gocryptfs unlocking into its own separate service

/mnt/persist/private can be depended on by both s6 user services and systemd system services (which will become useful for servo)

/mnt/persist/private can be unlocked by dropping the key in remotely, however that won't kill unl0kr

TODO: fix unl0kr to not also output text to the tty

TODO: ensure gocryptfs mount can handle being fed a wrong password
This commit is contained in:
2024-07-26 08:08:21 +00:00
parent 8ef5920d84
commit af905a2f58
4 changed files with 64 additions and 15 deletions

View File

@@ -265,6 +265,7 @@ in
# N.B.: gtk apps support absolute paths for this; webkit apps (e.g. geary) support only relative paths (relative to $XDG_RUNTIME_DIR)
env.WAYLAND_DISPLAY = "wl/wayland-1";
services.private-storage.dependencyOf = [ "sway" ]; #< HACK: prevent unl0kr and sway from fighting over the tty
services.sway = {
description = "sway: tiling wayland desktop environment";
partOf = [

View File

@@ -65,18 +65,18 @@ in
services.unl0kr = {
description = "unl0kr framebuffer password entry/filesystem unlocker";
partOf = [ "private-storage" ];
# TODO: lift the gocryptfs stuff into a separate service
command = pkgs.writeShellScript "unl0kr-start" ''
# if ! test -d $XDG_RUNTIME_DIR/unl0kr; then
# mkdir $XDG_RUNTIME_DIR/unl0kr
# fi
# unl0kr > $XDG_RUNTIME_DIR/unl0kr/passwd
if ! test -f /mnt/persist/private/init; then
unl0kr | sudo gocryptfs -fg /nix/persist/private /mnt/persist/private -o allow_other
fi
#^ otherwise, if already unlocked, exit true
while ! test -f /mnt/persist/private/init; do
unl0kr > /run/gocryptfs/private.key.incoming
cp /run/gocryptfs/private.key.incoming /run/gocryptfs/private.key
#v give time for the fs to be mounted, before assuming user entered the wrong key & we should restart
# TODO: use inotify to wait for `init`
sleep 3
done
while true; do
sleep infinity
done
'';
readiness.waitExists = [ "/mnt/persist/private/init" ];
};
};

View File

@@ -38,17 +38,56 @@ 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"
];
noCheck = true;
};
# let sane.fs know about the mount
sane.fs."${origin}".mount = {};
# let sane.fs know about the mount, and let systemd know to wait for the pass key before attempting a mount
sane.fs."${origin}".mount = {
depends = let
keyfile = config.sane.fs."/run/gocryptfs/private.key";
in [ keyfile.unit ];
};
# it also needs to know that the underlying device is an ordinary folder
sane.fs."${backing}" = sane-lib.fs.wanted {
dir.acl.user = config.sane.defaultUser;
# dir.acl.mode = "755";
};
sane.fs."/run/gocryptfs/private.key".generated = let
script = pkgs.writeShellScript "wait-for-gocryptfs-private" ''
file="$1"
dir=$(dirname "$file")
succeedOrWait() {
if [ -f "$file" ]; then
exit 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.
${lib.getExe' pkgs.inotify-tools "inotifywait"} --timeout "$1" --event create "$dir" || true
fi
}
# 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 i in 4 4 8 8 16 16 16 16 16 16 16 16; do
succeedOrWait "$i"
done
while true; do
succeedOrWait 30
done
'';
in {
command = [ "${script}" "/run/gocryptfs/private.key" ];
# no need for anyone else to be able to read the key
acl.mode = "0400";
};
sane.fs."/run/gocryptfs" = sane-lib.fs.wanted {
dir.acl.user = config.sane.defaultUser;
dir.acl.mode = "0700";
};
# in order for non-systemd `mount` to work, the mount point has to already be created, so make that a default target
systemd.units = let
originUnit = config.sane.fs."${origin}".generated.unit;
@@ -58,5 +97,11 @@ lib.mkIf config.sane.persist.enable
# TODO: could add this *specifically* to the .mount file for the encrypted fs?
system.fsPackages = [ pkgs.gocryptfs ]; # fuse needs to find gocryptfs
sane.user.services.gocryptfs-private = {
description = "wait for /mnt/persist/private to be mounted";
startCommand = "${lib.getExe' pkgs.systemd "systemctl"} start mnt-persist-private.mount";
partOf = [ "private-storage" ];
};
}

View File

@@ -178,8 +178,7 @@ let
config = lib.mkMerge [
# if we're the default user, inherit whatever settings were routed to the default user
(lib.mkIf config.default {
inherit (sane-user-cfg) fs persist environment;
services = lib.mapAttrs (_: lib.mkMerge) sane-user-cfg.services;
inherit (sane-user-cfg) fs environment persist services;
})
{
fs."/".dir.acl = {
@@ -295,6 +294,10 @@ let
# grouping it like this is mostly a power-saving thing to make certain services not auto-launched
description = "service (bundle) which provides high-precision location info (e.g. from GPS)";
};
services."private-storage" = {
description = "service (bundle) which is active once the persist.private datastore has been opened";
dependencyOf = [ "graphical-session" ]; #< prevent any graphical environment from competing with the login/unlocker service over the framebuffer
};
services."sound" = {
description = "service (bundle) which represents functional sound input/output when active";
# partOf = [ "default" ];