diff --git a/README.md b/README.md index 10e6eed..06be218 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,158 @@ -# logfile Country resolver +# country-resolve — Logfile Country Resolver -## Use like +Ein kleiner Stream-Filter, der Logzeilen von **stdin** liest, die **erste Spalte als IP-Adresse** +interpretiert und die Zeile um **Kontinent** und **Land** anreichert wieder auf **stdout** ausgibt. -```bash +Die Auflösung erfolgt offline über eine lokale +[DB-IP Country Lite](https://db-ip.com/db/download/ip-to-country-lite) MMDB-Datenbank +(gelesen via [`geoip2`](https://pypi.org/project/geoip2/)). -head -1 access_crowdsec.log -93.229.122.46 - - [27/Feb/2025:00:00:02 +0100] "GET /pages/images/website/home/2502-01.jpg HTTP/1.1" 200 60233 "https://www.plainpicture.com/de" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36" +Typischer Einsatzzweck: Webserver-Access-Logs nach Herkunftsland filtern und daraus +z. B. [CrowdSec](https://www.crowdsec.net/)-Blockentscheidungen ableiten. -head -1 access_crowdsec.log | ./resolver -EU|Germany|93.229.122.46|-|-|[27/Feb/2025:00:00:02|+0100]|"GET|/pages/images/website/home/2502-01.jpg|HTTP/1.1"|200|60233|"https://www.plainpicture.com/de"|"Mozilla/5.0|(Macintosh;|Intel|Mac|OS|X|10_15_7)|AppleWebKit/537.36|(KHTML,|like|Gecko)|Chrome/133.0.0.0|Safari/537.36" +--- -# or +## Funktionsweise -cat access_crowdsec.log | stdbuf -o0 grep "/search" | grep -v "Safari/" | stdbuf -o0 /tmp/resolver | stdbuf -o0 egrep "Russia|Belarus|^SA|^AS|^AF" > block_20250227.txt -cscli decisions delete --all; cat block_20250227.txt | cut -d"|" -f3 | perl -ne 's/(\d+)\.(\d+)\..*/$1.$2/; print ' | uniq | perl -ne 'chomp;print $_,".0.0/16\n"' | cscli decisions import -i - --format values -d48h -R toomuch_AS_SA_AF +Für jede Eingabezeile: + +1. Zeile an Leerzeichen zerlegen, `parts[0]` ist die IP. +2. IP gegen die MMDB auflösen → `continent.code` (z. B. `EU`) und `country.name` (z. B. `Germany`). +3. Nicht auflösbare IPs (`AddressNotFoundError`) werden als Land `Unknown` markiert. +4. Ausgabe: alle Felder **pipe-separiert** (`|`), mit Kontinent und Land vorangestellt: +``` +||||... ``` -## packaging -`pyinstaller --onefile resolv.py` +Fehlerhafte Einzelzeilen werden übersprungen (kein Abbruch des Streams). + +### Ausgabeformat + +``` +EU|Germany|93.229.122.46|-|-|[27/Feb/2025:00:00:02|+0100]|"GET|/pages/...|HTTP/1.1"|200|... +``` + +| Feld 1 | Feld 2 | ab Feld 3 | +| ---------- | ------ | -------------------------------------------- | +| Kontinent-Code (`EU`, `AS`, `NA`, …) | Ländername (`Germany`, `Unknown`, …) | die ursprünglichen, an Leerzeichen getrennten Felder | + +--- + +## Voraussetzungen + +- Python **≥ 3.13** (siehe `.python-version`) +- Python-Paket **`geoip2`** (als Abhängigkeit in `pyproject.toml` deklariert) +- Eine **MMDB-Datei** namens `dbip-country-lite-2025-02.mmdb` im Arbeitsverzeichnis + (Pfad ist in `resolver.py` fest verdrahtet). MMDB-Dateien sind per `.gitignore` + vom Repository ausgeschlossen und müssen separat bezogen werden. + +--- + +## Installation + +Mit [uv](https://docs.astral.sh/uv/) — installiert die in `pyproject.toml` deklarierten Abhängigkeiten: + +```bash +uv sync +``` + +Oder klassisch mit pip: + +```bash +python3 -m venv .venv +source .venv/bin/activate +pip install geoip2 +``` + +### MMDB-Datenbank besorgen + +Die Lite-Datenbank kostenlos bei DB-IP herunterladen und passend benennen: + +```bash +# Beispiel (Dateiname muss zur Konstante in resolver.py passen) +mv dbip-country-lite-2025-02.mmdb ./dbip-country-lite-2025-02.mmdb +``` + +Für eine andere/aktuellere DB den Pfad in `resolver.py` (Zeile mit `geoip2.database.Reader(...)`) +anpassen. + +--- + +## Benutzung + +### Einzelne Zeile auflösen + +```bash +head -1 access_crowdsec.log +# 93.229.122.46 - - [27/Feb/2025:00:00:02 +0100] "GET /pages/images/website/home/2502-01.jpg HTTP/1.1" 200 60233 "https://www.plainpicture.com/de" "Mozilla/5.0 ..." + +head -1 access_crowdsec.log | ./resolver +# EU|Germany|93.229.122.46|-|-|[27/Feb/2025:00:00:02|+0100]|"GET|/pages/images/website/home/2502-01.jpg|HTTP/1.1"|200|... +``` + +`./resolver` ist das gepackte Binary (siehe [Packaging](#packaging)). Alternativ direkt über Python: + +```bash +head -1 access_crowdsec.log | python3 resolver.py +``` + +### Anwendungsbeispiel: Länder-basiertes Blocking mit CrowdSec + +Access-Log streamen, auf interessante Requests filtern, Herkunft auflösen und nach +bestimmten Ländern/Kontinenten filtern: + +```bash +cat access_crowdsec.log \ + | stdbuf -o0 grep "/search" \ + | grep -v "Safari/" \ + | stdbuf -o0 /tmp/resolver \ + | stdbuf -o0 egrep "Russia|Belarus|^SA|^AS|^AF" \ + > block_20250227.txt +``` + +Aus den Treffern /16-Netze ableiten und als CrowdSec-Decisions importieren: + +```bash +cscli decisions delete --all +cat block_20250227.txt \ + | cut -d"|" -f3 \ + | perl -ne 's/(\d+)\.(\d+)\..*/$1.$2/; print ' \ + | uniq \ + | perl -ne 'chomp; print $_,".0.0/16\n"' \ + | cscli decisions import -i - --format values -d48h -R toomuch_AS_SA_AF +``` + +> `stdbuf -o0` erzwingt ungepuffertes Streaming, damit die Pipeline zeilenweise (nahezu +> in Echtzeit) durchläuft. + +--- + +## Packaging + +Ein eigenständiges Binary (ohne Python-Runtime auf dem Zielsystem) mit +[PyInstaller](https://pyinstaller.org/) erzeugen: + +```bash +pyinstaller --onefile resolver.py +# oder mit der mitgelieferten Spec-Datei: +pyinstaller resolver.spec +``` + +Das Ergebnis liegt anschließend unter `dist/resolver` und kann z. B. nach `/tmp/resolver` +kopiert und in Pipelines eingesetzt werden. + +> Die MMDB-Datei wird **nicht** ins Binary eingebettet — sie muss zur Laufzeit im +> Arbeitsverzeichnis liegen (bzw. der Pfad in `resolver.py` entsprechend gesetzt sein). + +--- + +## Projektdateien + +| Datei | Zweck | +| -------------------- | ------------------------------------------------- | +| `resolver.py` | Der eigentliche Stream-Filter | +| `resolver.spec` | PyInstaller-Spezifikation für das `--onefile`-Binary | +| `pyproject.toml` | Projekt-Metadaten (Python ≥ 3.13) | +| `.python-version` | Fixierte Python-Version (3.13) | +| `*.mmdb` | GeoIP-Datenbank (nicht im Repo, per `.gitignore`) | diff --git a/pyproject.toml b/pyproject.toml index 8263f52..16c5dc1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,4 +4,6 @@ version = "0.1.0" description = "Add your description here" readme = "README.md" requires-python = ">=3.13" -dependencies = [] +dependencies = [ + "geoip2>=4.8", +]