0.3.8
This commit is contained in:
1 parent
50b53e72f0
commit
439cc20dcf
6 files changed
+110
-8
No files matched your search
@@ -25,3 +25,14 @@ RRDTOOL=rrdtool
|
||||
RRD_PATH=/var/lib/ts3monolith/linuxiarz.rrd
|
||||
GRAPH_DIR=/var/lib/ts3monolith/graphs
|
||||
STATS_INTERVAL=60s
|
||||
|
||||
# Per-IP rate limiting, off by default (see README "Rate limiting"). When
|
||||
# RATE_LIMIT_ENABLED=false, none of the RATE_LIMIT_* values below do anything.
|
||||
RATE_LIMIT_ENABLED=false
|
||||
RATE_LIMIT_STATS=60
|
||||
RATE_LIMIT_STATS_LIVE=60
|
||||
RATE_LIMIT_GRAPH=600
|
||||
RATE_LIMIT_CHANNEL=40
|
||||
RATE_LIMIT_VERIFY=10
|
||||
RATE_LIMIT_VERIFY_DONE=10
|
||||
RATE_LIMIT_CREATE=5
|
||||
@@ -127,3 +127,32 @@ Jeżeli docelowy serwer ma inną architekturę/endianness niż źródłowy x86_6
|
||||
- build z `-trimpath` i usuniętymi symbolami/debug info.
|
||||
|
||||
Nie da się zagwarantować, że stos technologiczny będzie całkowicie nierozpoznawalny na podstawie zachowania sieci/TLS. Celem jest brak prostych bannerów, wersji i sygnatur frameworka.
|
||||
|
||||
## Rate limiting
|
||||
|
||||
Domyślnie **wyłączony** (`RATE_LIMIT_ENABLED=false`) — endpointy mają limity
|
||||
zahardkodowane w kodzie od początku, ale same w sobie nie chronią niczego
|
||||
krytycznego (ServerQuery i tak ma własny timeout, `channelcreate` i tak
|
||||
wymaga poprawnego tokenu CSRF), a przy nietypowym ruchu (kilku odwiedzających
|
||||
za tym samym IP/NAT, kilka kart, lightbox z wykresami) dawały false-positive
|
||||
`429` bez realnej korzyści. Włącz (`RATE_LIMIT_ENABLED=true`), jeśli chcesz
|
||||
dodatkową warstwę ochrony przed nadużyciami na konkretnym endpoincie.
|
||||
|
||||
Każdy limit jest per-IP (po rozwiązaniu `TRUSTED_PROXIES`/`X-Forwarded-For`,
|
||||
patrz `clientIP()` w `internal/web/server.go`) i liczony w oknie czasowym
|
||||
podanym w nawiasie:
|
||||
|
||||
| Zmienna | Endpoint | Domyślnie | Co chroni |
|
||||
|---|---|---|---|
|
||||
| `RATE_LIMIT_STATS` | `GET /stats` (na minutę) | 60 | render strony statystyk |
|
||||
| `RATE_LIMIT_STATS_LIVE` | `GET /stats/live` (na minutę) | 60 | auto-odświeżanie licznika online co 60 s |
|
||||
| `RATE_LIMIT_GRAPH` | `GET /graph/*.png` (na minutę) | 600 | wykresy RRD — PNG są cache'owane na dysku ~55 s, więc limit jest wysoki: jeden widok strony sam zużywa 6 zakresów × 2 motywy, a NAT/kilka kart to łatwo pomnoży |
|
||||
| `RATE_LIMIT_CHANNEL` | `POST /channel/create` (na minutę) | 40 | tworzenie kanału |
|
||||
| `RATE_LIMIT_VERIFY` | `POST /verify/start` (na minutę) | 10 | start weryfikacji nicku (NAT) |
|
||||
| `RATE_LIMIT_VERIFY_DONE` | `POST /verify/complete` (na minutę) | 10 | wpisanie 6-cyfrowego kodu |
|
||||
| `RATE_LIMIT_CREATE` | `POST /channel/create` po weryfikacji (na godzinę) | 5 | utworzenie kanału po weryfikacji nicku przez NAT |
|
||||
|
||||
Przy przekroczeniu limitu endpoint odpowiada `429 Too Many Requests` z
|
||||
nagłówkiem `Retry-After: 60`. Wartości domyślne są w `.env.example` i
|
||||
`packaging/ts3monolith.default` — dostosuj je do realnego ruchu na danej
|
||||
instalacji zanim włączysz `RATE_LIMIT_ENABLED` na produkcji.
|
||||
@@ -30,6 +30,18 @@ type Config struct {
|
||||
RRDPath string
|
||||
GraphDir string
|
||||
StatsInterval time.Duration
|
||||
|
||||
// Per-IP rate limiting. Off by default -- see README "Rate limiting"
|
||||
// for what each endpoint's limit protects against and reasonable
|
||||
// values if you turn it on.
|
||||
RateLimitEnabled bool
|
||||
RateLimitStats int
|
||||
RateLimitStatsLive int
|
||||
RateLimitGraph int
|
||||
RateLimitChannel int
|
||||
RateLimitVerify int
|
||||
RateLimitVerifyDone int
|
||||
RateLimitCreate int
|
||||
}
|
||||
|
||||
func Load() (Config, error) {
|
||||
@@ -86,6 +98,36 @@ func Load() (Config, error) {
|
||||
if c.StatsInterval < 10*time.Second {
|
||||
return c, errors.New("STATS_INTERVAL must be at least 10s")
|
||||
}
|
||||
|
||||
c.RateLimitEnabled = envBool("RATE_LIMIT_ENABLED", false)
|
||||
c.RateLimitStats, err = envInt("RATE_LIMIT_STATS", 60)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
c.RateLimitStatsLive, err = envInt("RATE_LIMIT_STATS_LIVE", 60)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
c.RateLimitGraph, err = envInt("RATE_LIMIT_GRAPH", 600)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
c.RateLimitChannel, err = envInt("RATE_LIMIT_CHANNEL", 40)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
c.RateLimitVerify, err = envInt("RATE_LIMIT_VERIFY", 10)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
c.RateLimitVerifyDone, err = envInt("RATE_LIMIT_VERIFY_DONE", 10)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
c.RateLimitCreate, err = envInt("RATE_LIMIT_CREATE", 5)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
||||
+14
-7
@@ -144,7 +144,7 @@ func (s *Server) security(next http.Handler) http.Handler {
|
||||
|
||||
func (s *Server) stats(w http.ResponseWriter, r *http.Request) {
|
||||
ip := s.clientIP(r).String()
|
||||
if !s.limit.allow("stats|"+ip, 60, time.Minute) {
|
||||
if s.cfg.RateLimitEnabled && !s.limit.allow("stats|"+ip, s.cfg.RateLimitStats, time.Minute) {
|
||||
s.tooMany(w)
|
||||
return
|
||||
}
|
||||
@@ -173,7 +173,7 @@ func (s *Server) stats(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (s *Server) statsLive(w http.ResponseWriter, r *http.Request) {
|
||||
ip := s.clientIP(r).String()
|
||||
if !s.limit.allow("statslive|"+ip, 60, time.Minute) {
|
||||
if s.cfg.RateLimitEnabled && !s.limit.allow("statslive|"+ip, s.cfg.RateLimitStatsLive, time.Minute) {
|
||||
s.tooMany(w)
|
||||
return
|
||||
}
|
||||
@@ -252,7 +252,14 @@ func (s *Server) asset(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (s *Server) graph(w http.ResponseWriter, r *http.Request) {
|
||||
ip := s.clientIP(r).String()
|
||||
if !s.limit.allow("graph|"+ip, 120, time.Minute) {
|
||||
// Graph PNGs are cheap to serve: rrd.Manager.Graph caches each
|
||||
// key+theme render on disk for ~55s, so most hits here are a plain
|
||||
// file read, not an rrdtool invocation. One page view alone can
|
||||
// legitimately touch this 6 ranges x 2 themes (lightbox open, theme
|
||||
// toggle) times, before counting multiple tabs or several visitors
|
||||
// sharing one IP (same household/NAT) -- see README "Rate limiting"
|
||||
// for tuning RATE_LIMIT_GRAPH if this still trips in practice.
|
||||
if s.cfg.RateLimitEnabled && !s.limit.allow("graph|"+ip, s.cfg.RateLimitGraph, time.Minute) {
|
||||
s.tooMany(w)
|
||||
return
|
||||
}
|
||||
@@ -288,7 +295,7 @@ func (s *Server) channel(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "invalid client address", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if !s.limit.allow("channel|"+ip.String(), 40, time.Minute) {
|
||||
if s.cfg.RateLimitEnabled && !s.limit.allow("channel|"+ip.String(), s.cfg.RateLimitChannel, time.Minute) {
|
||||
s.tooMany(w)
|
||||
return
|
||||
}
|
||||
@@ -316,7 +323,7 @@ func (s *Server) verifyStart(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.requirePost(w, r, ip.String()) {
|
||||
return
|
||||
}
|
||||
if !s.limit.allow("verify|"+ip.String(), 10, time.Minute) {
|
||||
if s.cfg.RateLimitEnabled && !s.limit.allow("verify|"+ip.String(), s.cfg.RateLimitVerify, time.Minute) {
|
||||
s.tooMany(w)
|
||||
return
|
||||
}
|
||||
@@ -362,7 +369,7 @@ func (s *Server) verifyComplete(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.requirePost(w, r, ip.String()) {
|
||||
return
|
||||
}
|
||||
if !s.limit.allow("verifydone|"+ip.String(), 10, time.Minute) {
|
||||
if s.cfg.RateLimitEnabled && !s.limit.allow("verifydone|"+ip.String(), s.cfg.RateLimitVerifyDone, time.Minute) {
|
||||
s.tooMany(w)
|
||||
return
|
||||
}
|
||||
@@ -391,7 +398,7 @@ func (s *Server) createChannel(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.requirePost(w, r, ip.String()) {
|
||||
return
|
||||
}
|
||||
if !s.limit.allow("create|"+ip.String(), 5, time.Hour) {
|
||||
if s.cfg.RateLimitEnabled && !s.limit.allow("create|"+ip.String(), s.cfg.RateLimitCreate, time.Hour) {
|
||||
s.tooMany(w)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -31,3 +31,16 @@ STATS_INTERVAL=60s
|
||||
# co faktycznie trafilo do RRD). Przydatne tylko przy diagnozowaniu -- zalewa
|
||||
# journald jednym wpisem na STATS_INTERVAL, zostaw wylaczone na co dzien.
|
||||
DEBUG=false
|
||||
|
||||
# === Rate limiting ============================================================
|
||||
# Domyslnie wylaczone. Przy wlaczeniu limity sa per-IP (patrz README,
|
||||
# sekcja "Rate limiting") -- wartosci nizej to sensowne domyslne, dostosuj
|
||||
# do realnego ruchu zanim wlaczysz na produkcji.
|
||||
RATE_LIMIT_ENABLED=false
|
||||
RATE_LIMIT_STATS=60
|
||||
RATE_LIMIT_STATS_LIVE=60
|
||||
RATE_LIMIT_GRAPH=600
|
||||
RATE_LIMIT_CHANNEL=40
|
||||
RATE_LIMIT_VERIFY=10
|
||||
RATE_LIMIT_VERIFY_DONE=10
|
||||
RATE_LIMIT_CREATE=5
|
||||
Reference in new issue
Block a user