Security(Go): Login rate limit + WS origin allowlist + REST bearer auth

This commit is contained in:
yuanyuanxiang
2026-05-18 23:37:58 +02:00
parent 5947d41617
commit c6244462a9
8 changed files with 566 additions and 41 deletions

View File

@@ -611,6 +611,27 @@ func parsePorts(portStr string) ([]int, error) {
return ports, nil
}
// splitCSV splits a comma-separated env-var value into trimmed, non-empty
// entries. Returns nil for an empty input so callers can keep the natural
// "no value → no restriction" semantics with a single nil check.
func splitCSV(s string) []string {
if s == "" {
return nil
}
parts := strings.Split(s, ",")
out := make([]string, 0, len(parts))
for _, p := range parts {
p = strings.TrimSpace(p)
if p != "" {
out = append(out, p)
}
}
if len(out) == 0 {
return nil
}
return out
}
func main() {
// Parse command line flags
portStr := flag.String("port", "6543", "Server listen ports (semicolon-separated, e.g. 6543;6544;6545)")
@@ -733,9 +754,33 @@ func main() {
}
}
// Web-UI hardening knobs for public-HTTPS deployment.
//
// YAMA_WEB_ALLOWED_ORIGINS: comma-separated Origin allowlist (e.g.
// "https://yama.example.com,https://yama-mobile.example.com").
// Empty (default) → only same-origin WS upgrades accepted, which
// is correct when the web UI and WS endpoint share a host.
//
// Login rate limits are hard-coded at sensible defaults for the
// small-user web UI: 10 attempts / minute per IP, 5 / 15 min per
// username. The handler also injects a 250 ms delay on every failure
// so online brute force is impractical even within budget.
allowedOrigins := splitCSV(os.Getenv("YAMA_WEB_ALLOWED_ORIGINS"))
trustProxy := os.Getenv("YAMA_WEB_TRUST_PROXY") == "1"
if trustProxy {
log.Info("Trusting X-Forwarded-For for client IP — make sure a reverse proxy is in front")
}
webCfg := web.Config{
AllowedOrigins: allowedOrigins,
LoginIPLimit: wsauth.NewRateLimiter(10, time.Minute),
LoginUserLimit: wsauth.NewRateLimiter(5, 15*time.Minute),
TrustForwardedFor: trustProxy,
}
// Start HTTP server for web UI. Hub gives it read-only access to the
// device registry; the authenticator owns user accounts and session tokens.
httpSrv := web.New(*httpPort, log.WithPrefix("Web"), deviceHub, webAuth)
httpSrv := web.New(*httpPort, log.WithPrefix("Web"), deviceHub, webAuth).
WithConfig(webCfg)
if err := httpSrv.Start(); err != nil {
log.Fatal("Failed to start HTTP server: %v", err)
}