From 2b61601dd95244e31d82613621955effb91f7222 Mon Sep 17 00:00:00 2001 From: Franck Cuny Date: Fri, 28 Nov 2025 14:05:44 -0800 Subject: add a module to remotely unlock machines For machines with full disk encryption, we can remotely unlock them from bree. A systemd timer will run every 10 minutes and check if we need to unlock the host. If we need to, it will SSH and provide the passphrase to unlock the disk(s). --- modules/remote-unlock.nix | 111 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 modules/remote-unlock.nix (limited to 'modules/remote-unlock.nix') diff --git a/modules/remote-unlock.nix b/modules/remote-unlock.nix new file mode 100644 index 0000000..fea9345 --- /dev/null +++ b/modules/remote-unlock.nix @@ -0,0 +1,111 @@ +{ + config, + lib, + pkgs, + ... +}: + +with lib; + +let + cfg = config.services.remoteDiskUnlock; + + unlockScript = pkgs.writeShellScript "remote-disk-unlock" '' + #!/usr/bin/env bash + set -euo pipefail + + SSH_KEY="$CREDENTIALS_DIRECTORY/ssh-key" + PASSPHRASE_FILE="$CREDENTIALS_DIRECTORY/passphrase" + + for server in ${concatStringsSep " " cfg.hosts}; do + echo "Probing host $server on port 22" + if ${pkgs.netcat}/bin/nc -z -w 5 "$server" 22 2>/dev/null; then + echo "Host $server is already unlocked, skipping" + continue + fi + echo "No response on port 22, probing host $server on port 911" + if ${pkgs.netcat}/bin/nc -z -w 5 "$server" 911 2>/dev/null; then + echo "Host $server is waiting for unlock - unlocking" + ${pkgs.openssh}/bin/ssh -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null \ + -i "$SSH_KEY" -p 911 "root@$server" < "$PASSPHRASE_FILE" || true + else + echo "Host $server is down, retry later" + fi + done + ''; +in +{ + options.services.remoteDiskUnlock = { + enable = mkEnableOption "remote disk unlock service"; + + hosts = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "List of hostnames/IPs to monitor and unlock"; + example = [ + "server1.local" + "192.168.1.100" + ]; + }; + + sshKeyPath = mkOption { + type = types.path; + description = "Path to SSH private key"; + example = "/run/agenix/disk-unlock-key"; + }; + + passphrasePath = mkOption { + type = types.path; + description = "Path to disk passphrase file"; + example = "/run/agenix/disk-passphrase"; + }; + + interval = mkOption { + type = types.str; + default = "10min"; + description = "How often to check hosts (systemd timer format)"; + }; + }; + + config = mkIf cfg.enable { + systemd.services.remote-disk-unlock = { + description = "Unlock remote encrypted disks"; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${unlockScript}"; + DynamicUser = true; + LoadCredential = [ + "ssh-key:${cfg.sshKeyPath}" + "passphrase:${cfg.passphrasePath}" + ]; + PrivateTmp = true; + ProtectSystem = "strict"; + ProtectHome = true; + NoNewPrivileges = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + ]; + RestrictNamespaces = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + }; + }; + + systemd.timers.remote-disk-unlock = { + description = "Check and unlock remote disks periodically"; + wantedBy = [ "timers.target" ]; + timerConfig = { + OnBootSec = "1min"; + OnUnitActiveSec = cfg.interval; + Persistent = false; + }; + }; + }; +} -- cgit v1.2.3