mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-10 00:14:38 +00:00
Broker provider control-plane Docker access through a socket proxy, remove broad host mounts, align audit and rate-limit proxy trust, harden tenant runtime containers, restrict workspace report logo paths, and update provider deploy guardrails.
88 lines
1.9 KiB
Go
88 lines
1.9 KiB
Go
package cloudcp
|
|
|
|
import (
|
|
"math"
|
|
"net/http"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/cloudcp/proxytrust"
|
|
)
|
|
|
|
const (
|
|
defaultCPRateLimit = 120
|
|
defaultCPRateWindow = time.Minute
|
|
)
|
|
|
|
// CPRateLimiter provides simple IP-based rate limiting for control plane endpoints.
|
|
type CPRateLimiter struct {
|
|
mu sync.Mutex
|
|
attempts map[string][]time.Time
|
|
limit int
|
|
window time.Duration
|
|
}
|
|
|
|
// NewCPRateLimiter creates a rate limiter with the given limit per window.
|
|
func NewCPRateLimiter(limit int, window time.Duration) *CPRateLimiter {
|
|
if limit <= 0 {
|
|
limit = defaultCPRateLimit
|
|
}
|
|
if window <= 0 {
|
|
window = defaultCPRateWindow
|
|
}
|
|
return &CPRateLimiter{
|
|
attempts: make(map[string][]time.Time),
|
|
limit: limit,
|
|
window: window,
|
|
}
|
|
}
|
|
|
|
// Allow checks whether the given IP is within the rate limit.
|
|
func (rl *CPRateLimiter) Allow(ip string) bool {
|
|
rl.mu.Lock()
|
|
defer rl.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
cutoff := now.Add(-rl.window)
|
|
|
|
// Filter expired entries
|
|
valid := rl.attempts[ip][:0]
|
|
for _, t := range rl.attempts[ip] {
|
|
if t.After(cutoff) {
|
|
valid = append(valid, t)
|
|
}
|
|
}
|
|
|
|
if len(valid) >= rl.limit {
|
|
rl.attempts[ip] = valid
|
|
return false
|
|
}
|
|
|
|
rl.attempts[ip] = append(valid, now)
|
|
return true
|
|
}
|
|
|
|
// Middleware wraps an http.Handler with rate limiting.
|
|
func (rl *CPRateLimiter) Middleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
ip := clientIP(r)
|
|
if !rl.Allow(ip) {
|
|
retryAfter := int(math.Ceil(rl.window.Seconds()))
|
|
if retryAfter < 1 {
|
|
retryAfter = 1
|
|
}
|
|
|
|
w.Header().Set("Retry-After", strconv.Itoa(retryAfter))
|
|
w.Header().Set("X-RateLimit-Limit", strconv.Itoa(rl.limit))
|
|
w.Header().Set("X-RateLimit-Remaining", "0")
|
|
http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
func clientIP(r *http.Request) string {
|
|
return proxytrust.ClientIP(r)
|
|
}
|