17 KiB
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.example |
Beispiel-Konfiguration (Vorlage im Repo, siehe unten) |
geoblock.ini |
Eure eigene, lokale Konfiguration (Laenderliste, Chain, Logging, ...) — nicht im Repo/Git, siehe unten |
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.examplenach/opt/geoblock/und erzeugt daraus, falls noch keine vorhanden ist,geoblock.ini(bestehendegeoblock.iniwird NIE ueberschrieben) - legt
/var/log/geoblock/an - installiert + aktiviert
geoblock.timer(systemctl enable --now)
Wichtig: geoblock.ini ist eure lokale, echte Konfiguration (Laender,
Ports, ggf. Proxy-Zugangsdaten) — die Datei steht deshalb in .gitignore
und wird NIE ins Repo committed. Vorlage/Referenz ist geoblock.ini.example
(die landet im Repo und wird bei jeder Installation frisch mitkopiert).
Wer das Script direkt aus dem Repo-Verzeichnis heraus nutzt (ohne
install_geoblock.sh), muss die .ini einmal selbst anlegen:
cp geoblock.ini.example geoblock.ini
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:
- Im Script-Kopf
LOG_FILEauf den Pfad aus der.ini(log_file) anpassen. - Ausfuehrbar machen und kopieren:
(Pfad haengt vom Setup ab — bei OMD-Sites z.B.
chmod 755 geoblock_checkmk cp geoblock_checkmk /usr/lib/check_mk_agent/local/~/local/lib/check_mk_agent/local/.) - 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 pruefenlog: iptables-LOG-Eintrag vor dem DROP (dmesg/kern.log) an/ausipset_name: Name des ipset-Setslog_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 wennportsgesetzt ist —tcp(Default),udpodertcp,udpproxy: optional, HTTP(S)-Proxy fuer den Abruf der Zonefiles von ipdeny.com (z.B.http://10.0.0.5:3128, mit Authhttp://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 (Default8899)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_passauf127.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:
- selbst direkt mit einer oeffentlichen IP/Port erreichbar sein (kein Proxy dazwischen) — z.B. indem die Testmaschine testweise eine eigene oeffentliche IP bekommt, oder
- 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.
Betrieb hinter einem AWS Load Balancer (ALB vs. NLB)
Gleiches Grundproblem wie beim Reverse-Proxy-Abschnitt oben, nur mit AWS-spezifischer Antwort — ob die echte Client-IP am Server ankommt, haengt vom Load-Balancer-Typ ab:
- Application Load Balancer (ALB): Arbeitet auf Layer 7 (HTTP). Der
ALB terminiert die TCP-Verbindung vom Client komplett und baut eine
neue TCP-Verbindung zum Backend auf — mit der IP des ALB-Nodes
(private VPC-IP) als Absender, nicht der des Clients. Die echte Client-IP
steckt nur noch im HTTP-Header
X-Forwarded-For. iptables/ipset sehen aber ausschliesslich die TCP-Ebene, also die ALB-Node-IP — Geoblocking auf dem Backend bringt hinter einem ALB nichts, es wuerde effektiv die eigene AWS-Infrastruktur "blocken" bzw. gar nichts filtern. Optionen in diesem Fall: AWS WAF mit Geo-Match-Regel direkt am ALB (die vorgesehene AWS-Loesung dafuer), oder auf NLB wechseln, falls auf L7-Features wie Pfad-Routing verzichtet werden kann. - Network Load Balancer (NLB): Arbeitet auf Layer 4, terminiert die
TCP-Verbindung NICHT, sondern leitet sie durch. Die Quell-IP im TCP/IP-Paket,
das am Server ankommt, ist die echte Client-IP — genau das, was
iptables/ipset zur Filterung brauchen.
geoblock.pyfunktioniert hinter einem NLB in der Standard-Konfiguration also wie auf einem Server ohne Load Balancer davor.- Standardfall (funktioniert direkt): Targets liegen im selben VPC und sind als Instance-Target registriert — die echte Client-IP kommt transparent im TCP-Paket an, nichts weiter zu konfigurieren.
- Vorbehalt: Bei IP-Targets oder wenn der NLB ueber PrivateLink/Cross-VPC/Cross-Account angesprochen wird, bewahrt AWS die Client-IP nur, wenn am NLB Proxy Protocol v2 aktiviert ist. Ist das der Fall, kommt die IP nicht mehr "nackt" im TCP-Paket an, sondern als PROXY-Header vor dem eigentlichen Payload — iptables sieht dann weiterhin nur die NLB-Node-IP, die echte IP muesste applikationsseitig aus dem PROXY-Header ausgewertet werden, nicht von iptables. In diesem Fall vorher pruefen, ob Proxy Protocol aktiv ist.
Kurz zusammengefasst: ALB → Geoblocking auf dem Backend funktioniert
nicht (WAF am ALB nutzen). NLB mit Instance-Targets im selben VPC (Standard)
→ geoblock.py funktioniert unveraendert. NLB mit IP-Targets/PrivateLink →
vorher pruefen, ob Proxy Protocol v2 aktiv ist.
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.