diff --git a/cmd/pulse-sensor-proxy/config.example.yaml b/cmd/pulse-sensor-proxy/config.example.yaml new file mode 100644 index 0000000..9bc3741 --- /dev/null +++ b/cmd/pulse-sensor-proxy/config.example.yaml @@ -0,0 +1,39 @@ +# Pulse Sensor Proxy Configuration +# This file is optional. If not provided, the proxy will use sensible defaults. + +# Network Configuration +# Specify which networks are allowed to connect to the proxy +# If not specified, the proxy will auto-detect host IP addresses +allowed_source_subnets: + - "127.0.0.1/32" # Localhost + - "192.168.0.0/24" # Local network + +# Peer Authorization +# Specify which UIDs/GIDs are allowed to connect +# Required when running Pulse in a container (use mapped UID/GID from container) +allowed_peer_uids: [100999] # Container pulse user UID +allowed_peer_gids: [100996] # Container pulse group GID + +# ID-Mapped Root Authentication +# Allow connections from ID-mapped root users (for LXC containers) +allow_idmapped_root: true +allowed_idmap_users: + - root + +# Metrics Server +# Address for Prometheus metrics endpoint +metrics_address: "127.0.0.1:9127" + +# Rate Limiting (Optional) +# Control how frequently peers can make requests to prevent abuse +# Adjust these values based on your deployment size: +# - Small (1-3 nodes): Use defaults (1000ms, burst 5) +# - Medium (4-10 nodes): 500ms, burst 10 +# - Large (10+ nodes): 250ms, burst 20 +rate_limit: + per_peer_interval_ms: 1000 # Minimum milliseconds between requests per peer (1000ms = 1 qps) + per_peer_burst: 5 # Number of requests allowed in a burst (supports up to 5 simultaneous requests) + +# Default values if not specified: +# per_peer_interval_ms: 1000 (1 second = 1 qps = 60 requests/min) +# per_peer_burst: 5 diff --git a/cmd/pulse-sensor-proxy/config.go b/cmd/pulse-sensor-proxy/config.go index 70f5205..aa779af 100644 --- a/cmd/pulse-sensor-proxy/config.go +++ b/cmd/pulse-sensor-proxy/config.go @@ -11,6 +11,12 @@ import ( "gopkg.in/yaml.v3" ) +// RateLimitConfig holds rate limiting configuration +type RateLimitConfig struct { + PerPeerIntervalMs int `yaml:"per_peer_interval_ms"` // Milliseconds between requests per peer + PerPeerBurst int `yaml:"per_peer_burst"` // Number of requests allowed in a burst +} + // Config holds proxy configuration type Config struct { AllowedSourceSubnets []string `yaml:"allowed_source_subnets"` @@ -20,6 +26,8 @@ type Config struct { AllowedPeerUIDs []uint32 `yaml:"allowed_peer_uids"` AllowedPeerGIDs []uint32 `yaml:"allowed_peer_gids"` AllowedIDMapUsers []string `yaml:"allowed_idmap_users"` + + RateLimit *RateLimitConfig `yaml:"rate_limit,omitempty"` } // loadConfig loads configuration from file and environment variables @@ -150,6 +158,14 @@ func loadConfig(configPath string) (*Config, error) { } } + // Log rate limit configuration if provided + if cfg.RateLimit != nil { + log.Info(). + Int("per_peer_interval_ms", cfg.RateLimit.PerPeerIntervalMs). + Int("per_peer_burst", cfg.RateLimit.PerPeerBurst). + Msg("Rate limit configuration loaded from config file") + } + return cfg, nil } diff --git a/cmd/pulse-sensor-proxy/main.go b/cmd/pulse-sensor-proxy/main.go index e8ce44f..34857ab 100644 --- a/cmd/pulse-sensor-proxy/main.go +++ b/cmd/pulse-sensor-proxy/main.go @@ -351,7 +351,7 @@ func runProxy() { socketPath: socketPath, sshKeyPath: sshKeyPath, knownHosts: knownHostsManager, - rateLimiter: newRateLimiter(metrics), + rateLimiter: newRateLimiter(metrics, cfg.RateLimit), nodeGate: newNodeGate(), config: cfg, metrics: metrics, diff --git a/cmd/pulse-sensor-proxy/throttle.go b/cmd/pulse-sensor-proxy/throttle.go index 59cc327..8b8b3bf 100644 --- a/cmd/pulse-sensor-proxy/throttle.go +++ b/cmd/pulse-sensor-proxy/throttle.go @@ -50,14 +50,30 @@ var ( ) // newRateLimiter creates a new rate limiter with cleanup loop -func newRateLimiter(metrics *ProxyMetrics) *rateLimiter { +// If rateLimitCfg is provided, it overrides the default rate limit settings +func newRateLimiter(metrics *ProxyMetrics, rateLimitCfg *RateLimitConfig) *rateLimiter { + // Use defaults + perPeerLimit := defaultPerPeerLimit + perPeerBurst := defaultPerPeerBurst + + // Override with config if provided + if rateLimitCfg != nil { + if rateLimitCfg.PerPeerIntervalMs > 0 { + interval := time.Duration(rateLimitCfg.PerPeerIntervalMs) * time.Millisecond + perPeerLimit = rate.Every(interval) + } + if rateLimitCfg.PerPeerBurst > 0 { + perPeerBurst = rateLimitCfg.PerPeerBurst + } + } + rl := &rateLimiter{ entries: make(map[peerID]*limiterEntry), quitChan: make(chan struct{}), globalSem: make(chan struct{}, defaultGlobalConcurrency), policy: limiterPolicy{ - perPeerLimit: defaultPerPeerLimit, - perPeerBurst: defaultPerPeerBurst, + perPeerLimit: perPeerLimit, + perPeerBurst: perPeerBurst, perPeerConcurrency: defaultPerPeerConcurrency, globalConcurrency: defaultGlobalConcurrency, penaltyDuration: defaultPenaltyDuration, diff --git a/cmd/pulse-sensor-proxy/throttle_test.go b/cmd/pulse-sensor-proxy/throttle_test.go index 6cffe2d..d4b6470 100644 --- a/cmd/pulse-sensor-proxy/throttle_test.go +++ b/cmd/pulse-sensor-proxy/throttle_test.go @@ -7,7 +7,7 @@ import ( func TestRateLimiterPenalizeMetrics(t *testing.T) { metrics := NewProxyMetrics("test") - rl := newRateLimiter(metrics) + rl := newRateLimiter(metrics, nil) rl.policy.penaltyDuration = 10 * time.Millisecond start := time.Now()