tests: fix all fail2ban NixOS VM tests
- Add explicit iptables banaction in security.nix for test compatibility - Force IPv4 in all curl requests to prevent IPv4/IPv6 mismatch issues - Fix caddy test: use basic_auth directive (not basicauth) - Override service ports in tests to match direct connections (not via Caddy) - Vaultwarden: override ROCKET_ADDRESS and ROCKET_LOG for external access - Immich: increase VM memory to 4GB for stability - Jellyfin: create placeholder log file and reload fail2ban after startup - Add tests.nix entries for all 6 fail2ban tests All tests now pass: ssh, caddy, gitea, vaultwarden, immich, jellyfin
This commit is contained in:
138
tests/fail2ban-jellyfin.nix
Normal file
138
tests/fail2ban-jellyfin.nix
Normal file
@@ -0,0 +1,138 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
testServiceConfigs = {
|
||||
zpool_ssds = "";
|
||||
https = {
|
||||
domain = "test.local";
|
||||
};
|
||||
ports = {
|
||||
jellyfin = 8096;
|
||||
};
|
||||
jellyfin = {
|
||||
dataDir = "/var/lib/jellyfin";
|
||||
cacheDir = "/var/cache/jellyfin";
|
||||
};
|
||||
media_group = "media";
|
||||
};
|
||||
|
||||
testLib = lib.extend (
|
||||
final: prev: {
|
||||
serviceMountWithZpool = serviceName: zpool: dirs: { ... }: { };
|
||||
optimizePackage = pkg: pkg; # No-op for testing
|
||||
}
|
||||
);
|
||||
|
||||
jellyfinModule =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
(import ../services/jellyfin.nix {
|
||||
inherit config pkgs;
|
||||
lib = testLib;
|
||||
service_configs = testServiceConfigs;
|
||||
})
|
||||
];
|
||||
};
|
||||
in
|
||||
pkgs.testers.runNixOSTest {
|
||||
name = "fail2ban-jellyfin";
|
||||
|
||||
nodes = {
|
||||
server =
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
../modules/security.nix
|
||||
jellyfinModule
|
||||
];
|
||||
|
||||
# Create the media group
|
||||
users.groups.media = { };
|
||||
|
||||
# Disable ZFS mount dependency
|
||||
systemd.services."jellyfin-mounts".enable = lib.mkForce false;
|
||||
systemd.services.jellyfin = {
|
||||
wants = lib.mkForce [ ];
|
||||
after = lib.mkForce [ ];
|
||||
requires = lib.mkForce [ ];
|
||||
};
|
||||
|
||||
# Override for faster testing and correct port
|
||||
services.fail2ban.jails.jellyfin.settings = {
|
||||
maxretry = lib.mkForce 3;
|
||||
# In test, we connect directly to Jellyfin port, not via Caddy
|
||||
port = lib.mkForce "8096";
|
||||
};
|
||||
|
||||
# Create log directory and placeholder log file for fail2ban
|
||||
# Jellyfin logs to files, not systemd journal
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /var/lib/jellyfin/log 0755 jellyfin jellyfin"
|
||||
"f /var/lib/jellyfin/log/log_placeholder.log 0644 jellyfin jellyfin"
|
||||
];
|
||||
|
||||
# Make fail2ban start after Jellyfin
|
||||
systemd.services.fail2ban = {
|
||||
wants = [ "jellyfin.service" ];
|
||||
after = [ "jellyfin.service" ];
|
||||
};
|
||||
|
||||
# Give jellyfin more disk space and memory
|
||||
virtualisation.diskSize = 3 * 1024;
|
||||
virtualisation.memorySize = 2 * 1024;
|
||||
};
|
||||
|
||||
client = {
|
||||
environment.systemPackages = [ pkgs.curl ];
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
import time
|
||||
import re
|
||||
|
||||
start_all()
|
||||
server.wait_for_unit("jellyfin.service")
|
||||
server.wait_for_unit("fail2ban.service")
|
||||
server.wait_for_open_port(8096)
|
||||
server.wait_until_succeeds("curl -sf http://localhost:8096/health | grep -q Healthy", timeout=60)
|
||||
time.sleep(2)
|
||||
|
||||
# Wait for Jellyfin to create real log files and reload fail2ban
|
||||
server.wait_until_succeeds("ls /var/lib/jellyfin/log/log_2*.log", timeout=30)
|
||||
server.succeed("fail2ban-client reload jellyfin")
|
||||
|
||||
with subtest("Verify jellyfin jail is active"):
|
||||
status = server.succeed("fail2ban-client status")
|
||||
assert "jellyfin" in status, f"jellyfin jail not found in: {status}"
|
||||
|
||||
with subtest("Generate failed login attempts"):
|
||||
# Use -4 to force IPv4 for consistent IP tracking
|
||||
for i in range(4):
|
||||
client.execute("""
|
||||
curl -4 -s -X POST http://server:8096/Users/authenticatebyname \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'X-Emby-Authorization: MediaBrowser Client="test", Device="test", DeviceId="test", Version="1.0"' \
|
||||
-d '{"Username":"baduser","Pw":"badpass"}' || true
|
||||
""")
|
||||
time.sleep(0.5)
|
||||
|
||||
with subtest("Verify IP is banned"):
|
||||
time.sleep(3)
|
||||
status = server.succeed("fail2ban-client status jellyfin")
|
||||
print(f"jellyfin jail status: {status}")
|
||||
# Check that at least 1 IP is banned
|
||||
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("curl -4 -s --max-time 3 http://server:8096/ 2>&1")[0]
|
||||
assert exit_code != 0, "Connection should be blocked"
|
||||
'';
|
||||
}
|
||||
Reference in New Issue
Block a user