{ ... }: { _class = "clan.service"; manifest.name = "clan-core/sshd"; manifest.description = "Enables secure remote access to the machine over SSH with automatic host key management and optional CA-signed host certificates."; manifest.categories = [ "System" "Network" ]; manifest.readme = builtins.readFile ./README.md; roles.client = { description = "Installs the SSH CA public key into known_hosts for the configured domains, so this machine can verify servers’ host certificates without TOFU prompts."; interface = { lib, ... }: { options.certificate = { searchDomains = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; example = [ "mydomain.com" ]; description = '' List of domains to include in the certificate. This option will prepend the machine name in front of each domain before adding it to the certificate. ''; }; }; }; perInstance = { settings, roles, ... }: { nixosModule = { config, lib, pkgs, ... }: let uniqueStrings = list: builtins.attrNames (builtins.groupBy lib.id list); # Collect searchDomains from all servers in this instance allServerSearchDomains = lib.flatten ( lib.mapAttrsToList (_name: machineConfig: machineConfig.settings.certificate.searchDomains or [ ]) ( roles.server.machines or { } ) ); # Merge client's searchDomains with all servers' searchDomains searchDomains = uniqueStrings (settings.certificate.searchDomains ++ allServerSearchDomains); in { clan.core.vars.generators.openssh-ca = lib.mkIf (searchDomains != [ ]) { share = true; files.id_ed25519.deploy = false; files."id_ed25519.pub" = { deploy = false; secret = false; }; runtimeInputs = [ pkgs.openssh ]; script = '' ssh-keygen -t ed25519 -N "" -C "" -f "$out"/id_ed25519 ''; }; programs.ssh.knownHosts.ssh-ca = lib.mkIf (searchDomains != [ ]) { certAuthority = true; extraHostNames = builtins.map (domain: "*.${domain}") searchDomains; publicKey = config.clan.core.vars.generators.openssh-ca.files."id_ed25519.pub".value; }; }; }; }; roles.server = { description = "Runs sshd with persistent host keys and (if certificate.searchDomains is set) a CA‑signed host certificate for ., enabling TOFU‑less verification by clients that trust the CA."; interface = { lib, ... }: { options = { hostKeys.rsa.enable = lib.mkEnableOption "generating a RSA host key"; certificate = { searchDomains = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; example = [ "mydomain.com" ]; description = '' List of domains to include in the certificate. This option will prepend the machine name in front of each domain before adding it to the certificate. ''; }; }; }; }; perInstance = { settings, ... }: { nixosModule = { config, lib, pkgs, ... }: { clan.core.vars.generators = { openssh-ca = lib.mkIf (settings.certificate.searchDomains != [ ]) { share = true; files.id_ed25519.deploy = false; files."id_ed25519.pub" = { deploy = false; secret = false; }; runtimeInputs = [ pkgs.openssh ]; script = '' ssh-keygen -t ed25519 -N "" -C "" -f "$out"/id_ed25519 ''; }; openssh-cert = lib.mkIf (settings.certificate.searchDomains != [ ]) { files."ssh.id_ed25519-cert.pub".secret = false; dependencies = [ "openssh" "openssh-ca" ]; validation = { name = config.clan.core.settings.machine.name; domains = lib.genAttrs settings.certificate.searchDomains lib.id; }; runtimeInputs = [ pkgs.openssh pkgs.jq ]; script = let stringSet = list: builtins.attrNames (builtins.groupBy lib.id list); domains = stringSet settings.certificate.searchDomains; in '' ssh-keygen \ -s $in/openssh-ca/id_ed25519 \ -I ${config.clan.core.settings.machine.name} \ -h \ -n ${lib.concatMapStringsSep "," (d: "${config.clan.core.settings.machine.name}.${d}") domains} \ $in/openssh/ssh.id_ed25519.pub mv $in/openssh/ssh.id_ed25519-cert.pub "$out"/ssh.id_ed25519-cert.pub ''; }; openssh-rsa = lib.mkIf settings.hostKeys.rsa.enable { files."ssh.id_rsa" = { }; files."ssh.id_rsa.pub".secret = false; runtimeInputs = [ pkgs.coreutils pkgs.openssh ]; script = '' ssh-keygen -t rsa -b 4096 -N "" -C "" -f "$out"/ssh.id_rsa ''; }; openssh = { files."ssh.id_ed25519" = { }; files."ssh.id_ed25519.pub".secret = false; migrateFact = "openssh"; runtimeInputs = [ pkgs.coreutils pkgs.openssh ]; script = '' ssh-keygen -t ed25519 -N "" -C "" -f "$out"/ssh.id_ed25519 ''; }; }; programs.ssh.knownHosts.ssh-ca = lib.mkIf (settings.certificate.searchDomains != [ ]) { certAuthority = true; extraHostNames = builtins.map (domain: "*.${domain}") settings.certificate.searchDomains; publicKey = config.clan.core.vars.generators.openssh-ca.files."id_ed25519.pub".value; }; services.openssh = { enable = true; settings.PasswordAuthentication = false; settings.HostCertificate = lib.mkIf ( # this check needs to go first, as otherwise generators.openssh-cert does not exist settings.certificate.searchDomains != [ ] && config.clan.core.vars.generators.openssh-cert.files."ssh.id_ed25519-cert.pub".exists ) config.clan.core.vars.generators.openssh-cert.files."ssh.id_ed25519-cert.pub".path; hostKeys = [ { path = config.clan.core.vars.generators.openssh.files."ssh.id_ed25519".path; type = "ed25519"; } ] ++ lib.optional settings.hostKeys.rsa.enable { path = config.clan.core.vars.generators.openssh-rsa.files."ssh.id_rsa".path; type = "rsa"; }; }; programs.ssh.knownHosts.clan-sshd-self-ed25519 = { hostNames = [ "localhost" config.networking.hostName ] ++ (lib.optional (config.networking.domain != null) config.networking.fqdn); publicKey = config.clan.core.vars.generators.openssh.files."ssh.id_ed25519.pub".value; }; }; }; }; }