Commit c15e1f61 authored by Victor Engmark's avatar Victor Engmark Committed by Artturin
Browse files

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.
parent 3e8e1782
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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 {};
+103 −0
Original line number Diff line number Diff line
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})")
    '';
  }
)
+5 −0
Original line number Diff line number Diff line
{ 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";