From 679226b60f62207a7dfc926cbb739f36457d633a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Mon, 24 Jul 2023 09:23:20 +0300 Subject: [PATCH] zfsunlock --- hosts/vno1-oh2/configuration.nix | 11 ++++ modules/services/default.nix | 1 + modules/services/zfsunlock/default.nix | 69 ++++++++++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 modules/services/zfsunlock/default.nix diff --git a/hosts/vno1-oh2/configuration.nix b/hosts/vno1-oh2/configuration.nix index 9b45b8e..f47a1c7 100644 --- a/hosts/vno1-oh2/configuration.nix +++ b/hosts/vno1-oh2/configuration.nix @@ -34,6 +34,17 @@ motiejus.passwordFile = config.age.secrets.motiejus-passwd-hash.path; }; }; + + services.zfsunlock = { + enable = true; + targets."hel1-a.servers.jakst" = { + sshEndpoint = myData.ips.hel1a; + pingEndpoint = "hel1-a.servers.jakst"; + remotePubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEzt0eaSRTAfM2295x4vACEd5VFqVeYJPV/N9ZUq+voP"; + pwFile = config.age.secrets.zfs-passphrase-hel1-a.path; + startAt = "*-*-* *:00/5:00"; + }; + }; }; services = { diff --git a/modules/services/default.nix b/modules/services/default.nix index ec5c2c6..eacf82f 100644 --- a/modules/services/default.nix +++ b/modules/services/default.nix @@ -5,5 +5,6 @@ ... }: { imports = [ + ./zfsunlock ]; } diff --git a/modules/services/zfsunlock/default.nix b/modules/services/zfsunlock/default.nix new file mode 100644 index 0000000..37cde9c --- /dev/null +++ b/modules/services/zfsunlock/default.nix @@ -0,0 +1,69 @@ +{ + config, + lib, + pkgs, + ... +}: let + unlock = { sshEndpoint, pingEndpoint, remotePubkey, pwFile, pingTimeoutSec}: let + timeoutStr = builtins.toString pingTimeoutSec; + in '' + # if host is reachable via "pingEndpoint", which, we presume is + # VPN (which implies the rootfs has been unlocked for VPN to work), + # exit successfully. + ${pkgs.iputils}/bin/ping -W ${timeoutStr} -c 1 ${pingEndpoint} && exit 0 + + exec ${pkgs.openssh}/bin/ssh \ + -i /etc/ssh/ssh_host_ed25519_key \ + -o UserKnownHostsFile=none \ + -o GlobalKnownHostsFile=/dev/null \ + -o KnownHostsCommand="${pkgs.coreutils}/bin/echo ${sshEndpoint} ${remotePubkey}" \ + root@${sshEndpoint} < "${pwFile}" + ''; +in { + options.mj.services.zfsunlock = with lib.types; { + enable = lib.mkEnableOption "remotely unlock zfs-encrypted root volumes"; + + targets = lib.mkOption { + default = {}; + type = attrsOf (submodule ( + {...}: { + options = { + sshEndpoint = lib.mkOption {type = str;}; + pingEndpoint = lib.mkOption {type = str;}; + pingTimeoutSec = lib.mkOption {type = int; default = 20;}; + remotePubkey = lib.mkOption {type = str;}; + pwFile = lib.mkOption {type = path;}; + startAt = lib.mkOption {type = either str (listOf str);}; + }; + } + )); + }; + }; + + config = lib.mkIf config.mj.services.zfsunlock.enable { + systemd.services = lib.mapAttrs' + (name: cfg: + lib.nameValuePair "zfsunlock-${name}" { + description = "zfsunlock service for ${name}"; + script = unlock (builtins.removeAttrs cfg ["startAt"]); + serviceConfig = { + User = "root"; + ProtectSystem = "strict"; + }; + } + ) config.mj.services.zfsunlock.targets; + + systemd.timers = lib.mapAttrs' + (name: cfg: + lib.nameValuePair "zfsunlock-${name}" { + description = "zfsunlock timer for ${name}"; + wantedBy = [ "timers.target" ]; + timerConfig = { + OnCalendar = cfg.startAt; + }; + after = [ "network-online.target" ]; + } + ) config.mj.services.zfsunlock.targets; + + }; +}