Files
Pulse/cmd/pulse-sensor-proxy/throttle_test.go
rcourtman 885a62e96b feat(security): Implement range-based rate limiting
Prevents multi-UID rate limit bypass attacks from containers. Previously,
attackers could create multiple users in a container (each mapped to
unique host UIDs 100000-165535) to bypass per-UID rate limits.

Implementation:
- Automatic detection of ID-mapped UID ranges from /etc/subuid and /etc/subgid
- Rate limits applied per-range for container UIDs
- Rate limits applied per-UID for host UIDs (backwards compatible)
- identifyPeer() checks if BOTH UID AND GID are in mapped ranges
- Metrics show peer='range:100000-165535' or peer='uid:0'

Security benefit: Entire container limited as single entity, preventing
100+ UIDs from bypassing rate controls.

New metrics:
- pulse_proxy_limiter_rejections_total{peer,reason}
- pulse_proxy_limiter_penalties_total{peer,reason}
- pulse_proxy_global_concurrency_inflight

Related to security audit 2025-11-07.

Co-authored-by: Codex <codex@openai.com>
2025-11-07 17:08:45 +00:00

75 lines
1.9 KiB
Go

package main
import (
"testing"
"time"
)
func TestRateLimiterPenalizeMetrics(t *testing.T) {
metrics := NewProxyMetrics("test")
rl := newRateLimiter(metrics, nil, nil, nil)
rl.policy.penaltyDuration = 10 * time.Millisecond
start := time.Now()
rl.penalize("uid:42", "invalid_json")
if time.Since(start) < rl.policy.penaltyDuration {
t.Fatalf("expected penalize to sleep at least %v", rl.policy.penaltyDuration)
}
mf, err := metrics.registry.Gather()
if err != nil {
t.Fatalf("gather metrics: %v", err)
}
found := false
for _, fam := range mf {
if fam.GetName() != "pulse_proxy_limiter_penalties_total" {
continue
}
for _, metric := range fam.GetMetric() {
if metric.GetCounter().GetValue() == 0 {
continue
}
var reasonLabel, peerLabel string
for _, label := range metric.GetLabel() {
switch label.GetName() {
case "reason":
reasonLabel = label.GetValue()
case "peer":
peerLabel = label.GetValue()
}
}
if reasonLabel == "invalid_json" && peerLabel == "uid:42" {
found = true
}
}
}
if !found {
t.Fatalf("expected limiter penalty metric for invalid_json and peer uid:42")
}
}
func TestIdentifyPeerRangeVsUID(t *testing.T) {
uidRanges := []idRange{{start: 100000, length: 65536}}
gidRanges := []idRange{{start: 100000, length: 65536}}
rl := newRateLimiter(nil, nil, uidRanges, gidRanges)
containerCred := &peerCredentials{uid: 110000, gid: 110000}
containerPeer := rl.identifyPeer(containerCred)
if containerPeer.uidRange == nil {
t.Fatalf("expected container peer to map to UID range")
}
if got := containerPeer.String(); got != "range:100000-165535" {
t.Fatalf("unexpected container peer label: %s", got)
}
hostCred := &peerCredentials{uid: 1000, gid: 1000}
hostPeer := rl.identifyPeer(hostCred)
if hostPeer.uidRange != nil {
t.Fatalf("expected host peer to use UID label")
}
if got := hostPeer.String(); got != "uid:1000" {
t.Fatalf("unexpected host peer label: %s", got)
}
}