Files
nixos/tests/fail2ban-ssh.nix
primary 1719d54ee0 phase 3: new flake.nix + extract common-{nix,doas,shell-fish}; rewire imports
- New unified flake with two nixpkgs channels (unstable for desktops, 25.11 for muffin)
- modules/common-{doas,shell-fish,nix}.nix extracted from duplicated blocks
- modules/desktop-common.nix: renamed from system/common.nix; secret paths point to secrets/desktop/
- hosts/{mreow,yarn}/default.nix import desktop-common; yarn imports modules/no-rgb.nix
- hosts/muffin/default.nix imports common-* + server-prefixed modules + services/; duplicate doas/fish/nix blocks removed; gc retention preserved as mkForce override
- modules/age-secrets.nix: file paths → ../secrets/server/*.age
- services/{minecraft,matrix/livekit}: secret paths → ../secrets/server/
- home/profiles/*.nix: ./progs/ → ../progs/
- hosts/{mreow,yarn}/home.nix: imports rewired to ../../home/profiles/ and ../../home/progs/
- home/progs/pi.nix and hosts/yarn/home.nix: secret reads → ../../secrets/home/
- tests/*.nix: ../modules/security.nix → ../modules/server-security.nix; ../modules/overlays.nix → ../lib/overlays.nix
- lib/default.nix: takes explicit lib param (defaults to nixpkgs-stable.lib)
2026-04-18 00:58:55 -04:00

100 lines
2.6 KiB
Nix

{
config,
lib,
pkgs,
...
}:
let
securityModule = import ../modules/server-security.nix;
sshModule =
{
config,
lib,
pkgs,
...
}:
{
imports = [
(import ../services/ssh.nix {
inherit config lib pkgs;
username = "testuser";
})
];
};
in
pkgs.testers.runNixOSTest {
name = "fail2ban-ssh";
nodes = {
server =
{
config,
lib,
pkgs,
...
}:
{
imports = [
securityModule
sshModule
];
# Override for testing - enable password auth
services.openssh.settings.PasswordAuthentication = lib.mkForce true;
users.users.testuser = {
isNormalUser = true;
password = "correctpassword";
};
networking.firewall.allowedTCPPorts = [ 22 ];
};
client = {
environment.systemPackages = with pkgs; [
sshpass
openssh
];
};
};
testScript = ''
import time
start_all()
server.wait_for_unit("sshd.service")
server.wait_for_unit("fail2ban.service")
server.wait_for_open_port(22)
time.sleep(2)
with subtest("Verify sshd jail is active"):
status = server.succeed("fail2ban-client status")
assert "sshd" in status, f"sshd jail not found in: {status}"
with subtest("Generate failed SSH login attempts"):
# Use -4 to force IPv4, timeout and NumberOfPasswordPrompts=1 to ensure quick failure
# maxRetry is 3 in our config, so 4 attempts should trigger a ban
for i in range(4):
client.execute(
"timeout 5 sshpass -p 'wrongpassword' ssh -4 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=3 -o NumberOfPasswordPrompts=1 testuser@server echo test 2>/dev/null || true"
)
time.sleep(1)
with subtest("Verify IP is banned"):
# Wait for fail2ban to process the logs and apply the ban
time.sleep(5)
status = server.succeed("fail2ban-client status sshd")
print(f"sshd jail status: {status}")
# Check that at least 1 IP is banned
import re
match = re.search(r"Currently banned:\s*(\d+)", status)
assert match and int(match.group(1)) >= 1, f"Expected at least 1 banned IP, got: {status}"
with subtest("Verify banned client cannot connect"):
# Use -4 to test with same IP that was banned
exit_code = client.execute("timeout 3 nc -4 -z -w 2 server 22")[0]
assert exit_code != 0, "Connection should be blocked for banned IP"
'';
}