Security: Add per-IP connection and login rate limiting
The TCP device port is exposed directly to the public internet, so a single host must not be able to exhaust the global connection budget, flood new connection attempts, or brute-force the device authorization passcode. - server: cap concurrent connections and the accept rate per source IP (defaults 256 concurrent / 120 per minute, overridable via YAMA_PERIP_MAX_CONNS and YAMA_PERIP_CONN_RATE). Rejections happen at accept time, before any buffer or goroutine is allocated, and a rejected device simply retries on its reconnect loop. - cmd: throttle TOKEN_AUTH per source IP (120/min) and answer over-limit attempts with a normal failed response so clients get a definitive reply. - web: periodically sweep the login rate-limit maps so the username-keyed one (which is attacker-controlled) cannot grow without bound. Per-IP defaults are generous enough that a NAT office whose devices share one source IP is unaffected. Co-Authored-By: deepseek-v4-pro
This commit is contained in:
1 parent
2de21b2b6b
commit
8e0f1283c6
4 files changed
+344
-17
No files matched your search
@@ -10,6 +10,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/yuanyuanxiang/SimpleRemoter/server/go/hub"
|
||||
@@ -33,6 +34,8 @@ type Server struct {
|
||||
trustForwardedFor bool // honor X-Forwarded-For (behind trusted proxy only)
|
||||
basePath string // web UI mount prefix (e.g. "/remoter"); "" = domain root
|
||||
sso SSODetector // nil = SSO disabled
|
||||
sweepDone chan struct{} // closes to stop the login-limiter sweeper
|
||||
sweepStopOnce sync.Once
|
||||
}
|
||||
|
||||
// Config tunes the server's exposed-on-public-HTTPS hardening knobs.
|
||||
@@ -130,11 +133,40 @@ func (s *Server) Start() error {
|
||||
s.log.Error("HTTP server stopped: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Prune aged-out login rate-limit entries so the key maps (especially the
|
||||
// username-keyed one, which is attacker-controlled) stay bounded.
|
||||
s.sweepDone = make(chan struct{})
|
||||
go s.sweepLoginLimiters(s.sweepDone)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sweepLoginLimiters periodically sweeps both web login rate limiters. Runs
|
||||
// until `stop` is closed (from Stop).
|
||||
func (s *Server) sweepLoginLimiters(stop <-chan struct{}) {
|
||||
t := time.NewTicker(time.Minute)
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
case <-t.C:
|
||||
if s.loginIPLimit != nil {
|
||||
s.loginIPLimit.Sweep()
|
||||
}
|
||||
if s.loginUserLimit != nil {
|
||||
s.loginUserLimit.Sweep()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop gracefully shuts the server down.
|
||||
func (s *Server) Stop() {
|
||||
if s.sweepDone != nil {
|
||||
s.sweepStopOnce.Do(func() { close(s.sweepDone) })
|
||||
}
|
||||
if s.ws != nil {
|
||||
s.ws.stop()
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user