Commit bab03c3fdad4

Vincent Demeester <vincent@sbr.pm>
2026-07-29 14:33:59
refactor(ssh): simplify openssh.nix, drop override layers
Removed 5 nested recursiveUpdate override layers and identity pinning from libx.sshConfig. Homelab hosts now rely on agent fallback (TPM first, FIDO2 second). Only forges are pinned (identitiesOnly + TPM) to avoid touch spam. Server-side controls handle access scoping.
1 parent ad2edb8
Changed files (2)
home
common
lib
home/common/shell/openssh.nix
@@ -11,7 +11,6 @@ let
   # FIDO2 keys are only available on hosts with Yubikey attached
   hasFido2Keys = hostname == "kyushu" || hostname == "aomi";
   isAomi = hostname == "aomi";
-  isAion = hostname == "aion";
   isOkinawa = hostname == "okinawa";
   isKyushu = hostname == "kyushu";
   hasTPMAgent = isAomi || isOkinawa || isKyushu;
@@ -113,253 +112,166 @@ in
   programs.ssh = {
     enable = true;
     enableDefaultConfig = false;
-    matchBlocks =
+    matchBlocks = {
+      "*" = {
+        serverAliveInterval = 60;
+        hashKnownHosts = true;
+        userKnownHostsFile = "${config.home.homeDirectory}/.ssh/known_hosts";
+        addKeysToAgent = "yes";
+        controlMaster = "auto";
+        controlPersist = "10m";
+        controlPath = "${config.home.homeDirectory}/.ssh/master-%C";
+      };
+    }
+    # Shpool session aliases: ssh <host>/<session-name>
+    // (
       let
-        # Critical infra hosts that need touch-required key
-        criticalInfraOverrides = lib.optionalAttrs hasFido2Keys {
-          "athena.home".identityFile = "~/.ssh/id_critical_infra_sk";
-          "athena.vpn".identityFile = "~/.ssh/id_critical_infra_sk";
-          "athena.sbr.pm".identityFile = "~/.ssh/id_critical_infra_sk";
-          "demeter.home".identityFile = "~/.ssh/id_critical_infra_sk";
-          "demeter.vpn".identityFile = "~/.ssh/id_critical_infra_sk";
-          "demeter.sbr.pm".identityFile = "~/.ssh/id_critical_infra_sk";
-          "carthage.vpn".identityFile = "~/.ssh/id_critical_infra_sk";
-          "carthage.sbr.pm".identityFile = "~/.ssh/id_critical_infra_sk";
-        };
-        # Kyushu uses service-specific FIDO2 keys
-        kyushuOverrides = lib.optionalAttrs hasFido2Keys {
-          "github.com".identityFile = "~/.ssh/id_github_sk";
-          "gitlab.com".identityFile = "~/.ssh/id_gitlab_sk";
-          "codeberg.org".identityFile = "~/.ssh/id_codeberg_sk";
-          "git.sr.ht".identityFile = "~/.ssh/id_srht_sk";
-          "*.redhat.com".identityFile = "~/.ssh/id_redhat_sk";
-          "192.168.1.*".identityFile = "~/.ssh/id_homelab_sk";
-          "10.100.0.*".identityFile = "~/.ssh/id_homelab_sk";
-        };
-        # 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_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)
-        nonFido2Overrides =
+        mkShpoolAliases =
+          _: machine:
           let
-            # Get all host identifiers from all machines
-            allHostIds = lib.flatten (
-              lib.attrValues (
-                builtins.mapAttrs (
-                  _: machine:
-                  builtins.filter (
-                    x: (lib.hasSuffix ".home" x) || (lib.hasSuffix ".vpn" x) || (lib.hasSuffix ".sbr.pm" x)
-                  ) (libx.sshHostIdentifier machine)
-                ) globals.machines
-              )
-            );
-            # Override both the host entry and its shpool alias
-            mkOverride = id: lib.nameValuePair id { identityFile = "~/.ssh/id_ed25519"; };
-            mkShpoolOverride = id: lib.nameValuePair "${id}/*" { identityFile = "~/.ssh/id_ed25519"; };
-          in
-          lib.optionalAttrs (isAion || isOkinawa) (
-            builtins.listToAttrs (map mkOverride allHostIds ++ map mkShpoolOverride allHostIds)
-          );
-        # aomi: route GitHub through the TPM key (served by ssh-tpm-agent) instead
-        # of the FIDO2 sk-key, so pushes work unattended without a touch prompt.
-        aomiOverrides = lib.optionalAttrs (isAomi || isKyushu) {
-          "github.com" = {
-            identityFile = "~/.ssh/id_tpm_ecdsa.pub";
-            identitiesOnly = true;
-          };
-          # carthage gates the aomi TPM key to git push+pull (praetorian), so route
-          # carthage through it instead of the touch-required critical-infra key:
-          # unattended git works without a touch prompt, and the server-side gate
-          # makes the no-touch key safe (only git over ~/git is allowed).
-          "carthage.vpn" = {
-            identityFile = "~/.ssh/id_tpm_ecdsa.pub";
-            identitiesOnly = true;
-          };
-          "carthage.sbr.pm" = {
-            identityFile = "~/.ssh/id_tpm_ecdsa.pub";
-            identitiesOnly = true;
-          };
-          # Full interactive/admin access to carthage. The default carthage.vpn
-          # entry is pinned to the git-only TPM key, so shell access must go
-          # through the unrestricted FIDO2 sk-key (requires a Yubikey touch).
-          "carthage-admin" = {
-            hostname = "10.100.0.1";
-            user = "vincent";
-            identityFile = "~/.ssh/id_critical_infra_sk";
-            identitiesOnly = true;
-          };
-        };
-      in
-      lib.recursiveUpdate
-        (
-          {
-            "*" = {
-              serverAliveInterval = 60;
-              hashKnownHosts = true;
-              userKnownHostsFile = "${config.home.homeDirectory}/.ssh/known_hosts";
-              # "yes" adds keys without confirmation; FIDO2 touch-required keys still enforce touch at hardware level
-              addKeysToAgent = "yes";
-              controlMaster = "auto";
-              controlPersist = "10m";
-              controlPath = "${config.home.homeDirectory}/.ssh/master-%C";
-            };
-            # Shpool session aliases (https://bower.sh/you-might-not-need-tmux)
-            # Usage: ssh <host>/<session-name>
-            # Example: ssh rhea.home/music, ssh aomi.home/dev
-          }
-          // (
-            # Generate shpool session aliases for each machine dynamically
-            let
-              inherit (pkgs) lib;
-              # Critical infra hosts that need touch-required key
-              criticalInfraHosts = [
-                "athena.home"
-                "athena.vpn"
-                "athena.sbr.pm"
-                "demeter.home"
-                "demeter.vpn"
-                "demeter.sbr.pm"
-                "carthage.vpn"
-                "carthage.sbr.pm"
-              ];
-              isCriticalInfra = id: builtins.elem id criticalInfraHosts;
-              mkShpoolAliases =
-                _: machine:
-                let
-                  # Get hostname identifiers (e.g., "rhea.home", "rhea.vpn", "rhea.sbr.pm")
-                  identifiers = builtins.filter (
-                    x: (lib.hasSuffix ".home" x) || (lib.hasSuffix ".vpn" x) || (lib.hasSuffix ".sbr.pm" x)
-                  ) (libx.sshHostIdentifier machine);
-                  # For each identifier, create a Host block with /* wildcard
-                  mkSessionBlock = id: {
-                    name = "${id}/*";
-                    value = {
-                      hostname =
-                        if (lib.hasSuffix ".vpn" id) then
-                          builtins.head machine.net.vpn.ips
-                        else if (lib.hasSuffix ".home" id) then
-                          builtins.head machine.net.ips
-                        else
-                          id;
-                      user = machine.user or "vincent";
-                      # Use critical infra key for critical hosts, homelab key for others
-                      identityFile = if isCriticalInfra id then "~/.ssh/id_critical_infra_sk" else "~/.ssh/id_homelab_sk";
-                      identitiesOnly = true;
-                      extraOptions = {
-                        RemoteCommand = "shpool-ssh-wrapper $(echo '%k' | cut -d/ -f2-)";
-                        RequestTTY = "yes";
-                      };
-                    };
-                  };
-                in
-                builtins.listToAttrs (map mkSessionBlock identifiers);
-            in
-            # Merge all shpool aliases for all machines
-            lib.attrsets.mergeAttrsList (lib.attrsets.mapAttrsToList mkShpoolAliases globals.machines)
-          )
-          # Generated configs for all machines (sets default id_homelab_sk)
-          // libx.sshConfigs globals.machines
-          # External hosts (new entries, not overrides)
-          // {
-            "github.com" = {
-              hostname = "github.com";
-              user = "git";
-              # identityFile set by host-specific overrides
-              controlMaster = "auto";
-              controlPersist = "360";
-            };
-            "gitlab.com" = {
-              hostname = "gitlab.com";
-              user = "git";
-              # identityFile set by host-specific overrides
-              controlMaster = "auto";
-              controlPersist = "360";
-            };
-            "codeberg.org" = {
-              hostname = "codeberg.org";
-              user = "git";
-              # identityFile set by host-specific overrides
-              controlMaster = "auto";
-              controlPersist = "360";
-            };
-            "git.sr.ht" = {
-              hostname = "git.sr.ht";
-              user = "git";
-              # identityFile set by host-specific overrides
-              controlMaster = "auto";
-              controlPersist = "360";
-            };
-            "tangled.org" = {
-              hostname = "tangled.org";
-              user = "git";
-              identityFile = "~/.ssh/id_personal_sk";
-              controlMaster = "auto";
-              controlPersist = "360";
-            };
-            "*.redhat.com" = {
-              user = "vdemeest";
-              # identityFile set by host-specific overrides
-            };
-            "bootstrap.ospqa.com" = {
-              forwardAgent = true;
-            };
-            "192.168.1.*" = {
-              forwardAgent = true;
-              identitiesOnly = true;
-              # identityFile set by host-specific overrides (kyushu only)
-              extraOptions = {
-                StrictHostKeyChecking = "no";
-                UserKnownHostsFile = "/dev/null";
+            identifiers = builtins.filter (
+              x: (lib.hasSuffix ".home" x) || (lib.hasSuffix ".vpn" x) || (lib.hasSuffix ".sbr.pm" x)
+            ) (libx.sshHostIdentifier machine);
+            mkSessionBlock = id: {
+              name = "${id}/*";
+              value = {
+                hostname =
+                  if (lib.hasSuffix ".vpn" id) then
+                    builtins.head machine.net.vpn.ips
+                  else if (lib.hasSuffix ".home" id) then
+                    builtins.head machine.net.ips
+                  else
+                    id;
+                user = machine.user or "vincent";
+                extraOptions = {
+                  RemoteCommand = "shpool-ssh-wrapper $(echo '%k' | cut -d/ -f2-)";
+                  RequestTTY = "yes";
+                };
               };
             };
-            "10.100.0.*" = {
-              forwardAgent = true;
-              identitiesOnly = true;
-              # identityFile set by host-specific overrides (kyushu only)
-            };
-          }
-          # Pipelines-as-Code bootstrap host, only on workstations (aomi, kyushu)
-          // lib.optionalAttrs hasFido2Keys {
-            "bootstrap.pipelinesascode.com" = {
-              hostname = "bootstrap.pipelinesascode.com";
-              user = "pipelines";
-              forwardAgent = true;
-            };
-          }
-        )
-        (
-          lib.recursiveUpdate (lib.recursiveUpdate criticalInfraOverrides (lib.recursiveUpdate kyushuOverrides (lib.recursiveUpdate okinawaOverrides nonFido2Overrides))) aomiOverrides
-        );
+          in
+          builtins.listToAttrs (map mkSessionBlock identifiers);
+      in
+      lib.attrsets.mergeAttrsList (lib.attrsets.mapAttrsToList mkShpoolAliases globals.machines)
+    )
+    # Generated configs for all machines (hostname, user, forwardAgent)
+    // libx.sshConfigs globals.machines
+    # Forges: pin to TPM key on TPM hosts to avoid touch-prompt spam.
+    # Non-TPM hosts fall through to extraConfig IdentityFile order.
+    // lib.optionalAttrs hasTPMAgent {
+      "github.com" = {
+        hostname = "github.com";
+        user = "git";
+        identityFile = "~/.ssh/id_tpm_ecdsa.pub";
+        identitiesOnly = true;
+        controlMaster = "auto";
+        controlPersist = "360";
+      };
+      "gitlab.com" = {
+        hostname = "gitlab.com";
+        user = "git";
+        identityFile = "~/.ssh/id_tpm_ecdsa.pub";
+        identitiesOnly = true;
+        controlMaster = "auto";
+        controlPersist = "360";
+      };
+      "codeberg.org" = {
+        hostname = "codeberg.org";
+        user = "git";
+        identityFile = "~/.ssh/id_tpm_ecdsa.pub";
+        identitiesOnly = true;
+        controlMaster = "auto";
+        controlPersist = "360";
+      };
+      "git.sr.ht" = {
+        hostname = "git.sr.ht";
+        user = "git";
+        identityFile = "~/.ssh/id_tpm_ecdsa.pub";
+        identitiesOnly = true;
+        controlMaster = "auto";
+        controlPersist = "360";
+      };
+      "*.redhat.com" = {
+        user = "vdemeest";
+        identityFile = "~/.ssh/id_tpm_ecdsa.pub";
+        identitiesOnly = true;
+      };
+    }
+    # Forges on non-TPM hosts (no identity pinning, agent/extraConfig fallback)
+    // lib.optionalAttrs (!hasTPMAgent) {
+      "github.com" = {
+        hostname = "github.com";
+        user = "git";
+        controlMaster = "auto";
+        controlPersist = "360";
+      };
+      "gitlab.com" = {
+        hostname = "gitlab.com";
+        user = "git";
+        controlMaster = "auto";
+        controlPersist = "360";
+      };
+      "codeberg.org" = {
+        hostname = "codeberg.org";
+        user = "git";
+        controlMaster = "auto";
+        controlPersist = "360";
+      };
+      "git.sr.ht" = {
+        hostname = "git.sr.ht";
+        user = "git";
+        controlMaster = "auto";
+        controlPersist = "360";
+      };
+      "*.redhat.com" = {
+        user = "vdemeest";
+      };
+    }
+    // {
+      "tangled.org" = {
+        hostname = "tangled.org";
+        user = "git";
+        identityFile = "~/.ssh/id_personal_sk";
+        controlMaster = "auto";
+        controlPersist = "360";
+      };
+      "bootstrap.ospqa.com" = {
+        forwardAgent = true;
+      };
+      "192.168.1.*" = {
+        forwardAgent = true;
+        extraOptions = {
+          StrictHostKeyChecking = "no";
+          UserKnownHostsFile = "/dev/null";
+        };
+      };
+      "10.100.0.*" = {
+        forwardAgent = true;
+      };
+    }
+    // lib.optionalAttrs hasFido2Keys {
+      "bootstrap.pipelinesascode.com" = {
+        hostname = "bootstrap.pipelinesascode.com";
+        user = "pipelines";
+        forwardAgent = true;
+      };
+    }
+    # carthage-admin: explicit shell access bypassing praetorian-gated TPM key
+    // lib.optionalAttrs (isAomi || isKyushu) {
+      "carthage-admin" = {
+        hostname = "10.100.0.1";
+        user = "vincent";
+        identityFile = "~/.ssh/id_critical_infra_sk";
+        identitiesOnly = true;
+      };
+    };
     extraConfig = ''
       # IdentityAgent /run/user/1000/yubikey-agent/yubikey-agent.sock
       GlobalKnownHostsFile ~/.ssh/ssh_known_hosts ~/.ssh/ssh_known_hosts.redhat ~/.ssh/ssh_known_hosts.mutable
       StrictHostKeyChecking yes
       PreferredAuthentications publickey,password
       StreamLocalBindUnlink yes
-      IdentityFile ~/.ssh/keys/%h
       ${lib.optionalString isOkinawa "IdentityFile ~/.ssh/id_ed25519_sk"}
-      IdentityFile ~/.ssh/id_ed25519
     '';
   };
   # Fedora hosts have no NixOS openssh.authorizedKeys; manage via home-manager.
lib/functions.nix
@@ -157,11 +157,6 @@ let
                 x;
             user = machine.user or "vincent";
             forwardAgent = false;
-            # Use FIDO2 homelab key for all homelab hosts
-            identityFile = "~/.ssh/id_homelab_sk";
-            identitiesOnly = true;
-            # Disable IdentityAgent only for aomi.home (prevents yubikey prompts in TRAMP)
-            identityAgent = lib.mkIf (x == "aomi.home") "none";
           };
         })
         (