fix: address spec compliance gaps - tenant check, XFF fallback, rate limiting
- Gap 1: Add tenant ID verification after device lookup in SSH relay handleSSH,
closing cross-tenant token reuse vulnerability
- Gap 2: Add X-Forwarded-For fallback (last entry) when X-Real-IP is absent in
SSH relay source IP extraction; import strings package
- Gap 3: Add @limiter.limit("10/minute") to POST /winbox-session and POST
/ssh-session using existing slowapi pattern from app.middleware.rate_limit
- Gap 4: Add TODO comment in open_ssh_session explaining that SSH session count
enforcement is at the poller level; no NATS subject exists yet for API-side
pre-check
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -123,8 +124,15 @@ func (s *Server) handleSSH(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
ws.SetReadLimit(1 << 20)
|
||||
|
||||
// Extract source IP (nginx sets X-Real-IP)
|
||||
// Extract source IP (nginx sets X-Real-IP, fall back to X-Forwarded-For then RemoteAddr)
|
||||
sourceIP := r.Header.Get("X-Real-IP")
|
||||
if sourceIP == "" {
|
||||
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
||||
// Use last entry (closest proxy)
|
||||
parts := strings.Split(xff, ",")
|
||||
sourceIP = strings.TrimSpace(parts[len(parts)-1])
|
||||
}
|
||||
}
|
||||
if sourceIP == "" {
|
||||
sourceIP = r.RemoteAddr
|
||||
}
|
||||
@@ -137,6 +145,13 @@ func (s *Server) handleSSH(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Verify device belongs to the tenant in the token
|
||||
if dev.TenantID != payload.TenantID {
|
||||
slog.Warn("ssh: tenant mismatch", "device_tenant", dev.TenantID, "token_tenant", payload.TenantID)
|
||||
ws.Close(websocket.StatusPolicyViolation, "unauthorized")
|
||||
return
|
||||
}
|
||||
|
||||
// Decrypt credentials — GetCredentials returns (username, password, error)
|
||||
username, password, err := s.credCache.GetCredentials(
|
||||
dev.ID,
|
||||
|
||||
Reference in New Issue
Block a user