feat(proxy): jail fail2ban gitea + rate-limit et fermeture de /metrics #28

Closed
Damien wants to merge 1 commits from feat/proxy-jail-gitea into feat/proxy-rsyslog
+103 -1
View File
@@ -157,6 +157,14 @@ main() {
sudo chown "$USER":"$USER" /var/log/traefik sudo chown "$USER":"$USER" /var/log/traefik
sudo touch /var/log/traefik/access.log sudo touch /var/log/traefik/access.log
# DEFAULT ignoreip guards every jail below, including gitea-auth: a
# misconfigured X-Forwarded-For chain must never be able to ban the
# tailnet itself. 100.64.0.0/10 is Tailscale's CGNAT range.
sudo tee /etc/fail2ban/jail.local > /dev/null << 'EOF'
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 100.64.0.0/10
EOF
sudo tee /etc/fail2ban/filter.d/traefik.conf > /dev/null << 'EOF' sudo tee /etc/fail2ban/filter.d/traefik.conf > /dev/null << 'EOF'
[Definition] [Definition]
# Match JSON log lines where ClientHost is the offending IP and DownstreamStatus # Match JSON log lines where ClientHost is the offending IP and DownstreamStatus
@@ -177,6 +185,31 @@ maxretry = 10
findtime = 5m findtime = 5m
bantime = 1h bantime = 1h
action = iptables-multiport[name=traefik, port="80,443", protocol=tcp] action = iptables-multiport[name=traefik, port="80,443", protocol=tcp]
EOF
log_info "Configuring Fail2ban for Gitea..."
# Gitea returns HTTP 200 on a failed web login (the page is simply
# re-rendered with an error) — the traefik-auth jail above, which
# matches on DownstreamStatus, is structurally blind to brute force on
# the login form. This jail reads the application log instead
# (forwarded via rsyslog below), which does log failed attempts.
sudo mkdir -p /var/log/gitea
sudo tee /etc/fail2ban/filter.d/gitea.conf > /dev/null << 'EOF'
[Definition]
failregex = .*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from <HOST>
ignoreregex =
EOF
sudo tee /etc/fail2ban/jail.d/gitea.conf > /dev/null << 'EOF'
[gitea-auth]
enabled = true
filter = gitea
logpath = /var/log/gitea/gitea.log
maxretry = 5
findtime = 10m
bantime = 1h
action = iptables-multiport[name=gitea, port="80,443", protocol=tcp]
EOF EOF
sudo systemctl restart fail2ban sudo systemctl restart fail2ban
@@ -211,6 +244,30 @@ EOF
notifempty notifempty
copytruncate copytruncate
} }
EOF
log_info "Configuring rsyslog routing for Gitea..."
# Routes the "gitea" tag (set by gitea/install.sh's imfile forwarder)
# into its own file, on top of the generic 10-remote-receiver.conf
# catch-all — the gitea-auth fail2ban jail above reads this file.
sudo tee /etc/rsyslog.d/50-gitea.conf > /dev/null << 'EOF'
$RuleSet remoteLogs
if $programname == 'gitea' then {
action(type="omfile" file="/var/log/gitea/gitea.log")
stop
}
$RuleSet RSYSLOG_DefaultRuleset
EOF
sudo tee /etc/logrotate.d/gitea > /dev/null << 'EOF'
/var/log/gitea/gitea.log {
daily
rotate 7
compress
missingok
notifempty
copytruncate
}
EOF EOF
sudo systemctl restart rsyslog sudo systemctl restart rsyslog
@@ -313,22 +370,67 @@ accessLog:
EOF EOF
# --- conf.d/gitea.yml (dynamic config) --- # --- conf.d/gitea.yml (dynamic config) ---
# NOTE: backend is https://gitea.taila5ad8.ts.net (443, no port) — the
# result of gitea/install.sh (#19) moving to `tailscale serve
# --https=443`. This is NOT compatible with the previous :3000 backend
# of the old community-scripts deployment; only apply this once the
# data migration to the new instance has actually happened.
cat > "$TRAEFIK_DIR/conf.d/gitea.yml" << 'EOF' cat > "$TRAEFIK_DIR/conf.d/gitea.yml" << 'EOF'
http: http:
routers: routers:
# /metrics carries the Prometheus bearer token; never expose it publicly.
# Highest priority so it wins over the catch-all "gitea" router below.
gitea-metrics-deny:
rule: "Host(`gitea.arnodo.fr`) && PathPrefix(`/metrics`)"
priority: 200
entryPoints:
- websecure
service: gitea
middlewares:
- deny-public
tls:
certResolver: letsencrypt
# Gitea returns HTTP 200 on a failed web login, so the traefik-auth
# jail (which matches on DownstreamStatus) can't see login brute
# force. Rate-limit the login/signup/forgot-password surface directly
# as a second layer on top of the gitea-auth fail2ban jail.
gitea-auth:
rule: "Host(`gitea.arnodo.fr`) && (Path(`/user/login`) || Path(`/user/sign_up`) || Path(`/user/forgot_password`))"
priority: 100
entryPoints:
- websecure
service: gitea
middlewares:
- auth-ratelimit
tls:
certResolver: letsencrypt
gitea: gitea:
rule: "Host(`gitea.arnodo.fr`)" rule: "Host(`gitea.arnodo.fr`)"
priority: 1
entryPoints: entryPoints:
- websecure - websecure
service: gitea service: gitea
tls: tls:
certResolver: letsencrypt certResolver: letsencrypt
middlewares:
auth-ratelimit:
rateLimit:
average: 6
period: 1m
burst: 12
deny-public:
ipAllowList:
sourceRange:
- "127.0.0.1/32"
services: services:
gitea: gitea:
loadBalancer: loadBalancer:
servers: servers:
- url: "http://gitea.taila5ad8.ts.net:3000" - url: "https://gitea.taila5ad8.ts.net"
EOF EOF
log_info "Starting Traefik stack..." log_info "Starting Traefik stack..."