Pulse/pkg/proxmox/cluster_client_test.go
rcourtman 524f42cc28 security: complete Phase 1 sensor proxy hardening
Implements comprehensive security hardening for pulse-sensor-proxy:
- Privilege drop from root to unprivileged user (UID 995)
- Hash-chained tamper-evident audit logging with remote forwarding
- Per-UID rate limiting (0.2 QPS, burst 2) with concurrency caps
- Enhanced command validation with 10+ attack pattern tests
- Fuzz testing (7M+ executions, 0 crashes)
- SSH hardening, AppArmor/seccomp profiles, operational runbooks

All 27 Phase 1 tasks complete. Ready for production deployment.
2025-10-20 15:13:37 +00:00

65 lines
1.7 KiB
Go

package proxmox
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"
"time"
)
func TestClusterClientHandlesRateLimitWithoutMarkingUnhealthy(t *testing.T) {
var requestCount int32
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api2/json/nodes":
current := atomic.AddInt32(&requestCount, 1)
if current == 1 {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusTooManyRequests)
fmt.Fprint(w, `{"error":"rate limited"}`)
return
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, `{"data":[{"node":"node1","status":"online","cpu":0,"maxcpu":1,"mem":0,"maxmem":1,"disk":0,"maxdisk":1,"uptime":1,"level":"normal"}]}`)
default:
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, `{"data":{}}`)
}
}))
defer server.Close()
cfg := ClientConfig{
Host: server.URL,
TokenName: "pulse@pve!token",
TokenValue: "sometokenvalue",
VerifySSL: false,
Timeout: 2 * time.Second,
}
cc := NewClusterClient("test-cluster", cfg, []string{server.URL})
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
nodes, err := cc.GetNodes(ctx)
if err != nil {
t.Fatalf("expected GetNodes to succeed after retry, got error: %v", err)
}
if len(nodes) != 1 {
t.Fatalf("expected 1 node after retry, got %d", len(nodes))
}
health := cc.GetHealthStatus()
if healthy, ok := health[server.URL]; !ok || !healthy {
t.Fatalf("expected endpoint %s to remain healthy, got health map: %+v", server.URL, health)
}
if atomic.LoadInt32(&requestCount) < 2 {
t.Fatalf("expected at least 2 requests to backend, got %d", requestCount)
}
}