Compare commits

...

2 Commits

Author SHA1 Message Date
a23b3d8c5f minecraft: fail2ban 2026-01-21 20:21:23 -05:00
4bf05f8b51 hostPlatform -> targetPlatform 2026-01-21 15:25:25 -05:00
6 changed files with 195 additions and 3 deletions

View File

@@ -173,7 +173,7 @@
pkgs = import nixpkgs { pkgs = import nixpkgs {
inherit system; inherit system;
hostPlatform = system; targetPlatform = system;
buildPlatform = builtins.currentSystem; buildPlatform = builtins.currentSystem;
}; };
lib = import ./modules/lib.nix { inherit inputs pkgs; }; lib = import ./modules/lib.nix { inherit inputs pkgs; };

View File

@@ -5,7 +5,8 @@
... ...
}: }:
let let
graphing-calculator = inputs.ytbn-graphing-software.packages.${pkgs.stdenv.hostPlatform.system}.web; graphing-calculator =
inputs.ytbn-graphing-software.packages.${pkgs.stdenv.targetPlatform.system}.web;
in in
{ {
services.caddy.virtualHosts."graphing.${service_configs.https.domain}".extraConfig = '' services.caddy.virtualHosts."graphing.${service_configs.https.domain}".extraConfig = ''

View File

@@ -151,4 +151,24 @@
"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 = "";
};
};
} }

View File

@@ -0,0 +1,170 @@
{
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"
'';
}

View File

@@ -8,7 +8,7 @@
let let
# Create pkgs with nix-minecraft overlay and unfree packages allowed # Create pkgs with nix-minecraft overlay and unfree packages allowed
testPkgs = import inputs.nixpkgs { testPkgs = import inputs.nixpkgs {
system = pkgs.stdenv.hostPlatform.system; system = pkgs.stdenv.targetPlatform.system;
config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "minecraft-server" ]; config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "minecraft-server" ];
overlays = [ overlays = [
inputs.nix-minecraft.overlay inputs.nix-minecraft.overlay

View File

@@ -20,4 +20,5 @@ 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;
} }