Compare commits
2 Commits
d15ec9fe0b
...
a23b3d8c5f
| Author | SHA1 | Date | |
|---|---|---|---|
|
a23b3d8c5f
|
|||
|
4bf05f8b51
|
@@ -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; };
|
||||||
|
|||||||
@@ -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 = ''
|
||||||
|
|||||||
@@ -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 = "";
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
170
tests/fail2ban-minecraft.nix
Normal file
170
tests/fail2ban-minecraft.nix
Normal 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"
|
||||||
|
'';
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user