fail2ban test

This commit is contained in:
2025-07-16 14:07:14 +02:00
parent 26bd1b3487
commit 4116a0e557
10 changed files with 165 additions and 19 deletions

View File

@@ -28,6 +28,9 @@
};
network = {
enable = true;
extraHosts = ''
192.168.88.14 vault.cnst.dev
'';
interfaces = {
"eno1" = {
allowedTCPPorts = [22 80 443];

View File

@@ -128,8 +128,8 @@
services = {
agenix = {
enable = true;
kima = {
enable = false;
sobotka = {
enable = true;
};
};
blueman = {

View File

@@ -1,10 +1,15 @@
{
{config, ...}: {
server = {
email = "adam@cnst.dev";
domain = "cnst.dev";
caddy = {
enable = true;
};
cfFail2ban = {
enable = true;
apiKeyFile = config.age.secrets.cloudflareFirewallApiKey.path;
zoneId = "0027acdfb8bbe010f55b676ad8698dfb";
};
vaultwarden = {
enable = true;
};

View File

@@ -121,6 +121,7 @@
imports = [
./server
./server/caddy
./server/cfFail2ban
./server/vaultwarden
];
};

View File

@@ -27,6 +27,11 @@ in {
default = {};
description = "Network interface configurations.";
};
extraHosts = mkOption {
type = types.lines;
default = "";
description = "Extra entries for /etc/hosts.";
};
};
};
@@ -45,6 +50,7 @@ in {
enable = true;
inherit (cfg) interfaces;
};
extraHosts = cfg.extraHosts;
};
systemd.services.NetworkManager = {

View File

@@ -57,7 +57,7 @@ in {
})
(mkIf cfg.sobotka.enable {
secrets = {
# Add sobotka specific secrets here
cloudflareFirewallApiKey.file = "${self}/secrets/cloudflareFirewallApiKey.age";
};
})
(mkIf cfg.toothpc.enable {

View File

@@ -0,0 +1,109 @@
# from @notthebee
{
lib,
config,
pkgs,
...
}: let
cfg = config.server.cfFail2ban;
in {
options.server.cfFail2ban = {
enable = lib.mkEnableOption {
description = "Enable cloudflare fail2ban";
};
apiKeyFile = lib.mkOption {
description = "File containing your API key, scoped to Firewall Rules: Edit";
type = lib.types.str;
example = lib.literalExpression ''
Authorization: Bearer Qj06My1wXJEzcW46QCyjFbSMgVtwIGfX63Ki3NOj79o=
'''
'';
};
zoneId = lib.mkOption {
type = lib.types.str;
};
jails = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submodule {
options = {
serviceName = lib.mkOption {
example = "vaultwarden";
type = lib.types.str;
};
failRegex = lib.mkOption {
type = lib.types.str;
example = "Login failed from IP: <HOST>";
};
ignoreRegex = lib.mkOption {
type = lib.types.str;
default = "";
};
maxRetry = lib.mkOption {
type = lib.types.int;
default = 3;
};
};
}
);
};
};
config = lib.mkIf cfg.enable {
services.fail2ban = {
enable = true;
extraPackages = [
pkgs.curl
pkgs.jq
];
jails =
lib.attrsets.mapAttrs (name: value: {
settings = {
bantime = "30d";
findtime = "1h";
enabled = true;
backend = "systemd";
journalmatch = "_SYSTEMD_UNIT=${value.serviceName}.service";
port = "http,https";
filter = "${name}";
maxretry = 3;
action = "cloudflare-token-agenix";
};
})
cfg.jails;
};
environment.etc = lib.attrsets.mergeAttrsList [
(lib.attrsets.mapAttrs' (
name: value: (lib.nameValuePair "fail2ban/filter.d/${name}.conf" {
text = ''
[Definition]
failregex = ${value.failRegex}
ignoreregex = ${value.ignoreRegex}
'';
})
)
cfg.jails)
{
"fail2ban/action.d/cloudflare-token-agenix.conf".text = let
notes = "Fail2Ban on ${config.networking.hostName}";
cfapi = "https://api.cloudflare.com/client/v4/zones/${cfg.zoneId}/firewall/access_rules/rules";
in ''
[Definition]
actionstart =
actionstop =
actioncheck =
actionunban = id=$(curl -s -X GET "${cfapi}" \
-H @${cfg.apiKeyFile} -H "Content-Type: application/json" \
| jq -r '.result[] | select(.notes == "${notes}" and .configuration.target == "ip" and .configuration.value == "<ip>") | .id')
if [ -z "$id" ]; then echo "id for <ip> cannot be found"; exit 0; fi; \
curl -s -X DELETE "${cfapi}/$id" \
-H @${cfg.apiKeyFile} -H "Content-Type: application/json" \
--data '{"cascade": "none"}'
actionban = curl -X POST "${cfapi}" -H @${cfg.apiKeyFile} -H "Content-Type: application/json" --data '{"mode":"block","configuration":{"target":"ip","value":"<ip>"},"notes":"${notes}"}'
[Init]
name = cloudflare-token-agenix
'';
}
];
};
}

View File

@@ -37,7 +37,16 @@ in {
}
'';
services.vaultwarden = {
services = {
cfFail2ban = lib.mkIf config.server.cfFail2ban.enable {
jails = {
vaultwarden = {
serviceName = "vaultwarden";
failRegex = "^.*Username or password is incorrect. Try again. IP: <HOST>. Username: <F-USER>.*</F-USER>.$";
};
};
};
vaultwarden = {
enable = true;
# environmentFile = config.age.secrets.vaultwarden-env.path;
@@ -58,4 +67,5 @@ in {
};
};
};
};
}

View File

@@ -0,0 +1,11 @@
age-encryption.org/v1
-> ssh-ed25519 t9iOEg +WyTc7ny6fCCkqYl/y6IpkxOvXqqKEKCgQaFym1ZLSE
fWBQRK/iIuPCJ4ktL0Sq9gJR8oI/vczLkDMbZr8IvNI
-> ssh-ed25519 KUYMFA BkGncK6jhjVqFPaVAzE9e2d2IXdCdatmDL6qdsD/giQ
l06NmVxHODsCOu1vWRoUpZGypsJYjSLOb1tT/lpbAi8
-> ssh-ed25519 76RhUQ i3fS2lTIKCclVln0sygcwf1CRW1uDtytLwxgvpL37Co
FwSf7i06olqD+XyvAkc6VHHEih70Gks31XYVow5teZU
-> ssh-ed25519 Jf8sqw rNEYb8GjN1xn7LSzkx9B570trzum1oT+gB+qNxPA0l4
DgX9IPNRImBz9JjSNopiCVyXWAnyFNa88fvw3LMJxBE
--- CcmMHcWD0iWgn705hfOLdqo2xpJWIdJ7y+xEHicME6g
<EFBFBD><EFBFBD><<3C>v<7F><76><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>0EG<45><47><1C>4<EFBFBD><34><EFBFBD><EFBFBD>ҥ<><>e<EFBFBD><65>ji<12><><EFBFBD><06><1E>]4yP<79><50>Ӥ,<2C>z<EFBFBD><7A>`#<13><><EFBFBD>l

View File

@@ -12,4 +12,5 @@ in {
"gcapi.age".publicKeys = [cnst kima];
"cloudflare-env.age".publicKeys = [cnst kima usobotka rsobotka];
"vaultwarden-env.age".publicKeys = [cnst kima usobotka rsobotka];
"cloudflareFirewallApiKey.age".publicKeys = [cnst kima usobotka rsobotka];
}