Commit e4e7527c8af6

Vincent Demeester <vincent@sbr.pm>
2026-07-03 21:58:15
feat(okinawa): set up ssh-tpm-agent and passage TPM
Added TPM-sealed SSH key and age identity on okinawa. Generalized ssh-tpm-agent wiring from aomi-only to shared hasTPMAgent guard. Switched git signing and forge SSH routing to TPM key. Removed PASSAGE_IDENTITIES_FILE mkForce override so passage uses TPM+Yubikey identities.
1 parent 3fd3d59
Changed files (4)
home
systems
okinawa
home/common/shell/git.nix
@@ -31,8 +31,8 @@ let
   sshkeyPerHost = {
     # FIDO2 resident key (ssh:signing) - no touch required for signing
     kyushu = "${pkgs.writeText "yubikey5-fido2-signing" "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIGF/BoGqFc5/pM40bF/2UhWzRFaDmS4hJ45VtpXjUh36AAAAC3NzaDpzaWduaW5n"}";
-    # Okinawa uses a regular ed25519 key (no yubikey dependency)
-    okinawa = "${pkgs.writeText "okinawa-ed25519" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILfT4qPT4nH+K6wfhnM4JCtflrUEIXPAYpqdN7W7TOBo vincent@okinawa-passage"}";
+    # Okinawa: TPM-sealed ecdsa key via ssh-tpm-agent (machine-bound, no touch)
+    okinawa = "${pkgs.writeText "okinawa-tpm" "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBODwU2/sE+S51ShfL8t+QA9IMcVuQL+VLYuENUc5VDVD4AVoVj97BggowbAhtqDW6hdc0S3XSVrZs+aOSd9ukbg= vincent@okinawa-tpm"}";
     # aomi (Fedora): TPM-sealed ecdsa key via ssh-tpm-agent (machine-bound, no touch)
     aomi = "${pkgs.writeText "aomi-tpm" "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGEL1A++UUsFnGmnrIR8S6A8IUFkR5WyxDPol2bs02TY0O+sWOPJFJZIAKqcWVSUIkxRoktUBMRe0mMZyiHa3jw= vdemeest@aomi-tpm"}";
   };
@@ -48,7 +48,7 @@ let
   allowedSigners = ''
     vincent@aomi ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBGEL1A++UUsFnGmnrIR8S6A8IUFkR5WyxDPol2bs02TY0O+sWOPJFJZIAKqcWVSUIkxRoktUBMRe0mMZyiHa3jw=
     vincent@kyushu sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIGF/BoGqFc5/pM40bF/2UhWzRFaDmS4hJ45VtpXjUh36AAAAC3NzaDpzaWduaW5n
-    vincent@okinawa ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILfT4qPT4nH+K6wfhnM4JCtflrUEIXPAYpqdN7W7TOBo
+    vincent@okinawa ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBODwU2/sE+S51ShfL8t+QA9IMcVuQL+VLYuENUc5VDVD4AVoVj97BggowbAhtqDW6hdc0S3XSVrZs+aOSd9ukbg=
   '';
 in
 {
home/common/shell/openssh.nix
@@ -13,6 +13,7 @@ let
   isAomi = hostname == "aomi";
   isAion = hostname == "aion";
   isOkinawa = hostname == "okinawa";
+  hasTPMAgent = isAomi || isOkinawa;
 in
 {
   home.packages =
@@ -24,17 +25,19 @@ in
     # so FIDO2 sk-key PIN prompts work with the home-manager ssh-agent.
     ++ lib.optionals isAomi [
       openssh-askpass
+    ]
+    ++ lib.optionals hasTPMAgent [
       ssh-tpm-agent
     ];
 
-  # NixOS hosts use programs.ssh.startAgent (proper SSH_ASKPASS for FIDO2).
   # aomi has no NixOS layer, so run a home-manager systemd user ssh-agent.
+  # NixOS hosts (okinawa) use programs.ssh.startAgent instead.
   services.ssh-agent.enable = isAomi;
 
   # aomi: ssh-tpm-agent is the *front* agent. It serves the machine-bound TPM
   # SSH key and proxies (-A) the FIDO2 ssh-agent above for fallback, so a single
   # socket (SSH_AUTH_SOCK) exposes both the TPM key and all FIDO2 sk-keys.
-  systemd.user.sockets.ssh-tpm-agent = lib.mkIf isAomi {
+  systemd.user.sockets.ssh-tpm-agent = lib.mkIf hasTPMAgent {
     Unit.Description = "SSH TPM agent socket";
     Socket = {
       ListenStream = "%t/ssh-tpm-agent.sock";
@@ -43,7 +46,7 @@ in
     };
     Install.WantedBy = [ "sockets.target" ];
   };
-  systemd.user.services.ssh-tpm-agent = lib.mkIf isAomi {
+  systemd.user.services.ssh-tpm-agent = lib.mkIf hasTPMAgent {
     Unit = {
       Description = "ssh-tpm-agent service";
       Requires = [ "ssh-tpm-agent.socket" ];
@@ -62,7 +65,7 @@ in
   # ssh-tpm-agent socket instead of the plain ssh-agent one.  The original
   # service runs dbus-update-activation-environment --systemd SSH_AUTH_SOCK
   # which overwrites environment.d values in the systemd user manager env.
-  systemd.user.services.set-SSH_AUTH_SOCK = lib.mkIf isAomi {
+  systemd.user.services.set-SSH_AUTH_SOCK = lib.mkIf hasTPMAgent {
     Service.ExecStart = lib.mkForce (
       pkgs.writeScript "set-SSH_AUTH_SOCK" ''
         #!/bin/sh
@@ -74,13 +77,17 @@ in
     );
   };
 
-  # FIDO2 sk-keys spawn ssh-sk-helper which needs an askpass for the PIN.
-  home.sessionVariables = lib.mkIf isAomi {
-    SSH_ASKPASS = "${pkgs.openssh-askpass}/libexec/gtk-ssh-askpass";
-    SSH_ASKPASS_REQUIRE = "prefer";
-    # Route ssh through the front ssh-tpm-agent (TPM key + proxied FIDO2 keys).
-    SSH_AUTH_SOCK = lib.mkForce "$XDG_RUNTIME_DIR/ssh-tpm-agent.sock";
-  };
+  # Route ssh through the front ssh-tpm-agent (TPM key + proxied FIDO2 keys).
+  home.sessionVariables = lib.mkIf hasTPMAgent (
+    {
+      SSH_AUTH_SOCK = lib.mkForce "$XDG_RUNTIME_DIR/ssh-tpm-agent.sock";
+    }
+    # aomi (Fedora) needs its own askpass; NixOS sets SSH_ASKPASS via programs.ssh.startAgent.
+    // lib.optionalAttrs isAomi {
+      SSH_ASKPASS = "${pkgs.openssh-askpass}/libexec/gtk-ssh-askpass";
+      SSH_ASKPASS_REQUIRE = "prefer";
+    }
+  );
 
   # Override the sshAuthSock module's shell initialization so all shells
   # (bash, zsh, fish) point at the TPM agent socket, not the plain ssh-agent.
@@ -91,13 +98,13 @@ in
   # which activates the upstream module's config block and forces a read of
   # `.bash`/`.fish`/`.nushell` (no defaults) -> eval failure on non-aomi hosts.
   # On aomi, services.ssh-agent supplies fish/nushell; we only override bash.
-  sshAuthSock.initialization = lib.mkIf isAomi {
+  sshAuthSock.initialization = lib.mkIf hasTPMAgent {
     bash = lib.mkForce ''export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/ssh-tpm-agent.sock"'';
   };
 
   # GUI terminals spawned under niri inherit the systemd user manager
   # environment, so set SSH_AUTH_SOCK there too via environment.d.
-  xdg.configFile = lib.mkIf isAomi {
+  xdg.configFile = lib.mkIf hasTPMAgent {
     "environment.d/95-ssh-auth-sock.conf".text = ''
       SSH_AUTH_SOCK=''${XDG_RUNTIME_DIR}/ssh-tpm-agent.sock
     '';
@@ -128,13 +135,28 @@ in
           "192.168.1.*".identityFile = "~/.ssh/id_homelab_sk";
           "10.100.0.*".identityFile = "~/.ssh/id_homelab_sk";
         };
-        # Okinawa uses a single FIDO2 key (id_ed25519_sk) for everything
+        # Okinawa: route forges through TPM key (like aomi), FIDO2 sk-key as fallback via agent proxy
         okinawaOverrides = lib.optionalAttrs isOkinawa {
-          "github.com".identityFile = "~/.ssh/id_ed25519_sk";
-          "gitlab.com".identityFile = "~/.ssh/id_ed25519_sk";
-          "codeberg.org".identityFile = "~/.ssh/id_ed25519_sk";
-          "git.sr.ht".identityFile = "~/.ssh/id_ed25519_sk";
-          "*.redhat.com".identityFile = "~/.ssh/id_ed25519_sk";
+          "github.com" = {
+            identityFile = "~/.ssh/id_tpm_ecdsa.pub";
+            identitiesOnly = true;
+          };
+          "gitlab.com" = {
+            identityFile = "~/.ssh/id_tpm_ecdsa.pub";
+            identitiesOnly = true;
+          };
+          "codeberg.org" = {
+            identityFile = "~/.ssh/id_tpm_ecdsa.pub";
+            identitiesOnly = true;
+          };
+          "git.sr.ht" = {
+            identityFile = "~/.ssh/id_tpm_ecdsa.pub";
+            identitiesOnly = true;
+          };
+          "*.redhat.com" = {
+            identityFile = "~/.ssh/id_tpm_ecdsa.pub";
+            identitiesOnly = true;
+          };
         };
         # Special case for aomi and okinawa: no FIDO2 homelab key, use regular ed25519
         # Override all homelab machine entries (generated by libx.sshConfigs and shpool aliases)
systems/okinawa/home.nix
@@ -1,7 +1,6 @@
 {
   pkgs,
   lib,
-  config,
   inputs,
   system,
   ...
@@ -69,8 +68,7 @@ in
   home.sessionVariables = {
     LLAMA_ARG_FLASH_ATTN = "off";
     LLAMA_ARG_MAIN_GPU = "0";
-    # Use local SSH key for passage instead of YubiKey identity
-    PASSAGE_IDENTITIES_FILE = pkgs.lib.mkForce "${config.home.homeDirectory}/.ssh/id_ed25519";
+    # Use TPM + Yubikey identities for passage (set in home/common/desktop/passage.nix)
   };
 
   # Use Vulkan variant for GPU-accelerated transcription (RX 6700S / RDNA2)
globals.nix
@@ -68,6 +68,14 @@ _: {
           gated = "passage-pull";
         };
       };
+      # okinawa user key: TPM-sealed ecdsa via ssh-tpm-agent (machine-bound, no touch)
+      okinawa-tpm = {
+        key = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBODwU2/sE+S51ShfL8t+QA9IMcVuQL+VLYuENUc5VDVD4AVoVj97BggowbAhtqDW6hdc0S3XSVrZs+aOSd9ukbg= vincent@okinawa-tpm";
+        access = {
+          vincent.default = "trusted";
+          root.default = "trusted";
+        };
+      };
       # aomi user key: TPM-sealed ecdsa via ssh-tpm-agent (machine-bound, no touch)
       # Gated to git push+pull on carthage only.
       aomi-tpm = {