Commit bab03c3fdad4
Changed files (2)
home
common
shell
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";
};
})
(