Block requests from TOR
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -552,3 +553,39 @@ func TestDeleteUser(t *testing.T) {
|
|||||||
t.Fatalf("bobs antwort verwaist: %+v", tv.Replies)
|
t.Fatalf("bobs antwort verwaist: %+v", tv.Replies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// postFormFrom verhält sich wie postForm, setzt aber X-Real-IP -> middleware.RealIP
|
||||||
|
// überschreibt damit r.RemoteAddr, sodass blockTOR die simulierte Quell-IP sieht.
|
||||||
|
func postFormFrom(t *testing.T, c *http.Client, urlStr, realIP string, form url.Values) *http.Response {
|
||||||
|
t.Helper()
|
||||||
|
req, err := http.NewRequest(http.MethodPost, urlStr, strings.NewReader(form.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("request %s: %v", urlStr, err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
req.Header.Set("X-Real-IP", realIP)
|
||||||
|
resp, err := c.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("POST %s: %v", urlStr, err)
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBlockTOREntry prüft, dass Beiträge aus dem TOR-Netz (Default 10.89.0.0/16)
|
||||||
|
// mit 403 abgewiesen werden, aus einem anderen Netz aber durchgehen.
|
||||||
|
func TestBlockTOREntry(t *testing.T) {
|
||||||
|
srv := newTestServer(t)
|
||||||
|
c := registerAndLogin(t, srv, "toruser")
|
||||||
|
|
||||||
|
resp := postFormFrom(t, c, srv.URL+"/entry/create", "10.89.1.2", url.Values{"content": {"aus tor"}})
|
||||||
|
resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusForbidden {
|
||||||
|
t.Fatalf("TOR-Beitrag: erwartet 403, bekam %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = postFormFrom(t, c, srv.URL+"/entry/create", "203.0.113.7", url.Values{"content": {"normal"}})
|
||||||
|
resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusCreated {
|
||||||
|
t.Fatalf("Nicht-TOR-Beitrag: erwartet 201, bekam %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ func routes() http.Handler {
|
|||||||
// --- Geschützt (Session erforderlich) ---
|
// --- Geschützt (Session erforderlich) ---
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Use(requireAuth)
|
r.Use(requireAuth)
|
||||||
r.Post("/entry/create", handleCreateEntry)
|
r.With(blockTOR).Post("/entry/create", handleCreateEntry)
|
||||||
r.Post("/entry/{pid}/vote", handleVote)
|
r.Post("/entry/{pid}/vote", handleVote)
|
||||||
r.Post("/entry/{pid}/bump", handleBump)
|
r.Post("/entry/{pid}/bump", handleBump)
|
||||||
r.Post("/entry/{pid}/edit", handleEditEntry)
|
r.Post("/entry/{pid}/edit", handleEditEntry)
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -9,6 +11,41 @@ type ctxKey string
|
|||||||
|
|
||||||
const uidKey ctxKey = "uid"
|
const uidKey ctxKey = "uid"
|
||||||
|
|
||||||
|
// torNet ist das interne Netz, über das TOR-Anfragen beim Backend ankommen:
|
||||||
|
// der Onion-Hidden-Service reicht über einen lokalen Proxy weiter, eine echte
|
||||||
|
// Client-IP gibt es dabei nicht -> die Anfragen erscheinen mit einer IP aus
|
||||||
|
// diesem Netz (siehe Impression-Log: 10.89.0.0). Beiträge von dort werden
|
||||||
|
// vorerst komplett abgewiesen; eine Moderations-/Freigabeseite kommt später.
|
||||||
|
// Per KVER_TOR_NET (CIDR) konfigurierbar; leer schaltet die Sperre ab.
|
||||||
|
var torNet *net.IPNet
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cidr := envOr("KVER_TOR_NET", "10.89.0.0/16")
|
||||||
|
if cidr == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, n, err := net.ParseCIDR(cidr); err == nil {
|
||||||
|
torNet = n
|
||||||
|
} else {
|
||||||
|
log.Printf("KVER_TOR_NET ignoriert (ungültiges CIDR %q): %v", cidr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockTOR weist Anfragen aus dem TOR-Netz ab (siehe torNet). Die volle IP wird
|
||||||
|
// hier nur für diese Zugriffsentscheidung geprüft, nicht gespeichert oder
|
||||||
|
// geloggt. Ist kein Netz konfiguriert, ist die Middleware ein No-op.
|
||||||
|
func blockTOR(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if torNet != nil {
|
||||||
|
if ip := clientIP(r.RemoteAddr); ip != nil && torNet.Contains(ip) {
|
||||||
|
writeError(w, http.StatusForbidden, "Beiträge über TOR sind derzeit nicht möglich.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// requireAuth lehnt Anfragen ohne gültige Session ab und legt die uid in den Context.
|
// requireAuth lehnt Anfragen ohne gültige Session ab und legt die uid in den Context.
|
||||||
func requireAuth(next http.Handler) http.Handler {
|
func requireAuth(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
Reference in New Issue
Block a user