aboutsummaryrefslogtreecommitdiff
path: root/modules/remote-unlock.nix
diff options
context:
space:
mode:
authorFranck Cuny <franck@fcuny.net>2025-11-28 14:05:44 -0800
committerFranck Cuny <franck@fcuny.net>2025-11-28 14:05:44 -0800
commit2b61601dd95244e31d82613621955effb91f7222 (patch)
treed8101b0d9ee7d87382df4c0373c9823f26ae7d76 /modules/remote-unlock.nix
parentadd a profile for wireguard configuration (diff)
downloadinfra-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 '')
-rw-r--r--modules/remote-unlock.nix111
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;
+ };
+ };
+ };
+}