ip blocklist by clanker

This commit is contained in:
Kopatz
2026-03-14 23:13:10 +01:00
parent d9246f76e6
commit 1180c1b65f
4 changed files with 196 additions and 17 deletions

View File

@@ -0,0 +1,192 @@
{
config,
lib,
pkgs,
...
}:
let
# --- Blocklist sources ---
# Add or remove URLs here. Each must be a plain list of IPs or CIDRs (one per line).
# Comments (#) and blank lines are stripped automatically.
blocklistSources = [
# DShield top attacking subnets (SANS ISC) — tab-separated: IP / mask / CIDR prefix
"https://feeds.dshield.org/block.txt"
# blocklist.de — scanners, bots, brute-force, scrapers
"https://lists.blocklist.de/lists/all.txt"
# GreenSnow — bots and port scanners
"https://blocklist.greensnow.co/greensnow.txt"
# Spamhaus DROP — hijacked netblocks used by cybercrime operations
"https://www.spamhaus.org/drop/drop.txt"
# FireHOL level2 — 48-hour attack tracker, pre-merged CIDRs (good all-rounder)
"https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_level2.netset"
];
# nftables table and set names — must match your existing nftables config
nftTable = "inet ip_drop";
nftSet = "blocked-ip4";
# How often to refresh (systemd calendar format)
refreshInterval = "*:0/4"; # every 4 hours
# --- The updater script ---
# Written as a pkgs.writeShellApplication so nix handles the PATH / dependencies.
updaterScript = pkgs.writeShellApplication {
name = "nft-blocklist-update";
runtimeInputs = with pkgs; [
curl
gawk
iproute2 # provides 'ip' for basic sanity; not strictly needed but good to have
nftables # provides 'nft'
coreutils
];
text = ''
set -euo pipefail
NFT_TABLE="${nftTable}"
NFT_SET="${nftSet}"
TMPFILE=$(mktemp /tmp/nft-blocklist-XXXXXX.txt)
trap 'rm -f "$TMPFILE"' EXIT
echo "[blocklist] Fetching sources..."
# --- Per-source fetch + normalise ---
# Each source may use a different format; we normalise to bare CIDRs.
fetch_and_normalise() {
local url="$1"
curl --silent --fail --max-time 30 --retry 3 "$url" | \
awk '
# Skip blank lines and comment lines
/^[[:space:]]*$/ { next }
/^[[:space:]]*#/ { next }
# DShield format: "IP<tab>netmask<tab>prefix ..." convert to CIDR
/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[[:space:]]+[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[[:space:]]+[0-9]+/ {
print $1 "/" $3
next
}
# Plain CIDR (e.g. 1.2.3.0/24) pass through
/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\/[0-9]+/ {
print $1
next
}
# Plain host IP (no prefix) treat as /32
/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ {
print $1 "/32"
next
}
'
}
# Fetch all sources into the temp file, skip on individual failure
${lib.concatMapStringsSep "\n" (url: ''
echo "[blocklist] Fetching: ${url}"
fetch_and_normalise "${url}" >> "$TMPFILE" || \
echo "[blocklist] WARNING: failed to fetch ${url}, skipping" >&2
'') blocklistSources}
# Deduplicate lines (nft auto-merge handles overlapping CIDRs at load time)
sort -u "$TMPFILE" -o "$TMPFILE"
TOTAL=$(wc -l < "$TMPFILE")
echo "[blocklist] Total entries after dedup: $TOTAL"
if [ "$TOTAL" -eq 0 ]; then
echo "[blocklist] ERROR: no entries fetched aborting to avoid flushing the set" >&2
exit 1
fi
# Build the nft element string: "1.2.3.0/24, 5.6.7.8/32, ..."
ELEMENTS=$(paste -sd ',' "$TMPFILE")
echo "[blocklist] Loading into nft set $NFT_TABLE / $NFT_SET ..."
# Atomic-ish update: flush then reload inside a single nft transaction
nft -f - <<EOF
flush set $NFT_TABLE $NFT_SET
add element $NFT_TABLE $NFT_SET { $ELEMENTS }
EOF
echo "[blocklist] Done. $TOTAL entries active."
'';
};
in
{
# -------------------------------------------------------------------------
# systemd one-shot service that runs the updater
# -------------------------------------------------------------------------
systemd.services.nft-blocklist-update = {
description = "Update nftables IP blocklist (scanners / bots / scrapers)";
after = [
"network-online.target"
"nftables.service"
];
wants = [ "network-online.target" ];
# Also run once at boot (after nftables is up) so the set is populated immediately
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
ExecStart = lib.getExe updaterScript;
# Run as root — required to call nft
User = "root";
# Basic hardening
PrivateTmp = true;
ProtectSystem = "strict";
ProtectHome = true;
# Allow writing only to /tmp (via PrivateTmp)
ReadWritePaths = [ ];
};
};
# -------------------------------------------------------------------------
# systemd timer — fires on the configured interval
# -------------------------------------------------------------------------
systemd.timers.nft-blocklist-update = {
description = "Periodically refresh nftables IP blocklist";
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = refreshInterval;
# Spread the start time by up to 5 min to avoid thundering-herd
RandomizedDelaySec = "5m";
Persistent = true; # catch up if the machine was off
};
};
# -------------------------------------------------------------------------
# nftables — make sure the table + set exist so the updater can populate them
# This merges with whatever other rules you already have.
# -------------------------------------------------------------------------
networking.nftables.tables.ip_drop = {
family = "inet";
content = ''
set blocked-ip4 {
typeof ip saddr
flags interval
auto-merge
# starts empty; nft-blocklist-update.service fills it at boot + every 4h
elements = { 45.144.212.240, 74.7.227.136 }
}
chain input {
type filter hook input priority -100; policy accept;
ip saddr @blocked-ip4 log prefix "nftables drop: " level info counter drop
}
chain forward {
type filter hook forward priority -100; policy accept;
ip saddr @blocked-ip4 drop
}
'';
};
# Make the script available on PATH for manual runs: nft-blocklist-update
environment.systemPackages = [ updaterScript ];
}

View File

@@ -17,6 +17,7 @@ in
description = "FQDN under which gitea is available";
};
};
# https://docs.gitea.com/next/administration/config-cheat-sheet
config = lib.mkIf cfg.enable {
# Initial setup requires
services.gitea = {

View File

@@ -128,6 +128,7 @@ in
enable = true;
port = 9000;
globalConfig.scrape_interval = "1m";
retentionTime = "15d";
#stateDir = "../../${base}/prometheus";
exporters = {
node = {

View File

@@ -20,6 +20,7 @@
../../modules/services/ddclient-cloudflare.nix
../../modules/services/grafana.nix
../../modules/services/coturn.nix
../../modules/services/blocklist.nix
./disk-config.nix
./mail.nix
(modulesPath + "/installer/scan/not-detected.nix")
@@ -183,25 +184,9 @@
1234 #kop-audio default port
#9987 # teamspeak6 voice port
];
networking.nftables.tables.ip_drop = {
family = "inet";
content = ''
set blocked-ip4 {
typeof ip saddr
flags interval
auto-merge
elements = { 45.144.212.240 }
}
chain input {
# -100 priority to run before the default filter input chain (0)
type filter hook input priority -100; policy accept;
ip saddr @blocked-ip4 log prefix "nftables drop: " level info counter drop
}
'';
};
networking.hostName = "server-vm"; # Define your hostname.
#containers.privnetwork = {
# autoStart = true;
# privateNetwork = true;