diff options
| author | Franck Cuny <franck@fcuny.net> | 2025-11-28 14:05:44 -0800 |
|---|---|---|
| committer | Franck Cuny <franck@fcuny.net> | 2025-11-28 14:05:44 -0800 |
| commit | 2b61601dd95244e31d82613621955effb91f7222 (patch) | |
| tree | d8101b0d9ee7d87382df4c0373c9823f26ae7d76 /modules/remote-unlock.nix | |
| parent | add a profile for wireguard configuration (diff) | |
| download | infra-2b61601dd95244e31d82613621955effb91f7222.tar.gz | |
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).
Diffstat (limited to 'modules/remote-unlock.nix')
| -rw-r--r-- | modules/remote-unlock.nix | 111 |
1 files changed, 111 insertions, 0 deletions
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; + }; + }; + }; +} |
