From c15e1f61b005d2606b16efa8e36bdf3f96f6e09a Mon Sep 17 00:00:00 2001 From: Victor Engmark Date: Mon, 16 Oct 2023 21:31:39 +1300 Subject: [PATCH] ssh-audit: add test of audited configuration On current nixpkgs, no modifications to the server settings were necessary to pass the audit. However, some of the client algorithms were considered insecure. The client configuration lists all algorithms which were listed as acceptable by `ssh-audit`. This can be used as an example of a configuration currently considered acceptable by `ssh-audit`, and verifies that such a configuration results in a compatible client/server configuration. Beware that this test will continue passing when future versions of `ssh-audit` add support for new algorithms. In other words, the example configuration represents a subset of what the current version of `ssh-audit` would consider acceptable. --- nixos/tests/all-tests.nix | 1 + nixos/tests/ssh-audit.nix | 103 ++++++++++++++++++++++ pkgs/tools/security/ssh-audit/default.nix | 5 ++ 3 files changed, 109 insertions(+) create mode 100644 nixos/tests/ssh-audit.nix diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 4a3f4a331ca80..21b11fe7d9f14 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -750,6 +750,7 @@ in { spark = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./spark {}; sqlite3-to-mysql = handleTest ./sqlite3-to-mysql.nix {}; sslh = handleTest ./sslh.nix {}; + ssh-audit = handleTest ./ssh-audit.nix {}; sssd = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./sssd.nix {}; sssd-ldap = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./sssd-ldap.nix {}; stalwart-mail = handleTest ./stalwart-mail.nix {}; diff --git a/nixos/tests/ssh-audit.nix b/nixos/tests/ssh-audit.nix new file mode 100644 index 0000000000000..bd6255b8044d9 --- /dev/null +++ b/nixos/tests/ssh-audit.nix @@ -0,0 +1,103 @@ +import ./make-test-python.nix ( + {pkgs, ...}: let + sshKeys = import (pkgs.path + "/nixos/tests/ssh-keys.nix") pkgs; + sshUsername = "any-user"; + serverName = "server"; + clientName = "client"; + sshAuditPort = 2222; + in { + name = "ssh"; + + nodes = { + "${serverName}" = { + networking.firewall.allowedTCPPorts = [ + sshAuditPort + ]; + services.openssh.enable = true; + users.users."${sshUsername}" = { + isNormalUser = true; + openssh.authorizedKeys.keys = [ + sshKeys.snakeOilPublicKey + ]; + }; + }; + "${clientName}" = { + programs.ssh = { + ciphers = [ + "aes128-ctr" + "aes128-gcm@openssh.com" + "aes192-ctr" + "aes256-ctr" + "aes256-gcm@openssh.com" + "chacha20-poly1305@openssh.com" + ]; + extraConfig = '' + IdentitiesOnly yes + ''; + hostKeyAlgorithms = [ + "rsa-sha2-256" + "rsa-sha2-256-cert-v01@openssh.com" + "rsa-sha2-512" + "rsa-sha2-512-cert-v01@openssh.com" + "sk-ssh-ed25519-cert-v01@openssh.com" + "sk-ssh-ed25519@openssh.com" + "ssh-ed25519" + "ssh-ed25519-cert-v01@openssh.com" + ]; + kexAlgorithms = [ + "curve25519-sha256" + "curve25519-sha256@libssh.org" + "diffie-hellman-group-exchange-sha256" + "diffie-hellman-group16-sha512" + "diffie-hellman-group18-sha512" + "sntrup761x25519-sha512@openssh.com" + ]; + macs = [ + "hmac-sha2-256-etm@openssh.com" + "hmac-sha2-512-etm@openssh.com" + "umac-128-etm@openssh.com" + ]; + }; + }; + }; + + testScript = '' + start_all() + + ${serverName}.wait_for_open_port(22) + + # Should pass SSH server audit + ${serverName}.succeed("${pkgs.ssh-audit}/bin/ssh-audit 127.0.0.1") + + # Wait for client to be able to connect to the server + ${clientName}.wait_for_unit("network-online.target") + + # Set up trusted private key + ${clientName}.succeed("cat ${sshKeys.snakeOilPrivateKey} > privkey.snakeoil") + ${clientName}.succeed("chmod 600 privkey.snakeoil") + + # Fail fast and disable interactivity + ssh_options = "-o BatchMode=yes -o ConnectTimeout=1 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" + + # Should deny root user + ${clientName}.fail(f"ssh {ssh_options} root@${serverName} true") + + # Should deny non-root user password login + ${clientName}.fail(f"ssh {ssh_options} -o PasswordAuthentication=yes ${sshUsername}@${serverName} true") + + # Should allow non-root user certificate login + ${clientName}.succeed(f"ssh {ssh_options} -i privkey.snakeoil ${sshUsername}@${serverName} true") + + # Should pass SSH client audit + service_name = "ssh-audit.service" + ${serverName}.succeed(f"systemd-run --unit={service_name} ${pkgs.ssh-audit}/bin/ssh-audit --client-audit --port=${toString sshAuditPort}") + ${clientName}.sleep(5) # We can't use wait_for_open_port because ssh-audit exits as soon as anything talks to it + ${clientName}.execute( + f"ssh {ssh_options} -i privkey.snakeoil -p ${toString sshAuditPort} ${sshUsername}@${serverName} true", + check_return=False, + timeout=10 + ) + ${serverName}.succeed(f"exit $(systemctl show --property=ExecMainStatus --value {service_name})") + ''; + } +) diff --git a/pkgs/tools/security/ssh-audit/default.nix b/pkgs/tools/security/ssh-audit/default.nix index 2a4abc11ea7b8..668f3c206f611 100644 --- a/pkgs/tools/security/ssh-audit/default.nix +++ b/pkgs/tools/security/ssh-audit/default.nix @@ -1,5 +1,6 @@ { lib , fetchFromGitHub +, nixosTests , python3Packages }: @@ -19,6 +20,10 @@ python3Packages.buildPythonApplication rec { pytestCheckHook ]; + passthru.tests = { + inherit (nixosTests) ssh-audit; + }; + meta = with lib; { description = "Tool for ssh server auditing"; homepage = "https://github.com/jtesta/ssh-audit";