Add per-IP rate limiting and Unix socket support for gRPC admin API
Rate limiting: per-source-IP connection rate limiter in the firewall layer with configurable limit and sliding window. Blocklisted IPs are rejected before rate limit evaluation to avoid wasting quota. Unix socket: the gRPC admin API can now listen on a Unix domain socket (no TLS required), secured by file permissions (0600), as a simpler alternative for local-only access. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,9 +5,9 @@ import (
|
||||
"net/netip"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/oschwald/maxminddb-golang"
|
||||
|
||||
)
|
||||
|
||||
type geoIPRecord struct {
|
||||
@@ -23,17 +23,23 @@ type Firewall struct {
|
||||
blockedCountries map[string]struct{}
|
||||
geoDBPath string
|
||||
geoDB *maxminddb.Reader
|
||||
rl *rateLimiter
|
||||
mu sync.RWMutex // protects all mutable state
|
||||
}
|
||||
|
||||
// New creates a Firewall from raw rule lists and an optional GeoIP database path.
|
||||
func New(geoIPPath string, ips, cidrs, countries []string) (*Firewall, error) {
|
||||
// If rateLimit > 0, per-source-IP rate limiting is enabled with the given window.
|
||||
func New(geoIPPath string, ips, cidrs, countries []string, rateLimit int64, rateWindow time.Duration) (*Firewall, error) {
|
||||
f := &Firewall{
|
||||
blockedIPs: make(map[netip.Addr]struct{}),
|
||||
blockedCountries: make(map[string]struct{}),
|
||||
geoDBPath: geoIPPath,
|
||||
}
|
||||
|
||||
if rateLimit > 0 && rateWindow > 0 {
|
||||
f.rl = newRateLimiter(rateLimit, rateWindow)
|
||||
}
|
||||
|
||||
for _, ip := range ips {
|
||||
addr, err := netip.ParseAddr(ip)
|
||||
if err != nil {
|
||||
@@ -89,6 +95,12 @@ func (f *Firewall) Blocked(addr netip.Addr) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// Rate limiting is checked after blocklist — no point tracking state
|
||||
// for already-blocked IPs.
|
||||
if f.rl != nil && !f.rl.Allow(addr) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -190,6 +202,10 @@ func (f *Firewall) ReloadGeoIP() error {
|
||||
|
||||
// Close releases resources held by the firewall.
|
||||
func (f *Firewall) Close() error {
|
||||
if f.rl != nil {
|
||||
f.rl.Stop()
|
||||
}
|
||||
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user