298 lines
12 KiB
Markdown
298 lines
12 KiB
Markdown
# Geoblocking
|
|
|
|
Blockt Verbindungen aus bestimmten Laendern auf Kernel-Ebene (iptables +
|
|
ipset). Die IP-Bereiche je Land kommen kostenlos (kein API-Key) von
|
|
[ipdeny.com](https://www.ipdeny.com/ipblocks/).
|
|
|
|
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):
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
iptables -L -n | grep -i geoblock
|
|
ipset list -n | grep -i geoblock
|
|
```
|
|
|
|
## Manuelle Nutzung (ohne systemd)
|
|
|
|
```bash
|
|
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:**
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```ini
|
|
[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:
|
|
```bash
|
|
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`
|
|
|
|
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.
|