diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index b855729692f6..e7c7d13e1c0a 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -342,6 +342,8 @@ See . - `services.openssh` now supports generating host SSH keys by setting `services.openssh.generateHostKeys = true` while leaving `services.openssh.enable` disabled. This is particularly useful for systems that have no need of an SSH daemon but want SSH host keys for other purposes such as using agenix or sops-nix. +- `services.openssh.enableRecommendedAlgorithms` has been added to allow users to opt out of NixOS's curated set of recommended algorithms. This set to true by default, and thus is not a breaking change. Users may want to set this to false if they prefer upstream's default algorithms. See . + - IPVLAN interfaces can now be configured through the `networking.ipvlans` option in the networking module. - `services.caddy` now supports setting `httpPort` and `httpsPort` and opening them in the firewall via `openFirewall`. diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix index b87e5ccad65f..9736387fd57a 100644 --- a/nixos/modules/services/networking/ssh/sshd.nix +++ b/nixos/modules/services/networking/ssh/sshd.nix @@ -412,6 +412,16 @@ in ''; }; + enableRecommendedAlgorithms = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Use algorithms curated and recommended by NixOS. + + Set to false to use upstream's default algorithms. + ''; + }; + authorizedKeysFiles = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; @@ -571,37 +581,64 @@ in }; KexAlgorithms = lib.mkOption { type = lib.types.nullOr (lib.types.listOf lib.types.str); - default = [ - "mlkem768x25519-sha256" - "sntrup761x25519-sha512" - "sntrup761x25519-sha512@openssh.com" - "curve25519-sha256" - "curve25519-sha256@libssh.org" - "diffie-hellman-group-exchange-sha256" - ]; + default = + if config.services.openssh.enableRecommendedAlgorithms then + [ + "mlkem768x25519-sha256" + "sntrup761x25519-sha512" + "sntrup761x25519-sha512@openssh.com" + "curve25519-sha256" + "curve25519-sha256@libssh.org" + "diffie-hellman-group-exchange-sha256" + ] + else + null; + defaultText = '' + if config.services.openssh.enableRecommendedAlgorithms then + [ + "mlkem768x25519-sha256" + "sntrup761x25519-sha512" + "sntrup761x25519-sha512@openssh.com" + "curve25519-sha256" + "curve25519-sha256@libssh.org" + "diffie-hellman-group-exchange-sha256" + ] + else + null; + ''; description = '' Allowed key exchange algorithms - Uses the lower bound recommended in both - - and - + Defaults to a curated set of algorithms. + Set enableRecommendedAlgorithms to false to use upstream's defaults. ''; }; Macs = lib.mkOption { type = lib.types.nullOr (lib.types.listOf lib.types.str); - default = [ - "hmac-sha2-512-etm@openssh.com" - "hmac-sha2-256-etm@openssh.com" - "umac-128-etm@openssh.com" - ]; + default = + if config.services.openssh.enableRecommendedAlgorithms then + [ + "hmac-sha2-512-etm@openssh.com" + "hmac-sha2-256-etm@openssh.com" + "umac-128-etm@openssh.com" + ] + else + null; + defaultText = '' + if config.services.openssh.enableRecommendedAlgorithms then + [ + "hmac-sha2-512-etm@openssh.com" + "hmac-sha2-256-etm@openssh.com" + "umac-128-etm@openssh.com" + ] + else + null; + ''; description = '' Allowed MACs - Defaults to recommended settings from both - - and - + Defaults to a curated set of algorithms. + Set enableRecommendedAlgorithms to false to use upstream's defaults. ''; }; StrictModes = lib.mkOption { @@ -613,21 +650,36 @@ in }; Ciphers = lib.mkOption { type = lib.types.nullOr (lib.types.listOf lib.types.str); - default = [ - "chacha20-poly1305@openssh.com" - "aes256-gcm@openssh.com" - "aes128-gcm@openssh.com" - "aes256-ctr" - "aes192-ctr" - "aes128-ctr" - ]; + default = + if config.services.openssh.enableRecommendedAlgorithms then + [ + "chacha20-poly1305@openssh.com" + "aes256-gcm@openssh.com" + "aes128-gcm@openssh.com" + "aes256-ctr" + "aes192-ctr" + "aes128-ctr" + ] + else + null; + defaultText = '' + if config.services.openssh.enableRecommendedAlgorithms then + [ + "chacha20-poly1305@openssh.com" + "aes256-gcm@openssh.com" + "aes128-gcm@openssh.com" + "aes256-ctr" + "aes192-ctr" + "aes128-ctr" + ] + else + null; + ''; description = '' Allowed ciphers - Defaults to recommended settings from both - - and - + Defaults to a curated set of algorithms. + Set enableRecommendedAlgorithms to false to use upstream's defaults. ''; }; AllowUsers = lib.mkOption { diff --git a/nixos/tests/openssh.nix b/nixos/tests/openssh.nix index fca192c15fc7..c6b2f6d707d6 100644 --- a/nixos/tests/openssh.nix +++ b/nixos/tests/openssh.nix @@ -193,12 +193,32 @@ in path = "/etc/ssh/ssh_host_ed25519_key"; } ]; + # The NixOS-curated algorithms require OpenSSL, and so since this test is against an OpenSSH-without-OpenSSL, we have to use the default algorithms, which adapt to not having OpenSSL. + enableRecommendedAlgorithms = false; + }; + users.users.root.openssh.authorizedKeys.keys = [ + snakeOilEd25519PublicKey + ]; + }; + + server-default-algorithms = + { ... }: + { + services.openssh = { + enable = true; + enableRecommendedAlgorithms = false; + }; + users.users.root.openssh.authorizedKeys.keys = [ + snakeOilEd25519PublicKey + ]; + }; + + server-null-algorithms = + { ... }: + { + services.openssh = { + enable = true; settings = { - # Since this test is against an OpenSSH-without-OpenSSL, - # we have to override NixOS's defaults ciphers (which require OpenSSL) - # and instead set these to null, which will mean OpenSSH uses its defaults. - # Expectedly, OpenSSH's defaults don't require OpenSSL when it's compiled - # without OpenSSL. Ciphers = null; KexAlgorithms = null; Macs = null; @@ -294,10 +314,12 @@ in server.wait_for_unit("sshd", timeout=60) server_allowed_users.wait_for_unit("sshd", timeout=60) + server_default_algorithms.wait_for_unit("sshd", timeout=60) server_localhost_only.wait_for_unit("sshd", timeout=60) server_match_rule.wait_for_unit("sshd", timeout=60) server_no_openssl.wait_for_unit("sshd", timeout=60) server_no_pam.wait_for_unit("sshd", timeout=60) + server_null_algorithms.wait_for_unit("sshd", timeout=60) server_null_pam.wait_for_unit("sshd", timeout=60) server_null_pam.fail("journalctl -u sshd.service | grep 'Unsupported option UsePAM'") server_sftp.wait_for_unit("sshd", timeout=60) @@ -402,6 +424,26 @@ in timeout=30 ) + with subtest("null-algorithms"): + client.succeed( + "cat ${snakeOilEd25519PrivateKey} > privkey.snakeoil" + ) + client.succeed("chmod 600 privkey.snakeoil") + client.succeed( + "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server-null-algorithms true", + timeout=30 + ) + + with subtest("no-openssl"): + client.succeed( + "cat ${snakeOilEd25519PrivateKey} > privkey.snakeoil" + ) + client.succeed("chmod 600 privkey.snakeoil") + client.succeed( + "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil server-default-algorithms true", + timeout=30 + ) + with subtest("no-pam"): client.succeed( "cat ${snakeOilPrivateKey} > privkey.snakeoil"