From 830e9a61f6e548db3ab27b3acbd0d5bfbfb8b1db Mon Sep 17 00:00:00 2001 From: duffyduck Date: Tue, 28 Jul 2026 10:37:40 +0200 Subject: [PATCH] geoblock: eigene Block-IPs/CIDRs zusaetzlich zu den Laendern (block_ips) Neue Option 'block_ips': komma-getrennte Liste eigener IPv4-IPs/CIDRs, die zusaetzlich zu den ipdeny-Laender-Ranges ins selbe ipset kommen (z.B. bekannte Angreifer, einzelne Hosts/Netze). Werden von denselben iptables-Regeln/Ports erfasst. - load_config validiert + normalisiert jede Angabe (ip_network strict=False, Host -> /32); IPv6 wird mit klarer Meldung abgelehnt (ipset ist IPv4). - apply_geoblock nimmt sie nach den Laender-CIDRs mit ins Set auf; Fuell-Log und --status weisen die Anzahl/Liste aus. - Doku: geoblock.ini.example (Kommentar + block_ips-Zeile) + README-Optionsliste. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 5 +++++ geoblock.ini.example | 9 +++++++++ geoblock.py | 27 +++++++++++++++++++++++++-- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c674dba..66a0662 100644 --- a/README.md +++ b/README.md @@ -541,6 +541,11 @@ Siehe Kommentare in der Datei selbst — kurz: den **urspruenglichen** Zielport (vor DNAT) statt des umgeschriebenen. Dann in `ports` die oeffentlichen Ports eintragen. Siehe Abschnitt "INPUT vs FORWARD" bzw. "Dienst hinter Portweiterleitung/DNAT" unten. +- `block_ips`: optional, komma-getrennte Liste **eigener** IPs/CIDRs (IPv4), die + **zusaetzlich** zu den Laender-Ranges geblockt werden (z.B. bekannte Angreifer, + einzelne Hosts/Netze). Einzel-IP (`203.0.113.7`) oder CIDR (`203.0.113.0/24`). + Kommt ins selbe `ipset` wie die Laender, wird also von denselben Regeln/Ports + erfasst. Leer = keine. - `proxy`: optional, HTTP(S)-Proxy fuer den Abruf der Zonefiles von ipdeny.com (z.B. `http://10.0.0.5:3128`, mit Auth `http://user:pass@host:port`). Nur noetig, wenn der Host selbst keine direkte Internetverbindung hat, aber diff --git a/geoblock.ini.example b/geoblock.ini.example index f7cd61a..9f5f6d6 100644 --- a/geoblock.ini.example +++ b/geoblock.ini.example @@ -56,6 +56,12 @@ ; die OEFFENTLICHEN Ports eintragen (z.B. 1194, 443), nicht die ; internen -- immun gegen den Remap. Meist zusammen mit chain=FORWARD. ; +; block_ips: optional, komma-getrennte Liste EIGENER IPs/CIDRs, die zusaetzlich +; zu den Laender-Ranges geblockt werden (z.B. bekannte Angreifer, +; einzelne Hosts/Netze). Einzel-IP ("203.0.113.7") oder CIDR +; ("203.0.113.0/24"). Nur IPv4. Kommt ins selbe ipset wie die Laender, +; wird also von denselben Regeln/Ports erfasst. Leer lassen = keine. +; ; proxy: optional, HTTP(S)-Proxy-URL, ueber den das Script ipdeny.com ; erreicht -- fuer Hosts, die zwar aus dem Internet erreichbar ; sind (z.B. hinter Reverse-Proxy/Firewall), selbst aber KEINE @@ -77,6 +83,9 @@ log_file = /var/log/geoblock/geoblock.log ; z.B. "8899" setzen (nur der Testserver wird geblockt, SSH bleibt offen). ports = protocol = tcp +; Eigene Block-IPs/CIDRs zusaetzlich zu den Laendern (komma-getrennt, IPv4). +; Leer lassen = keine. Beispiel: block_ips = 203.0.113.7, 198.51.100.0/24 +block_ips = ; Leer lassen, ausser der Host hat keine direkte Internetverbindung. proxy = diff --git a/geoblock.py b/geoblock.py index f3725ad..8d507d9 100755 --- a/geoblock.py +++ b/geoblock.py @@ -184,6 +184,19 @@ def load_config(path): # 'ports' die oeffentlichen Ports (z.B. 1194, 443) eintragen, immun gegen Remap. match_orig_dport = sec.getboolean("match_original_dport", fallback=False) + # block_ips: optionale, komma-getrennte Liste EIGENER IPs/CIDRs, die zusaetzlich + # zu den Laender-Ranges ins selbe ipset kommen (z.B. bekannte Angreifer, einzelne + # Hosts/Netze). Nur IPv4 (das ipset ist IPv4). Wird hier validiert + normalisiert. + block_ips = [] + for entry in (e.strip() for e in sec.get("block_ips", "").split(",") if e.strip()): + try: + net = ipaddress.ip_network(entry, strict=False) # akzeptiert Host (->/32) und CIDR + except ValueError: + raise ValueError(f"Ungueltige IP/CIDR in 'block_ips': '{entry}'") + if net.version != 4: + raise ValueError(f"'block_ips': nur IPv4 unterstuetzt (ipset ist IPv4), ungueltig: '{entry}'") + block_ips.append(str(net)) + return { "countries": countries, "chains": chains, @@ -195,6 +208,7 @@ def load_config(path): "ports": ports, "protocols": protocols, "match_orig_dport": match_orig_dport, + "block_ips": block_ips, "proxy": sec.get("proxy", "").strip(), } @@ -480,6 +494,12 @@ def apply_geoblock(cfg, dry_run=False): if not all_cidrs and not dry_run: raise RuntimeError("Keine IP-Bereiche geladen — Abbruch, um kein leeres Set zu aktivieren") + # 2b) eigene Block-IPs/CIDRs aus der .ini zusaetzlich aufnehmen (bereits in + # load_config validiert/normalisiert). Kommen ins selbe Set wie die Laender. + n_block_ips = len(cfg.get("block_ips", [])) + if n_block_ips: + all_cidrs.extend(cfg["block_ips"]) + # 3) Set atomar neu befuellen: temp-Set bauen, dann swap (kein Traffic-Loch) tmp_name = f"{name}_tmp" run(["ipset", "create", tmp_name, "hash:net", "-exist"], dry_run=dry_run) @@ -490,8 +510,9 @@ def apply_geoblock(cfg, dry_run=False): run(["ipset", "swap", tmp_name, name], dry_run=dry_run) run(["ipset", "destroy", tmp_name], dry_run=dry_run, check=False) - log(f"ipset '{name}' befuellt mit {len(all_cidrs)} Bereichen " - f"aus Laendern: {', '.join(c.upper() for c in cfg['countries'])}") + extra = f" + {n_block_ips} eigene(n) Block-IP(s)/CIDR(s)" if n_block_ips else "" + log(f"ipset '{name}' befuellt mit {len(all_cidrs)} Eintraegen " + f"(Laender: {', '.join(c.upper() for c in cfg['countries'])}{extra})") # 4) Vor dem Neu-Setzen ALLE bestehenden geoblock-Regeln der Chain entfernen # (auch verwaiste aus frueheren Laeufen mit anderer ini, z.B. andere Ports). @@ -545,6 +566,8 @@ def show_status(cfg): f"-- '--reassert' oder '--apply' setzt sie wieder an den Anfang") print(f"Konfigurierte Laender: {', '.join(c.upper() for c in cfg['countries'])}") + if cfg.get("block_ips"): + print(f"Eigene Block-IPs/CIDRs ({len(cfg['block_ips'])}): {', '.join(cfg['block_ips'])}") if cfg["ports"]: modus = "Original-Zielport vor DNAT (conntrack)" if cfg.get("match_orig_dport") \ else "Zielport (dports)"