Geoblocking

Blockt Verbindungen aus bestimmten Laendern auf Kernel-Ebene (iptables + ipset). Die IP-Bereiche je Land kommen kostenlos (kein API-Key) von ipdeny.com.

Kein Live-GeoIP-Lookup pro Verbindung (langsam, meist kostenpflichtig) — stattdessen der Standardansatz von fail2ban/CSF: die komplette IP-Range- Liste jedes Landes liegt im Kernel (ipset), iptables matched dagegen und droppt Pakete bereits auf Netzwerk-Ebene.

Dateien

Datei Zweck
geoblock.py Hauptscript: laedt Laender-IPs, setzt/entfernt ipset+iptables-Regeln
geoblock.ini Konfiguration (Laenderliste, Chain, Logging, ...)
geoblock.service systemd-Unit, fuehrt --apply aus
geoblock.timer systemd-Timer: beim Booten + einmal taeglich
install_geoblock.sh Installer: kopiert alles nach /opt/geoblock, richtet Timer ein (Testserver optional, siehe unten)
geoblock_checkmk checkmk local-check Plugin (manuell kopieren)
geoblock_testserver.py Kleiner Test-Webserver, um die Regeln von aussen zu pruefen
geoblock-testserver.service systemd-Unit, startet den Test-Webserver dauerhaft
uninstall_geoblock.sh Deinstaller: raeumt Timer/Service/Testserver/Regeln wieder ab (siehe unten)

Installation

Auf dem zu schuetzenden Host (root, iptables+ipset+python3 installiert):

git clone https://git.hacker-net.de/Aria-Software/geoblocking-python-script.git
cd geoblocking-python-script
sudo ./install_geoblock.sh

Das:

  • installiert geoblock.py + geoblock.ini nach /opt/geoblock/ (bestehende .ini wird NICHT ueberschrieben, neue landet als .example)
  • legt /var/log/geoblock/ an
  • installiert + aktiviert geoblock.timer (systemctl enable --now)

Der Test-Webserver wird standardmaessig NICHT installiert. Wer ihn braucht (siehe Abschnitt "Verbindungstest" unten), haengt die Option --with-testserver an:

sudo ./install_geoblock.sh --with-testserver
# optional mit eigenem Zielverzeichnis:
sudo ./install_geoblock.sh --with-testserver /opt/geoblock

Nur dann wird geoblock_testserver.py kopiert und geoblock-testserver.service installiert + gestartet (offener Port 8899 standardmaessig). Ohne die Option bleibt der Port zu — nachtraeglich installieren geht jederzeit per erneutem Aufruf mit --with-testserver.

Danach /opt/geoblock/geoblock.ini anpassen (Laenderliste!) und einmal manuell testen:

sudo systemctl start geoblock.service
journalctl -u geoblock.service -f

Der Timer laeuft danach automatisch: ~2 Minuten nach jedem Systemstart und einmal taeglich (mit bis zu 15min Zufallsversatz, damit nicht alle Hosts gleichzeitig ipdeny.com anfragen). Verpasste Laeufe (Host war aus) werden dank Persistent=true beim naechsten Boot nachgeholt.

Deinstallation

uninstall_geoblock.sh macht die Installation wieder rueckgaengig. Zwei Modi:

# Alles runter: Timer/Service, Testserver (falls installiert),
# iptables/ipset-Regeln, Installationsverzeichnis
sudo ./uninstall_geoblock.sh

# Nur den Test-Webserver entfernen, Geoblocking selbst bleibt aktiv
sudo ./uninstall_geoblock.sh --testserver-only

Weitere Optionen:

sudo ./uninstall_geoblock.sh --purge-logs        # zusaetzlich /var/log/geoblock loeschen
sudo ./uninstall_geoblock.sh /opt/geoblock   # abweichendes Zielverzeichnis (Default: /opt/geoblock)

Ohne --purge-logs bleiben die bisherigen Logs unter /var/log/geoblock erhalten. Bei der Komplett-Deinstallation wird VOR dem Loeschen automatisch geoblock.py --remove ausgefuehrt, damit die iptables-Regel und das ipset sauber aus dem Kernel entfernt werden (kein verwaistes DROP, keine offene Verbindung bleibt blockiert). Fehlen Script/Config bereits, wird gewarnt und man sollte manuell pruefen:

iptables -L -n | grep -i geoblock
ipset list -n | grep -i geoblock

Manuelle Nutzung (ohne systemd)

sudo python3 geoblock.py --config geoblock.ini --apply            # aktivieren/aktualisieren
sudo python3 geoblock.py --config geoblock.ini --apply --dry-run  # nur simulieren
sudo python3 geoblock.py --config geoblock.ini --status           # Zustand pruefen
sudo python3 geoblock.py --config geoblock.ini --remove           # rueckgaengig

--apply ist idempotent (kein doppeltes Anlegen von Regeln) und tauscht das ipset atomar per swap, damit waehrend der Aktualisierung kein Loch im Blocking entsteht.

Achtung: iptables-Regeln sind nicht reboot-persistent. Wer das zusaetzlich braucht: iptables-persistent / netfilter-persistent save nutzen — der systemd-Timer sorgt aber ohnehin bei jedem Boot fuer einen frischen --apply-Lauf, das reicht in der Praxis meist aus.

Verbindungstest (Test-Webserver)

geoblock_testserver.py ist ein winziger HTTP-Server ohne Abhaengigkeiten (nur Python-Stdlib). Er beantwortet jeden Request mit einer kleinen Info-Seite (Client-IP, Zeitstempel, User-Agent) und loggt jeden Request. Er weiss selbst NICHTS von Laendern/Blocklisten — der eigentliche Test ist der Netzwerkeffekt: geblockte IPs erreichen ihn gar nicht erst (iptables droppt vor dem TCP-Handshake), erlaubte IPs bekommen die Seite.

Wird nur installiert, wenn install_geoblock.sh mit der Option --with-testserver aufgerufen wurde (Default: nicht installiert, siehe Abschnitt "Installation" oben). Ist er installiert, laeuft er als geoblock-testserver.service auf Port 8899 (einstellbar unter [testserver] in geoblock.ini, siehe unten).

Testablauf:

# 1) Von einem ERLAUBTEN Netz/Land aus aufrufen -> sollte laden
curl http://<host>:8899/

# 2) Testweise den eigenen Laendercode (z.B. DE) in geoblock.ini
#    eintragen und aktivieren:
sudo python3 geoblock.py --config geoblock.ini --apply

# 3) Jetzt von einem Client MIT einer IP aus diesem Land connecten
#    (z.B. Handy im Mobilfunknetz) -> sollte jetzt TIMEOUT geben
curl --max-time 5 http://<host>:8899/

# 4) Land wieder aus der .ini entfernen + --apply -> Zugriff geht wieder

Manuell ohne systemd starten:

python3 geoblock_testserver.py --config geoblock.ini
# oder komplett ohne .ini:
python3 geoblock_testserver.py --port 8899 --bind 0.0.0.0 \
    --log-file /var/log/geoblock/testserver.log

Requests landen (Zeit, Client-IP, Methode/Pfad, User-Agent) in der unter log_file im [testserver]-Abschnitt konfigurierten Datei.

Achtung Firewall/Portfreigabe: der Testserver selbst oeffnet nur den Port lokal — ob er von aussen erreichbar ist, haengt von eurer sonstigen Firewall/Portweiterleitung ab (das ist bewusst getrennt von den Geoblock-DROP-Regeln, die ja genau diesen Port betreffen sollen).

Gezieltes Blocken einzelner Ports (Selbstaussperr-Schutz)

Per Default blockt --apply alle Ports/Protokolle fuer die gelisteten Laender — das ist klassisches Geoblocking auf Host-Ebene. Das ist beim Testen riskant: testet man testweise den eigenen Laendercode (siehe oben), sperrt man sich damit auch von SSH & Co. aus, falls der Test-Client zufaellig aus demselben Land connectet.

Ueber ports (und optional protocol) in der .ini laesst sich das Blocking auf einzelne Anwendungen eingrenzen. Beispiel: nur den Testserver-Port blocken, SSH bleibt fuer alle Laender offen:

[geoblock]
countries = de
...
ports = 8899
protocol = tcp

--apply legt dann statt einer allgemeinen DROP-Regel eine mit -p tcp -m multiport --dports 8899 an — matched nur Pakete zu Port 8899, alles andere (inkl. Port 22) bleibt unberuehrt. Mehrere Ports/Bereiche gehen kommaseparat (80,443,8000:9000), mehrere Protokolle ebenso (protocol = tcp,udp, legt dann eine Regel pro Protokoll an).

--status zeigt an, ob gerade eine Port-Einschraenkung aktiv ist und welche. --remove entfernt automatisch die zur aktuellen .ini passenden Regel-Varianten — falls ports/protocol zwischen zwei Laeufen geaendert wurden, lohnt sich vor dem naechsten --apply ein Blick mit iptables -L INPUT -n --line-numbers, ob noch Altregeln mit der vorherigen Variante stehen.

Logging

In geoblock.ini unter log_file einen Pfad eintragen (Default: /var/log/geoblock/geoblock.log, wird vom Installer angelegt). Bei jedem Lauf wird eine Zeile angehaengt, u.a. eine maschinenlesbare RESULT status=OK/ERROR ...-Zeile — genau die wertet das checkmk-Plugin aus. Leer lassen = keine Datei-Logs (nur stderr/systemd-Journal).

checkmk-Monitoring

geoblock_checkmk ist ein klassisches checkmk local check-Plugin (kein extra Agent-Plugin-Verzeichnis noetig). Manuell einrichten:

  1. Im Script-Kopf LOG_FILE auf den Pfad aus der .ini (log_file) anpassen.
  2. Ausfuehrbar machen und kopieren:
    chmod 755 geoblock_checkmk
    cp geoblock_checkmk /usr/lib/check_mk_agent/local/
    
    (Pfad haengt vom Setup ab — bei OMD-Sites z.B. ~/local/lib/check_mk_agent/local/.)
  3. Naechster Agent-Abruf zeigt den Service Geoblock.

Status-Logik: ERROR im letzten Lauf → CRIT. Letzter erfolgreicher Lauf

36h alt → WARN, > 72h alt → CRIT (Timer laeuft taeglich, das faengt einen einzelnen verpassten Lauf ab, ohne sofort zu alarmieren). Sonst OK, inkl. Perfdata (Alter in Sekunden, Anzahl geblockter IP-Bereiche).

Konfiguration (geoblock.ini)

Siehe Kommentare in der Datei selbst — kurz:

  • countries: Komma-Liste ISO-3166-1-alpha-2 Codes (z.B. RU, CN, KP, IR)
  • chain: iptables-Chain (INPUT = eingehender Traffic zu diesem Host)
  • interface: optional, nur ein Interface pruefen
  • log: iptables-LOG-Eintrag vor dem DROP (dmesg/kern.log) an/aus
  • ipset_name: Name des ipset-Sets
  • log_file: Pfad zur script-eigenen Log-Datei (fuer checkmk)
  • ports: optional, Komma-Liste von Ports/Bereichen (z.B. 80,443,8000:9000). Leer = alle Ports/Protokolle werden geblockt (klassisches Geoblocking). Gesetzt = nur diese Ports werden fuer die gelisteten Laender geblockt, der Rest des Hosts bleibt erreichbar. Siehe Abschnitt "Gezieltes Blocken einzelner Ports" unten.
  • protocol: nur relevant wenn ports gesetzt ist — tcp (Default), udp oder tcp,udp
  • 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 ueber einen Proxy raus kann. Leer lassen = direkte Verbindung (Standard). Siehe Abschnitt "Proxy fuer den Zonefile-Abruf" unten.

Abschnitt [testserver] (fuer geoblock_testserver.py):

  • port: TCP-Port des Test-Webservers (Default 8899)
  • bind: Bind-Adresse (0.0.0.0 = alle IPv4-Interfaces)
  • log_file: Pfad zur Request-Log-Datei des Testservers

Betrieb hinter einem Reverse-Proxy (z.B. nginx)

Wichtig zu verstehen, weil es leicht zu falschen Testergebnissen fuehrt: Geoblocking mit iptables/ipset arbeitet auf Netzwerk-Ebene (TCP), nicht auf HTTP-Ebene. Es sieht immer nur die IP, die die TCP-Verbindung tatsaechlich zum jeweiligen Host aufbaut — unabhaengig davon, was in HTTP-Headern steht.

Wo die echte Client-IP ankommt, haengt vom Aufbau ab:

  • nginx ist der einzige "Proxy" (direkt am Internet, kein CDN/WAF davor): Die TCP-Verbindung vom Client landet direkt am nginx-Host. Dort sieht sowohl iptables als auch nginx selbst die echte oeffentliche Client-IP. In diesem Setup muessen die Geoblock-Regeln auf dem nginx-Host greifen, und zwar auf dem Port, auf dem nginx nach aussen lauscht (z.B. 80/443) — NICHT auf dem internen Port des dahinterliegenden Testservers/Backends.
  • Davor haengt zusaetzlich ein CDN/WAF (Cloudflare, Fastly, ...): Dann kommt am nginx-Host nur noch die IP des CDN-Rechenzentrums an, nicht die echte Client-IP. iptables auf dem nginx-Host wuerde dann effektiv das CDN blocken, nicht den Endnutzer — Geoblocking muesste in diesem Fall beim CDN selbst konfiguriert werden (die meisten bieten das nativ an).
  • Der Testserver haengt NUR intern hinter nginx (z.B. proxy_pass auf 127.0.0.1:8899): Der Testserver selbst sieht dann nur die IP von nginx (127.0.0.1), nicht die des echten Clients — die Verbindung des Clients wurde ja schon vorher bei nginx "verbraucht" und neu aufgebaut. Ein iptables-DROP auf Port 8899 wuerde in diesem Aufbau gar nichts bewirken, weil der Traffic zum Testserver nie ueber das oeffentliche Netz laeuft, sondern nur lokal von nginx zum Backend.

Praktische Konsequenz fuer den Verbindungstest:

Um den Testserver (geoblock_testserver.py, siehe oben) sinnvoll von aussen zu testen, muss er entweder:

  1. selbst direkt mit einer oeffentlichen IP/Port erreichbar sein (kein Proxy dazwischen) — z.B. indem die Testmaschine testweise eine eigene oeffentliche IP bekommt, oder
  2. hinter nginx haengen, wobei dann nginx (nicht der Testserver) der Punkt ist, an dem die Geoblock-Regeln greifen muessen.

Fuer einen sauberen End-to-End-Test der iptables-Regeln selbst (ohne Fragen zu Proxy-Ketten/CDN reinzumischen) ist Variante 1 — Testmaschine direkt mit eigener oeffentlicher IP, Geoblocking direkt auf dieser Maschine — der einfachste und eindeutigste Weg.

Proxy fuer den Zonefile-Abruf (proxy in geoblock.ini)

Wichtig — das ist ein ANDERES Thema als der Reverse-Proxy-Abschnitt oben. Dort ging es darum, wo die eingehende Client-Verbindung ankommt. Hier geht es darum, wie geoblock.py selbst ausgehend die Zonefiles von ipdeny.com laedt.

Wann noetig: Manche Hosts sind zwar aus dem Internet erreichbar (z.B. ueber einen vorgelagerten Reverse-Proxy oder eine Firewall mit Portweiterleitung), haben aber selbst keine direkte ausgehende Internetverbindung — nur einen Weg ueber einen internen HTTP(S)-Proxy nach draussen. Ohne diesen Proxy kann geoblock.py https://www.ipdeny.com/... nicht erreichen und bricht mit einem URL-Fehler ab.

Konfiguration in geoblock.ini, Abschnitt [geoblock]:

[geoblock]
countries = de
proxy = http://10.0.0.5:3128
; mit Basic-Auth am Proxy:
; proxy = http://user:pass@10.0.0.5:3128

Der Proxy wird sowohl fuer HTTP- als auch HTTPS-Abrufe genutzt (ipdeny liefert per HTTPS aus). Leer lassen (Standard) = das Script verbindet sich direkt, ohne Proxy — das ist der Normalfall auf den meisten Servern.

Betroffen ist nur der Abruf der Zonefiles beim Ausfuehren von --apply/--update. iptables/ipset selbst und der Testserver brauchen dafuer keinen Proxy — die filtern nur lokal am Kernel-Paketfilter.

S
Description
No description provided
Readme
98 KiB
Languages
Python 81.3%
Shell 18.7%