Skip to content

Commit

Permalink
ssh: add extraOptions and other nixpkgs ssh module options
Browse files Browse the repository at this point in the history
  • Loading branch information
kloenk committed Jan 29, 2025
1 parent bcdb602 commit 469e391
Showing 1 changed file with 180 additions and 64 deletions.
244 changes: 180 additions & 64 deletions modules/programs/ssh/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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 <command>ssh-keyscan</command>
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 <command>ssh-keyscan</command> command. The content
of the file should follow the same format as described for
the <literal>publicKey</literal> 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 <command>ssh-keyscan</command>
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 <command>ssh-keyscan</command> command. The content
of the file should follow the same format as described for
the <literal>publicKey</literal> 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 = [
"[email protected]"
"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 = [ "[email protected]" "[email protected]" ];
description = ''
Specifies the ciphers allowed and their order of preference.
'';
};

macs = mkOption {
type = types.nullOr (types.listOf types.str);
default = null;
example = [ "[email protected]" "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
'';
};
}

0 comments on commit 469e391

Please sign in to comment.