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 | |
| 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).
| -rw-r--r-- | machines/nixos/x86_64-linux/bree.nix | 12 | ||||
| -rw-r--r-- | modules/default.nix | 1 | ||||
| -rw-r--r-- | modules/remote-unlock.nix | 111 | ||||
| -rw-r--r-- | profiles/remote-unlock.nix | 3 | ||||
| -rw-r--r-- | secrets/bree/disk-passphrase.age | 8 | ||||
| -rw-r--r-- | secrets/bree/disk-unlock-key.age | bin | 0 -> 721 bytes | |||
| -rw-r--r-- | secrets/secrets.nix | 10 |
7 files changed, 145 insertions, 0 deletions
diff --git a/machines/nixos/x86_64-linux/bree.nix b/machines/nixos/x86_64-linux/bree.nix index 2f564b5..f91bf4f 100644 --- a/machines/nixos/x86_64-linux/bree.nix +++ b/machines/nixos/x86_64-linux/bree.nix @@ -27,5 +27,17 @@ }; }; + age.secrets.disk-unlock-key.file = ../../../secrets/bree/disk-unlock-key.age; + age.secrets.disk-passphrase.file = ../../../secrets/bree/disk-passphrase.age; + + services.remoteDiskUnlock = { + enable = true; + hosts = [ + "192.168.1.114" + ]; + sshKeyPath = config.age.secrets.disk-unlock-key.path; + passphrasePath = config.age.secrets.disk-passphrase.path; + }; + system.stateVersion = "23.11"; # Did you read the comment? } diff --git a/modules/default.nix b/modules/default.nix index d6d7b65..f936646 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -3,6 +3,7 @@ imports = [ ./home-manager.nix ./host-config.nix + ./remote-unlock.nix ./ssh.nix ./user.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; + }; + }; + }; +} diff --git a/profiles/remote-unlock.nix b/profiles/remote-unlock.nix index b0e3fe8..ea211ad 100644 --- a/profiles/remote-unlock.nix +++ b/profiles/remote-unlock.nix @@ -15,7 +15,10 @@ "/etc/initrd/ssh_host_ed25519_key" ]; authorizedKeys = [ + # my personal key "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINBkozy+X96u5ciX766bJ/AyQ3xm1tXZTIr5+4PVFZFi" + # key used to automatically unlock + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPr9Dv2MjZoRltmxi21PoS/42KnOhYxuq9r6ER62vjAx" ]; }; }; diff --git a/secrets/bree/disk-passphrase.age b/secrets/bree/disk-passphrase.age new file mode 100644 index 0000000..3811173 --- /dev/null +++ b/secrets/bree/disk-passphrase.age @@ -0,0 +1,8 @@ +age-encryption.org/v1 +-> ssh-ed25519 pFjJaA r/Q4nB/VcKaVXoJjDuIgnMVUr5K0rhrsVVq2lvQgQRQ +ZmwHs0sWxVKjS9njqPQR4rEV1aXxS80wWJQrAuf47vM +-> ssh-ed25519 OxmK1A /9e7fHg/Nh929cY7+0EagkxwME4jo0RxzBwdh8tuZnM +9UPI8Vnwebjick9WPlcT8lvNub687qchX4D4ntbanos +--- bwBCnL9gJhzuygCddmh0h0OXh/C6ysAgMfH9QBrQUMY + +I4ڍ:;X3T.n{A0^笆4F]P.uΕެ
\ No newline at end of file diff --git a/secrets/bree/disk-unlock-key.age b/secrets/bree/disk-unlock-key.age Binary files differnew file mode 100644 index 0000000..6d9a549 --- /dev/null +++ b/secrets/bree/disk-unlock-key.age diff --git a/secrets/secrets.nix b/secrets/secrets.nix index a8f01cf..155a88b 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -67,6 +67,16 @@ in hosts.bree ]; + "bree/disk-passphrase.age".publicKeys = [ + users.fcuny + hosts.bree + ]; + + "bree/disk-unlock-key.age".publicKeys = [ + users.fcuny + hosts.bree + ]; + "rivendell/wireguard.age".publicKeys = [ users.fcuny hosts.rivendell |
