{inputs, ... }: let userName = "john"; sshHostCAPubKey = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNug18oLH0vZxnibXJzMJvTWFPZTnSlhCDDVi+rHhgnIum6ZXQ4SF+VHOOAM5BbzZmMKitNJ5lcrGP15Eur7DzQ="; in { flake.modules.nixos.ssh = { pkgs, config, lib, ... }: let cfg = config.ssh; in { options.ssh = { certificates = { enable = lib.mkEnableOption "Enable SSH host certificates"; userCA = lib.mkOption { type = lib.types.path; default = ../../keys/ssh_user_ca.pub; }; }; }; config = { services.openssh = { enable = true; # require public key authentication for better security settings = lib.mkMerge [ { PasswordAuthentication = false; KbdInteractiveAuthentication = false; } (lib.mkIf cfg.certificates.enable { TrustedUserCAKeys = "/etc/ssh/ssh_user_ca.pub"; HostKey = "/etc/ssh/ssh_host_ed25519_key"; HostCertificate = "/etc/ssh/ssh_host_ed25519_key-cert.pub"; }) ]; }; environment.etc."ssh/ssh_user_ca.pub" = lib.mkIf cfg.certificates.enable { source = cfg.certificates.userCA; }; programs.ssh.knownHosts = lib.mkIf cfg.certificates.enable { "192.168.1.*" = { certAuthority = true; publicKey = sshHostCAPubKey; }; }; }; }; flake.modules.homeManager.ssh = { pkgs, config, lib, ... }: { imports = with inputs.self.modules.homeManager; [ step-client ]; options.ssh = { IdentityFile = lib.mkOption { # Intentionally not using a path type here because that will end up with the private key getting copied into the store type = lib.types.str; default = "${config.home.homeDirectory}/.ssh/id_ed25519"; description = "Path to the SSH identity file."; }; matchSets = { appdaemon = lib.mkEnableOption "Enable AppDaemon SSH targets"; certs = lib.mkEnableOption "Enable Janus and Soteria SSH targets"; homelab = lib.mkEnableOption "Enable various Homelab targets"; dev = lib.mkEnableOption "Enable development targets"; }; certificates = { enable = lib.mkEnableOption "Enable SSH user certificates"; }; }; # All this stuff has to be wrapped in a config attribute because of the presence of the options here? config = let cfg = config.ssh; identityFile = cfg.IdentityFile; publicKeyFile = "${identityFile}.pub"; certificateFile = "${identityFile}-cert.pub"; userKnownHostsFile = "${config.home.homeDirectory}/.ssh/known_hosts"; provisionerPasswordPath = config.sops.secrets."janus/admin_jwk".path; sshHostProvisioner = config.step-client.sshHostProvisioner; in { sops.secrets."janus/admin_jwk" = { mode = "0400"; }; home.packages = lib.optionals cfg.certificates.enable [ (pkgs.writeShellScriptBin "sign-ssh-cert" '' ${lib.getExe pkgs.step-cli} ssh certificate \ --sign \ --principal ${userName} \ --principal root \ --principal appdaemon \ --provisioner "${sshHostProvisioner}" \ --provisioner-password-file "${provisionerPasswordPath}" \ ${userName} ${publicKeyFile} '') ]; home.file.".ssh/known_hosts" = { text = lib.concatStringsSep "\n" ( [ "fded:fb16:653e:25da:be24:11ff:fea0:753f ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ9ZqiWPrCwHjxFCiu0lT4rlQs7KyMapxKJQQ5PJP1eh" ] ++ (lib.optional cfg.certificates.enable "@cert-authority 192.168.1.* ${sshHostCAPubKey}") ++ (lib.optional cfg.certificates.enable "@cert-authority *.john-stream.com ${sshHostCAPubKey}") ); }; programs.ssh = { enable = true; enableDefaultConfig = false; extraConfig = '' SetEnv TERM="xterm-256color" IdentityAgent ~/.1password/agent.sock ''; matchBlocks = lib.mkMerge [ { "*" = { user = "john"; compression = false; serverAliveInterval = 0; serverAliveCountMax = 3; identitiesOnly = true; inherit identityFile certificateFile; hashKnownHosts = false; userKnownHostsFile = userKnownHostsFile; addKeysToAgent = "yes"; forwardAgent = false; }; } (lib.mkIf cfg.matchSets.appdaemon { "appdaemon" = { hostname = "192.168.1.242"; user = "appdaemon"; }; "ad-nix" = { hostname = "192.168.1.201"; user = "appdaemon"; }; }) (lib.mkIf cfg.matchSets.certs { "janus" = { hostname = "janus.john-stream.com"; user = "root"; }; "soteria" = { hostname = "soteria.john-stream.com"; user = "john"; }; }) (lib.mkIf cfg.matchSets.homelab { "docs" = { hostname = "192.168.1.110"; user = "root"; }; "gitea" = { hostname = "192.168.1.104"; user = "john"; }; "hermes" = { hostname = "192.168.1.150"; user = "root"; }; "panoptes" = { hostname = "192.168.1.107"; user = "panoptes"; }; }) (lib.mkIf cfg.matchSets.dev { "test-nix" = { hostname = "fded:fb16:653e:25da:be24:11ff:fea0:753f"; user = "john"; identityFile = identityFile; }; }) ]; }; }; }; }