Härten Sie die Instanz mit Fail2ban ab¶
Évolution importante : depuis l'introduction de la crate
gitrust-ssh-guard, le jail[gitrust-ssh]ne lit plus les logsrusshviajournalctlmais consomme directement le flux JSON stable émis par ssh-guard dans/var/log/gitrust-ssh-guard.json. Cela rend le jail bien plus fiable (format garanti stable, pas de regex fragile sur les messages derussh). ActivezSSH_GUARD_LOG_TARGET=bothdans.env— voir Configurer ssh-guard.
Vollständige Konfiguration zum Schutz einer ausstellenden Gitrust-Bereitstellung:
- System-SSHD auf „:2022“ (Administrator)
- Nginx im HTTPS-Reverse-Proxy „:443“ + Weiterleitung „:80“ + „stream:22 →:2222“.
- Gitrust (web „127.0.0.1:4000“, SSH russh „127.0.0.1:2222“)
- Dagger CI-Runner (lokal, keine direkte Netzwerkeinbindung)
- Dependency-Track API „:8081“ + UI „:8080“ (falls verfügbar)
- PostgreSQL
127.0.0.1:5432(defensiv für den Fall, dass die Bindung leckt)
1. Installation¶
Überprüfen Sie, ob „ufw“ aktiv ist (geben Sie „banaction = ufw“ in der Konfiguration an):
2. Hauptdatei „/etc/fail2ban/jail.local“.¶
Zu platzieren über:
# =============================================================================
# Défauts globaux
# =============================================================================
[DEFAULT]
backend = systemd
bantime = 1h
findtime = 10m
maxretry = 5
# LAN privé et loopback jamais bannis
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24
# UFW
banaction = ufw
banaction_allports = ufw
# Notifications (optionnel — nécessite un relay SMTP local)
# destemail = contact@gitrust.eu
# sender = fail2ban@votre-serveur.example.com
# action = %(action_mwl)s
# =============================================================================
# 1) sshd admin système — port 2022
# =============================================================================
[sshd]
enabled = true
port = 2022
filter = sshd
backend = %(sshd_backend)s
logpath = %(sshd_log)s
maxretry = 3
findtime = 10m
bantime = 1h
# =============================================================================
# 2) Nginx — 401/403 auth basic (si un jour activé sur /admin, /metrics, etc.)
# =============================================================================
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/gitrust.error.log
maxretry = 5
# =============================================================================
# 3) Nginx — scanners d'URL (wp-admin, .env, phpmyadmin, config.php, etc.)
# =============================================================================
[nginx-botsearch]
enabled = true
port = http,https
filter = nginx-botsearch
logpath = /var/log/nginx/gitrust.access.log
maxretry = 2
findtime = 10m
bantime = 24h
# =============================================================================
# 4) Nginx — bad user-agents (scanners agressifs, masscan, nikto, etc.)
# =============================================================================
[nginx-badbots]
enabled = true
port = http,https
filter = nginx-badbots
logpath = /var/log/nginx/gitrust.access.log
maxretry = 2
bantime = 24h
# =============================================================================
# 5) Nginx — dépassement limit_req (voir section 4 — zones HTTPS)
# =============================================================================
[nginx-limit-req]
enabled = true
port = http,https
filter = nginx-limit-req
logpath = /var/log/nginx/gitrust.error.log
maxretry = 10
findtime = 5m
bantime = 1h
# =============================================================================
# 6) Gitrust — brute force login (formulaire + API JWT)
# =============================================================================
[gitrust-login]
enabled = true
port = http,https
filter = gitrust-login
logpath = /var/log/nginx/gitrust.access.log
maxretry = 5
findtime = 10m
bantime = 1h
# =============================================================================
# 7) Gitrust — abus API (tokens personnels leaked, scrapers)
# =============================================================================
[gitrust-api-abuse]
enabled = true
port = http,https
filter = gitrust-api-abuse
logpath = /var/log/nginx/gitrust.access.log
maxretry = 30
findtime = 1m
bantime = 2h
# =============================================================================
# 8) Gitrust SSH — consomme les événements JSON stables de ssh-guard
# =============================================================================
# IMPORTANT : ce jail consomme le flux JSON émis par la crate gitrust-ssh-guard
# (champs stables documentés dans ../reference/ssh-guard-evenements.md).
# Le ban est déclenché soit sur le "signal fort" ip_banned (ssh-guard a déjà
# détecté la brute-force, fail2ban relaye au firewall) — un seul ip_banned
# suffit (maxretry=1) — soit sur le brut auth_failed avec le seuil habituel.
#
# Prérequis dans /opt/gitrust/.env :
# SSH_GUARD_LOG_TARGET=both
# SSH_GUARD_LOG_FILE=/var/log/gitrust-ssh-guard.json
[gitrust-ssh]
enabled = true
port = 22,2222
filter = gitrust-ssh
logpath = /var/log/gitrust-ssh-guard.json
maxretry = 1 # ssh-guard a déjà corrélé : 1 ip_banned = 1 ban firewall
findtime = 10m
bantime = 1h
# =============================================================================
# 9) Gitrust import worker — tokens invalides sur clone de dépôts externes
# =============================================================================
[gitrust-import]
enabled = true
port = http,https
filter = gitrust-import
backend = systemd
journalmatch = _SYSTEMD_UNIT=gitrust.service
maxretry = 3
findtime = 5m
bantime = 30m
# =============================================================================
# 10) Dependency-Track UI — brute force login (port 8080)
# =============================================================================
# Activer UNIQUEMENT si DTrack est exposé publiquement.
# Si DTrack est sur réseau privé/loopback, laisser enabled=false.
[dtrack-login]
enabled = false
port = 8080,http,https
filter = dtrack-login
logpath = /var/log/nginx/dtrack.access.log
maxretry = 5
findtime = 10m
bantime = 2h
# =============================================================================
# 11) Dependency-Track API — abus clé API (port 8081)
# =============================================================================
[dtrack-api]
enabled = false
port = 8081,http,https
filter = dtrack-api
logpath = /var/log/nginx/dtrack.access.log
maxretry = 10
findtime = 5m
bantime = 1h
# =============================================================================
# 12) PostgreSQL — tentatives de connexion invalides (défensif)
# =============================================================================
# PG doit binder 127.0.0.1 uniquement. Ce jail protège si la config fuit.
[postgresql]
enabled = true
port = 5432
filter = postgresql
backend = systemd
journalmatch = _SYSTEMD_UNIT=postgresql.service + _SYSTEMD_UNIT=docker.service
maxretry = 5
findtime = 10m
bantime = 1h
# =============================================================================
# 13) Récidive — ban long pour IPs bannies 3x en 24h, tous jails confondus
# =============================================================================
[recidive]
enabled = true
logpath = /var/log/fail2ban.log
banaction = %(banaction_allports)s
bantime = 1w
findtime = 1d
maxretry = 3
3. Benutzerdefinierte Filter¶
Platzieren Sie es in „/etc/fail2ban/filter.d/“ (eine Datei pro Jail-Benutzerdefiniert).
3.1 gitrust-login.conf¶
sudo tee /etc/fail2ban/filter.d/gitrust-login.conf <<'EOF'
[Definition]
# POST sur /login ou endpoints JWT avec code 4xx (401/403/422/429)
failregex = ^<HOST> .* "POST /(login|api/v1/auth/login|api/v1/auth/refresh|api/v1/auth/2fa)[^"]*" (401|403|422|429) .*$
ignoreregex =
EOF
3.2 gitrust-api-abuse.conf¶
sudo tee /etc/fail2ban/filter.d/gitrust-api-abuse.conf <<'EOF'
[Definition]
# Abus API : 401/403 répétés sur /api/v1/* (hors auth déjà couvert par gitrust-login)
failregex = ^<HOST> .* "(GET|POST|PUT|DELETE|PATCH) /api/v1/(?!auth/)[^"]*" (401|403) .*$
ignoreregex =
EOF
3.3 gitrust-ssh.conf (consomme le JSON ssh-guard)¶
Le filtre matche les événements JSON stables produits par gitrust-ssh-guard. Voir Événements ssh-guard (JSON) pour le schéma complet.
Le « signal fort » ip_banned est privilégié : ssh-guard a déjà corrélé brute-force / énumération / scan de clés, et fail2ban n'a plus qu'à appliquer le ban au niveau firewall (UFW/iptables) pour les autres ports si désiré.
sudo tee /etc/fail2ban/filter.d/gitrust-ssh.conf <<'EOF'
[Definition]
# Filtre les événements stables émis par gitrust-ssh-guard dans
# /var/log/gitrust-ssh-guard.json. Format : une ligne JSON par événement.
#
# Capture <HOST> depuis le champ "ip" du JSON pour les variants pertinents.
#
# Tester :
# sudo fail2ban-regex /var/log/gitrust-ssh-guard.json \
# /etc/fail2ban/filter.d/gitrust-ssh.conf
failregex = ^.*"event":"ip_banned".*"ip":"<HOST>".*$
^.*"event":"brute_force_detected".*"ip":"<HOST>".*$
^.*"event":"user_enumeration_detected".*"ip":"<HOST>".*$
^.*"event":"key_scanning_detected".*"ip":"<HOST>".*$
^.*"event":"connection_dropped".*"ip":"<HOST>".*"reason":"untrusted_proxy".*$
^.*"event":"connection_dropped".*"ip":"<HOST>".*"reason":"proxy_header_invalid".*$
ignoreregex =
# Date au format ISO 8601 UTC produit par ssh-guard
datepattern = "ts":"%%Y-%%m-%%dT%%H:%%M:%%S
EOF
Variante « brute uniquement » — si vous préférez que fail2ban corrèle lui-même à partir des
auth_failedsans dépendre du verdict ssh-guard, remplacez le bloc ci-dessus parfailregex = ^.*"event":"auth_failed".*"ip":"<HOST>".*$et passezmaxretry = 5dans le jail. Les deux approches sont valides ; la première est plus rapide à réagir, la seconde est plus indépendante.
3.4 gitrust-import.conf¶
sudo tee /etc/fail2ban/filter.d/gitrust-import.conf <<'EOF'
[Definition]
# Import worker : tokens OAuth/GitHub invalides sur clone de dépôts externes
failregex = ^.*import.*authentication failed.*from <HOST>.*$
^.*import.*invalid (token|credentials).*from <HOST>.*$
^.*import.*clone failed.*401.*from <HOST>.*$
ignoreregex =
EOF
3.5 „dtrack-login.conf“ (falls DTrack verfügbar gemacht wird)¶
sudo tee /etc/fail2ban/filter.d/dtrack-login.conf <<'EOF'
[Definition]
# Dependency-Track : brute force du POST /api/v1/user/login
failregex = ^<HOST> .* "POST /api/v1/user/login[^"]*" (401|403) .*$
^<HOST> .* "POST /api/v1/user/forceChangePassword[^"]*" (401|403) .*$
ignoreregex =
EOF
3.6 „dtrack-api.conf“ (wenn DTrack verfügbar gemacht wird)¶
sudo tee /etc/fail2ban/filter.d/dtrack-api.conf <<'EOF'
[Definition]
# Dependency-Track API : abus de clé API ou requêtes non authentifiées
failregex = ^<HOST> .* "[^"]+ /api/v1/[^"]*" 401 .*$
^<HOST> .* "[^"]+ /api/v1/[^"]*" 403 .*$
ignoreregex =
EOF
3.7 „postgresql.conf“ (erstellen, falls nicht vorhanden)¶
if [ ! -f /etc/fail2ban/filter.d/postgresql.conf ]; then
sudo tee /etc/fail2ban/filter.d/postgresql.conf <<'EOF'
[Definition]
failregex = ^.*authentication failed for user.*host=<HOST>.*$
^.*FATAL:.*password authentication failed.*<HOST>.*$
^.*no pg_hba\.conf entry for host "<HOST>".*$
ignoreregex =
EOF
fi
3.8 „nginx-limit-req.conf“ (erstellen, falls in Ihrer Distribution fehlend)¶
if [ ! -f /etc/fail2ban/filter.d/nginx-limit-req.conf ]; then
sudo tee /etc/fail2ban/filter.d/nginx-limit-req.conf <<'EOF'
[Definition]
failregex = ^.*limiting requests, excess: .* by zone .*, client: <HOST>.*$
ignoreregex =
EOF
fi
3.9 „nginx-badbots.conf“ – ERFORDERLICH (nicht von Debian/Ubuntu bereitgestellt)¶
Das Debian/Ubuntu-Paket „fail2ban“ stellt „apache-badbots.conf“, aber NICHT „nginx-badbots.conf“ bereit. Ohne diesen Filter zeigt „fail2ban-client reload“ Folgendes an:
Der folgende Filter verwendet die Liste der fehlerhaften Benutzeragenten von „Apache-Badbots“ mit einem „failregex“, der an das „kombinierte“ Format von Nginx angepasst ist:
sudo tee /etc/fail2ban/filter.d/nginx-badbots.conf <<'EOF'
[Definition]
badbotscustom = EmailCollector|WebEMailExtrac|TrackBack/1\.02|sogou music spider
badbots = Atomic_Email_Hunter/4\.0|atSpider/1\.0|autoemailspider|bwh3_user_agent|China Local Browse 2\.6|ContactBot/0\.2|ContentSmartz|DataCha0s/2\.0|DBrowse 1\.4b|DBrowse 1\.4d|Demo Bot DOT 16b|Demo Bot Z 16b|DSurf15a 01|DSurf15a 71|DSurf15a 81|DSurf15a VA|EBrowse 1\.4b|Educate Search VxB|EmailSiphon|EmailSpider|EmailWolf 1\.00|ExtractorPro|Franklin Locator 1\.8|Full Web Bot 0416B|Guestbook Auto Submitter|ISC Systems iRc Search 2\.1|LMQueueBot/0\.2|LWP\:\:Simple/5\.803|Microsoft URL Control - 6\.00\.8xxx|Missigua Locator 1\.9|Mozilla/4\.0 efp@gmx\.net|Nsauditor/1\.x|PBrowse 1\.4b|PEval 1\.4b|Poirot|psycheclone|sogou spider|sohu agent|VadixBot|WebVulnCrawl\.unknown/1\.0 libwww-perl/5\.803|Wells Search II|WEP Search 00
failregex = ^<HOST> -.*"(GET|POST|HEAD).*HTTP.*"(?:%(badbots)s|%(badbotscustom)s)"$
ignoreregex =
datepattern = ^[^\[]*\[({DATE})
{^LN-BEG}
EOF
Alternative: Wenn Sie die Liste nicht pflegen möchten, deaktivieren Sie einfach den Jail („enabled = false“ in den „[nginx-badbots]“) – die Kombination „nginx-botsearch“ + „nginx-limit-req“ + „gitrust-api-abuse“ deckt bereits das Wesentliche ab.
4. Ratenbegrenzung auf der Nginx-Seite (erforderlich durch „nginx-limit-req“)¶
4.1 Globale Zonen deklarieren¶
sudo tee /etc/nginx/conf.d/gitrust-limit.conf <<'EOF'
# Zones de rate limiting gitrust
limit_req_zone $binary_remote_addr zone=gitrust_login:10m rate=1r/s;
limit_req_zone $binary_remote_addr zone=gitrust_api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=gitrust_git:10m rate=30r/s;
EOF
4.2 Fügen Sie „location“ in „/etc/nginx/sites-available/gitrust“ hinzu¶
Im server { listen 443 ssl; ... }, vor location / final:
# Protection brute force login (1 req/s par IP, burst 5)
location ~ ^/(login|api/v1/auth/) {
limit_req zone=gitrust_login burst=5 nodelay;
proxy_pass http://gitrust_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Protection API (10 req/s par IP, burst 20)
location ^~ /api/v1/ {
limit_req zone=gitrust_api burst=20 nodelay;
proxy_pass http://gitrust_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Protection Git smart HTTP (30 req/s — clone/push légitime peut être verbeux)
location ~ ^/[^/]+/[^/]+\.git/ {
limit_req zone=gitrust_git burst=50 nodelay;
proxy_pass http://gitrust_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_request_buffering off;
client_max_body_size 2G;
}
4.3 Neu laden¶
5. Start und Validierung¶
sudo systemctl restart fail2ban
# Liste des jails actifs
sudo fail2ban-client status
# Attendu :
# sshd, nginx-http-auth, nginx-botsearch, nginx-badbots, nginx-limit-req,
# gitrust-login, gitrust-api-abuse, gitrust-ssh, gitrust-import,
# postgresql, recidive
# (+ dtrack-login, dtrack-api si activées)
# Stats par jail
sudo fail2ban-client status gitrust-login
sudo fail2ban-client status sshd
Validieren Sie jeden benutzerdefinierten Filter¶
# 1. gitrust-login contre l'access log nginx
sudo fail2ban-regex /var/log/nginx/gitrust.access.log \
/etc/fail2ban/filter.d/gitrust-login.conf
# 2. gitrust-api-abuse
sudo fail2ban-regex /var/log/nginx/gitrust.access.log \
/etc/fail2ban/filter.d/gitrust-api-abuse.conf
# 3. gitrust-ssh contre le flux JSON ssh-guard
sudo fail2ban-regex /var/log/gitrust-ssh-guard.json \
/etc/fail2ban/filter.d/gitrust-ssh.conf
# Si /var/log/gitrust-ssh-guard.json n'existe pas, vérifiez SSH_GUARD_LOG_TARGET
# et SSH_GUARD_LOG_FILE dans /opt/gitrust/.env (voir how-to/configurer-ssh-guard.md).
# 4. postgresql
journalctl -u docker --no-pager -n 5000 | grep -i postgres > /tmp/pg.log
sudo fail2ban-regex /tmp/pg.log \
/etc/fail2ban/filter.d/postgresql.conf
„0 failregex gefunden“ = Regex, der nach Beobachtung der tatsächlichen Protokolle jedes Dienstes angepasst werden soll.
Verbotstest¶
# Depuis un host HORS LAN (sinon ignoreip s'applique)
for i in $(seq 1 10); do
curl -sk -o /dev/null -w "%{http_code}\n" \
-X POST https://<votre-domaine>/login \
-d 'username=admin&password=wrong'
done
# Vérifier le ban
ssh gitrust-host 'sudo fail2ban-client status gitrust-login'
# Currently banned : 1 — Banned IP list : <IP test>
# Débannir manuellement
ssh gitrust-host 'sudo fail2ban-client unban <IP>'
6. Überwachung¶
# Logs fail2ban temps réel
sudo tail -f /var/log/fail2ban.log
# Toutes les IPs bannies, tous jails confondus
sudo fail2ban-client banned
# Stats détaillées d'un jail
sudo fail2ban-client status gitrust-ssh
# Currently failed : 2
# Total failed : 47
# Currently banned : 1
# Total banned : 12
# Banned IP list : 203.0.113.42
sudo grep "Ban " /var/log/fail2ban.log | tail -20
7. Zusammenfassungsmatrix¶
| # | Jail | Port(s) | Source logs | Max retry | Ban time | Surface protégée |
|---|---|---|---|---|---|---|
| 1 | sshd |
2022 | journald sshd | 3 | 1h | SSH admin |
| 2 | nginx-http-auth |
80,443 | nginx error | 5 | 1h | Basic auth (admin futur) |
| 3 | nginx-botsearch |
80,443 | nginx access | 2 | 24h | Scanners WP/PHP |
| 4 | nginx-badbots |
80,443 | nginx access | 2 | 24h | User-agents malveillants |
| 5 | nginx-limit-req |
80,443 | nginx error | 10 | 1h | Flood global |
| 6 | gitrust-login |
80,443 | nginx access | 5 | 1h | Brute force login UI + API |
| 7 | gitrust-api-abuse |
80,443 | nginx access | 30 | 2h | Scrapers API (tokens fuités) |
| 8 | gitrust-ssh |
22,2222 | /var/log/gitrust-ssh-guard.json (JSON stable ssh-guard) |
1 | 1h | Brute force / scan clés / énumération SSH Git |
| 9 | gitrust-import |
80,443 | journald gitrust | 3 | 30m | Brute force PAT/OAuth import |
| 10 | dtrack-login |
8080 | nginx access | 5 | 2h | Brute force UI Dep-Track |
| 11 | dtrack-api |
8081 | nginx access | 10 | 1h | Abus clé API Dep-Track |
| 12 | postgresql |
5432 | journald | 5 | 1h | Défense en profondeur PG |
| 13 | recidive |
all | fail2ban.log | 3 | 1w | Méta-ban 3×/24h |
Typische Auslösereihenfolge: Brute Force → spezifisches Gefängnis (Verbot für 1 Stunde) → bei 3-maligem Wiederholungsvergehen → Wiederholungsvergehen (Verbot für 1 Woche, alle Häfen).
8. Spezifische Hinweise zum Gitrust-Stack¶
8.1 Dolch (CI)¶
Dagger s'exécute localement sur la machine gitrust (ou un runner distant — voir admin/how-to/configurer-ci-runner-remote.md). Aucun port réseau ouvert → pas de jail dédié.
8.2 Abhängigkeits-Track¶
Wenn DTrack auf demselben Server bereitgestellt und offengelegt wird, aktivieren Sie die Jails 10 und 11, indem Sie „enabled = true“ festlegen und „logpath“ auf die Nginx-Protokolle des DTrack-Vhosts verweisen.
Wenn DTrack intern ist (VPN, privates Netzwerk), lassen Sie „enabled = false“ – die Firewall reicht aus.
Config DTrack recommandée : - Binden Sie über „dtrack.url.base=http://127.0.0.1:8080“ an „127.0.0.1“.
8.3 PostgreSQL¶
Das „Postgresql“-Gefängnis ist defensiv. PG muss über Docker an „127.0.0.1:5432“ gebunden bleiben. Überprüfen :
8.4 Benachrichtigungen¶
So erhalten Sie bei jedem Verbot eine E-Mail:
- Installieren Sie ein lokales SMTP-Relay: „sudo apt install msmtp-mta“.
- Konfigurieren Sie „/etc/msmtprc“ mit einem SMTP-Konto
- Kommentieren Sie in „[DEFAULT]“ aus:
sudo systemctl restart fail2ban
9. Grenzen und Entwicklungen¶
- IPv6: Alle regulären Ausdrücke verwenden „
“, was IPv4 UND IPv6 entspricht. Stellen Sie sicher, dass UFW auch für Version 6 konfiguriert ist. - CDN/Cloudflare vorne: Wenn ein CDN hinzugefügt wird, ist „$remote_addr“ auf der Nginx-Seite die IP des CDN – Sie müssen die echte IP über „X-Forwarded-For“ abrufen und an den Nginx-Eintrag weitergeben. Ändern Sie „failregex“ entsprechend. Andernfalls werden die Gefängnisse das CDN verbieten.
- Docker/Podman : si gitrust passe en conteneur, les logs de
gitrust.servicedeviennentdocker.serviceoupodman.service→ mettre à jourjournalmatchdans le jail 9 (gitrust-import). Le jail 8 (gitrust-ssh) n'est pas affecté car il consomme le fichier JSON ssh-guard, à condition que ce fichier soit monté côté hôte. - GeoIP: Um ganze Länder im Upstream zu blockieren, fügen Sie „geo $blocked_country“ in Nginx hinzu (Modul „ngx_http_geoip2_module“) – ergänzend zu fail2ban.