diff --git a/modules/programs/ssh/default.nix b/modules/programs/ssh/default.nix
index f93890fbc..f4b1f2024 100644
--- a/modules/programs/ssh/default.nix
+++ b/modules/programs/ssh/default.nix
@@ -3,89 +3,205 @@
with lib;
let
- cfg = config.programs.ssh;
+ cfg = config.programs.ssh;
knownHosts = map (h: getAttr h cfg.knownHosts) (attrNames cfg.knownHosts);
- host =
- { name, ... }:
- {
- options = {
- hostNames = mkOption {
- type = types.listOf types.str;
- default = [];
- description = ''
- A list of host names and/or IP numbers used for accessing
- the host's ssh service.
- '';
- };
- publicKey = mkOption {
- default = null;
- type = types.nullOr types.str;
- example = "ecdsa-sha2-nistp521 AAAAE2VjZHN...UEPg==";
- description = ''
- The public key data for the host. You can fetch a public key
- from a running SSH server with the ssh-keyscan
- command. The public key should not include any host names, only
- the key type and the key itself.
- '';
- };
- publicKeyFile = mkOption {
- default = null;
- type = types.nullOr types.path;
- description = ''
- The path to the public key file for the host. The public
- key file is read at build time and saved in the Nix store.
- You can fetch a public key file from a running SSH server
- with the ssh-keyscan command. The content
- of the file should follow the same format as described for
- the publicKey option.
- '';
- };
+ host = { name, ... }: {
+ options = {
+ hostNames = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = ''
+ A list of host names and/or IP numbers used for accessing
+ the host's ssh service.
+ '';
};
- config = {
- hostNames = mkDefault [ name ];
+ publicKey = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ example = "ecdsa-sha2-nistp521 AAAAE2VjZHN...UEPg==";
+ description = ''
+ The public key data for the host. You can fetch a public key
+ from a running SSH server with the ssh-keyscan
+ command. The public key should not include any host names, only
+ the key type and the key itself.
+ '';
+ };
+ publicKeyFile = mkOption {
+ default = null;
+ type = types.nullOr types.path;
+ description = ''
+ The path to the public key file for the host. The public
+ key file is read at build time and saved in the Nix store.
+ You can fetch a public key file from a running SSH server
+ with the ssh-keyscan command. The content
+ of the file should follow the same format as described for
+ the publicKey option.
+ '';
};
};
-in
+ config = { hostNames = mkDefault [ name ]; };
+ };
-{
+in {
options = {
- programs.ssh.knownHosts = mkOption {
- default = {};
- type = types.attrsOf (types.submodule host);
+ users.users =
+ mkOption { type = with types; attrsOf (submodule userOptions); };
+
+ services.openssh.authorizedKeysFiles = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
description = ''
- The set of system-wide known SSH hosts.
- '';
- example = literalExpression ''
- [
- {
- hostNames = [ "myhost" "myhost.mydomain.com" "10.10.1.4" ];
- publicKeyFile = ./pubkeys/myhost_ssh_host_dsa_key.pub;
- }
- {
- hostNames = [ "myhost2" ];
- publicKeyFile = ./pubkeys/myhost2_ssh_host_dsa_key.pub;
- }
- ]
+ Specify the rules for which files to read on the host.
+
+ This is an advanced option. If you're looking to configure user
+ keys, you can generally use [](#opt-users.users._name_.openssh.authorizedKeys.keys)
+ or [](#opt-users.users._name_.openssh.authorizedKeys.keyFiles).
+
+ These are paths relative to the host root file system or home
+ directories and they are subject to certain token expansion rules.
+ See AuthorizedKeysFile in man sshd_config for details.
'';
};
+
+ programs.ssh = {
+ knownHosts = mkOption {
+ default = { };
+ type = types.attrsOf (types.submodule host);
+ description = ''
+ The set of system-wide known SSH hosts.
+ '';
+ example = literalExpression ''
+ [
+ {
+ hostNames = [ "myhost" "myhost.mydomain.com" "10.10.1.4" ];
+ publicKeyFile = ./pubkeys/myhost_ssh_host_dsa_key.pub;
+ }
+ {
+ hostNames = [ "myhost2" ];
+ publicKeyFile = ./pubkeys/myhost2_ssh_host_dsa_key.pub;
+ }
+ ]
+ '';
+ };
+
+ pubkeyAcceptedKeyTypes = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "ssh-ed25519" "ssh-rsa" ];
+ description = lib.mdDoc ''
+ Specifies the key types that will be used for public key authentication.
+ '';
+ };
+
+ hostKeyAlgorithms = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "ssh-ed25519" "ssh-rsa" ];
+ description = ''
+ Specifies the host key algorithms that the client wants to use in order of preference.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration text written to `/etc/ssh/ssh_config.d/10-extra-nix.conf`.
+ See {manpage}`ssh_config(5)` for help.
+ '';
+ };
+
+ kexAlgorithms = mkOption {
+ type = types.nullOr (types.listOf types.str);
+ default = null;
+ example = [
+ "curve25519-sha256@libssh.org"
+ "diffie-hellman-group-exchange-sha256"
+ ];
+ description = ''
+ Specifies the available KEX (Key Exchange) algorithms.
+ '';
+ };
+
+ ciphers = mkOption {
+ type = types.nullOr (types.listOf types.str);
+ default = null;
+ example = [ "chacha20-poly1305@openssh.com" "aes256-gcm@openssh.com" ];
+ description = ''
+ Specifies the ciphers allowed and their order of preference.
+ '';
+ };
+
+ macs = mkOption {
+ type = types.nullOr (types.listOf types.str);
+ default = null;
+ example = [ "hmac-sha2-512-etm@openssh.com" "hmac-sha1" ];
+ description = ''
+ Specifies the MAC (message authentication code) algorithms in order of preference. The MAC algorithm is used
+ for data integrity protection.
+ '';
+ };
+ };
};
config = {
assertions = flip mapAttrsToList cfg.knownHosts (name: data: {
- assertion = (data.publicKey == null && data.publicKeyFile != null) ||
- (data.publicKey != null && data.publicKeyFile == null);
- message = "knownHost ${name} must contain either a publicKey or publicKeyFile";
+ assertion = (data.publicKey == null && data.publicKeyFile != null)
+ || (data.publicKey != null && data.publicKeyFile == null);
+ message =
+ "knownHost ${name} must contain either a publicKey or publicKeyFile";
});
- environment.etc."ssh/ssh_known_hosts".text = (flip (concatMapStringsSep "\n") knownHosts
- (h: assert h.hostNames != [];
- concatStringsSep "," h.hostNames + " "
- + (if h.publicKey != null then h.publicKey else readFile h.publicKeyFile)
- )) + "\n";
+ environment.etc = authKeysFiles // {
+ "ssh/ssh_known_hosts" = mkIf (builtins.length knownHosts > 0) {
+ text = (flip (concatMapStringsSep "\n") knownHosts (h:
+ assert h.hostNames != [ ];
+ concatStringsSep "," h.hostNames + " " + (if h.publicKey != null then
+ h.publicKey
+ else
+ readFile h.publicKeyFile))) + "\n";
+ };
+ "ssh/sshd_config.d/101-authorized-keys.conf" = {
+ text = ''
+ AuthorizedKeysFile ${
+ toString config.services.openssh.authorizedKeysFiles
+ }
+ '';
+ # Allows us to automatically migrate from using a file to a symlink
+ knownSha256Hashes = [ oldAuthorizedKeysHash ];
+ };
+ "ssh/sshd_config.d/10-extra-nix.conf" = {
+ text = ''
+ ${optionalString (cfg.pubkeyAcceptedKeyTypes != [ ])
+ "PubkeyAcceptedKeyTypes ${
+ concatStringsSep "," cfg.pubkeyAcceptedKeyTypes
+ }"}
+
+ ${config.programs.ssh.extraConfig}
+
+ ${optionalString (cfg.hostKeyAlgorithms != [ ])
+ "HostKeyAlgorithms ${concatStringsSep "," cfg.hostKeyAlgorithms}"}
+ ${optionalString (cfg.kexAlgorithms != null)
+ "KexAlgorithms ${concatStringsSep "," cfg.kexAlgorithms}"}
+ ${optionalString (cfg.ciphers != null)
+ "Ciphers ${concatStringsSep "," cfg.ciphers}"}
+ ${optionalString (cfg.macs != null)
+ "MACs ${concatStringsSep "," cfg.macs}"}
+ '';
+ };
+ };
+
+ # Clean up .before-nix-darwin file left over from using knownSha256Hashes
+ system.activationScripts.etc.text = ''
+ auth_keys_orig=/etc/ssh/sshd_config.d/101-authorized-keys.conf.before-nix-darwin
+ if [ -e "$auth_keys_orig" ] && [ "$(shasum -a 256 $auth_keys_orig | cut -d ' ' -f 1)" = "${oldAuthorizedKeysHash}" ]; then
+ rm "$auth_keys_orig"
+ fi
+ '';
};
}