Rate limiter für Gitea

3 Minuten Lesezeit

Hier halte ich eine Schritt-für-Schritt Anleitung fest, die das Problem von Gitea-Abstürze lösen soll. Die Erfahrung aus meiner Erstanwendung eines Rate Limiters für meinen Blog lassen sich hier einfließen. Somit läuft die Anwendung viel robuster und ist gegen ungerichtete Denial of Service Attacken geschützt.

Schritt 1: Log-Quelle für rate limiter festlegen

Gitea erzeugt natürlich selbst Logs, theoretisch bis herunter auf Zugriffsebene durch externe Clients. Doch schaffte ich es in der Vergangenheit nicht, Giteas Access-Logs aus Docker auszuleiten. Daher geben wir jetzt dem als Reverse Proxy angeflanschten Caddyserver die Anweisung, Logs stellvertretend zu erstellen. In ihnen tauchen dann Zugriffe auf das Webinterface von Gitea auf.

In der Caddyfile sieht das Log-Modul total unspektakulär aus.

# /caddy/Caddyfile

#[...]
log {
    output file /log/gitea/access.log
  }

Internen Traffic vom Log ausschließen

Nun schaue ich die Logs mal an und sehe Einträge, die ich gar nicht haben will. So erzeugt act_runner zum Beispiel alle zwei Sekunden einen Fetch Task POST nach Gitea und alle zehn Sekunden einen GET Request mit demselben Ziel als Health check. Diese brauchen für mich im Log gar nicht erst auftauchen. Mein erster Gedanke hier war, einfach Anfragen von internen IP-Adressen nicht zu loggen. Da Caddy allerdings als Reverse Proxy fungiert, laufen ausnahmslos alle IP-Adressen “intern”.

Also muss ich act_runner spezifische Logs anders erkennen:

# /caddy/Caddyfile

#[...]
git.schallbert.de {
  reverse_proxy * http://gitea:3000

  # Enable logging for fail2ban, don't log for runner
  log_skip /api/actions*
  log {
    output file /log/gitea/access.log
  }
}

Die log_skip Direktive weist caddy nun an, keine Logs für Zugriffe auf /api/actions* mehr anzulegen.

Reverse-Proxy: Remote IPs anzeigen

Dennoch habe ich noch immer ein Problem: Wenn im Log alle externen Anfragen auf einer internen IP-Adresse hereinkommen, wie soll ich “böse” Anfragen dann wegblocken? Eine Websuche zeigt, dass dies für Reverse Proxy in Docker ein üblicher Stolperstein ist. Leider helfen mir viele Lösungen nicht, da sie andere Serversoftware wie nginx verwenden oder andere Services hinter ihren Proxies laufen haben als Gitea.

Doch tatsächlich ist es ganz einfach: Nur eine Zeile muss an der richtigen Stelle in die Caddyfile eingefügt werden und schon kommen die remote IP-Adressen unverfälscht rein.

# /caddy/Caddyfile

#[...]
git.schallbert.de {
  reverse_proxy * http://gitea:3000 {
    trusted_proxies 172.16.0.0/12 # Docker-internal netwock traffic runs with these IPs
  }
#[...]  

Die Direktive trusted_proxies teilt Caddy mit, dass dem von Docker bereitgestelltem Netzwerk vertraut werden kann. Somit wird die eigentliche Quell-IP angezeigt statt der internen Schnittstelladresse des Containers. Genau das brauche ich, um die Adressen später mit fail2ban analysieren zu können.

Schritt 2: Anwendungsfall bestimmen

Schauen wir uns an, wie viele HTTP 200 ok Anfragen im Grenzfall zwischen Normalnutzung und “abuse” kommen. Dazu surfe ich auf Gitea herum und klicke einen Haufen Dinge an, wie ich das als Mensch sonst nie in der Geschwindigkeit tun würde. Danach mache ich eine Auswertung der Logs.

Damit haben wir erste Richtwerte für den Rate Limiter.

# rate limiter tests
findtime    = 10s
maxretry    = 10
bantime     = 6h

Schritt 3: Fail2ban konfigurieren

Anschließend konfigurieren wir Filter und Jail-Datei von fail2ban so, dass sie die oben definierten Logs analysieren.

Filter

Ich verwende hier schlicht denselben Filter für meinen Rate-Limiter aus dem vorherigen Artikel wieder. Uns interessieren nur erfolgreiche Anfragen, die wir innerhalb eines Zeitfensters zählen.

Jail

Ich verwende die Werte aus den rate limiter Tests 1:1 hier in der Jail-Datei weiter. Als Filter verweise ich auf caddy-ratelimit (Link oben).

# /fail2ban/config/fail2ban/jail.local
# [...]

[gitea-ratelimit]
enabled     = true
chain       = DOCKER-USER
port        = http,https
filter      = caddy-ratelimit
logpath     = /var/log/caddy2/gitea/access.log
findtime    = 10s
maxretry    = 10
bantime     = 6h

Mit einem Neustart von fail2ban schalte ich den Schutz scharf.

Schritt 4: Rate limiter testen

Hier gehe ich genauso vor wie in meinem Artikel fail2ban with caddy und komme zum selben Ergebnis. Es funktionert!