From 6e1e9687d6419fb3639919a10d4b9cf274a6fccc Mon Sep 17 00:00:00 2001 From: ARIA Date: Tue, 21 Jul 2026 12:08:30 +0000 Subject: [PATCH] Proxy-Option fuer Zonefile-Abruf (Host ohne direkte Internetverbindung) --- README.md | 37 +++++++++++++++++++++++++++++++++++++ geoblock.ini | 11 +++++++++++ geoblock.py | 32 +++++++++++++++++++++++++++----- 3 files changed, 75 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ef8cc6c..e367fef 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,11 @@ Siehe Kommentare in der Datei selbst — kurz: 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`): @@ -295,3 +300,35 @@ 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]`: + +```ini +[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. diff --git a/geoblock.ini b/geoblock.ini index f0106fd..c23bb8a 100644 --- a/geoblock.ini +++ b/geoblock.ini @@ -40,6 +40,15 @@ ; protocol: nur relevant wenn "ports" gesetzt ist. tcp (Standard), udp, ; oder "tcp,udp" fuer beides. Fuer die meisten Web-/App-Ports ; reicht "tcp". +; +; 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 +; direkte ausgehende Internetverbindung haben. +; Format: http://host:port oder mit Auth http://user:pass@host:port +; Wird fuer http UND https genutzt (ipdeny liefert per https aus). +; Leer lassen (Standard) = direkte Verbindung, kein Proxy. +; Beispiel: proxy = http://10.0.0.5:3128 [geoblock] countries = RU, CN, KP, IR @@ -53,6 +62,8 @@ log_file = /var/log/geoblock/geoblock.log ; z.B. "8899" setzen (nur der Testserver wird geblockt, SSH bleibt offen). ports = protocol = tcp +; Leer lassen, ausser der Host hat keine direkte Internetverbindung. +proxy = ; --- Test-Webserver (geoblock_testserver.py) -------------------------- ; Kleiner HTTP-Server ohne Abhaengigkeiten, um Geoblocking-Regeln von aussen diff --git a/geoblock.py b/geoblock.py index 515509f..f0d6768 100755 --- a/geoblock.py +++ b/geoblock.py @@ -20,6 +20,8 @@ VORAUSSETZUNGEN (auf dem Ziel-Host, NICHT im Container): - Pakete installiert: iptables, ipset Debian/Ubuntu: apt install iptables ipset - Internetzugang zum Laden der Zonefiles von ipdeny.com + (falls der Host selbst keine direkte Verbindung hat: 'proxy' in der + .ini setzen -- siehe README) BENUTZUNG: sudo python3 geoblock.py --config geoblock.ini --apply @@ -167,6 +169,7 @@ def load_config(path): "log_file": sec.get("log_file", "").strip(), "ports": ports, "protocols": protocols, + "proxy": sec.get("proxy", "").strip(), } @@ -213,12 +216,31 @@ def _variant_label(variant): return f"{variant['proto']}/{variant['dports']}" -def fetch_country_cidrs(country_code): - """Laedt die CIDR-Liste eines Landes von ipdeny.com. Gibt Liste von Strings zurueck.""" +def _build_url_opener(proxy): + """Baut einen urllib-Opener. Mit 'proxy' gesetzt (z.B. 'http://host:3128' oder + 'http://user:pass@host:3128') wird dieser fuer http UND https genutzt -- fuer + Server, die selbst keine direkte Internetverbindung haben und nur ueber + einen Vorgelagerten Proxy raus koennen.""" + if not proxy: + return urllib.request.build_opener() + handler = urllib.request.ProxyHandler({"http": proxy, "https": proxy}) + return urllib.request.build_opener(handler) + + +def fetch_country_cidrs(country_code, proxy=""): + """Laedt die CIDR-Liste eines Landes von ipdeny.com. Gibt Liste von Strings zurueck. + + 'proxy' (optional): HTTP(S)-Proxy-URL, falls der Server selbst keine direkte + Internetverbindung hat und ipdeny.com nur ueber einen Proxy erreichen kann. + """ url = ZONE_URL_TMPL.format(cc=country_code) - log(f"Lade IP-Ranges fuer '{country_code}' von {url}") + if proxy: + log(f"Lade IP-Ranges fuer '{country_code}' von {url} (ueber Proxy {proxy})") + else: + log(f"Lade IP-Ranges fuer '{country_code}' von {url}") + opener = _build_url_opener(proxy) try: - with urllib.request.urlopen(url, timeout=20) as resp: + with opener.open(url, timeout=20) as resp: text = resp.read().decode("utf-8", errors="ignore") except urllib.error.HTTPError as e: raise RuntimeError( @@ -276,7 +298,7 @@ def apply_geoblock(cfg, dry_run=False): # 2) alle CIDRs aller konfigurierten Laender sammeln all_cidrs = [] for cc in cfg["countries"]: - all_cidrs.extend(fetch_country_cidrs(cc)) + all_cidrs.extend(fetch_country_cidrs(cc, proxy=cfg.get("proxy", ""))) if not all_cidrs and not dry_run: raise RuntimeError("Keine IP-Bereiche geladen — Abbruch, um kein leeres Set zu aktivieren")