services/openssh: manage host keys (#1701)
Some checks failed
Test / test-stable (push) Has been cancelled
Test / install-against-stable (push) Has been cancelled
Test / install-flake (push) Has been cancelled
Update website / Build (push) Has been cancelled
Update website / Deploy (push) Has been cancelled

This commit is contained in:
Michael Hoang 2026-03-08 20:03:47 +00:00 committed by GitHub
commit da529ac9e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 116 additions and 3 deletions

View file

@ -1,7 +1,72 @@
{ config, lib, ... }:
{ config, lib, pkgs, ... }:
let
cfg = config.services.openssh;
hostKeyOpts = {
options = {
type = lib.mkOption {
type = lib.types.enum [ "dsa" "ecdsa" "ed25519" "rsa" ];
description = ''
Key type passed to `ssh-keygen -t`.
'';
};
path = lib.mkOption {
type = lib.types.str;
description = ''
Path to the private key file.
'';
};
bits = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
description = ''
Key size in bits. If `null`, `ssh-keygen` uses the default
for the given key type (RSA=3072, ECDSA=256, ED25519=fixed).
'';
};
comment = lib.mkOption {
type = lib.types.str;
default = "";
description = ''
Comment for the key, passed to `ssh-keygen -C`.
Defaults to an empty string to match Apple's built-in host key
generation and avoid leaking the hostname.
'';
};
};
};
hostKeysConfig = lib.concatMapStringsSep "\n"
(k: "HostKey ${k.path}")
cfg.hostKeys;
keygenScript = lib.concatMapStrings (k:
let
escapedPath = lib.escapeShellArg k.path;
in ''
if ! [[ -s ${escapedPath} ]]; then
if ! [[ -L ${escapedPath} ]]; then
rm -f ${escapedPath}
fi
keygenArgs=(
-t ${lib.escapeShellArg k.type}
${lib.optionalString (k.bits != null) "-b ${toString k.bits}"}
-C ${lib.escapeShellArg k.comment}
-f ${escapedPath}
-N ""
)
mkdir -p "$(dirname ${escapedPath})"
chmod 0755 "$(dirname ${escapedPath})"
${lib.getExe' pkgs.openssh "ssh-keygen"} "''${keygenArgs[@]}"
fi
'') cfg.hostKeys;
in
{
options = {
@ -24,6 +89,22 @@ in
See {manpage}`sshd_config(5)` for help.
'';
};
hostKeys = lib.mkOption {
type = lib.types.listOf (lib.types.submodule hostKeyOpts);
default = [
{ type = "rsa"; path = "/etc/ssh/ssh_host_rsa_key"; }
{ type = "ecdsa"; path = "/etc/ssh/ssh_host_ecdsa_key"; }
{ type = "ed25519"; path = "/etc/ssh/ssh_host_ed25519_key"; }
];
description = ''
SSH host key declarations. Each entry specifies a key type and path.
`HostKey` directives are written to the sshd configuration for each
entry.
The default matches the keys that macOS automatically generates.
'';
};
};
};
@ -41,6 +122,12 @@ in
fi
'');
environment.etc."ssh/sshd_config.d/099-host-keys.conf" = lib.mkIf (cfg.hostKeys != []) {
text = hostKeysConfig;
};
environment.etc."ssh/sshd_config.d/100-nix-darwin.conf".text = cfg.extraConfig;
system.activationScripts.openssh.text = lib.mkIf (cfg.hostKeys != []) keygenScript;
};
}

View file

@ -121,6 +121,7 @@ in
${cfg.activationScripts.applications.text}
${cfg.activationScripts.pam.text}
${cfg.activationScripts.patches.text}
${cfg.activationScripts.openssh.text}
${cfg.activationScripts.etc.text}
${cfg.activationScripts.defaults.text}
${cfg.activationScripts.userDefaults.text}

View file

@ -1,12 +1,37 @@
{ config, pkgs, ... }:
{ config, ... }:
{
services.openssh.enable = true;
services.openssh.extraConfig = ''
StreamLocalBindUnlink yes
'';
services.openssh.hostKeys = [
{ type = "ed25519"; path = "/etc/ssh/custom_host_ed25519_key"; comment = "my-host"; }
{ type = "rsa"; path = "/etc/ssh/custom_host_rsa_key"; bits = 4096; }
];
test = ''
echo >&2 "checking for StreamLocalBindUnlink in /etc/ssh/ssh_known_hosts"
echo >&2 "checking for StreamLocalBindUnlink in /etc/ssh/sshd_config.d/100-nix-darwin.conf"
grep 'StreamLocalBindUnlink yes' ${config.out}/etc/ssh/sshd_config.d/100-nix-darwin.conf
echo >&2 "checking for HostKey directives in /etc/ssh/sshd_config.d/099-host-keys.conf"
grep 'HostKey /etc/ssh/custom_host_ed25519_key' ${config.out}/etc/ssh/sshd_config.d/099-host-keys.conf
grep 'HostKey /etc/ssh/custom_host_rsa_key' ${config.out}/etc/ssh/sshd_config.d/099-host-keys.conf
echo >&2 "checking that default keys are absent from config"
(! grep 'HostKey /etc/ssh/ssh_host_rsa_key' ${config.out}/etc/ssh/sshd_config.d/099-host-keys.conf)
(! grep 'HostKey /etc/ssh/ssh_host_ecdsa_key' ${config.out}/etc/ssh/sshd_config.d/099-host-keys.conf)
(! grep 'HostKey /etc/ssh/ssh_host_ed25519_key' ${config.out}/etc/ssh/sshd_config.d/099-host-keys.conf)
echo >&2 "checking for ssh-keygen commands in activation script"
grep 'ssh-keygen.*keygenArgs' ${config.out}/activate
echo >&2 "checking for keygenArgs in activation script"
grep '\-t ed25519' ${config.out}/activate
grep '\-C my-host' ${config.out}/activate
grep '\-f /etc/ssh/custom_host_ed25519_key' ${config.out}/activate
grep '\-t rsa' ${config.out}/activate
grep '\-b 4096' ${config.out}/activate
grep '\-f /etc/ssh/custom_host_rsa_key' ${config.out}/activate
'';
}