@@ -151,24 +151,4 @@
|
|||||||
"z ${service_configs.minecraft.parent_dir}/${service_configs.minecraft.server_name}/squaremap 710 ${config.services.minecraft-servers.user} ${config.services.minecraft-servers.group}"
|
"z ${service_configs.minecraft.parent_dir}/${service_configs.minecraft.server_name}/squaremap 710 ${config.services.minecraft-servers.user} ${config.services.minecraft-servers.group}"
|
||||||
"Z ${service_configs.minecraft.parent_dir}/${service_configs.minecraft.server_name}/squaremap/web 750 ${config.services.minecraft-servers.user} ${config.services.minecraft-servers.group}"
|
"Z ${service_configs.minecraft.parent_dir}/${service_configs.minecraft.server_name}/squaremap/web 750 ${config.services.minecraft-servers.user} ${config.services.minecraft-servers.group}"
|
||||||
];
|
];
|
||||||
|
|
||||||
# Protect Minecraft server from connection spam / brute force attempts
|
|
||||||
# Based on https://github.com/fail2ban/fail2ban/pull/2852#issuecomment-3105039910
|
|
||||||
# Only bans IPs that fail whitelist/ban checks - NOT legitimate player disconnects
|
|
||||||
services.fail2ban.jails.minecraft = {
|
|
||||||
enabled = true;
|
|
||||||
settings = {
|
|
||||||
backend = "auto";
|
|
||||||
port = builtins.toString config.services.minecraft-servers.servers.${service_configs.minecraft.server_name}.serverProperties.server-port;
|
|
||||||
logpath = "${config.services.minecraft-servers.dataDir}/${service_configs.minecraft.server_name}/logs/latest.log";
|
|
||||||
# defaults: maxretry=5, findtime=10m, bantime=10m
|
|
||||||
};
|
|
||||||
filter.Definition = {
|
|
||||||
# Only match whitelist rejections and bans - safe patterns that won't affect legitimate players
|
|
||||||
# Format: [HH:MM:SS] [Server thread/INFO]: Disconnecting <name> (/<IP>:<PORT>): <reason>
|
|
||||||
datepattern = "^\\[%%H:%%M:%%S\\]";
|
|
||||||
failregex = "^\\s*\\[Server thread/INFO\\]: Disconnecting .+ \\(/<HOST>:\\d+\\): (?:You are not white-listed on this server|You are banned from this server)";
|
|
||||||
ignoreregex = "";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,170 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
inputs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
testServerName = "testserver";
|
|
||||||
|
|
||||||
# Create pkgs with nix-minecraft overlay and unfree packages allowed
|
|
||||||
testPkgs = import inputs.nixpkgs {
|
|
||||||
system = pkgs.stdenv.targetPlatform.system;
|
|
||||||
config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "minecraft-server" ];
|
|
||||||
overlays = [
|
|
||||||
inputs.nix-minecraft.overlay
|
|
||||||
(import ../modules/overlays.nix)
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
testServiceConfigs = {
|
|
||||||
zpool_ssds = "";
|
|
||||||
https = {
|
|
||||||
domain = "test.local";
|
|
||||||
};
|
|
||||||
minecraft = {
|
|
||||||
parent_dir = "/var/lib/minecraft";
|
|
||||||
server_name = testServerName;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testLib = lib.extend (
|
|
||||||
final: prev: {
|
|
||||||
serviceMountWithZpool =
|
|
||||||
serviceName: zpool: dirs:
|
|
||||||
{ ... }:
|
|
||||||
{ };
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
minecraftModule =
|
|
||||||
{ config, lib, ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
(import ../services/minecraft.nix {
|
|
||||||
inherit config inputs;
|
|
||||||
pkgs = testPkgs;
|
|
||||||
lib = testLib;
|
|
||||||
service_configs = testServiceConfigs;
|
|
||||||
})
|
|
||||||
];
|
|
||||||
# Override nixpkgs config to prevent conflicts in test environment
|
|
||||||
nixpkgs.config = lib.mkForce {
|
|
||||||
allowUnfreePredicate = pkg: builtins.elem (testPkgs.lib.getName pkg) [ "minecraft-server" ];
|
|
||||||
};
|
|
||||||
# Disable whitelist import to avoid missing secrets file and reduce memory
|
|
||||||
services.minecraft-servers.servers.${testServerName} = {
|
|
||||||
whitelist = lib.mkForce { };
|
|
||||||
jvmOpts = lib.mkForce "-Xmx1G -Xms1G";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
testPkgs.testers.runNixOSTest {
|
|
||||||
name = "fail2ban-minecraft";
|
|
||||||
|
|
||||||
nodes = {
|
|
||||||
server =
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
../modules/security.nix
|
|
||||||
minecraftModule
|
|
||||||
];
|
|
||||||
|
|
||||||
# Disable ZFS mount dependency
|
|
||||||
systemd.services."minecraft-server-${testServerName}-mounts".enable = lib.mkForce false;
|
|
||||||
systemd.services."minecraft-server-${testServerName}" = {
|
|
||||||
wants = lib.mkForce [ ];
|
|
||||||
after = lib.mkForce [ ];
|
|
||||||
requires = lib.mkForce [ ];
|
|
||||||
};
|
|
||||||
|
|
||||||
# Override for faster testing
|
|
||||||
services.fail2ban.jails.minecraft.settings = {
|
|
||||||
maxretry = lib.mkForce 3;
|
|
||||||
findtime = lib.mkForce "5m";
|
|
||||||
bantime = lib.mkForce "10m";
|
|
||||||
};
|
|
||||||
|
|
||||||
# Create log directory and placeholder for fail2ban
|
|
||||||
systemd.tmpfiles.rules = [
|
|
||||||
"d /var/lib/minecraft/${testServerName}/logs 0755 minecraft minecraft"
|
|
||||||
"f /var/lib/minecraft/${testServerName}/logs/latest.log 0644 minecraft minecraft"
|
|
||||||
];
|
|
||||||
|
|
||||||
# Make fail2ban start after minecraft
|
|
||||||
systemd.services.fail2ban = {
|
|
||||||
wants = [ "minecraft-server-${testServerName}.service" ];
|
|
||||||
after = [ "minecraft-server-${testServerName}.service" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
# Give minecraft server more resources
|
|
||||||
virtualisation.diskSize = 4 * 1024;
|
|
||||||
virtualisation.memorySize = 4 * 1024;
|
|
||||||
};
|
|
||||||
|
|
||||||
client =
|
|
||||||
{ pkgs, ... }:
|
|
||||||
{
|
|
||||||
environment.systemPackages = [
|
|
||||||
(pkgs.python3.withPackages (ps: [ ps.mcstatus ]))
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
testScript = ''
|
|
||||||
import time
|
|
||||||
|
|
||||||
start_all()
|
|
||||||
|
|
||||||
# Wait for minecraft server to fully start
|
|
||||||
server.wait_for_unit("minecraft-server-${testServerName}.service", timeout=180)
|
|
||||||
server.wait_for_unit("fail2ban.service")
|
|
||||||
server.wait_for_open_port(25565, timeout=120)
|
|
||||||
|
|
||||||
# Wait for server to be ready (shows "Done" in logs)
|
|
||||||
server.wait_until_succeeds(
|
|
||||||
"grep -q 'Done' /var/lib/minecraft/${testServerName}/logs/latest.log",
|
|
||||||
timeout=120
|
|
||||||
)
|
|
||||||
time.sleep(2)
|
|
||||||
|
|
||||||
# Reload fail2ban now that the real log file exists
|
|
||||||
server.succeed("fail2ban-client reload minecraft")
|
|
||||||
time.sleep(2)
|
|
||||||
|
|
||||||
with subtest("Verify minecraft jail is active"):
|
|
||||||
status = server.succeed("fail2ban-client status")
|
|
||||||
print(f"fail2ban status:\n{status}")
|
|
||||||
assert "minecraft" in status, f"minecraft jail not found in: {status}"
|
|
||||||
|
|
||||||
with subtest("Verify jail configuration"):
|
|
||||||
# Check jail status shows it's monitoring the log file
|
|
||||||
status = server.succeed("fail2ban-client status minecraft")
|
|
||||||
print(f"Jail status:\n{status}")
|
|
||||||
assert "minecraft" in status, "minecraft jail not properly configured"
|
|
||||||
|
|
||||||
with subtest("Check server logs"):
|
|
||||||
logs = server.succeed("tail -20 /var/lib/minecraft/${testServerName}/logs/latest.log")
|
|
||||||
print(f"Server logs:\n{logs}")
|
|
||||||
|
|
||||||
with subtest("Test regex with fail2ban-regex"):
|
|
||||||
# Test the filter regex against the log file
|
|
||||||
result = server.execute("fail2ban-regex /var/lib/minecraft/${testServerName}/logs/latest.log /etc/fail2ban/filter.d/minecraft.local 2>&1")
|
|
||||||
print(f"Regex test result:\n{result}")
|
|
||||||
|
|
||||||
with subtest("Verify jail is functional"):
|
|
||||||
# The jail should be running and monitoring - mcstatus won't trigger bans
|
|
||||||
# since it only does status pings, not login attempts that would fail whitelist
|
|
||||||
status = server.succeed("fail2ban-client status minecraft")
|
|
||||||
print(f"Final jail status:\n{status}")
|
|
||||||
# Verify the jail is running (has filter file loaded)
|
|
||||||
assert "Filter" in status or "File list" in status or "Currently" in status, "Jail not properly running"
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
@@ -20,5 +20,4 @@ in
|
|||||||
fail2banVaultwardenTest = handleTest ./fail2ban-vaultwarden.nix;
|
fail2banVaultwardenTest = handleTest ./fail2ban-vaultwarden.nix;
|
||||||
fail2banImmichTest = handleTest ./fail2ban-immich.nix;
|
fail2banImmichTest = handleTest ./fail2ban-immich.nix;
|
||||||
fail2banJellyfinTest = handleTest ./fail2ban-jellyfin.nix;
|
fail2banJellyfinTest = handleTest ./fail2ban-jellyfin.nix;
|
||||||
fail2banMinecraftTest = handleTest ./fail2ban-minecraft.nix;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user