zunächst vielen Dank für das Importskript. Ich hatte eine kleine Idee, wie man dessen Ausführungszeit signifikant reduzieren kann.
Man kann viel Zeit sparen, indem man vor dem Import der einzelnen IPs nachschaut, welche Adressen bereits in der f2b-Datenbank stehen, um so die heruntergeladene Datei auf die bisher noch unbekannten IPs zu beschränken. Das Ergebnis des folgenden Progrämmchens ist eine stark reduzierte Blocklisten-Datei, die, wie oben vorgeschlagen, Zeile für Zeile eingelesen werden kann.
Sicher geht das mit php auch, ich hatte es aber schneller mit Python zusammengekloppt, was die Erstellung einer virtuellen Umgebung erfordert, damit man nicht Python Packages in die Systemumgebung installiert.
Alles weitere steht im Skript.
shrink_blocklist.py:
Code: Select all
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
/root/banip/py/shrink_blocklist.py
----------------------------------
Kürzere Sperrliste für f2b erstellen.
Ref: https://community.keyhelp.de/viewtopic.php?p=54945
Holt die Datei aus der url und vergleicht die ip-Adressen mit denen, die in der f2b-Datenbank gespeichert sind.
Erstellt aus dem Vergleich der Adressen eine Datei, die nur die neu hinzugekommenen IPs beinhaltet.
Bitte vor der Installation der requirements ein virtuelles environment erstellen.
(Pandas gehört normalerweise nicht zur Systemumgebung.)
Getestet mit Keyhelp 15.1 (Python 3.11.*) / Debian 12.11
Vorbereitung:
-------------
Einloggen als root
$ apt install sqlite3 pip python3.11-venv
$ mkdir /root/banip/py
$ cd /root/banip/py
In das Verzeichnis muss diese Datei kopiert werden.
Wichtig: Virtual environment 'mypy' (oder anderer Name) im Verzeichnis erstellen:
------------------------------------------------------
$ python -m venv /root/banip/py/mypy
Wichtig: Virtual environment aktivieren:
----------------------------------------
§ source mypy/bin/activate
Jetzt sollte die Kommandozeile ein dem Prompt vorangestelltes (mypy) aufweisen.
(mypy)$ ls -al
drwxr-xr-x 3 root root 4096 3. Jul 00:01 .
drwxr-xr-x 3 root root 4096 3. Jul 00:01 ..
drwxr-xr-x 5 root root 4096 3. Jul 00:01 mypy
-rw-r--r-- 1 root root 7787 3. Jul 00:01 shrink_blocklist.py
Python Packages installieren:
------------------------------
(mypy)$ pip install pandas
(mypy)$ pip install requests
(mypy)$ pip install sqlite3
Kontrolle:
----------
Z.B.:
(mypy)$ pip list
Package Version
------------------ -----------
certifi 2025.6.15
charset-normalizer 3.4.2
idna 3.10
numpy 2.3.1
pandas 2.3.0
pip 23.0.1
python-dateutil 2.9.0.post0
pytz 2025.2
requests 2.32.4
setuptools 66.1.1
six 1.17.0
tzdata 2025.2
urllib3 2.5.0
as Environment wird mit
$ deactivate
wieder auf die Basis des Systems zurückgesetzt.
Programm starten:
-----------------
$ python3 shrink_blocklist.py
Wenn alles installiert ist, dann sollte die Ausgabe etwa so aussehen:
Datei holen von https://raw.githubusercontent.com/borestad/blocklist-abuseipdb/refs/heads/main/abuseipdb-s100-3d.ipv4
Download beendet.
Die Dateigröße von blocklist.txt beträgt 7508167 Bytes.
Die Datei beinhaltet 129695 Zeilen.
Suche die erste Zeile in 'blocklist.txt', die mit einer gültigen ip-Adresse beginnt.
Beginn der ip-Adressen in Zeile: 14
Datenbank /var/lib/fail2ban/fail2ban.sqlite3 öffnen.
Die Tabelle 'bips' enthält 236318 Zeilen für den jail 'iplistblock'.
IP-Adressen der Datei laden.
Anzahl: 129681
IP-Adressen der f2b-Datenbank einlesen.
Anzahl: 236318
Neue IP-Adressen ermitteln
Anzahl: 11483
Datei mit neuen IP-Adressen erstellen
Alles OK. Die Dateigröße von shrinked_blocklist.txt beträgt 166190 Bytes.
Nun sollte das aktuelle Verzeichnis zusätzlich die Dateien 'blocklist.txt' und 'shrinked_blocklist.txt' aufweisen.
Das Environment wird mit
$ deactivate
wieder auf die Basis des Systems zurückgesetzt, falls noch andere Arbeiten anstehen, die Python-Skripte verwenden.
"""
__author__ = 'whocares'
__created__ = '03.07.2025'
import os
import shutil
import requests as req
import re
import sqlite3
import pandas as pd
url = 'https://raw.githubusercontent.com/borestad/blocklist-abuseipdb/refs/heads/main/abuseipdb-s100-3d.ipv4'
# größere 30-Tage-Liste alternativ, wie beschrieben unter https://github.com/borestad/blocklist-abuseipdb
# url = 'https://raw.githubusercontent.com/borestad/blocklist-abuseipdb/refs/heads/main/abuseipdb-s100-30d.ipv4
blfname = 'blocklist.txt' # Heruntergeladene Blocklist
newblfname = 'shrinked_blocklist.txt' # Blocklist mit ausschließlich neuen Adressen, die f2b noch nicht kennt
jail = 'iplistblock'
minlines = 100 # Willkürlicher Wert für die Mindestanzahl an Zeilen in der Input-Datei
maxipstart = 25 # Willkürlicher Wert für die erste Zeile mit einer ip-Adresse in der Input-Blocklist
# Idee: Ist der Header zu lang, wird abgebrochen. Vlt. stehen in der Datei ganz andere Dinge?
db_filename = "/var/lib/fail2ban/fail2ban.sqlite3"
def getnumoflines(fname):
with open(fname) as f:
return len(f.readlines())
def errorexit(msg):
if len(msg) > 0:
for elem in msg:
print(elem)
print('Das Programm wird nach Fehler beendet.')
exit(1)
def successexit(fname):
fstat = os.stat(fname)
print('Alles OK. Die Dateigröße von ' + fname + ' beträgt ' + str(fstat.st_size) + ' Bytes.')
exit(0)
def main():
"""
main()
"""
print('Datei holen von ' + url)
r = req.get(url, timeout=10)
if r.ok:
with open(blfname, mode="wb") as f:
f.write(r.content) # überschreibt die Datei, falls vorhanden
else:
errorexit(['Fehler beim Download', 'Fehlercode: ' + str(r.status_code), 'Stimmt die url?'])
print('Download beendet.')
fstat = os.stat(blfname)
if fstat.st_size == 0:
errorexit(['Die Dateigröße ist 0 Bytes', 'Stimmt die url?'])
else:
print('Die Dateigröße von ' + blfname + ' beträgt ' + str(fstat.st_size) + ' Bytes.')
numoflines = getnumoflines(blfname)
if numoflines < minlines:
errorexit(['Die heruntegeladene Datei hat ' + str(numoflines) + ' Zeilen.', 'Sie sollte jedoch mehr als ' + str(minlines) + ' beinhalten.'])
else:
print('Die Datei beinhaltet ' + str(numoflines) + ' Zeilen.')
print("Suche die erste Zeile in '" + blfname + "', die mit einer gültigen ip-Adresse beginnt.")
# Regex: Nur ip-Adresse holen, die zu Beginn einer Zeile steht
pat = re.compile(r"(?:\b|^)((?:(?:(?:\d)|(?:\d{2})|(?:1\d{2})|(?:2[0-4]\d)|(?:25[0-5]))\.){3}(?:(?:(?:\d)|(?:\d{2})|(?:1\d{2})|(?:2[0-4]\d)|(?:25[0-5]))))(?:\b|$)")
with open(blfname) as f:
i = 1
for x in f:
checkip4 = pat.findall(x)
# print(checkip4)
if len(checkip4) == 0:
i += 1
if i > maxipstart:
errorexit(['In den ersten ' + str(maxipstart) + ' Zeilen der Datei wurde keine IP-Adresse identifiziert.', \
'Header länger als erwartet? Enthält die Datei gültige ip-Adressen, die am Zeilenanfang stehen?'])
else:
break
print('Beginn der ip-Adressen in Zeile: ' + str(i))
print('Datenbank ' + db_filename + ' öffnen.')
# ToDo: Fehlerbehandlung
with sqlite3.connect(db_filename) as conn:
cur = conn.cursor()
sql = "SELECT COUNT(*) FROM bips WHERE jail='" + jail + "' or jail IS NULL;"
cur.execute(sql)
numofrows = cur.fetchone()[0]
print("Die Tabelle 'bips' enthält " + str(numofrows) + " Zeilen für den jail '" + jail + "'." )
if (numofrows == 0):
print("In der Datenbak sind bisher keine Einträge für den jail '" + jail + "' vorhanden.")
print('Die heruntergeladene Datei muss komplett eingelesen werden.')
print('Kopiere ' + blfname + ' nach ' + newblfname)
shutil.copy2(blfname, newblfname)
else:
print('IP-Adressen der Datei laden.')
df_file = pd.read_csv(blfname, sep='#', skiprows=i, names=['ip', 'comment'])
df_file['ip'] = df_file['ip'].str.strip() # Whitespace am Anfang und Ende der ip-Adressen für späteren Vergleich entfernen
# print(df_file)
print('Anzahl: ' + str(len(df_file.index)))
print('IP-Adressen der f2b-Datenbank einlesen.')
with sqlite3.connect(db_filename) as conn:
df_db = pd.read_sql("SELECT ip FROM bips WHERE jail='" + jail + "'", conn)
# print(df_db)
print('Anzahl: ' + str(len(df_db.index)))
print('Neue IP-Adressen ermitteln')
df_new = df_file[~df_file['ip'].isin(df_db['ip'])]
# print(df_new)
print('Anzahl: ' + str(len(df_new.index)))
print('Datei mit neuen IP-Adressen erstellen')
df_new.to_csv(newblfname, columns=['ip'], header=False, index=False)
successexit(newblfname)
if __name__ == "__main__":
main()
cron.sh:
Code: Select all
#! /usr/bin/bash
echo `date "+%Y-%m-%d %H:%M:%S"`
cd /root/banip/py
source mypy/bin/activate
python3 shrink_blocklist.py
exit_status=$?
if [ "${exit_status}" -ne 0 ];
then
echo "Python-Skript mit Fehler beendet. Shellskript wird abgebrochen(1)"
echo `date "+%Y-%m-%d %H:%M:%S"`
exit 1
fi
echo `date "+%Y-%m-%d %H:%M:%S"`
INFILE=/root/banip/py/shrinked_blocklist.txt
IFS=$'\n'
for IP in $(cat "$INFILE")
do
fail2ban-client set iplistblock banip $IP >/dev/null 2>&1
done
echo `date "+%Y-%m-%d %H:%M:%S"`
echo "IP Blocklist verarbeitet"
rm /root/banip/py/blocklist.txt
rm /root/banip/py/shrinked_blocklist.txt
echo `date "+%Y-%m-%d %H:%M:%S"`
echo "fertig"
Vielleicht wäre das eine Anregung, ein funktionierendes Paging zu programmieren?
Eine Alternative an dieser Stelle könnte dort eine nach Jails gruppierte Darstellung sein, sodass man auf den ersten Blick nur die Gesamtanzahl der Einträge per Gruppe erhielte. Denn wer will sich schon durch hundertausende Einträge blättern? Eine Suchfunktion für einzelne IPs wäre dann ausreichend.
Have phun!