Test-Webserver fuer Verbindungstests hinzufuegen (systemd-Service, .ini-Sektion, README)

This commit is contained in:
ARIA
2026-07-21 12:02:48 +02:00
parent de65890c4f
commit 9c635c49e3
5 changed files with 349 additions and 2 deletions
+2
View File
@@ -0,0 +1,2 @@
__pycache__/
*.pyc
+58 -1
View File
@@ -17,8 +17,10 @@ und droppt Pakete bereits auf Netzwerk-Ebene.
| `aria_geoblock.ini` | Konfiguration (Laenderliste, Chain, Logging, ...) | | `aria_geoblock.ini` | Konfiguration (Laenderliste, Chain, Logging, ...) |
| `aria-geoblock.service` | systemd-Unit, fuehrt `--apply` aus | | `aria-geoblock.service` | systemd-Unit, fuehrt `--apply` aus |
| `aria-geoblock.timer` | systemd-Timer: beim Booten + einmal taeglich | | `aria-geoblock.timer` | systemd-Timer: beim Booten + einmal taeglich |
| `install_aria_geoblock.sh` | Installer: kopiert alles nach `/opt/aria-geoblock`, richtet Timer ein | | `install_aria_geoblock.sh` | Installer: kopiert alles nach `/opt/aria-geoblock`, richtet Timer + Testserver ein |
| `aria_geoblock_checkmk` | checkmk local-check Plugin (manuell kopieren) | | `aria_geoblock_checkmk` | checkmk local-check Plugin (manuell kopieren) |
| `aria_geoblock_testserver.py` | Kleiner Test-Webserver, um die Regeln von aussen zu pruefen |
| `aria-geoblock-testserver.service` | systemd-Unit, startet den Test-Webserver dauerhaft |
## Installation ## Installation
@@ -35,6 +37,8 @@ Das:
(bestehende `.ini` wird NICHT ueberschrieben, neue landet als `.example`) (bestehende `.ini` wird NICHT ueberschrieben, neue landet als `.example`)
- legt `/var/log/aria_geoblock/` an - legt `/var/log/aria_geoblock/` an
- installiert + aktiviert `aria-geoblock.timer` (`systemctl enable --now`) - installiert + aktiviert `aria-geoblock.timer` (`systemctl enable --now`)
- installiert + startet `aria-geoblock-testserver.service` (Test-Webserver,
siehe Abschnitt "Verbindungstest" unten)
Danach `/opt/aria-geoblock/aria_geoblock.ini` anpassen (Laenderliste!) und Danach `/opt/aria-geoblock/aria_geoblock.ini` anpassen (Laenderliste!) und
einmal manuell testen: einmal manuell testen:
@@ -67,6 +71,53 @@ zusaetzlich braucht: `iptables-persistent` / `netfilter-persistent save`
nutzen — der systemd-Timer sorgt aber ohnehin bei jedem Boot fuer einen nutzen — der systemd-Timer sorgt aber ohnehin bei jedem Boot fuer einen
frischen `--apply`-Lauf, das reicht in der Praxis meist aus. frischen `--apply`-Lauf, das reicht in der Praxis meist aus.
## Verbindungstest (Test-Webserver)
`aria_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.
Laeuft nach `install_aria_geoblock.sh` automatisch als
`aria-geoblock-testserver.service` auf Port `8899` (einstellbar unter
`[testserver]` in `aria_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 aria_geoblock.ini
# eintragen und aktivieren:
sudo python3 aria_geoblock.py --config aria_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 aria_geoblock_testserver.py --config aria_geoblock.ini
# oder komplett ohne .ini:
python3 aria_geoblock_testserver.py --port 8899 --bind 0.0.0.0 \
--log-file /var/log/aria_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).
## Logging ## Logging
In `aria_geoblock.ini` unter `log_file` einen Pfad eintragen (Default: In `aria_geoblock.ini` unter `log_file` einen Pfad eintragen (Default:
@@ -105,3 +156,9 @@ Siehe Kommentare in der Datei selbst — kurz:
- `log`: iptables-LOG-Eintrag vor dem DROP (dmesg/kern.log) an/aus - `log`: iptables-LOG-Eintrag vor dem DROP (dmesg/kern.log) an/aus
- `ipset_name`: Name des ipset-Sets - `ipset_name`: Name des ipset-Sets
- `log_file`: Pfad zur script-eigenen Log-Datei (fuer checkmk) - `log_file`: Pfad zur script-eigenen Log-Datei (fuer checkmk)
Abschnitt `[testserver]` (fuer `aria_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
+21
View File
@@ -0,0 +1,21 @@
[Unit]
Description=ARIA Geoblocking - Test-Webserver fuer Verbindungstests von aussen
Documentation=https://git.hacker-net.de/Aria-Software/geoblocking-python-script
After=network-online.target
Wants=network-online.target
ConditionPathExists=__INSTALL_DIR__/aria_geoblock.ini
[Service]
Type=simple
User=root
ExecStart=/usr/bin/python3 __INSTALL_DIR__/aria_geoblock_testserver.py --config __INSTALL_DIR__/aria_geoblock.ini
Restart=on-failure
RestartSec=5
# Laeuft dauerhaft (kein oneshot wie aria-geoblock.service) - beantwortet
# Requests solange der Host laeuft. Port/Bind/Log kommen aus [testserver]
# in der .ini. User=root nur wegen evtl. niedriger Portnummer/Log-Verzeichnis-
# Rechten - wer will kann hier einen unprivilegierten User eintragen, sofern
# Port >1024 ist und der User Schreibrechte auf das Log-Verzeichnis hat.
[Install]
WantedBy=multi-user.target
+251
View File
@@ -0,0 +1,251 @@
#!/usr/bin/env python3
"""
aria_geoblock_testserver.py — kleiner Test-Webserver fuer aria-geoblock
ZWECK:
Ein minimaler HTTP-Server (nur Python-Stdlib, keine Abhaengigkeiten),
gegen den man von AUSSEN (Handy im Mobilfunknetz, VPN mit anderem Land,
Freund im Ausland, ...) connecten kann, um zu pruefen ob die Geoblocking-
Regeln (ipset/iptables aus aria_geoblock.py) wirklich greifen.
Die Logik ist bewusst simpel: der Server selbst weiss NICHTS von Laendern
oder Blocklisten. Er beantwortet einfach jeden Request, der ihn erreicht,
mit einer kleinen HTML-Seite (Client-IP, Zeitstempel, User-Agent) und
loggt den Request in eine Datei.
Der eigentliche TEST ist der Netzwerkeffekt: iptables droppt Pakete
geblockter Laender bereits VOR dem TCP-Handshake. Kommt eine Anfrage aus
einem geblockten Land, sieht dieser Server sie also gar nicht erst -
der Client bekommt stattdessen ein Timeout. Aus einem NICHT geblockten
Land/Netz laedt die Seite ganz normal.
Testablauf (siehe auch README.md):
1. Testserver starten (systemd-Service aria-geoblock-testserver, oder
manuell: siehe unten).
2. Von einem Client aus einem ERLAUBTEN Land/Netz aufrufen:
curl http://<host>:<port>/
-> sollte die Test-Seite liefern.
3. Ziel-Land testweise in aria_geoblock.ini eintragen (z.B. den eigenen
Ländercode DE), aria_geoblock.py --apply ausfuehren, dann von einem
Client MIT dieser Landes-IP erneut connecten
-> sollte jetzt in ein Timeout laufen (kein TCP-Connect möglich).
4. Land wieder aus der .ini entfernen, --apply, Zugriff geht wieder.
BENUTZUNG:
python3 aria_geoblock_testserver.py --config aria_geoblock.ini
-> Port/Bind-Adresse/Log-Pfad kommen aus dem Abschnitt [testserver]
der .ini (siehe aria_geoblock.ini im gleichen Repo)
python3 aria_geoblock_testserver.py --port 8899 --bind 0.0.0.0 \
--log-file /var/log/aria_geoblock/testserver.log
-> alternativ komplett ohne .ini, per CLI-Argumenten
CLI-Argumente ueberschreiben, falls angegeben, die Werte aus der .ini.
LOGGING:
Jeder eingehende Request wird als eine Zeile geloggt (Zeitstempel,
Client-IP, Methode, Pfad, User-Agent, Statuscode). Ohne --log-file/
ohne log_file in der .ini wird nur nach stderr (systemd-Journal)
geloggt.
AUTOSTART:
Siehe aria-geoblock-testserver.service im gleichen Repo sowie
install_aria_geoblock.sh, das den Service optional mit einrichtet.
"""
import argparse
import configparser
import datetime
import html
import ipaddress
import socket
import sys
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
DEFAULT_PORT = 8899
DEFAULT_BIND = "0.0.0.0"
# Wird in main() aus CLI/.ini gesetzt. None = keine Datei-Logs.
LOG_FILE = None
def _timestamp():
return datetime.datetime.now().astimezone().isoformat(timespec="seconds")
def _write_log_file(line):
if not LOG_FILE:
return
try:
with open(LOG_FILE, "a", encoding="utf-8") as f:
f.write(f"{_timestamp()} {line}\n")
except OSError as e:
print(f"[geoblock-testserver] WARNUNG: konnte Log-Datei nicht schreiben "
f"({LOG_FILE}): {e}", file=sys.stderr)
def log(msg):
line = f"[geoblock-testserver] {msg}"
print(line, file=sys.stderr, flush=True)
_write_log_file(line)
def load_config(path):
"""Liest [testserver] aus der aria_geoblock.ini. Fehlt der Abschnitt
komplett, werden die Defaults benutzt (die .ini ist fuer den Testserver
also optional, im Gegensatz zu aria_geoblock.py)."""
cfg = configparser.ConfigParser()
read_ok = cfg.read(path)
if not read_ok:
raise FileNotFoundError(f"Konnte .ini nicht lesen: {path}")
if "testserver" not in cfg:
log("Abschnitt [testserver] nicht in der .ini gefunden - benutze Defaults "
f"(port={DEFAULT_PORT}, bind={DEFAULT_BIND}, kein log_file)")
return {"port": DEFAULT_PORT, "bind": DEFAULT_BIND, "log_file": ""}
sec = cfg["testserver"]
return {
"port": sec.getint("port", fallback=DEFAULT_PORT),
"bind": sec.get("bind", fallback=DEFAULT_BIND).strip(),
"log_file": sec.get("log_file", fallback="").strip(),
}
def _client_ip(handler):
"""Bevorzugt X-Forwarded-For (falls hinter einem Reverse-Proxy), sonst
die direkte Peer-IP der TCP-Verbindung."""
xff = handler.headers.get("X-Forwarded-For")
if xff:
# erster Eintrag = urspruenglicher Client
candidate = xff.split(",")[0].strip()
try:
ipaddress.ip_address(candidate)
return candidate
except ValueError:
pass
return handler.client_address[0]
PAGE_TMPL = """<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>ARIA Geoblock — Verbindungstest</title>
<style>
body {{ font-family: -apple-system, Segoe UI, Roboto, sans-serif;
background: #0b1220; color: #e6ebf5; margin: 0;
display: flex; align-items: center; justify-content: center;
min-height: 100vh; }}
.card {{ background: #131c2e; border: 1px solid #24304a; border-radius: 12px;
padding: 2rem 2.5rem; max-width: 520px; box-shadow: 0 8px 24px rgba(0,0,0,.4); }}
h1 {{ font-size: 1.3rem; margin-top: 0; color: #6ee7a8; }}
table {{ width: 100%; border-collapse: collapse; margin-top: 1rem; }}
td {{ padding: .4rem 0; border-bottom: 1px solid #24304a; font-size: .92rem; }}
td.k {{ color: #8fa1c7; width: 40%; }}
.ok {{ color: #6ee7a8; font-weight: 600; }}
p.hint {{ color: #8fa1c7; font-size: .82rem; margin-top: 1.2rem; line-height: 1.4; }}
</style>
</head>
<body>
<div class="card">
<h1>&#10003; Verbindung erfolgreich</h1>
<p class="ok">Diese Anfrage kam beim Server an — die Quelle ist also NICHT geblockt.</p>
<table>
<tr><td class="k">Client-IP</td><td>{client_ip}</td></tr>
<tr><td class="k">Zeitstempel</td><td>{ts}</td></tr>
<tr><td class="k">Methode / Pfad</td><td>{method} {path}</td></tr>
<tr><td class="k">User-Agent</td><td>{ua}</td></tr>
<tr><td class="k">Host-Header</td><td>{host}</td></tr>
</table>
<p class="hint">Kommt eine Anfrage aus einem in aria_geoblock.ini geblockten Land,
sieht dieser Server sie gar nicht erst — iptables droppt sie vorher.
Der Client bekommt dann ein Timeout statt dieser Seite.</p>
</div>
</body>
</html>
"""
class TestHandler(BaseHTTPRequestHandler):
server_version = "ARIA-Geoblock-Testserver/1.0"
def log_message(self, format, *args):
# Default-Logging von BaseHTTPRequestHandler unterdruecken,
# wir loggen selbst strukturiert in do_GET/do_HEAD.
pass
def _handle(self, send_body):
client_ip = _client_ip(self)
ua = self.headers.get("User-Agent", "-")
host_hdr = self.headers.get("Host", "-")
log(f'request client_ip={client_ip} method={self.command} path={self.path} '
f'host="{host_hdr}" ua="{ua}"')
body = PAGE_TMPL.format(
client_ip=html.escape(client_ip),
ts=html.escape(_timestamp()),
method=html.escape(self.command),
path=html.escape(self.path),
ua=html.escape(ua),
host=html.escape(host_hdr),
).encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.send_header("Cache-Control", "no-store")
self.end_headers()
if send_body:
self.wfile.write(body)
def do_GET(self):
self._handle(send_body=True)
def do_HEAD(self):
self._handle(send_body=False)
class ThreadingHTTPServerV6Fallback(ThreadingHTTPServer):
"""Erlaubt Bind auf IPv6-Adressen (z.B. '::'), faellt aber sauber
auf IPv4 zurueck wenn eine IPv4-Adresse konfiguriert ist."""
def server_bind(self):
if ":" in self.server_address[0]:
self.address_family = socket.AF_INET6
super().server_bind()
def main():
global LOG_FILE
parser = argparse.ArgumentParser(
description="Kleiner Test-Webserver um Geoblocking-Regeln von aussen zu pruefen")
parser.add_argument("--config", help="Pfad zur aria_geoblock.ini (Abschnitt [testserver])")
parser.add_argument("--port", type=int, help="TCP-Port (ueberschreibt .ini)")
parser.add_argument("--bind", help="Bind-Adresse (ueberschreibt .ini)")
parser.add_argument("--log-file", help="Pfad zur Request-Log-Datei (ueberschreibt .ini)")
args = parser.parse_args()
if args.config:
cfg = load_config(args.config)
else:
cfg = {"port": DEFAULT_PORT, "bind": DEFAULT_BIND, "log_file": ""}
port = args.port if args.port is not None else cfg["port"]
bind = args.bind if args.bind else cfg["bind"]
LOG_FILE = args.log_file if args.log_file else (cfg["log_file"] or None)
log(f"Starte Test-Webserver auf {bind}:{port} "
f"(log_file={'aus' if not LOG_FILE else LOG_FILE})")
httpd = ThreadingHTTPServerV6Fallback((bind, port), TestHandler)
try:
httpd.serve_forever()
except KeyboardInterrupt:
log("Beende (Strg+C).")
finally:
httpd.server_close()
if __name__ == "__main__":
main()
+17 -1
View File
@@ -9,11 +9,17 @@
# Nutzung (als root, im Verzeichnis mit allen Repo-Dateien): # Nutzung (als root, im Verzeichnis mit allen Repo-Dateien):
# sudo ./install_aria_geoblock.sh [ZIELVERZEICHNIS] # sudo ./install_aria_geoblock.sh [ZIELVERZEICHNIS]
# #
# Installiert ausserdem optional einen kleinen Test-Webserver
# (aria_geoblock_testserver.py + aria-geoblock-testserver.service), gegen
# den man von aussen connecten kann um die Geoblocking-Regeln zu pruefen.
#
# Benoetigt im selben Verzeichnis: # Benoetigt im selben Verzeichnis:
# aria_geoblock.py # aria_geoblock.py
# aria_geoblock.ini # aria_geoblock.ini
# aria-geoblock.service # aria-geoblock.service
# aria-geoblock.timer # aria-geoblock.timer
# aria_geoblock_testserver.py
# aria-geoblock-testserver.service
# #
# Voraussetzungen auf dem Host: python3, iptables, ipset, systemd. # Voraussetzungen auf dem Host: python3, iptables, ipset, systemd.
@@ -28,7 +34,8 @@ SRC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
INSTALL_DIR="${1:-/opt/aria-geoblock}" INSTALL_DIR="${1:-/opt/aria-geoblock}"
LOG_DIR="/var/log/aria_geoblock" LOG_DIR="/var/log/aria_geoblock"
for f in aria_geoblock.py aria_geoblock.ini aria-geoblock.service aria-geoblock.timer; do for f in aria_geoblock.py aria_geoblock.ini aria-geoblock.service aria-geoblock.timer \
aria_geoblock_testserver.py aria-geoblock-testserver.service; do
if [[ ! -f "$SRC_DIR/$f" ]]; then if [[ ! -f "$SRC_DIR/$f" ]]; then
echo "FEHLER: $f fehlt in $SRC_DIR - bitte komplettes Repo verwenden." >&2 echo "FEHLER: $f fehlt in $SRC_DIR - bitte komplettes Repo verwenden." >&2
exit 1 exit 1
@@ -60,13 +67,20 @@ echo "==> Lege Log-Verzeichnis an: $LOG_DIR"
mkdir -p "$LOG_DIR" mkdir -p "$LOG_DIR"
chmod 750 "$LOG_DIR" chmod 750 "$LOG_DIR"
echo "==> Installiere Test-Webserver nach $INSTALL_DIR"
cp "$SRC_DIR/aria_geoblock_testserver.py" "$INSTALL_DIR/aria_geoblock_testserver.py"
chmod 755 "$INSTALL_DIR/aria_geoblock_testserver.py"
echo "==> Installiere systemd-Units" echo "==> Installiere systemd-Units"
sed "s#__INSTALL_DIR__#$INSTALL_DIR#g" "$SRC_DIR/aria-geoblock.service" \ sed "s#__INSTALL_DIR__#$INSTALL_DIR#g" "$SRC_DIR/aria-geoblock.service" \
> /etc/systemd/system/aria-geoblock.service > /etc/systemd/system/aria-geoblock.service
cp "$SRC_DIR/aria-geoblock.timer" /etc/systemd/system/aria-geoblock.timer cp "$SRC_DIR/aria-geoblock.timer" /etc/systemd/system/aria-geoblock.timer
sed "s#__INSTALL_DIR__#$INSTALL_DIR#g" "$SRC_DIR/aria-geoblock-testserver.service" \
> /etc/systemd/system/aria-geoblock-testserver.service
systemctl daemon-reload systemctl daemon-reload
systemctl enable --now aria-geoblock.timer systemctl enable --now aria-geoblock.timer
systemctl enable --now aria-geoblock-testserver.service
echo "" echo ""
echo "==> Fertig." echo "==> Fertig."
@@ -75,4 +89,6 @@ echo " Jetzt sofort testen: systemctl start aria-geoblock.service && journal
echo " Timer-Status: systemctl list-timers aria-geoblock.timer" echo " Timer-Status: systemctl list-timers aria-geoblock.timer"
echo " Naechste/letzte Läufe: systemctl status aria-geoblock.timer" echo " Naechste/letzte Läufe: systemctl status aria-geoblock.timer"
echo " Log-Datei: $LOG_DIR/aria_geoblock.log" echo " Log-Datei: $LOG_DIR/aria_geoblock.log"
echo " Test-Webserver: systemctl status aria-geoblock-testserver.service"
echo " Port/Bind/Log siehe [testserver] in $INSTALL_DIR/aria_geoblock.ini"
echo " checkmk-Plugin: siehe aria_geoblock_checkmk im Repo (manuell kopieren, README beachten)" echo " checkmk-Plugin: siehe aria_geoblock_checkmk im Repo (manuell kopieren, README beachten)"