Compare commits

...

2 Commits

Author SHA1 Message Date
c73fa9d3d1 Add extension enumeration detection and comprehensive SIP protection
Major features:
- Extension enumeration detection with 3 detection algorithms:
  - Max unique extensions threshold (default: 20 in 5 min)
  - Sequential pattern detection (e.g., 100,101,102...)
  - Rapid-fire detection (many extensions in short window)
- Prometheus metrics for all SIP Guardian operations
- SQLite persistent storage for bans and attack history
- Webhook notifications for ban/unban/suspicious events
- GeoIP-based country blocking with continent shortcuts
- Per-method rate limiting with token bucket algorithm

Bug fixes:
- Fix whitelist count always reporting zero in stats
- Fix whitelisted connections metric never incrementing
- Fix Caddyfile config not being applied to shared guardian

New files:
- enumeration.go: Extension enumeration detector
- enumeration_test.go: 14 comprehensive unit tests
- metrics.go: Prometheus metrics handler
- storage.go: SQLite persistence layer
- webhooks.go: Webhook notification system
- geoip.go: MaxMind GeoIP integration
- ratelimit.go: Per-method rate limiting

Testing:
- sandbox/ contains complete Docker Compose test environment
- All 14 enumeration tests pass
2025-12-07 15:22:28 -07:00
0b0fb53c9c Add Caddyfile support for sip_guardian_admin HTTP handler
Register handler directive with httpcaddyfile and implement
UnmarshalCaddyfile to enable Caddyfile configuration syntax.
2025-12-07 10:37:16 -07:00
19 changed files with 4662 additions and 544 deletions

View File

@ -1,15 +1,20 @@
# Build custom Caddy with SIP Guardian, Layer 4, Rate Limiting, and Docker Proxy # Build custom Caddy with SIP Guardian and Layer 4 support
FROM caddy:2.8-builder AS builder # Use latest builder with Go 1.25+ for caddy-l4 compatibility
FROM caddy:builder AS builder
# Copy local module source
COPY . /src/caddy-sip-guardian
# Build Caddy with local module (using replace directive)
# Using latest caddy-l4 which requires Go 1.25+
WORKDIR /src
RUN xcaddy build \ RUN xcaddy build \
--with github.com/lucaslorentz/caddy-docker-proxy/v2 \
--with github.com/mholt/caddy-l4 \ --with github.com/mholt/caddy-l4 \
--with github.com/mholt/caddy-ratelimit \ --with git.supported.systems/rsp2k/caddy-sip-guardian=/src/caddy-sip-guardian
--with git.supported.systems/rsp2k/caddy-sip-guardian
FROM caddy:2.8-alpine FROM caddy:alpine
COPY --from=builder /usr/bin/caddy /usr/bin/caddy COPY --from=builder /src/caddy /usr/bin/caddy
# Health check # Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
@ -18,6 +23,5 @@ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
EXPOSE 80 443 443/udp 5060 5060/udp 5061 EXPOSE 80 443 443/udp 5060 5060/udp 5061
ENTRYPOINT ["caddy"] ENTRYPOINT ["caddy"]
# Default: docker-proxy mode (reads Docker labels) # Default: run with Caddyfile
# Override with explicit Caddyfile if needed CMD ["run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
CMD ["docker-proxy"]

180
Makefile
View File

@ -1,4 +1,9 @@
.PHONY: build run stop logs test clean dev .PHONY: build run stop logs test clean dev sandbox-up sandbox-down sandbox-logs \
test-bruteforce test-scanner test-valid test-whitelist bans stats
# ============================================
# Main Development Targets
# ============================================
# Build the custom Caddy image # Build the custom Caddy image
build: build:
@ -16,34 +21,169 @@ stop:
logs: logs:
docker compose logs -f docker compose logs -f
# Run with mock asterisk for testing # Development mode - rebuild and run
test: dev: build run logs
docker compose --profile testing up -d
@echo "Testing SIP Guardian..."
@sleep 3
@curl -s http://localhost:2019/config/ | jq .
@echo "\nSending test SIP packet..."
@echo -e "OPTIONS sip:test@localhost SIP/2.0\r\nVia: SIP/2.0/UDP 127.0.0.1:5060\r\n\r\n" | nc -u -w1 localhost 5060
@echo "Check logs for SIP Guardian activity"
# Clean up # Clean up
clean: clean:
docker compose down -v docker compose down -v
docker rmi caddy-sip-guardian-caddy 2>/dev/null || true docker rmi caddy-sip-guardian-caddy 2>/dev/null || true
# Development mode - rebuild and run # ============================================
dev: build run logs # Sandbox Testing Environment
# ============================================
# Start the full testing sandbox (FreePBX + Caddy + test tools)
sandbox-up:
@echo "Starting SIP Guardian testing sandbox..."
cd sandbox && docker compose up -d
@echo ""
@echo "Sandbox is starting. FreePBX takes a few minutes to initialize."
@echo "Services:"
@echo " - Caddy (SIP Guardian): localhost:5060 (UDP/TCP), localhost:5061 (TLS)"
@echo " - Admin API: http://localhost:2020/api/sip-guardian/"
@echo " - FreePBX Web: http://localhost:80 (once ready)"
@echo ""
@echo "Run 'make sandbox-logs' to monitor startup"
# Stop sandbox
sandbox-down:
cd sandbox && docker compose down
# Stop sandbox and remove volumes
sandbox-clean:
cd sandbox && docker compose down -v
# View sandbox logs
sandbox-logs:
cd sandbox && docker compose logs -f
# View only Caddy logs
caddy-logs:
cd sandbox && docker compose logs -f caddy
# Start testing containers
sandbox-test-containers:
cd sandbox && docker compose --profile testing up -d
# ============================================
# Attack Simulation Tests
# ============================================
# Test brute force attack (should trigger ban)
test-bruteforce:
@echo "Starting brute force simulation..."
cd sandbox && docker compose --profile testing up -d bruteforcer
cd sandbox && docker compose exec bruteforcer python /scripts/bruteforce.py caddy -e 100-105 -c 5 -d 0.2
@echo ""
@echo "Check ban list:"
@curl -s http://localhost:2020/api/sip-guardian/bans | jq .
# Test scanner detection (sipvicious patterns)
test-scanner:
@echo "Starting scanner simulation..."
cd sandbox && docker compose --profile testing up -d attacker
cd sandbox && docker compose exec attacker bash -c "pip install -q sipvicious && sipvicious_svwar -e100-110 caddy"
@echo ""
@echo "Check ban list:"
@curl -s http://localhost:2020/api/sip-guardian/bans | jq .
# Test valid registration (should NOT be blocked)
test-valid:
@echo "Testing valid registration..."
cd sandbox && docker compose --profile testing up -d client
cd sandbox && docker compose exec client python3 /scripts/valid_register.py caddy -e 100 -s password123 -r 3
@echo ""
@echo "Stats (should show no bans for legitimate client):"
@curl -s http://localhost:2020/api/sip-guardian/stats | jq .
# Test whitelist functionality
test-whitelist:
@echo "Testing whitelist bypass..."
@echo "Whitelisted client (172.28.0.50) sending many requests:"
cd sandbox && docker compose --profile testing up -d client
cd sandbox && docker compose exec client sh -c 'for i in $$(seq 1 20); do echo -e "REGISTER sip:caddy SIP/2.0\r\n\r\n" | nc -u -w1 caddy 5060; done'
@echo ""
@echo "Ban list (should NOT contain 172.28.0.50):"
@curl -s http://localhost:2020/api/sip-guardian/bans | jq .
# Send raw SIP OPTIONS (quick test)
test-sip-options:
@echo "Sending SIP OPTIONS request..."
@echo -e "OPTIONS sip:test@localhost SIP/2.0\r\nVia: SIP/2.0/UDP 127.0.0.1:5060;branch=z9hG4bK-test\r\nFrom: <sip:test@localhost>;tag=123\r\nTo: <sip:test@localhost>\r\nCall-ID: test-call@localhost\r\nCSeq: 1 OPTIONS\r\nMax-Forwards: 70\r\nContent-Length: 0\r\n\r\n" | nc -u -w2 localhost 5060
# ============================================
# Admin API Operations
# ============================================
# Check ban list via admin API # Check ban list via admin API
bans: bans:
@curl -s http://localhost:2019/load | jq . @curl -s http://localhost:2020/api/sip-guardian/bans | jq .
# Add test ban
test-ban:
@curl -X POST http://localhost:2019/api/sip-guardian/ban/192.168.1.100 \
-H "Content-Type: application/json" \
-d '{"reason": "test_ban"}' | jq .
# View stats # View stats
stats: stats:
@curl -s http://localhost:2019/api/sip-guardian/stats | jq . @curl -s http://localhost:2020/api/sip-guardian/stats | jq .
# Add test ban
test-ban:
@curl -X POST http://localhost:2020/api/sip-guardian/ban/192.168.1.100 \
-H "Content-Type: application/json" \
-d '{"reason": "test_ban"}' | jq .
# Remove test ban
test-unban:
@curl -X POST http://localhost:2020/api/sip-guardian/unban/192.168.1.100 | jq .
# Health check
health:
@curl -s http://localhost:2020/health
# ============================================
# Debugging
# ============================================
# Start tcpdump container to capture SIP traffic
tcpdump:
cd sandbox && docker compose --profile debug up -d tcpdump
cd sandbox && docker compose logs -f tcpdump
# Shell into Caddy container
caddy-shell:
cd sandbox && docker compose exec caddy sh
# Shell into FreePBX container
freepbx-shell:
cd sandbox && docker compose exec freepbx bash
# View Caddy config
caddy-config:
@curl -s http://localhost:2019/config/ | jq .
# ============================================
# Help
# ============================================
help:
@echo "Caddy SIP Guardian - Development Makefile"
@echo ""
@echo "Main targets:"
@echo " build - Build Docker image"
@echo " dev - Build, run, and tail logs"
@echo " clean - Stop and remove volumes"
@echo ""
@echo "Sandbox targets:"
@echo " sandbox-up - Start full testing sandbox (FreePBX + Caddy)"
@echo " sandbox-down - Stop sandbox"
@echo " sandbox-logs - View sandbox logs"
@echo ""
@echo "Test targets:"
@echo " test-bruteforce - Simulate brute force attack (should ban)"
@echo " test-scanner - Simulate sipvicious scanner (should ban)"
@echo " test-valid - Test legitimate registration (should pass)"
@echo " test-whitelist - Test whitelist bypass"
@echo ""
@echo "Admin targets:"
@echo " bans - List banned IPs"
@echo " stats - View statistics"
@echo " test-ban - Add test ban"
@echo " test-unban - Remove test ban"

View File

@ -6,11 +6,23 @@ import (
"strings" "strings"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp" "github.com/caddyserver/caddy/v2/modules/caddyhttp"
) )
func init() { func init() {
caddy.RegisterModule(AdminHandler{}) caddy.RegisterModule(AdminHandler{})
httpcaddyfile.RegisterHandlerDirective("sip_guardian_admin", parseSIPGuardianAdmin)
// Register handler ordering so it can be used directly in handle blocks
httpcaddyfile.RegisterDirectiveOrder("sip_guardian_admin", httpcaddyfile.Before, "respond")
}
// parseSIPGuardianAdmin parses the sip_guardian_admin directive
func parseSIPGuardianAdmin(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
var handler AdminHandler
err := handler.UnmarshalCaddyfile(h.Dispenser)
return &handler, err
} }
// AdminHandler provides HTTP endpoints to manage SIP Guardian // AdminHandler provides HTTP endpoints to manage SIP Guardian
@ -26,10 +38,13 @@ func (AdminHandler) CaddyModule() caddy.ModuleInfo {
} }
func (h *AdminHandler) Provision(ctx caddy.Context) error { func (h *AdminHandler) Provision(ctx caddy.Context) error {
// Get the shared guardian instance // Get the shared guardian instance from the global registry
// In production, this would use proper module loading guardian, err := GetOrCreateGuardian(ctx, "default")
h.guardian = &SIPGuardian{} if err != nil {
return h.guardian.Provision(ctx) return err
}
h.guardian = guardian
return nil
} }
// ServeHTTP handles admin API requests // ServeHTTP handles admin API requests
@ -160,8 +175,30 @@ func (h *AdminHandler) handleBan(w http.ResponseWriter, r *http.Request, path st
}) })
} }
// UnmarshalCaddyfile implements caddyfile.Unmarshaler for AdminHandler.
// Usage in Caddyfile:
//
// handle /api/sip-guardian/* {
// sip_guardian_admin
// }
//
// Or simply: sip_guardian_admin
func (h *AdminHandler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// Move past "sip_guardian_admin" token
d.Next()
// This handler doesn't have any configuration options currently
// but we need to consume any block if present
for nesting := d.Nesting(); d.NextBlock(nesting); {
return d.Errf("unknown sip_guardian_admin directive: %s", d.Val())
}
return nil
}
// Interface guards // Interface guards
var ( var (
_ caddyhttp.MiddlewareHandler = (*AdminHandler)(nil) _ caddyhttp.MiddlewareHandler = (*AdminHandler)(nil)
_ caddy.Provisioner = (*AdminHandler)(nil) _ caddy.Provisioner = (*AdminHandler)(nil)
_ caddyfile.Unmarshaler = (*AdminHandler)(nil)
) )

365
enumeration.go Normal file
View File

@ -0,0 +1,365 @@
package sipguardian
import (
"sort"
"strconv"
"sync"
"time"
"go.uber.org/zap"
)
// EnumerationConfig holds configuration for extension enumeration detection
type EnumerationConfig struct {
// MaxExtensions is the maximum unique extensions an IP can probe before ban
MaxExtensions int `json:"max_extensions,omitempty"`
// ExtensionWindow is the time window for counting unique extensions
ExtensionWindow time.Duration `json:"extension_window,omitempty"`
// SequentialThreshold triggers on N+ consecutive extensions (100,101,102...)
SequentialThreshold int `json:"sequential_threshold,omitempty"`
// RapidFireCount triggers on N extensions in RapidFireWindow
RapidFireCount int `json:"rapid_fire_count,omitempty"`
// RapidFireWindow is the time window for rapid-fire detection
RapidFireWindow time.Duration `json:"rapid_fire_window,omitempty"`
// EnumBanTime is ban duration for enumeration attacks (longer than normal)
EnumBanTime time.Duration `json:"enum_ban_time,omitempty"`
// ExemptExtensions are extensions that don't count toward enumeration
ExemptExtensions []string `json:"exempt_extensions,omitempty"`
}
// DefaultEnumerationConfig returns sensible defaults
func DefaultEnumerationConfig() EnumerationConfig {
return EnumerationConfig{
MaxExtensions: 20,
ExtensionWindow: 5 * time.Minute,
SequentialThreshold: 5,
RapidFireCount: 10,
RapidFireWindow: 30 * time.Second,
EnumBanTime: 2 * time.Hour,
ExemptExtensions: []string{},
}
}
// EnumerationDetector detects extension scanning attacks
type EnumerationDetector struct {
config EnumerationConfig
tracker map[string]*ExtensionAttempts
exemptSet map[string]bool
logger *zap.Logger
mu sync.RWMutex
}
// ExtensionAttempts tracks extension attempts for a single IP
type ExtensionAttempts struct {
extensions map[string]time.Time // extension -> last attempt time
ordered []extAttempt // time-ordered for pattern detection
firstSeen time.Time
lastSeen time.Time
flaggedSeq bool // already detected sequential pattern
mu sync.Mutex
}
type extAttempt struct {
extension string
timestamp time.Time
numeric int // -1 if non-numeric
}
// EnumerationResult contains detection results
type EnumerationResult struct {
Detected bool
Reason string
UniqueCount int
SeqStart int // start of sequential range (if detected)
SeqEnd int // end of sequential range (if detected)
Extensions []string
}
// Global detector instance
var (
globalEnumDetector *EnumerationDetector
enumDetectorMu sync.Mutex
)
// GetEnumerationDetector returns the global enumeration detector
func GetEnumerationDetector(logger *zap.Logger) *EnumerationDetector {
enumDetectorMu.Lock()
defer enumDetectorMu.Unlock()
if globalEnumDetector == nil {
globalEnumDetector = NewEnumerationDetector(logger, DefaultEnumerationConfig())
}
return globalEnumDetector
}
// SetEnumerationConfig updates the global detector configuration
func SetEnumerationConfig(config EnumerationConfig) {
enumDetectorMu.Lock()
defer enumDetectorMu.Unlock()
if globalEnumDetector != nil {
globalEnumDetector.config = config
// Rebuild exempt set
globalEnumDetector.exemptSet = make(map[string]bool)
for _, ext := range config.ExemptExtensions {
globalEnumDetector.exemptSet[ext] = true
}
}
}
// NewEnumerationDetector creates a new detector
func NewEnumerationDetector(logger *zap.Logger, config EnumerationConfig) *EnumerationDetector {
exemptSet := make(map[string]bool)
for _, ext := range config.ExemptExtensions {
exemptSet[ext] = true
}
return &EnumerationDetector{
config: config,
tracker: make(map[string]*ExtensionAttempts),
exemptSet: exemptSet,
logger: logger,
}
}
// RecordAttempt records an extension attempt and returns detection result
func (ed *EnumerationDetector) RecordAttempt(ip, extension string) EnumerationResult {
// Skip exempt extensions
if ed.exemptSet[extension] {
return EnumerationResult{Detected: false}
}
ed.mu.Lock()
attempts, exists := ed.tracker[ip]
if !exists {
attempts = &ExtensionAttempts{
extensions: make(map[string]time.Time),
ordered: make([]extAttempt, 0),
firstSeen: time.Now(),
}
ed.tracker[ip] = attempts
}
ed.mu.Unlock()
return attempts.record(extension, ed.config)
}
// record adds an attempt and checks for enumeration patterns
func (ea *ExtensionAttempts) record(extension string, config EnumerationConfig) EnumerationResult {
ea.mu.Lock()
defer ea.mu.Unlock()
now := time.Now()
ea.lastSeen = now
// Clean old entries
ea.cleanup(config.ExtensionWindow)
// Parse as numeric if possible
numeric := -1
if n, err := strconv.Atoi(extension); err == nil {
numeric = n
}
// Record the attempt (update timestamp if already seen)
if _, exists := ea.extensions[extension]; !exists {
ea.extensions[extension] = now
ea.ordered = append(ea.ordered, extAttempt{
extension: extension,
timestamp: now,
numeric: numeric,
})
} else {
ea.extensions[extension] = now
}
// Check detection rules
result := EnumerationResult{
UniqueCount: len(ea.extensions),
Extensions: ea.getExtensionList(),
}
// Rule 1: Too many unique extensions
if len(ea.extensions) >= config.MaxExtensions {
result.Detected = true
result.Reason = "extension_count_exceeded"
return result
}
// Rule 2: Sequential pattern detection
if !ea.flaggedSeq && len(ea.extensions) >= config.SequentialThreshold {
if detected, start, end := ea.detectSequentialPattern(config.SequentialThreshold); detected {
ea.flaggedSeq = true
result.Detected = true
result.Reason = "sequential_enumeration"
result.SeqStart = start
result.SeqEnd = end
return result
}
}
// Rule 3: Rapid-fire detection
if detected := ea.detectRapidFire(config.RapidFireCount, config.RapidFireWindow); detected {
result.Detected = true
result.Reason = "rapid_fire_enumeration"
return result
}
return result
}
// detectSequentialPattern finds consecutive numeric sequences
func (ea *ExtensionAttempts) detectSequentialPattern(threshold int) (bool, int, int) {
var nums []int
for ext := range ea.extensions {
if n, err := strconv.Atoi(ext); err == nil {
nums = append(nums, n)
}
}
if len(nums) < threshold {
return false, 0, 0
}
sort.Ints(nums)
// Find longest consecutive sequence
maxRun, runStart := 1, nums[0]
currentRun, currentStart := 1, nums[0]
for i := 1; i < len(nums); i++ {
if nums[i] == nums[i-1]+1 {
currentRun++
if currentRun > maxRun {
maxRun = currentRun
runStart = currentStart
}
} else {
currentRun = 1
currentStart = nums[i]
}
}
return maxRun >= threshold, runStart, runStart + maxRun - 1
}
// detectRapidFire checks for many extensions in a short time
func (ea *ExtensionAttempts) detectRapidFire(count int, window time.Duration) bool {
cutoff := time.Now().Add(-window)
seen := make(map[string]bool)
for _, attempt := range ea.ordered {
if attempt.timestamp.After(cutoff) && !seen[attempt.extension] {
seen[attempt.extension] = true
}
}
return len(seen) >= count
}
// cleanup removes old entries outside the window
func (ea *ExtensionAttempts) cleanup(window time.Duration) {
cutoff := time.Now().Add(-window)
// Clean extensions map
for ext, ts := range ea.extensions {
if ts.Before(cutoff) {
delete(ea.extensions, ext)
}
}
// Clean ordered slice
newOrdered := make([]extAttempt, 0, len(ea.ordered))
for _, a := range ea.ordered {
if a.timestamp.After(cutoff) {
newOrdered = append(newOrdered, a)
}
}
ea.ordered = newOrdered
// Reset sequential flag if we've cleaned enough entries
if len(ea.extensions) < 3 {
ea.flaggedSeq = false
}
}
func (ea *ExtensionAttempts) getExtensionList() []string {
exts := make([]string, 0, len(ea.extensions))
for ext := range ea.extensions {
exts = append(exts, ext)
}
return exts
}
// Cleanup removes stale IP entries from the detector
func (ed *EnumerationDetector) Cleanup() {
ed.mu.Lock()
defer ed.mu.Unlock()
cutoff := time.Now().Add(-ed.config.ExtensionWindow * 2)
for ip, attempts := range ed.tracker {
attempts.mu.Lock()
if attempts.lastSeen.Before(cutoff) {
delete(ed.tracker, ip)
}
attempts.mu.Unlock()
}
}
// GetStats returns detector statistics
func (ed *EnumerationDetector) GetStats() map[string]interface{} {
ed.mu.RLock()
defer ed.mu.RUnlock()
// Count total tracked extensions across all IPs
totalExtensions := 0
for _, attempts := range ed.tracker {
attempts.mu.Lock()
totalExtensions += len(attempts.extensions)
attempts.mu.Unlock()
}
return map[string]interface{}{
"tracked_ips": len(ed.tracker),
"total_extensions": totalExtensions,
"max_extensions": ed.config.MaxExtensions,
"sequential_threshold": ed.config.SequentialThreshold,
"rapid_fire_count": ed.config.RapidFireCount,
"extension_window": ed.config.ExtensionWindow.String(),
"rapid_fire_window": ed.config.RapidFireWindow.String(),
}
}
// GetIPAttempts returns the extension attempts for a specific IP (for debugging/admin)
func (ed *EnumerationDetector) GetIPAttempts(ip string) *EnumerationResult {
ed.mu.RLock()
attempts, exists := ed.tracker[ip]
ed.mu.RUnlock()
if !exists {
return nil
}
attempts.mu.Lock()
defer attempts.mu.Unlock()
return &EnumerationResult{
Detected: false,
UniqueCount: len(attempts.extensions),
Extensions: attempts.getExtensionList(),
}
}
// ResetIP clears tracking for a specific IP (for admin use)
func (ed *EnumerationDetector) ResetIP(ip string) {
ed.mu.Lock()
defer ed.mu.Unlock()
delete(ed.tracker, ip)
}

432
enumeration_test.go Normal file
View File

@ -0,0 +1,432 @@
package sipguardian
import (
"strconv"
"testing"
"time"
"go.uber.org/zap"
)
func newTestDetector(config EnumerationConfig) *EnumerationDetector {
logger := zap.NewNop()
return NewEnumerationDetector(logger, config)
}
func TestDefaultConfig(t *testing.T) {
config := DefaultEnumerationConfig()
if config.MaxExtensions != 20 {
t.Errorf("Expected MaxExtensions=20, got %d", config.MaxExtensions)
}
if config.ExtensionWindow != 5*time.Minute {
t.Errorf("Expected ExtensionWindow=5m, got %v", config.ExtensionWindow)
}
if config.SequentialThreshold != 5 {
t.Errorf("Expected SequentialThreshold=5, got %d", config.SequentialThreshold)
}
if config.RapidFireCount != 10 {
t.Errorf("Expected RapidFireCount=10, got %d", config.RapidFireCount)
}
if config.RapidFireWindow != 30*time.Second {
t.Errorf("Expected RapidFireWindow=30s, got %v", config.RapidFireWindow)
}
}
func TestMaxExtensionsDetection(t *testing.T) {
config := EnumerationConfig{
MaxExtensions: 5,
ExtensionWindow: 5 * time.Minute,
SequentialThreshold: 100, // Disable sequential detection
RapidFireCount: 100, // Disable rapid-fire detection
RapidFireWindow: 30 * time.Second,
}
detector := newTestDetector(config)
ip := "192.168.1.100"
// Record 4 different extensions - should not trigger
for i := 0; i < 4; i++ {
ext := strconv.Itoa(1000 + i*100) // Non-sequential: 1000, 1100, 1200, 1300
result := detector.RecordAttempt(ip, ext)
if result.Detected {
t.Errorf("Should not detect on extension %d (count=%d)", i+1, result.UniqueCount)
}
}
// 5th unique extension should trigger
result := detector.RecordAttempt(ip, "2000")
if !result.Detected {
t.Error("Should detect when max_extensions reached")
}
if result.Reason != "extension_count_exceeded" {
t.Errorf("Expected reason 'extension_count_exceeded', got '%s'", result.Reason)
}
if result.UniqueCount != 5 {
t.Errorf("Expected unique_count=5, got %d", result.UniqueCount)
}
}
func TestSequentialPatternDetection(t *testing.T) {
config := EnumerationConfig{
MaxExtensions: 100, // High to avoid triggering count-based
ExtensionWindow: 5 * time.Minute,
SequentialThreshold: 5,
RapidFireCount: 100, // Disable rapid-fire
RapidFireWindow: 30 * time.Second,
}
detector := newTestDetector(config)
ip := "192.168.1.101"
// Record sequential extensions: 100, 101, 102, 103
for i := 100; i <= 103; i++ {
result := detector.RecordAttempt(ip, strconv.Itoa(i))
if result.Detected {
t.Errorf("Should not detect on extension %d", i)
}
}
// 5th sequential should trigger
result := detector.RecordAttempt(ip, "104")
if !result.Detected {
t.Error("Should detect sequential pattern at 5 consecutive")
}
if result.Reason != "sequential_enumeration" {
t.Errorf("Expected reason 'sequential_enumeration', got '%s'", result.Reason)
}
if result.SeqStart != 100 || result.SeqEnd != 104 {
t.Errorf("Expected SeqStart=100, SeqEnd=104, got %d-%d", result.SeqStart, result.SeqEnd)
}
}
func TestSequentialPatternGaps(t *testing.T) {
config := EnumerationConfig{
MaxExtensions: 100,
ExtensionWindow: 5 * time.Minute,
SequentialThreshold: 5,
RapidFireCount: 100,
RapidFireWindow: 30 * time.Second,
}
detector := newTestDetector(config)
ip := "192.168.1.102"
// Non-sequential extensions with gaps
extensions := []string{"100", "102", "104", "106", "108"}
for _, ext := range extensions {
result := detector.RecordAttempt(ip, ext)
if result.Detected && result.Reason == "sequential_enumeration" {
t.Errorf("Should not detect sequential pattern for non-consecutive: %s", ext)
}
}
}
func TestRapidFireDetection(t *testing.T) {
config := EnumerationConfig{
MaxExtensions: 100, // High to avoid triggering count-based
ExtensionWindow: 5 * time.Minute,
SequentialThreshold: 100, // Disable sequential
RapidFireCount: 5,
RapidFireWindow: 1 * time.Second, // Short window for testing
}
detector := newTestDetector(config)
ip := "192.168.1.103"
// Record 5 different extensions rapidly (within the window)
for i := 0; i < 4; i++ {
ext := strconv.Itoa(1000 + i*100) // Non-sequential
result := detector.RecordAttempt(ip, ext)
if result.Detected && result.Reason == "rapid_fire_enumeration" {
t.Errorf("Should not detect rapid-fire on attempt %d", i+1)
}
}
// 5th should trigger rapid-fire
result := detector.RecordAttempt(ip, "5000")
if !result.Detected {
t.Error("Should detect rapid-fire pattern")
}
if result.Reason != "rapid_fire_enumeration" {
t.Errorf("Expected reason 'rapid_fire_enumeration', got '%s'", result.Reason)
}
}
func TestExemptExtensions(t *testing.T) {
config := EnumerationConfig{
MaxExtensions: 3,
ExtensionWindow: 5 * time.Minute,
SequentialThreshold: 3,
RapidFireCount: 100,
RapidFireWindow: 30 * time.Second,
ExemptExtensions: []string{"100", "200", "emergency"},
}
detector := newTestDetector(config)
ip := "192.168.1.104"
// Exempt extensions should not count
exemptExts := []string{"100", "200", "emergency"}
for _, ext := range exemptExts {
result := detector.RecordAttempt(ip, ext)
if result.Detected {
t.Errorf("Exempt extension '%s' should not trigger detection", ext)
}
}
// Non-exempt extensions should still count
result := detector.RecordAttempt(ip, "1001")
if result.Detected {
t.Error("First non-exempt should not trigger")
}
result = detector.RecordAttempt(ip, "1002")
if result.Detected {
t.Error("Second non-exempt should not trigger")
}
result = detector.RecordAttempt(ip, "1003")
if !result.Detected {
t.Error("Third non-exempt should trigger (max_extensions=3)")
}
}
func TestDuplicateExtensions(t *testing.T) {
config := EnumerationConfig{
MaxExtensions: 3,
ExtensionWindow: 5 * time.Minute,
SequentialThreshold: 100,
RapidFireCount: 100,
RapidFireWindow: 30 * time.Second,
}
detector := newTestDetector(config)
ip := "192.168.1.105"
// Record same extension multiple times - should only count as 1
for i := 0; i < 10; i++ {
result := detector.RecordAttempt(ip, "1000")
if result.Detected {
t.Error("Duplicate extensions should not trigger detection")
}
if result.UniqueCount != 1 {
t.Errorf("Expected unique_count=1 for duplicates, got %d", result.UniqueCount)
}
}
}
func TestMultipleIPsIsolation(t *testing.T) {
config := EnumerationConfig{
MaxExtensions: 3,
ExtensionWindow: 5 * time.Minute,
SequentialThreshold: 100,
RapidFireCount: 100,
RapidFireWindow: 30 * time.Second,
}
detector := newTestDetector(config)
ip1 := "192.168.1.106"
ip2 := "192.168.1.107"
// Record extensions for IP1
for i := 0; i < 2; i++ {
detector.RecordAttempt(ip1, strconv.Itoa(1000+i))
}
// Record extensions for IP2 - should start fresh
result := detector.RecordAttempt(ip2, "2000")
if result.UniqueCount != 1 {
t.Errorf("IP2 should have independent count, expected 1, got %d", result.UniqueCount)
}
// IP1's 3rd should trigger
result = detector.RecordAttempt(ip1, "1002")
if !result.Detected {
t.Error("IP1 should trigger on 3rd unique extension")
}
// IP2 should still be fine
result = detector.RecordAttempt(ip2, "2001")
if result.Detected {
t.Error("IP2 should not trigger on 2nd unique extension")
}
}
func TestGetStats(t *testing.T) {
config := DefaultEnumerationConfig()
detector := newTestDetector(config)
// Initial stats
stats := detector.GetStats()
if stats["tracked_ips"].(int) != 0 {
t.Errorf("Expected tracked_ips=0 initially, got %d", stats["tracked_ips"])
}
// Record some attempts
detector.RecordAttempt("192.168.1.1", "1000")
detector.RecordAttempt("192.168.1.2", "2000")
stats = detector.GetStats()
if stats["tracked_ips"].(int) != 2 {
t.Errorf("Expected tracked_ips=2, got %d", stats["tracked_ips"])
}
if stats["total_extensions"].(int) != 2 {
t.Errorf("Expected total_extensions=2, got %d", stats["total_extensions"])
}
}
func TestGetIPAttempts(t *testing.T) {
config := DefaultEnumerationConfig()
detector := newTestDetector(config)
ip := "192.168.1.108"
// No attempts yet
result := detector.GetIPAttempts(ip)
if result != nil {
t.Error("Expected nil for non-tracked IP")
}
// Record some attempts
detector.RecordAttempt(ip, "1000")
detector.RecordAttempt(ip, "1001")
detector.RecordAttempt(ip, "1002")
result = detector.GetIPAttempts(ip)
if result == nil {
t.Fatal("Expected result for tracked IP")
}
if result.UniqueCount != 3 {
t.Errorf("Expected unique_count=3, got %d", result.UniqueCount)
}
if len(result.Extensions) != 3 {
t.Errorf("Expected 3 extensions, got %d", len(result.Extensions))
}
}
func TestResetIP(t *testing.T) {
config := EnumerationConfig{
MaxExtensions: 3,
ExtensionWindow: 5 * time.Minute,
SequentialThreshold: 100,
RapidFireCount: 100,
RapidFireWindow: 30 * time.Second,
}
detector := newTestDetector(config)
ip := "192.168.1.109"
// Record 2 extensions
detector.RecordAttempt(ip, "1000")
detector.RecordAttempt(ip, "1001")
// Reset the IP
detector.ResetIP(ip)
// Should start fresh - no detection yet
result := detector.RecordAttempt(ip, "2000")
if result.UniqueCount != 1 {
t.Errorf("After reset, expected unique_count=1, got %d", result.UniqueCount)
}
}
func TestNonNumericExtensions(t *testing.T) {
config := EnumerationConfig{
MaxExtensions: 100,
ExtensionWindow: 5 * time.Minute,
SequentialThreshold: 3,
RapidFireCount: 100,
RapidFireWindow: 30 * time.Second,
}
detector := newTestDetector(config)
ip := "192.168.1.110"
// Non-numeric extensions should not trigger sequential detection
nonNumeric := []string{"sales", "support", "main", "fax", "reception"}
for _, ext := range nonNumeric {
result := detector.RecordAttempt(ip, ext)
if result.Detected && result.Reason == "sequential_enumeration" {
t.Errorf("Non-numeric '%s' should not trigger sequential detection", ext)
}
}
}
func TestMixedNumericNonNumeric(t *testing.T) {
config := EnumerationConfig{
MaxExtensions: 100,
ExtensionWindow: 5 * time.Minute,
SequentialThreshold: 5,
RapidFireCount: 100,
RapidFireWindow: 30 * time.Second,
}
detector := newTestDetector(config)
ip := "192.168.1.111"
// Mix of numeric sequential with non-numeric interruptions
// Still should detect sequence in numeric ones
extensions := []string{"100", "main", "101", "support", "102", "sales", "103", "104"}
var detectedSeq bool
for _, ext := range extensions {
result := detector.RecordAttempt(ip, ext)
if result.Detected && result.Reason == "sequential_enumeration" {
detectedSeq = true
}
}
if !detectedSeq {
t.Error("Should detect 5 sequential numeric extensions even with non-numeric mixed in")
}
}
func TestExtractTargetExtension(t *testing.T) {
testCases := []struct {
name string
data string
expected string
}{
{
name: "REGISTER with extension",
data: "REGISTER sip:1001@example.com SIP/2.0\r\nVia: SIP/2.0/UDP 192.168.1.1\r\n",
expected: "1001",
},
{
name: "INVITE with extension",
data: "INVITE sip:2000@pbx.local SIP/2.0\r\nFrom: <sip:caller@example.com>\r\n",
expected: "2000",
},
{
name: "OPTIONS with extension",
data: "OPTIONS sip:100@domain.com SIP/2.0\r\n",
expected: "100",
},
{
name: "Extension too long (should skip)",
data: "REGISTER sip:verylongextensionname@example.com SIP/2.0\r\n",
expected: "",
},
{
name: "Domain-like user (should skip)",
data: "REGISTER sip:example.com@example.com SIP/2.0\r\n",
expected: "",
},
{
name: "BYE method (not tracked)",
data: "BYE sip:1001@example.com SIP/2.0\r\n",
expected: "",
},
{
name: "Fallback to To header",
data: "ACK sip:anything@example.com SIP/2.0\r\nTo: <sip:500@example.com>\r\n",
expected: "500",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := ExtractTargetExtension([]byte(tc.data))
if result != tc.expected {
t.Errorf("Expected '%s', got '%s'", tc.expected, result)
}
})
}
}

196
geoip.go Normal file
View File

@ -0,0 +1,196 @@
package sipguardian
import (
"fmt"
"net"
"sync"
"github.com/oschwald/maxminddb-golang"
)
// GeoIPLookup provides IP to country lookup using MaxMind databases
type GeoIPLookup struct {
db *maxminddb.Reader
mu sync.RWMutex
}
// GeoIPRecord represents the data we extract from MaxMind database
type GeoIPRecord struct {
Country struct {
ISOCode string `maxminddb:"iso_code"`
Names map[string]string `maxminddb:"names"`
} `maxminddb:"country"`
City struct {
Names map[string]string `maxminddb:"names"`
} `maxminddb:"city"`
Continent struct {
Code string `maxminddb:"code"`
Names map[string]string `maxminddb:"names"`
} `maxminddb:"continent"`
}
// NewGeoIPLookup creates a new GeoIP lookup from a MaxMind database file
// Supports both GeoLite2-Country and GeoLite2-City databases
func NewGeoIPLookup(path string) (*GeoIPLookup, error) {
db, err := maxminddb.Open(path)
if err != nil {
return nil, fmt.Errorf("failed to open GeoIP database: %w", err)
}
return &GeoIPLookup{
db: db,
}, nil
}
// LookupCountry returns the ISO country code for an IP address
func (g *GeoIPLookup) LookupCountry(ipStr string) (string, error) {
g.mu.RLock()
defer g.mu.RUnlock()
ip := net.ParseIP(ipStr)
if ip == nil {
return "", fmt.Errorf("invalid IP address: %s", ipStr)
}
var record GeoIPRecord
err := g.db.Lookup(ip, &record)
if err != nil {
return "", fmt.Errorf("lookup failed: %w", err)
}
return record.Country.ISOCode, nil
}
// LookupFull returns full GeoIP information for an IP address
func (g *GeoIPLookup) LookupFull(ipStr string) (*GeoIPRecord, error) {
g.mu.RLock()
defer g.mu.RUnlock()
ip := net.ParseIP(ipStr)
if ip == nil {
return nil, fmt.Errorf("invalid IP address: %s", ipStr)
}
var record GeoIPRecord
err := g.db.Lookup(ip, &record)
if err != nil {
return nil, fmt.Errorf("lookup failed: %w", err)
}
return &record, nil
}
// Close closes the database
func (g *GeoIPLookup) Close() error {
g.mu.Lock()
defer g.mu.Unlock()
return g.db.Close()
}
// CountryName returns the English name for a country code
func (g *GeoIPLookup) CountryName(ipStr string) (string, error) {
record, err := g.LookupFull(ipStr)
if err != nil {
return "", err
}
if name, ok := record.Country.Names["en"]; ok {
return name, nil
}
return record.Country.ISOCode, nil
}
// IsPrivate checks if an IP is in a private/reserved range
func IsPrivateIP(ipStr string) bool {
ip := net.ParseIP(ipStr)
if ip == nil {
return false
}
// Private IPv4 ranges
privateBlocks := []string{
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"127.0.0.0/8",
"169.254.0.0/16", // Link-local
"100.64.0.0/10", // Carrier-grade NAT
}
// Private IPv6 ranges
if ip.To4() == nil {
privateBlocks = append(privateBlocks,
"::1/128", // Loopback
"fc00::/7", // Unique local
"fe80::/10", // Link-local
)
}
for _, block := range privateBlocks {
_, cidr, err := net.ParseCIDR(block)
if err != nil {
continue
}
if cidr.Contains(ip) {
return true
}
}
return false
}
// Continent codes for grouping countries
var ContinentCountries = map[string][]string{
"AF": { // Africa
"AO", "BF", "BI", "BJ", "BW", "CD", "CF", "CG", "CI", "CM",
"CV", "DJ", "DZ", "EG", "EH", "ER", "ET", "GA", "GH", "GM",
"GN", "GQ", "GW", "KE", "KM", "LR", "LS", "LY", "MA", "MG",
"ML", "MR", "MU", "MW", "MZ", "NA", "NE", "NG", "RE", "RW",
"SC", "SD", "SH", "SL", "SN", "SO", "SS", "ST", "SZ", "TD",
"TG", "TN", "TZ", "UG", "YT", "ZA", "ZM", "ZW",
},
"AS": { // Asia
"AE", "AF", "AM", "AZ", "BD", "BH", "BN", "BT", "CN", "CY",
"GE", "HK", "ID", "IL", "IN", "IQ", "IR", "JO", "JP", "KG",
"KH", "KP", "KR", "KW", "KZ", "LA", "LB", "LK", "MM", "MN",
"MO", "MV", "MY", "NP", "OM", "PH", "PK", "PS", "QA", "SA",
"SG", "SY", "TH", "TJ", "TL", "TM", "TR", "TW", "UZ", "VN",
"YE",
},
"EU": { // Europe
"AD", "AL", "AT", "AX", "BA", "BE", "BG", "BY", "CH", "CZ",
"DE", "DK", "EE", "ES", "FI", "FO", "FR", "GB", "GG", "GI",
"GR", "HR", "HU", "IE", "IM", "IS", "IT", "JE", "LI", "LT",
"LU", "LV", "MC", "MD", "ME", "MK", "MT", "NL", "NO", "PL",
"PT", "RO", "RS", "RU", "SE", "SI", "SJ", "SK", "SM", "UA",
"VA", "XK",
},
"NA": { // North America
"AG", "AI", "AW", "BB", "BL", "BM", "BQ", "BS", "BZ", "CA",
"CR", "CU", "CW", "DM", "DO", "GD", "GL", "GP", "GT", "HN",
"HT", "JM", "KN", "KY", "LC", "MF", "MQ", "MS", "MX", "NI",
"PA", "PM", "PR", "SV", "SX", "TC", "TT", "US", "VC", "VG",
"VI",
},
"SA": { // South America
"AR", "BO", "BR", "CL", "CO", "EC", "FK", "GF", "GY", "PE",
"PY", "SR", "UY", "VE",
},
"OC": { // Oceania
"AS", "AU", "CK", "FJ", "FM", "GU", "KI", "MH", "MP", "NC",
"NF", "NR", "NU", "NZ", "PF", "PG", "PN", "PW", "SB", "TK",
"TO", "TV", "VU", "WF", "WS",
},
"AN": { // Antarctica
"AQ", "GS", "HM", "TF",
},
}
// ExpandContinentCode expands a continent code to its country codes
func ExpandContinentCode(code string) []string {
if countries, ok := ContinentCountries[code]; ok {
return countries
}
return nil
}

168
go.mod
View File

@ -1,117 +1,137 @@
module git.supported.systems/rsp2k/caddy-sip-guardian module git.supported.systems/rsp2k/caddy-sip-guardian
go 1.22.0 go 1.25
require ( require (
github.com/caddyserver/caddy/v2 v2.8.4 github.com/caddyserver/caddy/v2 v2.10.2
github.com/mholt/caddy-l4 v0.0.0-20241104153248-ec8fae209322 github.com/mholt/caddy-l4 v0.0.0-20251204151317-049ea4dcfaf0
go.uber.org/zap v1.27.0 github.com/oschwald/maxminddb-golang v1.13.1
github.com/prometheus/client_golang v1.23.0
go.uber.org/zap v1.27.1
modernc.org/sqlite v1.38.0
) )
require ( require (
cel.dev/expr v0.24.0 // indirect
cloud.google.com/go/auth v0.16.2 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.7.0 // indirect
dario.cat/mergo v1.0.1 // indirect
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
github.com/KimMachineGun/automemlimit v0.7.4 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/caddyserver/certmagic v0.21.3 // indirect github.com/caddyserver/certmagic v0.24.0 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/ccoveille/go-safecast v1.6.1 // indirect
github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chzyer/readline v1.5.1 // indirect github.com/chzyer/readline v1.5.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/cloudflare/circl v1.6.1 // indirect
github.com/coreos/go-oidc/v3 v3.14.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/dgraph-io/badger v1.6.2 // indirect github.com/dgraph-io/badger v1.6.2 // indirect
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dgraph-io/ristretto v0.2.0 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-jose/go-jose/v3 v3.0.3 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-kit/kit v0.13.0 // indirect github.com/go-jose/go-jose/v3 v3.0.4 // indirect
github.com/go-kit/log v0.2.1 // indirect github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/golang/glog v1.2.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/cel-go v0.20.1 // indirect github.com/google/cel-go v0.26.0 // indirect
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 // indirect github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/huandu/xstrings v1.4.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/imdario/mergo v0.3.16 // indirect github.com/googleapis/gax-go/v2 v2.14.2 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.14.3 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.14.0 // indirect github.com/jackc/pgx/v5 v5.6.0 // indirect
github.com/jackc/pgx/v4 v4.18.3 // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/klauspost/compress v1.17.8 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/libdns/libdns v0.2.2 // indirect github.com/libdns/libdns v1.1.0 // indirect
github.com/manifoldco/promptui v0.9.0 // indirect github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mholt/acmez/v2 v2.0.1 // indirect github.com/mholt/acmez/v3 v3.1.2 // indirect
github.com/miekg/dns v1.1.62 // indirect github.com/miekg/dns v1.1.68 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/onsi/ginkgo/v2 v2.15.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.19.1 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect
github.com/prometheus/procfs v0.12.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/quic-go v0.57.1 // indirect
github.com/quic-go/quic-go v0.44.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rs/xid v1.5.0 // indirect github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect github.com/shopspring/decimal v1.4.0 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/slackhq/nebula v1.7.2 // indirect github.com/slackhq/nebula v1.9.7 // indirect
github.com/smallstep/certificates v0.26.1 // indirect github.com/smallstep/certificates v0.28.4 // indirect
github.com/smallstep/nosql v0.6.1 // indirect github.com/smallstep/cli-utils v0.12.1 // indirect
github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect github.com/smallstep/linkedca v0.23.0 // indirect
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect github.com/smallstep/nosql v0.7.0 // indirect
github.com/smallstep/pkcs7 v0.2.1 // indirect
github.com/smallstep/scep v0.0.0-20240926084937-8cf1ca453101 // indirect
github.com/smallstep/truststore v0.13.0 // indirect github.com/smallstep/truststore v0.13.0 // indirect
github.com/spf13/cast v1.5.1 // indirect github.com/spf13/cast v1.7.0 // indirect
github.com/spf13/cobra v1.8.0 // indirect github.com/spf13/cobra v1.9.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.7 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933 // indirect github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 // indirect
github.com/urfave/cli v1.22.14 // indirect github.com/urfave/cli v1.22.17 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect github.com/zeebo/blake3 v0.2.4 // indirect
go.etcd.io/bbolt v1.3.9 // indirect go.etcd.io/bbolt v1.3.10 // indirect
go.step.sm/cli-utils v0.9.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.step.sm/crypto v0.45.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.step.sm/linkedca v0.20.1 // indirect go.opentelemetry.io/otel v1.37.0 // indirect
go.uber.org/automaxprocs v1.5.3 // indirect go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.uber.org/mock v0.4.0 // indirect go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.step.sm/crypto v0.67.0 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap/exp v0.2.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect
golang.org/x/crypto v0.28.0 // indirect golang.org/x/crypto v0.45.0 // indirect
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 // indirect golang.org/x/crypto/x509roots/fallback v0.0.0-20250305170421-49bf5b80c810 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/mod v0.18.0 // indirect golang.org/x/mod v0.29.0 // indirect
golang.org/x/net v0.30.0 // indirect golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.8.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sys v0.26.0 // indirect golang.org/x/sync v0.18.0 // indirect
golang.org/x/term v0.25.0 // indirect golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.19.0 // indirect golang.org/x/term v0.37.0 // indirect
golang.org/x/time v0.7.0 // indirect golang.org/x/text v0.31.0 // indirect
golang.org/x/tools v0.22.0 // indirect golang.org/x/time v0.14.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae // indirect golang.org/x/tools v0.38.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 // indirect google.golang.org/api v0.240.0 // indirect
google.golang.org/grpc v1.63.2 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/protobuf v1.34.1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/grpc v1.73.0 // indirect
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
howett.net/plist v1.0.0 // indirect howett.net/plist v1.0.0 // indirect
modernc.org/libc v1.65.10 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
) )

673
go.sum
View File

@ -1,31 +1,35 @@
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg= cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro= cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA=
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q=
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= cloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4=
cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA=
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0= cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY= cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc= cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= cloud.google.com/go/kms v1.22.0 h1:dBRIj7+GDeeEvatJeTB19oYZNV0aj6wEqSIT/7gLqtk=
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= cloud.google.com/go/kms v1.22.0/go.mod h1:U7mf8Sva5jpOb4bxYZdtw/9zsbIjrklYwPcvMk34AL8=
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/KimMachineGun/automemlimit v0.7.4 h1:UY7QYOIfrr3wjjOAqahFmC3IaQCLWvur9nmfIn6LnWk=
github.com/KimMachineGun/automemlimit v0.7.4/go.mod h1:QZxpHaGOQoYvFhv/r4u3U0JTC2ZcOwbSr11UZF46UBM=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
@ -36,47 +40,48 @@ github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw= github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw=
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= github.com/aws/aws-sdk-go-v2 v1.36.4 h1:GySzjhVvx0ERP6eyfAbAuAXLtAda5TEy19E5q5W8I9E=
github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= github.com/aws/aws-sdk-go-v2 v1.36.4/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
github.com/aws/aws-sdk-go-v2/config v1.27.13 h1:WbKW8hOzrWoOA/+35S5okqO/2Ap8hkkFUzoW8Hzq24A= github.com/aws/aws-sdk-go-v2/config v1.29.16 h1:XkruGnXX1nEZ+Nyo9v84TzsX+nj86icbFAeust6uo8A=
github.com/aws/aws-sdk-go-v2/config v1.27.13/go.mod h1:XLiyiTMnguytjRER7u5RIkhIqS8Nyz41SwAWb4xEjxs= github.com/aws/aws-sdk-go-v2/config v1.29.16/go.mod h1:uCW7PNjGwZ5cOGZ5jr8vCWrYkGIhPoTNV23Q/tpHKzg=
github.com/aws/aws-sdk-go-v2/credentials v1.17.13 h1:XDCJDzk/u5cN7Aple7D/MiAhx1Rjo/0nueJ0La8mRuE= github.com/aws/aws-sdk-go-v2/credentials v1.17.69 h1:8B8ZQboRc3uaIKjshve/XlvJ570R7BKNy3gftSbS178=
github.com/aws/aws-sdk-go-v2/credentials v1.17.13/go.mod h1:FMNcjQrmuBYvOTZDtOLCIu0esmxjF7RuA/89iSXWzQI= github.com/aws/aws-sdk-go-v2/credentials v1.17.69/go.mod h1:gPME6I8grR1jCqBFEGthULiolzf/Sexq/Wy42ibKK9c=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.31 h1:oQWSGexYasNpYp4epLGZxxjsDo8BMBh6iNWkTXQvkwk=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.31/go.mod h1:nc332eGUU+djP3vrMI6blS0woaCfHTe3KiSQUVTMRq0=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.35 h1:o1v1VFfPcDVlK3ll1L5xHsaQAFdNtZ5GXnNR7SwueC4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.35/go.mod h1:rZUQNYMNG+8uZxz9FOerQJ+FceCiodXvixpeRtdESrU=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.35 h1:R5b82ubO2NntENm3SAm0ADME+H630HomNJdgv+yZ3xw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.35/go.mod h1:FuA+nmgMRfkzVKYDNEqQadvEMxtxl9+RLT9ribCwEMs=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.16 h1:/ldKrPPXTC421bTNWrUIpq3CxwHwRI/kpc+jPUTJocM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.16/go.mod h1:5vkf/Ws0/wgIMJDQbjI4p2op86hNW6Hie5QtebrDgT8=
github.com/aws/aws-sdk-go-v2/service/kms v1.31.1 h1:5wtyAwuUiJiM3DHYeGZmP5iMonM7DFBWAEaaVPHYZA0= github.com/aws/aws-sdk-go-v2/service/kms v1.41.0 h1:2jKyib9msVrAVn+lngwlSplG13RpUZmzVte2yDao5nc=
github.com/aws/aws-sdk-go-v2/service/kms v1.31.1/go.mod h1:2snWQJQUKsbN66vAawJuOGX7dr37pfOq9hb0tZDGIqQ= github.com/aws/aws-sdk-go-v2/service/kms v1.41.0/go.mod h1:RyhzxkWGcfixlkieewzpO3D4P4fTMxhIDqDZWsh0u/4=
github.com/aws/aws-sdk-go-v2/service/sso v1.20.6 h1:o5cTaeunSpfXiLTIBx5xo2enQmiChtu1IBbzXnfU9Hs= github.com/aws/aws-sdk-go-v2/service/sso v1.25.4 h1:EU58LP8ozQDVroOEyAfcq0cGc5R/FTZjVoYJ6tvby3w=
github.com/aws/aws-sdk-go-v2/service/sso v1.20.6/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM= github.com/aws/aws-sdk-go-v2/service/sso v1.25.4/go.mod h1:CrtOgCcysxMvrCoHnvNAD7PHWclmoFG78Q2xLK0KKcs=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0 h1:Qe0r0lVURDDeBQJ4yP+BOrJkvkiCo/3FH/t+wY11dmw= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.2 h1:XB4z0hbQtpmBnb1FQYvKaCM7UsS6Y/u8jVBwIUGeCTk=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.2/go.mod h1:hwRpqkRxnQ58J9blRDrB4IanlXCpcKmsC83EhG77upg=
github.com/aws/aws-sdk-go-v2/service/sts v1.28.7 h1:et3Ta53gotFR4ERLXXHIHl/Uuk1qYpP5uU7cvNql8ns= github.com/aws/aws-sdk-go-v2/service/sts v1.33.21 h1:nyLjs8sYJShFYj6aiyjCBI3EcLn1udWrQTjEF+SOXB0=
github.com/aws/aws-sdk-go-v2/service/sts v1.28.7/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw= github.com/aws/aws-sdk-go-v2/service/sts v1.33.21/go.mod h1:EhdxtZ+g84MSGrSrHzZiUm9PYiZkrADNja15wtRJSJo=
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/caddyserver/caddy/v2 v2.8.4 h1:q3pe0wpBj1OcHFZ3n/1nl4V4bxBrYoSoab7rL9BMYNk= github.com/caddyserver/caddy/v2 v2.10.2 h1:g/gTYjGMD0dec+UgMw8SnfmJ3I9+M2TdvoRL/Ovu6U8=
github.com/caddyserver/caddy/v2 v2.8.4/go.mod h1:vmDAHp3d05JIvuhc24LmnxVlsZmWnUwbP5WMjzcMPWw= github.com/caddyserver/caddy/v2 v2.10.2/go.mod h1:TXLQHx+ev4HDpkO6PnVVHUbL6OXt6Dfe7VcIBdQnPL0=
github.com/caddyserver/certmagic v0.21.3 h1:pqRRry3yuB4CWBVq9+cUqu+Y6E2z8TswbhNx1AZeYm0= github.com/caddyserver/certmagic v0.24.0 h1:EfXTWpxHAUKgDfOj6MHImJN8Jm4AMFfMT6ITuKhrDF0=
github.com/caddyserver/certmagic v0.21.3/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI= github.com/caddyserver/certmagic v0.24.0/go.mod h1:xPT7dC1DuHHnS2yuEQCEyks+b89sUkMENh8dJF+InLE=
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
github.com/ccoveille/go-safecast v1.6.1 h1:Nb9WMDR8PqhnKCVs2sCB+OqhohwO5qaXtCviZkIff5Q=
github.com/ccoveille/go-safecast v1.6.1/go.mod h1:QqwNjxQ7DAqY0C721OIO9InMk9zCwcsO7tnRuHytad8=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
@ -86,18 +91,17 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -107,8 +111,8 @@ github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdw
github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk=
github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
@ -117,38 +121,20 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
@ -157,149 +143,94 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI=
github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 h1:heyoXNxkRT155x4jTAiSv5BVSVkueifPUm+Q8LUXMRo= github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 h1:heyoXNxkRT155x4jTAiSv5BVSVkueifPUm+Q8LUXMRo=
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k= github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98= github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU=
github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY= github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm-tools v0.4.5 h1:3fhthtyMDbIZFR5/0y1hvUoZ1Kf4i1eZ7C73R4Pvd+k=
github.com/google/go-tpm-tools v0.4.5/go.mod h1:ktjTNq8yZFD6TzdBFefUfen96rF3NpYwpSb2d8bc+Y8=
github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus= github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=
github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI= github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI=
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0=
github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA=
github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/libdns/libdns v1.1.0 h1:9ze/tWvt7Df6sbhOJRB8jT33GHEHpEQXdtkE3hPthbU=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/libdns/libdns v1.1.0/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k= github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U= github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/mholt/caddy-l4 v0.0.0-20241104153248-ec8fae209322 h1:2cH9FoIRmAOe7XGxUEBJvcu3RckQYX5uk9YyPJONsCU= github.com/mholt/caddy-l4 v0.0.0-20251204151317-049ea4dcfaf0 h1:QtoAgPSJe1NwdqHmhyQmCp9PHOOMQTrybrie6qXcfzg=
github.com/mholt/caddy-l4 v0.0.0-20241104153248-ec8fae209322/go.mod h1:zhoEExOYPSuKYLyJE88BOIHNNf3PdOLyYEYbtnmgcSw= github.com/mholt/caddy-l4 v0.0.0-20251204151317-049ea4dcfaf0/go.mod h1:QE3J4K4lDelBdkhqeybY6DnvhdiV6K+443fcFzymQyU=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU= github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU=
github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o= github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o=
@ -310,57 +241,55 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.44.0 h1:So5wOr7jyO4vzL2sd8/pD9Kesciv91zSk8BoFngItQ0= github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10=
github.com/quic-go/quic-go v0.44.0/go.mod h1:z4cx/9Ny9UtGITIPzmPTXh1ULfOyWh4qGQlpnPcWmek= github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E= github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E=
github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg= github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/slackhq/nebula v1.7.2 h1:Rko1Mlksz/nC0c919xjGpB8uOSrTJ5e6KPgZx+lVfYw= github.com/slackhq/nebula v1.9.7 h1:v5u46efIyYHGdfjFnozQbRRhMdaB9Ma1SSTcUcE2lfE=
github.com/slackhq/nebula v1.7.2/go.mod h1:cnaoahkUipDs1vrNoIszyp0QPRIQN9Pm68ppQEW1Fhg= github.com/slackhq/nebula v1.9.7/go.mod h1:1+4q4wd3dDAjO8rKCttSb9JIVbklQhuJiBp5I0lbIsQ=
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY=
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
github.com/smallstep/certificates v0.26.1 h1:FIUliEBcExSfJJDhRFA/s8aZgMIFuorexnRSKQd884o= github.com/smallstep/certificates v0.28.4 h1:JTU6/A5Xes6m+OsR6fw1RACSA362vJc9SOFVG7poBEw=
github.com/smallstep/certificates v0.26.1/go.mod h1:OQMrW39IrGKDViKSHrKcgSQArMZ8c7EcjhYKK7mYqis= github.com/smallstep/certificates v0.28.4/go.mod h1:LUqo+7mKZE7FZldlTb0zhU4A0bq4G4+akieFMcTaWvA=
github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 h1:kjYvkvS/Wdy0PVRDUAA0gGJIVSEZYhiAJtfwYgOYoGA= github.com/smallstep/cli-utils v0.12.1 h1:D9QvfbFqiKq3snGZ2xDcXEFrdFJ1mQfPHZMq/leerpE=
github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4= github.com/smallstep/cli-utils v0.12.1/go.mod h1:skV2Neg8qjiKPu2fphM89H9bIxNpKiiRTnX9Q6Lc+20=
github.com/smallstep/nosql v0.6.1 h1:X8IBZFTRIp1gmuf23ne/jlD/BWKJtDQbtatxEn7Et1Y= github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca h1:VX8L0r8vybH0bPeaIxh4NQzafKQiqvlOn8pmOXbFLO4=
github.com/smallstep/nosql v0.6.1/go.mod h1:vrN+CftYYNnDM+DQqd863ATynvYFm/6FuY9D4TeAm2Y= github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4=
github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 h1:B6cED3iLJTgxpdh4tuqByDjRRKan2EvtnOfHr2zHJVg= github.com/smallstep/linkedca v0.23.0 h1:5W/7EudlK1HcCIdZM68dJlZ7orqCCCyv6bm2l/0JmLU=
github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81/go.mod h1:SoUAr/4M46rZ3WaLstHxGhLEgoYIDRqxQEXLOmOEB0Y= github.com/smallstep/linkedca v0.23.0/go.mod h1:7cyRM9soAYySg9ag65QwytcgGOM+4gOlkJ/YA58A9E8=
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d h1:06LUHn4Ia2X6syjIaCMNaXXDNdU+1N/oOHynJbWgpXw= github.com/smallstep/nosql v0.7.0 h1:YiWC9ZAHcrLCrayfaF+QJUv16I2bZ7KdLC3RpJcnAnE=
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d/go.mod h1:4d0ub42ut1mMtvGyMensjuHYEUpRrASvkzLEJvoRQcU= github.com/smallstep/nosql v0.7.0/go.mod h1:H5VnKMCbeq9QA6SRY5iqPylfxLfYcLwvUff3onQ8+HU=
github.com/smallstep/pkcs7 v0.0.0-20240911091500-b1cae6277023/go.mod h1:CM5KrX7rxWgwDdMj9yef/pJB2OPgy/56z4IEx2UIbpc=
github.com/smallstep/pkcs7 v0.2.1 h1:6Kfzr/QizdIuB6LSv8y1LJdZ3aPSfTNhTLqAx9CTLfA=
github.com/smallstep/pkcs7 v0.2.1/go.mod h1:RcXHsMfL+BzH8tRhmrF1NkkpebKpq3JEM66cOFxanf0=
github.com/smallstep/scep v0.0.0-20240926084937-8cf1ca453101 h1:LyZqn24/ZiVg8v9Hq07K6mx6RqPtpDeK+De5vf4QEY4=
github.com/smallstep/scep v0.0.0-20240926084937-8cf1ca453101/go.mod h1:EuKQjYGQwhUa1mgD21zxIgOgUYLsqikJmvxNscxpS/Y=
github.com/smallstep/truststore v0.13.0 h1:90if9htAOblavbMeWlqNLnO9bsjjgVv2hQeQJCi/py4= github.com/smallstep/truststore v0.13.0 h1:90if9htAOblavbMeWlqNLnO9bsjjgVv2hQeQJCi/py4=
github.com/smallstep/truststore v0.13.0/go.mod h1:3tmMp2aLKZ/OA/jnFUB0cYPcho402UG2knuJoPh4j7A= github.com/smallstep/truststore v0.13.0/go.mod h1:3tmMp2aLKZ/OA/jnFUB0cYPcho402UG2knuJoPh4j7A=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@ -368,233 +297,227 @@ github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0b
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933 h1:pV0H+XIvFoP7pl1MRtyPXh5hqoxB5I7snOtTHgrn6HU= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU= github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 h1:uxMgm0C+EjytfAqyfBG55ZONKQ7mvd7x4YYCWsf8QHQ=
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ=
github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ= go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.step.sm/crypto v0.45.0 h1:Z0WYAaaOYrJmKP9sJkPW+6wy3pgN3Ija8ek/D4serjc= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.step.sm/crypto v0.45.0/go.mod h1:6IYlT0L2jfj81nVyCPpvA5cORy0EVHPhieSgQyuwHIY= go.step.sm/crypto v0.67.0 h1:1km9LmxMKG/p+mKa1R4luPN04vlJYnRLlLQrWv7egGU=
go.step.sm/linkedca v0.20.1 h1:bHDn1+UG1NgRrERkWbbCiAIvv4lD5NOFaswPDTyO5vU= go.step.sm/crypto v0.67.0/go.mod h1:+AoDpB0mZxbW/PmOXuwkPSpXRgaUaoIK+/Wx/HGgtAU=
go.step.sm/linkedca v0.20.1/go.mod h1:Vaq4+Umtjh7DLFI1KuIxeo598vfBzgSYZUjgVJ7Syxw= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap/exp v0.2.0 h1:FtGenNNeCATRB3CmB/yEUnjEFeJWpB/pMcy7e2bKPYs=
go.uber.org/zap/exp v0.2.0/go.mod h1:t0gqAIdh1MfKv9EwN/dLwfZnJxe9ITAZN78HEWPFWDQ=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595 h1:TgSqweA595vD0Zt86JzLv3Pb/syKg8gd5KMGGbJPYFw= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/crypto/x509roots/fallback v0.0.0-20240507223354-67b13616a595/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/crypto/x509roots/fallback v0.0.0-20250305170421-49bf5b80c810 h1:V5+zy0jmgNYmK1uW/sPpBw8ioFvalrhaUrYWmu1Fpe4=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/crypto/x509roots/fallback v0.0.0-20250305170421-49bf5b80c810/go.mod h1:lxN5T34bK4Z/i6cMaU7frUU57VkDXFD4Kamfl/cp9oU=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.240.0 h1:PxG3AA2UIqT1ofIzWV2COM3j3JagKTKSwy7L6RHNXNU=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.240.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4= google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78=
google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE= google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw= google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw= google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae h1:AH34z6WAGVNkllnKs5raNq3yRq93VnjBG6rpfub/jYk= google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
google.golang.org/genproto/googleapis/api v0.0.0-20240506185236-b8a5c65736ae/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y= google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 h1:DujSIu+2tC9Ht0aPNA7jgj23Iq8Ewi5sgkQ++wdvonE= google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
modernc.org/fileutil v1.3.3 h1:3qaU+7f7xxTUmvU1pJTZiDLAIoJVdUSSauJNHg9yXoA=
modernc.org/fileutil v1.3.3/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.65.10 h1:ZwEk8+jhW7qBjHIT+wd0d9VjitRyQef9BnzlzGwMODc=
modernc.org/libc v1.65.10/go.mod h1:StFvYpx7i/mXtBAfVOjaU0PWZOvIRoZSgXhrwXzr8Po=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

View File

@ -2,6 +2,7 @@ package sipguardian
import ( import (
"bytes" "bytes"
"fmt"
"io" "io"
"net" "net"
"regexp" "regexp"
@ -48,17 +49,21 @@ func (m *SIPMatcher) Provision(ctx caddy.Context) error {
// Match returns true if the connection appears to be SIP traffic // Match returns true if the connection appears to be SIP traffic
func (m *SIPMatcher) Match(cx *layer4.Connection) (bool, error) { func (m *SIPMatcher) Match(cx *layer4.Connection) (bool, error) {
// Peek at first 64 bytes to check for SIP signature // Read enough bytes to identify SIP traffic
// We need at least 8 bytes to identify SIP methods (e.g., "REGISTER " or "SIP/2.0 ")
buf := make([]byte, 64) buf := make([]byte, 64)
n, err := io.ReadAtLeast(cx, buf, 8) n, err := io.ReadFull(cx, buf)
if err != nil && err != io.ErrUnexpectedEOF { if err == io.ErrUnexpectedEOF && n >= 8 {
return false, nil // Got less than 64 bytes but enough to match - that's fine
}
buf = buf[:n] buf = buf[:n]
} else if err != nil {
// Return the error so caddy-l4 knows we need more data
// This includes ErrConsumedAllPrefetchedBytes which triggers prefetch
return false, err
}
// Check if it matches a SIP method // Check if it matches a SIP method (REGISTER, INVITE, OPTIONS, etc.)
if m.methodRegex.Match(buf) { if m.methodRegex.Match(buf) {
// Rewind the buffer for the handler
cx.SetVar("sip_peek", buf) cx.SetVar("sip_peek", buf)
return true, nil return true, nil
} }
@ -80,6 +85,10 @@ type SIPHandler struct {
// Upstream address to proxy to // Upstream address to proxy to
Upstream string `json:"upstream,omitempty"` Upstream string `json:"upstream,omitempty"`
// Embedded guardian config parsed from Caddyfile
// This gets applied to the shared guardian during Provision
SIPGuardian
logger *zap.Logger logger *zap.Logger
guardian *SIPGuardian guardian *SIPGuardian
} }
@ -94,13 +103,13 @@ func (SIPHandler) CaddyModule() caddy.ModuleInfo {
func (h *SIPHandler) Provision(ctx caddy.Context) error { func (h *SIPHandler) Provision(ctx caddy.Context) error {
h.logger = ctx.Logger() h.logger = ctx.Logger()
// Get or create the guardian instance // Get or create a shared guardian instance from the global registry
// In a real implementation, this would use Caddy's module loading // Pass our parsed config so the guardian can be configured
// For now, we'll create a default instance guardian, err := GetOrCreateGuardianWithConfig(ctx, "default", &h.SIPGuardian)
h.guardian = &SIPGuardian{} if err != nil {
if err := h.guardian.Provision(ctx); err != nil {
return err return err
} }
h.guardian = guardian
return nil return nil
} }
@ -116,59 +125,185 @@ func (h *SIPHandler) Handle(cx *layer4.Connection, next layer4.Handler) error {
// Check if IP is banned // Check if IP is banned
if h.guardian.IsBanned(host) { if h.guardian.IsBanned(host) {
h.logger.Debug("Blocked banned IP", zap.String("ip", host)) h.logger.Debug("Blocked banned IP", zap.String("ip", host))
if enableMetrics {
RecordConnection("blocked")
}
return cx.Close() return cx.Close()
} }
// Check if IP is whitelisted - skip further checks // Check if IP is whitelisted - skip further checks
if h.guardian.IsWhitelisted(host) { if h.guardian.IsWhitelisted(host) {
if enableMetrics {
RecordConnection("allowed")
}
return next.Handle(cx) return next.Handle(cx)
} }
// Get the peeked SIP data if available // Check GeoIP blocking (if configured)
if peekData := cx.GetVar("sip_peek"); peekData != nil { if blocked, country := h.guardian.IsCountryBlocked(host); blocked {
buf := peekData.([]byte) h.logger.Info("Blocked connection from blocked country",
// Check for suspicious patterns
if isSuspiciousSIP(buf) {
h.logger.Warn("Suspicious SIP traffic detected",
zap.String("ip", host), zap.String("ip", host),
zap.ByteString("sample", buf[:min(32, len(buf))]), zap.String("country", country),
) )
banned := h.guardian.RecordFailure(host, "suspicious_sip_pattern") if enableMetrics {
if banned { RecordConnection("geo_blocked")
}
return cx.Close()
}
// Read data from the connection for suspicious pattern detection
// caddy-l4 replays prefetched data on read, so we can read the full message here
buf := make([]byte, 1024)
n, err := cx.Read(buf)
if n > 0 {
buf = buf[:n]
h.logger.Debug("Read SIP data for inspection",
zap.String("ip", host),
zap.Int("bytes", n),
)
// Extract SIP method for rate limiting
method := ExtractSIPMethod(buf)
if method != "" {
// Check rate limit
rl := GetRateLimiter(h.logger)
if allowed, reason := rl.Allow(host, method); !allowed {
h.logger.Warn("Rate limit exceeded",
zap.String("ip", host),
zap.String("method", string(method)),
)
if enableMetrics {
RecordConnection("rate_limited")
}
// Record as failure (may trigger ban)
h.guardian.RecordFailure(host, reason)
return cx.Close() return cx.Close()
} }
} }
// Check for extension enumeration attacks
extension := ExtractTargetExtension(buf)
if extension != "" {
detector := GetEnumerationDetector(h.logger)
result := detector.RecordAttempt(host, extension)
if result.Detected {
h.logger.Warn("Enumeration attack detected",
zap.String("ip", host),
zap.String("reason", result.Reason),
zap.Int("unique_extensions", result.UniqueCount),
zap.Strings("extensions", result.Extensions),
)
if enableMetrics {
RecordEnumerationDetection(result.Reason)
RecordEnumerationExtensions(result.UniqueCount)
RecordConnection("enumeration_blocked")
}
// Store in persistent storage if enabled
if h.guardian.storage != nil {
go h.guardian.storage.RecordEnumerationAttempt(host, result.Reason, result.UniqueCount, result.Extensions)
}
// Emit webhook event
if enableWebhooks {
go EmitEnumerationEvent(h.logger, host, result)
}
// Ban the IP (use enumeration-specific ban time if configured)
h.guardian.RecordFailure(host, "enumeration_"+result.Reason)
return cx.Close()
}
// Update metrics for tracked IPs
if enableMetrics {
stats := detector.GetStats()
if trackedIPs, ok := stats["tracked_ips"].(int); ok {
UpdateEnumerationTrackedIPs(trackedIPs)
}
}
}
// Check for suspicious patterns in the SIP message
suspiciousPattern := detectSuspiciousPattern(buf)
if suspiciousPattern != "" {
h.logger.Warn("Suspicious SIP traffic detected",
zap.String("ip", host),
zap.String("pattern", suspiciousPattern),
zap.ByteString("sample", buf[:min(64, len(buf))]),
)
if enableMetrics {
RecordSuspiciousPattern(suspiciousPattern)
RecordConnection("suspicious")
}
// Store in persistent storage if enabled
if h.guardian.storage != nil {
go h.guardian.storage.RecordSuspiciousPattern(host, suspiciousPattern, string(buf[:min(200, len(buf))]))
}
banned := h.guardian.RecordFailure(host, "suspicious_sip_pattern")
if banned {
h.logger.Warn("IP banned due to suspicious activity",
zap.String("ip", host),
)
return cx.Close()
}
}
} else if err != nil {
h.logger.Debug("Failed to read SIP data for inspection",
zap.String("ip", host),
zap.Error(err),
)
}
// Record successful connection
if enableMetrics {
RecordConnection("allowed")
} }
// Continue to next handler // Continue to next handler
return next.Handle(cx) return next.Handle(cx)
} }
// isSuspiciousSIP checks for common attack patterns in SIP traffic // suspiciousPatternDefs defines patterns and their names for detection
var suspiciousPatternDefs = []struct {
name string
pattern string
}{
{"sipvicious", "sipvicious"},
{"friendly-scanner", "friendly-scanner"},
{"sipcli", "sipcli"},
{"sip-scan", "sip-scan"},
{"voipbuster", "voipbuster"},
{"asterisk-pbx-scanner", "asterisk pbx"},
{"sipsak", "sipsak"},
{"sundayddr", "sundayddr"},
{"iwar", "iwar"},
{"cseq-flood", "cseq: 1 options"}, // Repeated OPTIONS flood
{"zoiper-spoof", "user-agent: zoiper"},
{"test-extension-100", "sip:100@"},
{"test-extension-1000", "sip:1000@"},
{"null-user", "sip:@"},
{"anonymous", "anonymous@"},
}
// detectSuspiciousPattern checks for common attack patterns and returns the pattern name
func detectSuspiciousPattern(data []byte) string {
lower := strings.ToLower(string(data))
for _, def := range suspiciousPatternDefs {
if strings.Contains(lower, def.pattern) {
return def.name
}
}
return ""
}
// isSuspiciousSIP checks for common attack patterns in SIP traffic (legacy wrapper)
func isSuspiciousSIP(data []byte) bool { func isSuspiciousSIP(data []byte) bool {
s := string(data) return detectSuspiciousPattern(data) != ""
// Common scanner/attack patterns
suspiciousPatterns := []string{
"sipvicious",
"friendly-scanner",
"sipcli",
"sip-scan",
"User-Agent: Zoiper", // Often spoofed
"From: <sip:100@", // Common test extension
"From: <sip:1000@",
"To: <sip:100@",
}
lower := strings.ToLower(s)
for _, pattern := range suspiciousPatterns {
if strings.Contains(lower, strings.ToLower(pattern)) {
return true
}
}
return false
} }
func min(a, b int) int { func min(a, b int) int {
@ -214,6 +349,20 @@ func (m *SIPMatcher) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// find_time 10m // find_time 10m
// ban_time 1h // ban_time 1h
// whitelist 10.0.0.0/8 172.16.0.0/12 // whitelist 10.0.0.0/8 172.16.0.0/12
// storage /data/sip-guardian.db
// geoip_db /data/GeoLite2-Country.mmdb
// block_countries CN RU
// allow_countries US CA GB
// enumeration {
// max_extensions 20
// extension_window 5m
// sequential_threshold 5
// rapid_fire_count 10
// rapid_fire_window 30s
// ban_time 2h
// exempt_extensions 100 200 9999
// }
// webhook http://example.com/hook { ... }
// } // }
// //
// Or simply: sip_guardian (uses defaults) // Or simply: sip_guardian (uses defaults)
@ -221,18 +370,184 @@ func (h *SIPHandler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// Move past "sip_guardian" token // Move past "sip_guardian" token
d.Next() d.Next()
// The handler doesn't have its own configuration - it uses the shared SIPGuardian // Parse configuration into the embedded SIPGuardian struct
// But we need to parse any configuration blocks that might be present // This config will be applied to the shared guardian during Provision
// Check for inline args or block
for nesting := d.Nesting(); d.NextBlock(nesting); { for nesting := d.Nesting(); d.NextBlock(nesting); {
// For now, SIPHandler delegates to SIPGuardian
// In future, handler-specific config could go here
switch d.Val() { switch d.Val() {
case "max_failures", "find_time", "ban_time", "whitelist": case "max_failures":
// These are handled by the embedded SIPGuardian if !d.NextArg() {
// Skip to allow flexibility in config placement return d.ArgErr()
d.RemainingArgs() }
var val int
if _, err := fmt.Sscanf(d.Val(), "%d", &val); err != nil {
return d.Errf("invalid max_failures: %v", err)
}
h.MaxFailures = val
case "find_time":
if !d.NextArg() {
return d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return d.Errf("invalid find_time: %v", err)
}
h.FindTime = caddy.Duration(dur)
case "ban_time":
if !d.NextArg() {
return d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return d.Errf("invalid ban_time: %v", err)
}
h.BanTime = caddy.Duration(dur)
case "whitelist":
for d.NextArg() {
h.WhitelistCIDR = append(h.WhitelistCIDR, d.Val())
}
case "storage":
if !d.NextArg() {
return d.ArgErr()
}
h.StoragePath = d.Val()
case "geoip_db":
if !d.NextArg() {
return d.ArgErr()
}
h.GeoIPPath = d.Val()
case "block_countries":
for d.NextArg() {
country := d.Val()
if expanded := ExpandContinentCode(country); expanded != nil {
h.BlockedCountries = append(h.BlockedCountries, expanded...)
} else {
h.BlockedCountries = append(h.BlockedCountries, country)
}
}
case "allow_countries":
for d.NextArg() {
country := d.Val()
if expanded := ExpandContinentCode(country); expanded != nil {
h.AllowedCountries = append(h.AllowedCountries, expanded...)
} else {
h.AllowedCountries = append(h.AllowedCountries, country)
}
}
case "enumeration":
h.Enumeration = &EnumerationConfig{}
for innerNesting := d.Nesting(); d.NextBlock(innerNesting); {
switch d.Val() {
case "max_extensions":
if !d.NextArg() {
return d.ArgErr()
}
var val int
if _, err := fmt.Sscanf(d.Val(), "%d", &val); err != nil {
return d.Errf("invalid max_extensions: %v", err)
}
h.Enumeration.MaxExtensions = val
case "extension_window":
if !d.NextArg() {
return d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return d.Errf("invalid extension_window: %v", err)
}
h.Enumeration.ExtensionWindow = dur
case "sequential_threshold":
if !d.NextArg() {
return d.ArgErr()
}
var val int
if _, err := fmt.Sscanf(d.Val(), "%d", &val); err != nil {
return d.Errf("invalid sequential_threshold: %v", err)
}
h.Enumeration.SequentialThreshold = val
case "rapid_fire_count":
if !d.NextArg() {
return d.ArgErr()
}
var val int
if _, err := fmt.Sscanf(d.Val(), "%d", &val); err != nil {
return d.Errf("invalid rapid_fire_count: %v", err)
}
h.Enumeration.RapidFireCount = val
case "rapid_fire_window":
if !d.NextArg() {
return d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return d.Errf("invalid rapid_fire_window: %v", err)
}
h.Enumeration.RapidFireWindow = dur
case "ban_time":
if !d.NextArg() {
return d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return d.Errf("invalid enumeration ban_time: %v", err)
}
h.Enumeration.EnumBanTime = dur
case "exempt_extensions":
h.Enumeration.ExemptExtensions = d.RemainingArgs()
default:
return d.Errf("unknown enumeration directive: %s", d.Val())
}
}
case "webhook":
if !d.NextArg() {
return d.ArgErr()
}
webhook := WebhookConfig{
URL: d.Val(),
}
// Parse webhook block if present
for innerNesting := d.Nesting(); d.NextBlock(innerNesting); {
switch d.Val() {
case "events":
webhook.Events = d.RemainingArgs()
case "secret":
if !d.NextArg() {
return d.ArgErr()
}
webhook.Secret = d.Val()
case "timeout":
if !d.NextArg() {
return d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return d.Errf("invalid webhook timeout: %v", err)
}
webhook.Timeout = dur
case "header":
args := d.RemainingArgs()
if len(args) != 2 {
return d.Errf("header requires name and value")
}
if webhook.Headers == nil {
webhook.Headers = make(map[string]string)
}
webhook.Headers[args[0]] = args[1]
default:
return d.Errf("unknown webhook directive: %s", d.Val())
}
}
h.Webhooks = append(h.Webhooks, webhook)
default: default:
return d.Errf("unknown sip_guardian directive: %s", d.Val()) return d.Errf("unknown sip_guardian directive: %s", d.Val())
} }

287
metrics.go Normal file
View File

@ -0,0 +1,287 @@
package sipguardian
import (
"net/http"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func init() {
caddy.RegisterModule(MetricsHandler{})
httpcaddyfile.RegisterHandlerDirective("sip_guardian_metrics", parseSIPGuardianMetrics)
httpcaddyfile.RegisterDirectiveOrder("sip_guardian_metrics", httpcaddyfile.Before, "respond")
}
// Prometheus metrics for SIP Guardian
var (
sipConnectionsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: "sip_guardian",
Name: "connections_total",
Help: "Total number of SIP connections processed",
},
[]string{"status"}, // "allowed", "blocked", "suspicious"
)
sipBansTotal = prometheus.NewCounter(
prometheus.CounterOpts{
Namespace: "sip_guardian",
Name: "bans_total",
Help: "Total number of IP bans issued",
},
)
sipUnbansTotal = prometheus.NewCounter(
prometheus.CounterOpts{
Namespace: "sip_guardian",
Name: "unbans_total",
Help: "Total number of IP unbans (manual or expired)",
},
)
sipActiveBans = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "sip_guardian",
Name: "active_bans",
Help: "Current number of active IP bans",
},
)
sipFailuresTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: "sip_guardian",
Name: "failures_total",
Help: "Total number of recorded failures by reason",
},
[]string{"reason"},
)
sipTrackedIPs = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "sip_guardian",
Name: "tracked_ips",
Help: "Current number of IPs being tracked for failures",
},
)
sipWhitelistedConnections = prometheus.NewCounter(
prometheus.CounterOpts{
Namespace: "sip_guardian",
Name: "whitelisted_connections_total",
Help: "Total connections from whitelisted IPs",
},
)
sipSuspiciousPatterns = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: "sip_guardian",
Name: "suspicious_patterns_total",
Help: "Total suspicious patterns detected by type",
},
[]string{"pattern"},
)
sipBanDurationSeconds = prometheus.NewHistogram(
prometheus.HistogramOpts{
Namespace: "sip_guardian",
Name: "ban_duration_seconds",
Help: "Distribution of ban durations in seconds",
Buckets: []float64{60, 300, 600, 1800, 3600, 7200, 14400, 28800, 86400},
},
)
// Enumeration detection metrics
sipEnumerationDetections = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: "sip_guardian",
Name: "enumeration_detections_total",
Help: "Total enumeration attacks detected by reason",
},
[]string{"reason"}, // "extension_count_exceeded", "sequential_enumeration", "rapid_fire_enumeration"
)
sipEnumerationTrackedIPs = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "sip_guardian",
Name: "enumeration_tracked_ips",
Help: "Current number of IPs being tracked for enumeration",
},
)
sipEnumerationUniqueExtensions = prometheus.NewHistogram(
prometheus.HistogramOpts{
Namespace: "sip_guardian",
Name: "enumeration_unique_extensions",
Help: "Distribution of unique extensions per IP at detection time",
Buckets: []float64{5, 10, 15, 20, 30, 50, 100},
},
)
)
// metricsRegistered tracks if we've registered with Prometheus
var metricsRegistered bool
// RegisterMetrics registers all SIP Guardian metrics with Prometheus
func RegisterMetrics() {
if metricsRegistered {
return
}
metricsRegistered = true
prometheus.MustRegister(
sipConnectionsTotal,
sipBansTotal,
sipUnbansTotal,
sipActiveBans,
sipFailuresTotal,
sipTrackedIPs,
sipWhitelistedConnections,
sipSuspiciousPatterns,
sipBanDurationSeconds,
sipEnumerationDetections,
sipEnumerationTrackedIPs,
sipEnumerationUniqueExtensions,
)
}
// Metric recording functions - called from other modules
// RecordConnection records a connection event
func RecordConnection(status string) {
sipConnectionsTotal.WithLabelValues(status).Inc()
}
// RecordBan records a ban event
func RecordBan() {
sipBansTotal.Inc()
sipActiveBans.Inc()
}
// RecordUnban records an unban event
func RecordUnban() {
sipUnbansTotal.Inc()
sipActiveBans.Dec()
}
// RecordFailure records a failure event
func RecordFailure(reason string) {
sipFailuresTotal.WithLabelValues(reason).Inc()
}
// RecordWhitelistedConnection records a whitelisted connection
func RecordWhitelistedConnection() {
sipWhitelistedConnections.Inc()
}
// RecordSuspiciousPattern records a suspicious pattern detection
func RecordSuspiciousPattern(pattern string) {
sipSuspiciousPatterns.WithLabelValues(pattern).Inc()
}
// RecordBanDuration records the duration of a ban when it expires
func RecordBanDuration(seconds float64) {
sipBanDurationSeconds.Observe(seconds)
}
// UpdateActiveBans updates the active bans gauge
func UpdateActiveBans(count int) {
sipActiveBans.Set(float64(count))
}
// UpdateTrackedIPs updates the tracked IPs gauge
func UpdateTrackedIPs(count int) {
sipTrackedIPs.Set(float64(count))
}
// RecordEnumerationDetection records an enumeration attack detection
func RecordEnumerationDetection(reason string) {
sipEnumerationDetections.WithLabelValues(reason).Inc()
}
// UpdateEnumerationTrackedIPs updates the enumeration tracked IPs gauge
func UpdateEnumerationTrackedIPs(count int) {
sipEnumerationTrackedIPs.Set(float64(count))
}
// RecordEnumerationExtensions records the number of unique extensions at detection
func RecordEnumerationExtensions(count int) {
sipEnumerationUniqueExtensions.Observe(float64(count))
}
// MetricsHandler provides a Prometheus metrics endpoint for SIP Guardian
type MetricsHandler struct {
// Path prefix for metrics (default: /metrics)
Path string `json:"path,omitempty"`
}
func (MetricsHandler) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "http.handlers.sip_guardian_metrics",
New: func() caddy.Module { return new(MetricsHandler) },
}
}
func (h *MetricsHandler) Provision(ctx caddy.Context) error {
RegisterMetrics()
if h.Path == "" {
h.Path = "/metrics"
}
return nil
}
// ServeHTTP serves the Prometheus metrics
func (h *MetricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
// Update gauges from current state
if guardian := GetGuardian("default"); guardian != nil {
stats := guardian.GetStats()
if activeBans, ok := stats["active_bans"].(int); ok {
UpdateActiveBans(activeBans)
}
if trackedFailures, ok := stats["tracked_failures"].(int); ok {
UpdateTrackedIPs(trackedFailures)
}
}
promhttp.Handler().ServeHTTP(w, r)
return nil
}
// parseSIPGuardianMetrics parses the sip_guardian_metrics directive
func parseSIPGuardianMetrics(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
var handler MetricsHandler
err := handler.UnmarshalCaddyfile(h.Dispenser)
return &handler, err
}
// UnmarshalCaddyfile implements caddyfile.Unmarshaler for MetricsHandler.
func (h *MetricsHandler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
d.Next() // consume directive name
for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() {
case "path":
if !d.NextArg() {
return d.ArgErr()
}
h.Path = d.Val()
default:
return d.Errf("unknown sip_guardian_metrics directive: %s", d.Val())
}
}
return nil
}
// Interface guards
var (
_ caddyhttp.MiddlewareHandler = (*MetricsHandler)(nil)
_ caddy.Provisioner = (*MetricsHandler)(nil)
_ caddyfile.Unmarshaler = (*MetricsHandler)(nil)
)

360
ratelimit.go Normal file
View File

@ -0,0 +1,360 @@
package sipguardian
import (
"regexp"
"strings"
"sync"
"time"
"go.uber.org/zap"
)
// SIPMethod represents a SIP request method
type SIPMethod string
const (
MethodREGISTER SIPMethod = "REGISTER"
MethodINVITE SIPMethod = "INVITE"
MethodOPTIONS SIPMethod = "OPTIONS"
MethodACK SIPMethod = "ACK"
MethodBYE SIPMethod = "BYE"
MethodCANCEL SIPMethod = "CANCEL"
MethodINFO SIPMethod = "INFO"
MethodNOTIFY SIPMethod = "NOTIFY"
MethodSUBSCRIBE SIPMethod = "SUBSCRIBE"
MethodMESSAGE SIPMethod = "MESSAGE"
MethodUPDATE SIPMethod = "UPDATE"
MethodPRACK SIPMethod = "PRACK"
MethodREFER SIPMethod = "REFER"
MethodPUBLISH SIPMethod = "PUBLISH"
)
// MethodRateLimit defines rate limits per SIP method
type MethodRateLimit struct {
// Method to rate limit
Method SIPMethod `json:"method"`
// MaxRequests per time window
MaxRequests int `json:"max_requests"`
// Window is the time window for rate limiting
Window time.Duration `json:"window"`
// BurstSize allows temporary bursts above the rate
BurstSize int `json:"burst_size,omitempty"`
}
// RateLimiter provides per-IP, per-method rate limiting
type RateLimiter struct {
limits map[SIPMethod]*MethodRateLimit
buckets map[string]*methodBuckets
logger *zap.Logger
mu sync.RWMutex
// Default limits (used when no specific limit configured)
defaultMaxRequests int
defaultWindow time.Duration
}
// methodBuckets tracks request counts per method for an IP
type methodBuckets struct {
methods map[SIPMethod]*tokenBucket
lastReset time.Time
mu sync.Mutex
}
// tokenBucket implements a simple token bucket algorithm
type tokenBucket struct {
tokens float64
lastUpdate time.Time
maxTokens float64
refillRate float64 // tokens per second
}
// Global rate limiter instance
var (
globalRateLimiter *RateLimiter
rateLimiterMu sync.Mutex
)
// DefaultMethodLimits provides reasonable default rate limits per method
var DefaultMethodLimits = map[SIPMethod]*MethodRateLimit{
MethodREGISTER: {
Method: MethodREGISTER,
MaxRequests: 10,
Window: time.Minute,
BurstSize: 3,
},
MethodINVITE: {
Method: MethodINVITE,
MaxRequests: 30,
Window: time.Minute,
BurstSize: 5,
},
MethodOPTIONS: {
Method: MethodOPTIONS,
MaxRequests: 60,
Window: time.Minute,
BurstSize: 10,
},
MethodSUBSCRIBE: {
Method: MethodSUBSCRIBE,
MaxRequests: 20,
Window: time.Minute,
BurstSize: 5,
},
MethodMESSAGE: {
Method: MethodMESSAGE,
MaxRequests: 100,
Window: time.Minute,
BurstSize: 20,
},
}
// NewRateLimiter creates a new rate limiter
func NewRateLimiter(logger *zap.Logger) *RateLimiter {
return &RateLimiter{
limits: make(map[SIPMethod]*MethodRateLimit),
buckets: make(map[string]*methodBuckets),
logger: logger,
defaultMaxRequests: 100,
defaultWindow: time.Minute,
}
}
// GetRateLimiter returns the global rate limiter
func GetRateLimiter(logger *zap.Logger) *RateLimiter {
rateLimiterMu.Lock()
defer rateLimiterMu.Unlock()
if globalRateLimiter == nil {
globalRateLimiter = NewRateLimiter(logger)
// Apply default limits
for method, limit := range DefaultMethodLimits {
globalRateLimiter.SetLimit(method, limit)
}
}
return globalRateLimiter
}
// SetLimit configures a rate limit for a specific method
func (rl *RateLimiter) SetLimit(method SIPMethod, limit *MethodRateLimit) {
rl.mu.Lock()
defer rl.mu.Unlock()
rl.limits[method] = limit
}
// GetLimit returns the rate limit for a method
func (rl *RateLimiter) GetLimit(method SIPMethod) *MethodRateLimit {
rl.mu.RLock()
defer rl.mu.RUnlock()
if limit, ok := rl.limits[method]; ok {
return limit
}
// Return default limit
return &MethodRateLimit{
Method: method,
MaxRequests: rl.defaultMaxRequests,
Window: rl.defaultWindow,
}
}
// Allow checks if a request should be allowed based on rate limits
// Returns (allowed, reason) - if not allowed, reason explains why
func (rl *RateLimiter) Allow(ip string, method SIPMethod) (bool, string) {
rl.mu.Lock()
bucket, exists := rl.buckets[ip]
if !exists {
bucket = &methodBuckets{
methods: make(map[SIPMethod]*tokenBucket),
lastReset: time.Now(),
}
rl.buckets[ip] = bucket
}
rl.mu.Unlock()
limit := rl.GetLimit(method)
bucket.mu.Lock()
defer bucket.mu.Unlock()
tb, exists := bucket.methods[method]
if !exists {
// Create new token bucket for this method
burstSize := limit.BurstSize
if burstSize == 0 {
burstSize = limit.MaxRequests / 5 // Default burst is 20% of max
if burstSize < 1 {
burstSize = 1
}
}
tb = &tokenBucket{
tokens: float64(burstSize),
lastUpdate: time.Now(),
maxTokens: float64(burstSize),
refillRate: float64(limit.MaxRequests) / limit.Window.Seconds(),
}
bucket.methods[method] = tb
}
// Refill tokens based on elapsed time
now := time.Now()
elapsed := now.Sub(tb.lastUpdate).Seconds()
tb.tokens += elapsed * tb.refillRate
if tb.tokens > tb.maxTokens {
tb.tokens = tb.maxTokens
}
tb.lastUpdate = now
// Check if we have tokens
if tb.tokens >= 1.0 {
tb.tokens -= 1.0
return true, ""
}
// Rate limited
return false, "rate_limit_" + string(method)
}
// Cleanup removes old entries
func (rl *RateLimiter) Cleanup() {
rl.mu.Lock()
defer rl.mu.Unlock()
now := time.Now()
cutoff := now.Add(-10 * time.Minute)
for ip, bucket := range rl.buckets {
bucket.mu.Lock()
if bucket.lastReset.Before(cutoff) {
delete(rl.buckets, ip)
}
bucket.mu.Unlock()
}
}
// GetStats returns rate limiter statistics
func (rl *RateLimiter) GetStats() map[string]interface{} {
rl.mu.RLock()
defer rl.mu.RUnlock()
limitConfigs := make(map[string]interface{})
for method, limit := range rl.limits {
limitConfigs[string(method)] = map[string]interface{}{
"max_requests": limit.MaxRequests,
"window": limit.Window.String(),
"burst_size": limit.BurstSize,
}
}
return map[string]interface{}{
"tracked_ips": len(rl.buckets),
"limits": limitConfigs,
}
}
// SIP method extraction patterns
var (
sipMethodPattern = regexp.MustCompile(`^(REGISTER|INVITE|OPTIONS|ACK|BYE|CANCEL|INFO|NOTIFY|SUBSCRIBE|MESSAGE|UPDATE|PRACK|REFER|PUBLISH)\s+sip:`)
// Pattern to extract target extension from Request-URI: METHOD sip:extension@domain
sipTargetExtPattern = regexp.MustCompile(`^(?:REGISTER|INVITE|OPTIONS|MESSAGE|SUBSCRIBE)\s+sip:([^@\s>]+)@`)
)
// ExtractSIPMethod extracts the SIP method from a message
func ExtractSIPMethod(data []byte) SIPMethod {
s := string(data)
if matches := sipMethodPattern.FindStringSubmatch(s); len(matches) > 1 {
return SIPMethod(matches[1])
}
return ""
}
// ExtractTargetExtension extracts the target extension from a SIP message
// It looks at the Request-URI first, then falls back to the To header
func ExtractTargetExtension(data []byte) string {
s := string(data)
// First try Request-URI: REGISTER sip:1001@domain.com
if matches := sipTargetExtPattern.FindStringSubmatch(s); len(matches) > 1 {
ext := matches[1]
// Filter out domain-like values and overly long strings
if !strings.Contains(ext, ".") && len(ext) <= 10 && len(ext) > 0 {
return ext
}
}
// Fall back to To header
user, _ := ParseToHeader(data)
if user != "" && !strings.Contains(user, ".") && len(user) <= 10 {
return user
}
return ""
}
// ParseUserAgent extracts User-Agent from SIP message
func ParseUserAgent(data []byte) string {
s := string(data)
lines := strings.Split(s, "\r\n")
for _, line := range lines {
lower := strings.ToLower(line)
if strings.HasPrefix(lower, "user-agent:") {
return strings.TrimSpace(line[11:])
}
}
return ""
}
// ParseFromHeader extracts From header info
func ParseFromHeader(data []byte) (user, domain string) {
s := string(data)
lines := strings.Split(s, "\r\n")
for _, line := range lines {
lower := strings.ToLower(line)
if strings.HasPrefix(lower, "from:") {
// Extract sip:user@domain from From header
fromPattern := regexp.MustCompile(`sip:([^@]+)@([^>;\s]+)`)
if matches := fromPattern.FindStringSubmatch(line); len(matches) > 2 {
return matches[1], matches[2]
}
}
}
return "", ""
}
// ParseToHeader extracts To header info
func ParseToHeader(data []byte) (user, domain string) {
s := string(data)
lines := strings.Split(s, "\r\n")
for _, line := range lines {
lower := strings.ToLower(line)
if strings.HasPrefix(lower, "to:") {
// Extract sip:user@domain from To header
toPattern := regexp.MustCompile(`sip:([^@]+)@([^>;\s]+)`)
if matches := toPattern.FindStringSubmatch(line); len(matches) > 2 {
return matches[1], matches[2]
}
}
}
return "", ""
}
// ParseCallID extracts Call-ID from SIP message
func ParseCallID(data []byte) string {
s := string(data)
lines := strings.Split(s, "\r\n")
for _, line := range lines {
lower := strings.ToLower(line)
if strings.HasPrefix(lower, "call-id:") {
return strings.TrimSpace(line[8:])
}
if strings.HasPrefix(lower, "i:") { // Short form
return strings.TrimSpace(line[2:])
}
}
return ""
}

View File

@ -1,9 +1,12 @@
package sipguardian package sipguardian
import ( import (
"net"
"sync" "sync"
"time"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2"
"go.uber.org/zap"
) )
// Global registry to share guardian instances across modules // Global registry to share guardian instances across modules
@ -12,8 +15,13 @@ var (
registryMu sync.RWMutex registryMu sync.RWMutex
) )
// GetOrCreateGuardian returns a shared guardian instance by name // GetOrCreateGuardian returns a shared guardian instance by name (backward compat)
func GetOrCreateGuardian(ctx caddy.Context, name string) (*SIPGuardian, error) { func GetOrCreateGuardian(ctx caddy.Context, name string) (*SIPGuardian, error) {
return GetOrCreateGuardianWithConfig(ctx, name, nil)
}
// GetOrCreateGuardianWithConfig returns a shared guardian instance, merging config if provided
func GetOrCreateGuardianWithConfig(ctx caddy.Context, name string, config *SIPGuardian) (*SIPGuardian, error) {
if name == "" { if name == "" {
name = "default" name = "default"
} }
@ -22,11 +30,33 @@ func GetOrCreateGuardian(ctx caddy.Context, name string) (*SIPGuardian, error) {
defer registryMu.Unlock() defer registryMu.Unlock()
if g, exists := guardianRegistry[name]; exists { if g, exists := guardianRegistry[name]; exists {
// Guardian exists - merge any new config
if config != nil {
mergeGuardianConfig(ctx, g, config)
}
return g, nil return g, nil
} }
// Create new guardian // Create new guardian with config
g := &SIPGuardian{} var g *SIPGuardian
if config != nil {
// Copy config values to a new guardian
g = &SIPGuardian{
MaxFailures: config.MaxFailures,
FindTime: config.FindTime,
BanTime: config.BanTime,
WhitelistCIDR: config.WhitelistCIDR,
Webhooks: config.Webhooks,
StoragePath: config.StoragePath,
GeoIPPath: config.GeoIPPath,
BlockedCountries: config.BlockedCountries,
AllowedCountries: config.AllowedCountries,
Enumeration: config.Enumeration,
}
} else {
g = &SIPGuardian{}
}
if err := g.Provision(ctx); err != nil { if err := g.Provision(ctx); err != nil {
return nil, err return nil, err
} }
@ -35,6 +65,142 @@ func GetOrCreateGuardian(ctx caddy.Context, name string) (*SIPGuardian, error) {
return g, nil return g, nil
} }
// mergeGuardianConfig merges new config into an existing guardian
// This handles cases where multiple handlers might specify overlapping config
func mergeGuardianConfig(ctx caddy.Context, g *SIPGuardian, config *SIPGuardian) {
g.mu.Lock()
defer g.mu.Unlock()
logger := ctx.Logger()
// Merge whitelist CIDRs (add new ones, avoid duplicates)
for _, cidr := range config.WhitelistCIDR {
found := false
for _, existing := range g.WhitelistCIDR {
if existing == cidr {
found = true
break
}
}
if !found {
g.WhitelistCIDR = append(g.WhitelistCIDR, cidr)
// Parse and add to whitelistNets
if _, network, err := net.ParseCIDR(cidr); err == nil {
g.whitelistNets = append(g.whitelistNets, network)
logger.Debug("Added whitelist CIDR from handler config",
zap.String("cidr", cidr),
)
}
}
}
// Override numeric values if they're non-zero (handler specified them)
if config.MaxFailures > 0 && config.MaxFailures != g.MaxFailures {
g.MaxFailures = config.MaxFailures
}
if config.FindTime > 0 && config.FindTime != g.FindTime {
g.FindTime = config.FindTime
}
if config.BanTime > 0 && config.BanTime != g.BanTime {
g.BanTime = config.BanTime
}
// Initialize storage if specified and not yet initialized
if config.StoragePath != "" && g.storage == nil {
storage, err := InitStorage(logger, StorageConfig{
Path: config.StoragePath,
})
if err != nil {
logger.Warn("Failed to initialize storage from handler config",
zap.Error(err),
)
} else {
g.storage = storage
g.StoragePath = config.StoragePath
// Load existing bans from storage
if bans, err := storage.LoadActiveBans(); err == nil {
for _, ban := range bans {
entry := ban
g.bannedIPs[entry.IP] = &entry
}
logger.Info("Loaded bans from storage", zap.Int("count", len(bans)))
}
}
}
// Initialize GeoIP if specified and not yet initialized
if config.GeoIPPath != "" && g.geoIP == nil {
geoIP, err := NewGeoIPLookup(config.GeoIPPath)
if err != nil {
logger.Warn("Failed to initialize GeoIP from handler config",
zap.Error(err),
)
} else {
g.geoIP = geoIP
g.GeoIPPath = config.GeoIPPath
}
}
// Merge blocked/allowed countries
for _, country := range config.BlockedCountries {
found := false
for _, existing := range g.BlockedCountries {
if existing == country {
found = true
break
}
}
if !found {
g.BlockedCountries = append(g.BlockedCountries, country)
}
}
for _, country := range config.AllowedCountries {
found := false
for _, existing := range g.AllowedCountries {
if existing == country {
found = true
break
}
}
if !found {
g.AllowedCountries = append(g.AllowedCountries, country)
}
}
// Merge webhooks (add new ones by URL)
for _, webhook := range config.Webhooks {
found := false
for _, existing := range g.Webhooks {
if existing.URL == webhook.URL {
found = true
break
}
}
if !found {
g.Webhooks = append(g.Webhooks, webhook)
// Register with webhook manager
if enableWebhooks {
wm := GetWebhookManager(logger)
wm.AddWebhook(webhook)
}
}
}
// Apply enumeration config if specified
if config.Enumeration != nil && g.Enumeration == nil {
g.Enumeration = config.Enumeration
// Apply to global detector
SetEnumerationConfig(*config.Enumeration)
logger.Debug("Applied enumeration config from handler")
}
logger.Debug("Merged guardian config",
zap.Int("whitelist_count", len(g.whitelistNets)),
zap.Int("webhook_count", len(g.Webhooks)),
zap.Duration("ban_time", time.Duration(g.BanTime)),
)
}
// GetGuardian returns an existing guardian instance // GetGuardian returns an existing guardian instance
func GetGuardian(name string) *SIPGuardian { func GetGuardian(name string) *SIPGuardian {
if name == "" { if name == "" {

107
sandbox/Caddyfile Normal file
View File

@ -0,0 +1,107 @@
# Sandbox Caddyfile for SIP Guardian Testing
#
# This configuration showcases all the new features:
# - Prometheus metrics endpoint
# - Rate limiting per method (built-in defaults)
# - Suspicious pattern detection
#
# Note: Storage and webhooks are configured in JSON config mode,
# as the L4 handler uses the shared global guardian instance
{
debug
admin 0.0.0.0:2019
layer4 {
# SIP over UDP
udp/:5060 {
@sip sip {
methods REGISTER INVITE OPTIONS ACK BYE CANCEL INFO NOTIFY SUBSCRIBE MESSAGE
}
route @sip {
sip_guardian {
max_failures 3 # Lower for faster testing
find_time 2m # Shorter window
ban_time 5m # Short bans for testing
# Whitelist legitimate test clients
whitelist 10.55.0.50/32 # client container
whitelist 10.55.0.51/32 # linphone container
# Enumeration detection (low thresholds for testing)
enumeration {
max_extensions 10
extension_window 2m
sequential_threshold 5
rapid_fire_count 8
rapid_fire_window 10s
ban_time 10m
exempt_extensions 100 200
}
}
proxy udp/{$SIP_UPSTREAM_HOST}:{$SIP_UPSTREAM_PORT}
}
# Unmatched traffic - drop silently
route {
}
}
# SIP over TCP
tcp/:5060 {
@sip sip
route @sip {
sip_guardian {
max_failures 3
find_time 2m
ban_time 5m
whitelist 10.55.0.50/32
whitelist 10.55.0.51/32
}
proxy tcp/{$SIP_UPSTREAM_HOST}:{$SIP_UPSTREAM_PORT}
}
}
# SIP over TLS
tcp/:5061 {
@sip sip
route @sip {
sip_guardian {
max_failures 3
find_time 2m
ban_time 5m
whitelist 10.55.0.50/32
whitelist 10.55.0.51/32
}
proxy tcp/{$SIP_UPSTREAM_HOST}:{$SIP_UPSTREAM_TLS_PORT}
}
}
}
}
# Admin API and Metrics
:2020 {
# SIP Guardian admin endpoints
handle /api/sip-guardian/* {
sip_guardian_admin
}
# Prometheus metrics endpoint
handle /metrics {
sip_guardian_metrics
}
# Health check
handle /health {
respond "OK" 200
}
# Stats (alias for backwards compatibility)
handle /stats {
sip_guardian_admin
}
}

219
sandbox/docker-compose.yml Normal file
View File

@ -0,0 +1,219 @@
# SIP Guardian Testing Sandbox
#
# This provides a complete testing environment with:
# - FreePBX (real PBX for testing)
# - Caddy with SIP Guardian (the proxy under test)
# - Attack containers (sipvicious, custom scripts)
# - Valid client containers (pjsip for legitimate traffic)
#
# Usage:
# docker compose up -d
# docker compose logs -f caddy
# docker compose run --rm attacker sipvicious_svwar -e100-200 caddy
# docker compose run --rm client pjsua --registrar=sip:caddy
services:
# ============================================
# FreePBX - The Protected PBX
# ============================================
freepbx:
image: tiredofit/freepbx:latest
container_name: sandbox-freepbx
hostname: freepbx
restart: unless-stopped
privileged: true
environment:
- VIRTUAL_HOST=pbx.sandbox.local
- VIRTUAL_NETWORK=sandbox
- HTTP_PORT=80
- HTTPS_PORT=443
- UCP_FIRST_RUN=true
- DB_HOST=mariadb
- DB_PORT=3306
- DB_NAME=asterisk
- DB_USER=asterisk
- DB_PASS=asteriskpass
- ENABLE_FAIL2BAN=FALSE # We're replacing this!
- TZ=UTC
volumes:
- freepbx_data:/data
- freepbx_logs:/var/log
- freepbx_www:/var/www/html
depends_on:
- mariadb
networks:
sandbox:
ipv4_address: 10.55.0.10
mariadb:
image: mariadb:10.11
container_name: sandbox-mariadb
restart: unless-stopped
environment:
- MYSQL_ROOT_PASSWORD=rootpass
- MYSQL_DATABASE=asterisk
- MYSQL_USER=asterisk
- MYSQL_PASSWORD=asteriskpass
volumes:
- mariadb_data:/var/lib/mysql
networks:
sandbox:
ipv4_address: 10.55.0.11
# ============================================
# Caddy with SIP Guardian - The Proxy
# ============================================
caddy:
build:
context: ..
dockerfile: Dockerfile
container_name: sandbox-caddy
hostname: caddy
restart: unless-stopped
# Override default command to use explicit Caddyfile (ENTRYPOINT is "caddy")
command: ["run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
ports:
# Expose SIP ports to host for external testing
- "5060:5060/udp"
- "5060:5060/tcp"
- "5061:5061/tcp"
# Admin API
- "2020:2020"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
environment:
- SIP_UPSTREAM_HOST=freepbx
- SIP_UPSTREAM_PORT=5060
- SIP_UPSTREAM_TLS_PORT=5061
networks:
sandbox:
ipv4_address: 10.55.0.2
depends_on:
- freepbx
# ============================================
# Attack Simulation Containers
# ============================================
# SIPVicious scanner - for testing scanner detection
attacker:
image: python:3.11-slim
container_name: sandbox-attacker
hostname: attacker
profiles:
- testing
command: >
bash -c "
pip install sipvicious pysip3 &&
echo 'SIPVicious and tools installed. Use: sipvicious_svwar, sipvicious_svcrack, sipvicious_svmap' &&
tail -f /dev/null
"
networks:
sandbox:
ipv4_address: 10.55.0.100
depends_on:
- caddy
# Brute force simulation
bruteforcer:
image: python:3.11-slim
container_name: sandbox-bruteforcer
hostname: bruteforcer
profiles:
- testing
volumes:
- ./scripts:/scripts:ro
command: >
bash -c "
pip install sipsimple requests &&
echo 'Brute force tools ready. Run: python /scripts/bruteforce.py' &&
tail -f /dev/null
"
networks:
sandbox:
ipv4_address: 10.55.0.101
depends_on:
- caddy
# ============================================
# Legitimate Client Containers
# ============================================
# PJSIP-based SIP client
client:
image: alpine:latest
container_name: sandbox-client
hostname: client
profiles:
- testing
command: >
sh -c "
apk add --no-cache pjsua netcat-openbsd curl jq &&
echo 'PJSIP client ready. Use pjsua for SIP registration.' &&
tail -f /dev/null
"
networks:
sandbox:
ipv4_address: 10.55.0.50
depends_on:
- caddy
# Linphone CLI client
linphone:
image: alpine:latest
container_name: sandbox-linphone
hostname: linphone
profiles:
- testing
command: >
sh -c "
apk add --no-cache linphone netcat-openbsd curl &&
echo 'Linphone ready.' &&
tail -f /dev/null
"
networks:
sandbox:
ipv4_address: 10.55.0.51
depends_on:
- caddy
# ============================================
# Monitoring & Debugging
# ============================================
# Network sniffer for SIP traffic analysis
tcpdump:
image: alpine:latest
container_name: sandbox-tcpdump
hostname: tcpdump
profiles:
- debug
cap_add:
- NET_ADMIN
- NET_RAW
network_mode: "service:caddy"
command: >
sh -c "
apk add --no-cache tcpdump &&
tcpdump -i any -n 'udp port 5060 or tcp port 5060 or tcp port 5061' -vvv
"
depends_on:
- caddy
volumes:
freepbx_data:
freepbx_logs:
freepbx_www:
mariadb_data:
caddy_data:
caddy_config:
networks:
sandbox:
driver: bridge
ipam:
config:
- subnet: 10.55.0.0/24
gateway: 10.55.0.1

View File

@ -0,0 +1,123 @@
#!/usr/bin/env python3
"""
SIP Brute Force Simulation for SIP Guardian Testing
Simulates authentication failures to test rate limiting and banning.
"""
import socket
import time
import argparse
import random
import string
def generate_call_id():
"""Generate a random SIP Call-ID"""
return ''.join(random.choices(string.ascii_letters + string.digits, k=32))
def generate_branch():
"""Generate a random Via branch parameter"""
return 'z9hG4bK' + ''.join(random.choices(string.ascii_letters + string.digits, k=16))
def create_register_request(target_host: str, target_port: int, extension: str, from_ip: str) -> bytes:
"""Create a SIP REGISTER request"""
call_id = generate_call_id()
branch = generate_branch()
tag = ''.join(random.choices(string.digits, k=8))
request = f"""REGISTER sip:{target_host}:{target_port} SIP/2.0\r
Via: SIP/2.0/UDP {from_ip}:5060;branch={branch}\r
Max-Forwards: 70\r
From: <sip:{extension}@{target_host}>;tag={tag}\r
To: <sip:{extension}@{target_host}>\r
Call-ID: {call_id}@{from_ip}\r
CSeq: 1 REGISTER\r
Contact: <sip:{extension}@{from_ip}:5060>\r
Expires: 3600\r
User-Agent: BruteForcer/1.0\r
Content-Length: 0\r
\r
"""
return request.encode()
def send_register(sock: socket.socket, target: tuple, request: bytes) -> str:
"""Send REGISTER and receive response"""
try:
sock.sendto(request, target)
sock.settimeout(2.0)
response, _ = sock.recvfrom(4096)
return response.decode()
except socket.timeout:
return "TIMEOUT"
except Exception as e:
return f"ERROR: {e}"
def main():
parser = argparse.ArgumentParser(description='SIP Brute Force Simulator')
parser.add_argument('target', help='Target host (Caddy proxy)')
parser.add_argument('-p', '--port', type=int, default=5060, help='Target port')
parser.add_argument('-e', '--extensions', default='100-105', help='Extension range (e.g., 100-200)')
parser.add_argument('-c', '--count', type=int, default=10, help='Attempts per extension')
parser.add_argument('-d', '--delay', type=float, default=0.1, help='Delay between attempts')
parser.add_argument('--udp', action='store_true', default=True, help='Use UDP (default)')
parser.add_argument('--tcp', action='store_true', help='Use TCP')
args = parser.parse_args()
# Parse extension range
if '-' in args.extensions:
start, end = map(int, args.extensions.split('-'))
extensions = list(range(start, end + 1))
else:
extensions = [int(args.extensions)]
# Get our IP
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect((args.target, args.port))
from_ip = sock.getsockname()[0]
sock.close()
print(f"[*] Starting brute force simulation")
print(f"[*] Target: {args.target}:{args.port}")
print(f"[*] Source IP: {from_ip}")
print(f"[*] Extensions: {extensions}")
print(f"[*] Attempts per extension: {args.count}")
print()
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
target = (args.target, args.port)
attempts = 0
blocked_at = None
for ext in extensions:
for i in range(args.count):
request = create_register_request(args.target, args.port, str(ext), from_ip)
response = send_register(sock, target, request)
attempts += 1
if 'TIMEOUT' in response:
if blocked_at is None:
blocked_at = attempts
print(f"[!] BLOCKED after {attempts} attempts (extension {ext}, attempt {i+1})")
print(f"[+] SIP Guardian is working! Blocked after {blocked_at} attempts")
return
elif '401' in response or '407' in response:
print(f"[*] Auth required: ext={ext} attempt={i+1} total={attempts}")
elif '403' in response:
print(f"[!] FORBIDDEN: ext={ext} - Connection blocked")
if blocked_at is None:
blocked_at = attempts
else:
# Print first line of response
first_line = response.split('\r\n')[0] if response else 'No response'
print(f"[?] Response: {first_line} (ext={ext})")
time.sleep(args.delay)
print(f"\n[*] Completed {attempts} attempts without being blocked")
print("[!] SIP Guardian may not be working correctly")
sock.close()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,168 @@
#!/usr/bin/env python3
"""
Valid SIP Registration Test
Tests that legitimate registrations pass through SIP Guardian successfully.
"""
import socket
import time
import argparse
import random
import string
import hashlib
def generate_call_id():
return ''.join(random.choices(string.ascii_letters + string.digits, k=32))
def generate_branch():
return 'z9hG4bK' + ''.join(random.choices(string.ascii_letters + string.digits, k=16))
def compute_digest_response(username: str, password: str, realm: str, nonce: str, uri: str, method: str = 'REGISTER') -> str:
"""Compute SIP Digest authentication response"""
ha1 = hashlib.md5(f"{username}:{realm}:{password}".encode()).hexdigest()
ha2 = hashlib.md5(f"{method}:{uri}".encode()).hexdigest()
response = hashlib.md5(f"{ha1}:{nonce}:{ha2}".encode()).hexdigest()
return response
def create_register_with_auth(target_host: str, target_port: int, extension: str,
password: str, from_ip: str, realm: str, nonce: str) -> bytes:
"""Create an authenticated SIP REGISTER request"""
call_id = generate_call_id()
branch = generate_branch()
tag = ''.join(random.choices(string.digits, k=8))
uri = f"sip:{target_host}:{target_port}"
response = compute_digest_response(extension, password, realm, nonce, uri)
request = f"""REGISTER sip:{target_host}:{target_port} SIP/2.0\r
Via: SIP/2.0/UDP {from_ip}:5060;branch={branch}\r
Max-Forwards: 70\r
From: <sip:{extension}@{target_host}>;tag={tag}\r
To: <sip:{extension}@{target_host}>\r
Call-ID: {call_id}@{from_ip}\r
CSeq: 2 REGISTER\r
Contact: <sip:{extension}@{from_ip}:5060>\r
Authorization: Digest username="{extension}",realm="{realm}",nonce="{nonce}",uri="{uri}",response="{response}",algorithm=MD5\r
Expires: 3600\r
User-Agent: ValidClient/1.0\r
Content-Length: 0\r
\r
"""
return request.encode()
def create_initial_register(target_host: str, target_port: int, extension: str, from_ip: str) -> bytes:
"""Create initial REGISTER without auth (to get challenge)"""
call_id = generate_call_id()
branch = generate_branch()
tag = ''.join(random.choices(string.digits, k=8))
request = f"""REGISTER sip:{target_host}:{target_port} SIP/2.0\r
Via: SIP/2.0/UDP {from_ip}:5060;branch={branch}\r
Max-Forwards: 70\r
From: <sip:{extension}@{target_host}>;tag={tag}\r
To: <sip:{extension}@{target_host}>\r
Call-ID: {call_id}@{from_ip}\r
CSeq: 1 REGISTER\r
Contact: <sip:{extension}@{from_ip}:5060>\r
Expires: 3600\r
User-Agent: ValidClient/1.0\r
Content-Length: 0\r
\r
"""
return request.encode()
def parse_www_authenticate(response: str) -> tuple:
"""Parse WWW-Authenticate header to get realm and nonce"""
for line in response.split('\r\n'):
if line.lower().startswith('www-authenticate:'):
# Extract realm
realm_start = line.find('realm="') + 7
realm_end = line.find('"', realm_start)
realm = line[realm_start:realm_end] if realm_start > 6 else ''
# Extract nonce
nonce_start = line.find('nonce="') + 7
nonce_end = line.find('"', nonce_start)
nonce = line[nonce_start:nonce_end] if nonce_start > 6 else ''
return realm, nonce
return '', ''
def main():
parser = argparse.ArgumentParser(description='Valid SIP Registration Test')
parser.add_argument('target', help='Target host (Caddy proxy)')
parser.add_argument('-p', '--port', type=int, default=5060, help='Target port')
parser.add_argument('-e', '--extension', default='100', help='Extension to register')
parser.add_argument('-s', '--secret', default='password123', help='Extension password')
parser.add_argument('-r', '--repeat', type=int, default=1, help='Number of registration cycles')
args = parser.parse_args()
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect((args.target, args.port))
from_ip = sock.getsockname()[0]
sock.close()
print(f"[*] Valid Registration Test")
print(f"[*] Target: {args.target}:{args.port}")
print(f"[*] Extension: {args.extension}")
print(f"[*] Source IP: {from_ip}")
print()
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
target = (args.target, args.port)
for cycle in range(args.repeat):
print(f"[*] Registration cycle {cycle + 1}/{args.repeat}")
# Send initial REGISTER
request = create_initial_register(args.target, args.port, args.extension, from_ip)
sock.sendto(request, target)
try:
sock.settimeout(5.0)
response, _ = sock.recvfrom(4096)
response = response.decode()
except socket.timeout:
print("[!] TIMEOUT - Connection may be blocked")
continue
first_line = response.split('\r\n')[0]
print(f"[*] Initial response: {first_line}")
if '401' in response or '407' in response:
# Parse auth challenge
realm, nonce = parse_www_authenticate(response)
print(f"[*] Got challenge: realm={realm}")
# Send authenticated REGISTER
auth_request = create_register_with_auth(
args.target, args.port, args.extension, args.secret,
from_ip, realm, nonce
)
sock.sendto(auth_request, target)
try:
response, _ = sock.recvfrom(4096)
response = response.decode()
first_line = response.split('\r\n')[0]
print(f"[*] Auth response: {first_line}")
if '200' in response:
print("[+] SUCCESS - Registration completed!")
else:
print(f"[!] Failed: {first_line}")
except socket.timeout:
print("[!] TIMEOUT after auth - may be blocked")
elif '200' in response:
print("[+] SUCCESS - Already registered or no auth required")
else:
print(f"[?] Unexpected response: {first_line}")
if cycle < args.repeat - 1:
time.sleep(2)
sock.close()
if __name__ == '__main__':
main()

View File

@ -13,6 +13,13 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
// Feature flags for optional components
var (
enableMetrics = true
enableWebhooks = true
enableStorage = true
)
func init() { func init() {
caddy.RegisterModule(SIPGuardian{}) caddy.RegisterModule(SIPGuardian{})
} }
@ -34,12 +41,28 @@ type SIPGuardian struct {
BanTime caddy.Duration `json:"ban_time,omitempty"` BanTime caddy.Duration `json:"ban_time,omitempty"`
WhitelistCIDR []string `json:"whitelist_cidr,omitempty"` WhitelistCIDR []string `json:"whitelist_cidr,omitempty"`
// Webhook configuration
Webhooks []WebhookConfig `json:"webhooks,omitempty"`
// Storage configuration
StoragePath string `json:"storage_path,omitempty"`
// GeoIP configuration
GeoIPPath string `json:"geoip_path,omitempty"`
BlockedCountries []string `json:"blocked_countries,omitempty"`
AllowedCountries []string `json:"allowed_countries,omitempty"`
// Enumeration detection configuration
Enumeration *EnumerationConfig `json:"enumeration,omitempty"`
// Runtime state // Runtime state
logger *zap.Logger logger *zap.Logger
bannedIPs map[string]*BanEntry bannedIPs map[string]*BanEntry
failureCounts map[string]*failureTracker failureCounts map[string]*failureTracker
whitelistNets []*net.IPNet whitelistNets []*net.IPNet
mu sync.RWMutex mu sync.RWMutex
storage *Storage
geoIP *GeoIPLookup
} }
type failureTracker struct { type failureTracker struct {
@ -82,6 +105,63 @@ func (g *SIPGuardian) Provision(ctx caddy.Context) error {
g.whitelistNets = append(g.whitelistNets, network) g.whitelistNets = append(g.whitelistNets, network)
} }
// Initialize metrics
if enableMetrics {
RegisterMetrics()
}
// Initialize webhooks
if enableWebhooks && len(g.Webhooks) > 0 {
wm := GetWebhookManager(g.logger)
for _, config := range g.Webhooks {
wm.AddWebhook(config)
}
}
// Initialize persistent storage
if enableStorage && g.StoragePath != "" {
storage, err := InitStorage(g.logger, StorageConfig{
Path: g.StoragePath,
})
if err != nil {
g.logger.Warn("Failed to initialize storage, continuing without persistence",
zap.Error(err),
)
} else {
g.storage = storage
// Load existing bans from storage
if err := g.loadBansFromStorage(); err != nil {
g.logger.Warn("Failed to load bans from storage", zap.Error(err))
}
}
}
// Initialize GeoIP if configured
if g.GeoIPPath != "" {
geoIP, err := NewGeoIPLookup(g.GeoIPPath)
if err != nil {
g.logger.Warn("Failed to initialize GeoIP, country blocking disabled",
zap.Error(err),
)
} else {
g.geoIP = geoIP
g.logger.Info("GeoIP initialized",
zap.Int("blocked_countries", len(g.BlockedCountries)),
zap.Int("allowed_countries", len(g.AllowedCountries)),
)
}
}
// Initialize enumeration detection with config if specified
if g.Enumeration != nil {
SetEnumerationConfig(*g.Enumeration)
g.logger.Info("Enumeration detection configured",
zap.Int("max_extensions", g.Enumeration.MaxExtensions),
zap.Int("sequential_threshold", g.Enumeration.SequentialThreshold),
zap.Duration("extension_window", g.Enumeration.ExtensionWindow),
)
}
// Start cleanup goroutine // Start cleanup goroutine
go g.cleanupLoop(ctx) go g.cleanupLoop(ctx)
@ -90,11 +170,38 @@ func (g *SIPGuardian) Provision(ctx caddy.Context) error {
zap.Duration("find_time", time.Duration(g.FindTime)), zap.Duration("find_time", time.Duration(g.FindTime)),
zap.Duration("ban_time", time.Duration(g.BanTime)), zap.Duration("ban_time", time.Duration(g.BanTime)),
zap.Int("whitelist_count", len(g.whitelistNets)), zap.Int("whitelist_count", len(g.whitelistNets)),
zap.Bool("storage_enabled", g.storage != nil),
zap.Bool("geoip_enabled", g.geoIP != nil),
zap.Int("webhook_count", len(g.Webhooks)),
zap.Bool("enumeration_enabled", g.Enumeration != nil),
) )
return nil return nil
} }
// loadBansFromStorage loads active bans from persistent storage
func (g *SIPGuardian) loadBansFromStorage() error {
if g.storage == nil {
return nil
}
bans, err := g.storage.LoadActiveBans()
if err != nil {
return err
}
g.mu.Lock()
defer g.mu.Unlock()
for _, ban := range bans {
entry := ban // Create a copy
g.bannedIPs[entry.IP] = &entry
}
g.logger.Info("Loaded bans from storage", zap.Int("count", len(bans)))
return nil
}
// IsWhitelisted checks if an IP is in the whitelist // IsWhitelisted checks if an IP is in the whitelist
func (g *SIPGuardian) IsWhitelisted(ip string) bool { func (g *SIPGuardian) IsWhitelisted(ip string) bool {
parsedIP := net.ParseIP(ip) parsedIP := net.ParseIP(ip)
@ -103,12 +210,47 @@ func (g *SIPGuardian) IsWhitelisted(ip string) bool {
} }
for _, network := range g.whitelistNets { for _, network := range g.whitelistNets {
if network.Contains(parsedIP) { if network.Contains(parsedIP) {
if enableMetrics {
RecordWhitelistedConnection()
}
return true return true
} }
} }
return false return false
} }
// IsCountryBlocked checks if an IP's country is blocked (or not in allowed list)
func (g *SIPGuardian) IsCountryBlocked(ip string) (bool, string) {
if g.geoIP == nil {
return false, ""
}
country, err := g.geoIP.LookupCountry(ip)
if err != nil {
g.logger.Debug("GeoIP lookup failed", zap.String("ip", ip), zap.Error(err))
return false, ""
}
// If allowed countries are specified, only those are allowed
if len(g.AllowedCountries) > 0 {
for _, allowed := range g.AllowedCountries {
if country == allowed {
return false, country
}
}
return true, country // Not in allowed list
}
// Check blocked countries
for _, blocked := range g.BlockedCountries {
if country == blocked {
return true, country
}
}
return false, country
}
// IsBanned checks if an IP is currently banned // IsBanned checks if an IP is currently banned
func (g *SIPGuardian) IsBanned(ip string) bool { func (g *SIPGuardian) IsBanned(ip string) bool {
g.mu.RLock() g.mu.RLock()
@ -154,6 +296,24 @@ func (g *SIPGuardian) RecordFailure(ip, reason string) bool {
zap.Int("count", tracker.count), zap.Int("count", tracker.count),
) )
// Record metrics
if enableMetrics {
RecordFailure(reason)
UpdateTrackedIPs(len(g.failureCounts))
}
// Record in storage (async)
if g.storage != nil {
go func() {
g.storage.RecordFailure(ip, reason, nil)
}()
}
// Emit failure event via webhook
if enableWebhooks {
EmitFailureEvent(g.logger, ip, reason, tracker.count)
}
// Check if we should ban // Check if we should ban
if tracker.count >= g.MaxFailures { if tracker.count >= g.MaxFailures {
g.banIP(ip, reason) g.banIP(ip, reason)
@ -168,13 +328,19 @@ func (g *SIPGuardian) banIP(ip, reason string) {
now := time.Now() now := time.Now()
banDuration := time.Duration(g.BanTime) banDuration := time.Duration(g.BanTime)
g.bannedIPs[ip] = &BanEntry{ hitCount := 0
if tracker := g.failureCounts[ip]; tracker != nil {
hitCount = tracker.count
}
entry := &BanEntry{
IP: ip, IP: ip,
Reason: reason, Reason: reason,
BannedAt: now, BannedAt: now,
ExpiresAt: now.Add(banDuration), ExpiresAt: now.Add(banDuration),
HitCount: g.failureCounts[ip].count, HitCount: hitCount,
} }
g.bannedIPs[ip] = entry
// Clear failure counter // Clear failure counter
delete(g.failureCounts, ip) delete(g.failureCounts, ip)
@ -184,6 +350,25 @@ func (g *SIPGuardian) banIP(ip, reason string) {
zap.String("reason", reason), zap.String("reason", reason),
zap.Duration("duration", banDuration), zap.Duration("duration", banDuration),
) )
// Record metrics
if enableMetrics {
RecordBan()
}
// Save to persistent storage
if g.storage != nil {
go func() {
if err := g.storage.SaveBan(entry); err != nil {
g.logger.Error("Failed to save ban to storage", zap.Error(err))
}
}()
}
// Emit webhook event
if enableWebhooks {
EmitBanEvent(g.logger, entry)
}
} }
// UnbanIP manually removes an IP from the ban list // UnbanIP manually removes an IP from the ban list
@ -191,9 +376,31 @@ func (g *SIPGuardian) UnbanIP(ip string) bool {
g.mu.Lock() g.mu.Lock()
defer g.mu.Unlock() defer g.mu.Unlock()
if _, exists := g.bannedIPs[ip]; exists { if entry, exists := g.bannedIPs[ip]; exists {
// Record ban duration for metrics
if enableMetrics {
duration := time.Since(entry.BannedAt).Seconds()
RecordBanDuration(duration)
RecordUnban()
}
delete(g.bannedIPs, ip) delete(g.bannedIPs, ip)
g.logger.Info("IP unbanned", zap.String("ip", ip)) g.logger.Info("IP unbanned", zap.String("ip", ip))
// Update storage
if g.storage != nil {
go func() {
if err := g.storage.RemoveBan(ip, "manual_unban"); err != nil {
g.logger.Error("Failed to update storage on unban", zap.Error(err))
}
}()
}
// Emit webhook event
if enableWebhooks {
EmitUnbanEvent(g.logger, ip, "manual_unban")
}
return true return true
} }
return false return false
@ -273,6 +480,29 @@ func (g *SIPGuardian) cleanup() {
} }
// UnmarshalCaddyfile implements caddyfile.Unmarshaler. // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
// Extended configuration options:
//
// sip_guardian {
// max_failures 5
// find_time 10m
// ban_time 1h
// whitelist 10.0.0.0/8 192.168.0.0/16
//
// # Persistent storage
// storage /var/lib/sip-guardian/guardian.db
//
// # GeoIP blocking (requires MaxMind database)
// geoip_db /path/to/GeoLite2-Country.mmdb
// block_countries CN RU KP
// allow_countries US CA GB # Alternative: only allow these
//
// # Webhook notifications
// webhook https://example.com/hook {
// events ban unban suspicious
// secret my-webhook-secret
// timeout 10s
// }
// }
func (g *SIPGuardian) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (g *SIPGuardian) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() { for d.Next() {
for d.NextBlock(0) { for d.NextBlock(0) {
@ -312,6 +542,80 @@ func (g *SIPGuardian) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
g.WhitelistCIDR = append(g.WhitelistCIDR, d.Val()) g.WhitelistCIDR = append(g.WhitelistCIDR, d.Val())
} }
case "storage":
if !d.NextArg() {
return d.ArgErr()
}
g.StoragePath = d.Val()
case "geoip_db":
if !d.NextArg() {
return d.ArgErr()
}
g.GeoIPPath = d.Val()
case "block_countries":
for d.NextArg() {
country := d.Val()
// Support continent expansion (e.g., "AS" for all of Asia)
if expanded := ExpandContinentCode(country); expanded != nil {
g.BlockedCountries = append(g.BlockedCountries, expanded...)
} else {
g.BlockedCountries = append(g.BlockedCountries, country)
}
}
case "allow_countries":
for d.NextArg() {
country := d.Val()
if expanded := ExpandContinentCode(country); expanded != nil {
g.AllowedCountries = append(g.AllowedCountries, expanded...)
} else {
g.AllowedCountries = append(g.AllowedCountries, country)
}
}
case "webhook":
if !d.NextArg() {
return d.ArgErr()
}
webhook := WebhookConfig{
URL: d.Val(),
}
// Parse webhook block if present
for nesting := d.Nesting(); d.NextBlock(nesting); {
switch d.Val() {
case "events":
webhook.Events = d.RemainingArgs()
case "secret":
if !d.NextArg() {
return d.ArgErr()
}
webhook.Secret = d.Val()
case "timeout":
if !d.NextArg() {
return d.ArgErr()
}
dur, err := caddy.ParseDuration(d.Val())
if err != nil {
return d.Errf("invalid webhook timeout: %v", err)
}
webhook.Timeout = dur
case "header":
args := d.RemainingArgs()
if len(args) != 2 {
return d.Errf("header requires name and value")
}
if webhook.Headers == nil {
webhook.Headers = make(map[string]string)
}
webhook.Headers[args[0]] = args[1]
default:
return d.Errf("unknown webhook directive: %s", d.Val())
}
}
g.Webhooks = append(g.Webhooks, webhook)
default: default:
return d.Errf("unknown directive: %s", d.Val()) return d.Errf("unknown directive: %s", d.Val())
} }

608
storage.go Normal file
View File

@ -0,0 +1,608 @@
package sipguardian
import (
"database/sql"
"encoding/json"
"fmt"
"os"
"path/filepath"
"sync"
"time"
"go.uber.org/zap"
_ "modernc.org/sqlite" // Pure Go SQLite driver
)
// StorageConfig holds persistent storage configuration
type StorageConfig struct {
// Path to the SQLite database file
Path string `json:"path,omitempty"`
// SyncInterval for periodic state sync
SyncInterval time.Duration `json:"sync_interval,omitempty"`
// RetainExpired keeps expired bans for analysis (default: 7 days)
RetainExpired time.Duration `json:"retain_expired,omitempty"`
}
// Storage provides persistent storage for SIP Guardian state
type Storage struct {
db *sql.DB
logger *zap.Logger
config StorageConfig
mu sync.Mutex
done chan struct{}
}
// Global storage instance
var (
globalStorage *Storage
storageMu sync.Mutex
)
// GetStorage returns the global storage instance
func GetStorage() *Storage {
storageMu.Lock()
defer storageMu.Unlock()
return globalStorage
}
// InitStorage initializes the persistent storage
func InitStorage(logger *zap.Logger, config StorageConfig) (*Storage, error) {
storageMu.Lock()
defer storageMu.Unlock()
if globalStorage != nil {
return globalStorage, nil
}
// Set defaults
if config.Path == "" {
// Default to data directory
dataDir := os.Getenv("XDG_DATA_HOME")
if dataDir == "" {
homeDir, _ := os.UserHomeDir()
dataDir = filepath.Join(homeDir, ".local", "share")
}
config.Path = filepath.Join(dataDir, "sip-guardian", "guardian.db")
}
if config.SyncInterval == 0 {
config.SyncInterval = 30 * time.Second
}
if config.RetainExpired == 0 {
config.RetainExpired = 7 * 24 * time.Hour // 7 days
}
// Ensure directory exists
dir := filepath.Dir(config.Path)
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, fmt.Errorf("failed to create storage directory: %w", err)
}
// Open database
db, err := sql.Open("sqlite", config.Path)
if err != nil {
return nil, fmt.Errorf("failed to open database: %w", err)
}
// Enable WAL mode for better concurrent access
if _, err := db.Exec("PRAGMA journal_mode=WAL"); err != nil {
db.Close()
return nil, fmt.Errorf("failed to set WAL mode: %w", err)
}
storage := &Storage{
db: db,
logger: logger,
config: config,
done: make(chan struct{}),
}
// Initialize schema
if err := storage.initSchema(); err != nil {
db.Close()
return nil, fmt.Errorf("failed to initialize schema: %w", err)
}
globalStorage = storage
logger.Info("Storage initialized",
zap.String("path", config.Path),
zap.Duration("sync_interval", config.SyncInterval),
)
return storage, nil
}
// initSchema creates the database tables
func (s *Storage) initSchema() error {
schema := `
CREATE TABLE IF NOT EXISTS bans (
ip TEXT PRIMARY KEY,
reason TEXT NOT NULL,
banned_at DATETIME NOT NULL,
expires_at DATETIME NOT NULL,
hit_count INTEGER DEFAULT 0,
unbanned_at DATETIME,
unban_reason TEXT,
metadata TEXT
);
CREATE INDEX IF NOT EXISTS idx_bans_expires ON bans(expires_at);
CREATE INDEX IF NOT EXISTS idx_bans_banned_at ON bans(banned_at);
CREATE TABLE IF NOT EXISTS failures (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ip TEXT NOT NULL,
reason TEXT NOT NULL,
recorded_at DATETIME NOT NULL,
metadata TEXT
);
CREATE INDEX IF NOT EXISTS idx_failures_ip ON failures(ip);
CREATE INDEX IF NOT EXISTS idx_failures_recorded_at ON failures(recorded_at);
CREATE TABLE IF NOT EXISTS stats (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at DATETIME NOT NULL
);
CREATE TABLE IF NOT EXISTS suspicious_patterns (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ip TEXT NOT NULL,
pattern TEXT NOT NULL,
sample TEXT,
detected_at DATETIME NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_suspicious_ip ON suspicious_patterns(ip);
CREATE INDEX IF NOT EXISTS idx_suspicious_pattern ON suspicious_patterns(pattern);
CREATE TABLE IF NOT EXISTS enumeration_attempts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ip TEXT NOT NULL,
reason TEXT NOT NULL,
unique_count INTEGER NOT NULL,
extensions TEXT,
detected_at DATETIME NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_enum_ip ON enumeration_attempts(ip);
CREATE INDEX IF NOT EXISTS idx_enum_detected ON enumeration_attempts(detected_at);
`
_, err := s.db.Exec(schema)
return err
}
// SaveBan persists a ban entry
func (s *Storage) SaveBan(entry *BanEntry) error {
s.mu.Lock()
defer s.mu.Unlock()
_, err := s.db.Exec(`
INSERT OR REPLACE INTO bans (ip, reason, banned_at, expires_at, hit_count)
VALUES (?, ?, ?, ?, ?)
`, entry.IP, entry.Reason, entry.BannedAt, entry.ExpiresAt, entry.HitCount)
if err != nil {
s.logger.Error("Failed to save ban", zap.Error(err), zap.String("ip", entry.IP))
return err
}
s.logger.Debug("Ban saved to storage", zap.String("ip", entry.IP))
return nil
}
// RemoveBan marks a ban as unbanned (keeps for history)
func (s *Storage) RemoveBan(ip, reason string) error {
s.mu.Lock()
defer s.mu.Unlock()
_, err := s.db.Exec(`
UPDATE bans SET unbanned_at = ?, unban_reason = ? WHERE ip = ? AND unbanned_at IS NULL
`, time.Now().UTC(), reason, ip)
if err != nil {
s.logger.Error("Failed to remove ban", zap.Error(err), zap.String("ip", ip))
return err
}
s.logger.Debug("Ban removed from storage", zap.String("ip", ip))
return nil
}
// LoadActiveBans returns all currently active bans
func (s *Storage) LoadActiveBans() ([]BanEntry, error) {
s.mu.Lock()
defer s.mu.Unlock()
rows, err := s.db.Query(`
SELECT ip, reason, banned_at, expires_at, hit_count
FROM bans
WHERE expires_at > ? AND unbanned_at IS NULL
`, time.Now().UTC())
if err != nil {
return nil, fmt.Errorf("failed to query active bans: %w", err)
}
defer rows.Close()
var bans []BanEntry
for rows.Next() {
var entry BanEntry
if err := rows.Scan(&entry.IP, &entry.Reason, &entry.BannedAt, &entry.ExpiresAt, &entry.HitCount); err != nil {
s.logger.Error("Failed to scan ban row", zap.Error(err))
continue
}
bans = append(bans, entry)
}
return bans, rows.Err()
}
// RecordFailure records a failure event for historical analysis
func (s *Storage) RecordFailure(ip, reason string, metadata map[string]interface{}) error {
s.mu.Lock()
defer s.mu.Unlock()
var metadataJSON []byte
if metadata != nil {
var err error
metadataJSON, err = json.Marshal(metadata)
if err != nil {
return fmt.Errorf("failed to marshal metadata: %w", err)
}
}
_, err := s.db.Exec(`
INSERT INTO failures (ip, reason, recorded_at, metadata)
VALUES (?, ?, ?, ?)
`, ip, reason, time.Now().UTC(), metadataJSON)
return err
}
// RecordSuspiciousPattern records a suspicious pattern detection
func (s *Storage) RecordSuspiciousPattern(ip, pattern, sample string) error {
s.mu.Lock()
defer s.mu.Unlock()
_, err := s.db.Exec(`
INSERT INTO suspicious_patterns (ip, pattern, sample, detected_at)
VALUES (?, ?, ?, ?)
`, ip, pattern, sample, time.Now().UTC())
return err
}
// RecordEnumerationAttempt records an enumeration attack detection
func (s *Storage) RecordEnumerationAttempt(ip, reason string, uniqueCount int, extensions []string) error {
s.mu.Lock()
defer s.mu.Unlock()
var extensionsJSON []byte
if extensions != nil {
var err error
extensionsJSON, err = json.Marshal(extensions)
if err != nil {
return fmt.Errorf("failed to marshal extensions: %w", err)
}
}
_, err := s.db.Exec(`
INSERT INTO enumeration_attempts (ip, reason, unique_count, extensions, detected_at)
VALUES (?, ?, ?, ?, ?)
`, ip, reason, uniqueCount, extensionsJSON, time.Now().UTC())
if err != nil {
s.logger.Error("Failed to record enumeration attempt",
zap.Error(err),
zap.String("ip", ip),
zap.String("reason", reason),
)
return err
}
s.logger.Debug("Enumeration attempt recorded",
zap.String("ip", ip),
zap.String("reason", reason),
zap.Int("unique_extensions", uniqueCount),
)
return nil
}
// GetEnumerationStats returns statistics on enumeration attempts
func (s *Storage) GetEnumerationStats(since time.Duration) ([]map[string]interface{}, error) {
s.mu.Lock()
defer s.mu.Unlock()
rows, err := s.db.Query(`
SELECT reason, COUNT(*) as count, COUNT(DISTINCT ip) as unique_ips, AVG(unique_count) as avg_extensions
FROM enumeration_attempts
WHERE detected_at > ?
GROUP BY reason
ORDER BY count DESC
`, time.Now().Add(-since).UTC())
if err != nil {
return nil, fmt.Errorf("failed to query enumeration stats: %w", err)
}
defer rows.Close()
var stats []map[string]interface{}
for rows.Next() {
var reason string
var count, uniqueIPs int
var avgExtensions float64
if err := rows.Scan(&reason, &count, &uniqueIPs, &avgExtensions); err != nil {
continue
}
stats = append(stats, map[string]interface{}{
"reason": reason,
"count": count,
"unique_ips": uniqueIPs,
"avg_extensions": avgExtensions,
})
}
return stats, rows.Err()
}
// GetRecentEnumerationAttempts returns recent enumeration attempts
func (s *Storage) GetRecentEnumerationAttempts(since time.Duration, limit int) ([]map[string]interface{}, error) {
s.mu.Lock()
defer s.mu.Unlock()
if limit == 0 {
limit = 100
}
rows, err := s.db.Query(`
SELECT ip, reason, unique_count, extensions, detected_at
FROM enumeration_attempts
WHERE detected_at > ?
ORDER BY detected_at DESC
LIMIT ?
`, time.Now().Add(-since).UTC(), limit)
if err != nil {
return nil, fmt.Errorf("failed to query recent enumeration attempts: %w", err)
}
defer rows.Close()
var attempts []map[string]interface{}
for rows.Next() {
var ip, reason string
var uniqueCount int
var extensionsJSON sql.NullString
var detectedAt time.Time
if err := rows.Scan(&ip, &reason, &uniqueCount, &extensionsJSON, &detectedAt); err != nil {
continue
}
entry := map[string]interface{}{
"ip": ip,
"reason": reason,
"unique_count": uniqueCount,
"detected_at": detectedAt,
}
if extensionsJSON.Valid {
var extensions []string
if err := json.Unmarshal([]byte(extensionsJSON.String), &extensions); err == nil {
entry["extensions"] = extensions
}
}
attempts = append(attempts, entry)
}
return attempts, rows.Err()
}
// GetBanHistory returns ban history for an IP
func (s *Storage) GetBanHistory(ip string) ([]BanEntry, error) {
s.mu.Lock()
defer s.mu.Unlock()
rows, err := s.db.Query(`
SELECT ip, reason, banned_at, expires_at, hit_count
FROM bans
WHERE ip = ?
ORDER BY banned_at DESC
`, ip)
if err != nil {
return nil, fmt.Errorf("failed to query ban history: %w", err)
}
defer rows.Close()
var bans []BanEntry
for rows.Next() {
var entry BanEntry
if err := rows.Scan(&entry.IP, &entry.Reason, &entry.BannedAt, &entry.ExpiresAt, &entry.HitCount); err != nil {
continue
}
bans = append(bans, entry)
}
return bans, rows.Err()
}
// GetRecentFailures returns recent failures (for analysis)
func (s *Storage) GetRecentFailures(since time.Duration, limit int) ([]map[string]interface{}, error) {
s.mu.Lock()
defer s.mu.Unlock()
if limit == 0 {
limit = 100
}
rows, err := s.db.Query(`
SELECT ip, reason, recorded_at, metadata
FROM failures
WHERE recorded_at > ?
ORDER BY recorded_at DESC
LIMIT ?
`, time.Now().Add(-since).UTC(), limit)
if err != nil {
return nil, fmt.Errorf("failed to query recent failures: %w", err)
}
defer rows.Close()
var failures []map[string]interface{}
for rows.Next() {
var ip, reason string
var recordedAt time.Time
var metadataJSON sql.NullString
if err := rows.Scan(&ip, &reason, &recordedAt, &metadataJSON); err != nil {
continue
}
entry := map[string]interface{}{
"ip": ip,
"reason": reason,
"recorded_at": recordedAt,
}
if metadataJSON.Valid {
var metadata map[string]interface{}
if err := json.Unmarshal([]byte(metadataJSON.String), &metadata); err == nil {
entry["metadata"] = metadata
}
}
failures = append(failures, entry)
}
return failures, rows.Err()
}
// GetTopOffenders returns IPs with most failures/bans
func (s *Storage) GetTopOffenders(since time.Duration, limit int) ([]map[string]interface{}, error) {
s.mu.Lock()
defer s.mu.Unlock()
if limit == 0 {
limit = 10
}
rows, err := s.db.Query(`
SELECT ip, COUNT(*) as count, MAX(recorded_at) as last_seen
FROM failures
WHERE recorded_at > ?
GROUP BY ip
ORDER BY count DESC
LIMIT ?
`, time.Now().Add(-since).UTC(), limit)
if err != nil {
return nil, fmt.Errorf("failed to query top offenders: %w", err)
}
defer rows.Close()
var offenders []map[string]interface{}
for rows.Next() {
var ip string
var count int
var lastSeen time.Time
if err := rows.Scan(&ip, &count, &lastSeen); err != nil {
continue
}
offenders = append(offenders, map[string]interface{}{
"ip": ip,
"count": count,
"last_seen": lastSeen,
})
}
return offenders, rows.Err()
}
// GetPatternStats returns statistics on detected patterns
func (s *Storage) GetPatternStats(since time.Duration) ([]map[string]interface{}, error) {
s.mu.Lock()
defer s.mu.Unlock()
rows, err := s.db.Query(`
SELECT pattern, COUNT(*) as count, COUNT(DISTINCT ip) as unique_ips
FROM suspicious_patterns
WHERE detected_at > ?
GROUP BY pattern
ORDER BY count DESC
`, time.Now().Add(-since).UTC())
if err != nil {
return nil, fmt.Errorf("failed to query pattern stats: %w", err)
}
defer rows.Close()
var stats []map[string]interface{}
for rows.Next() {
var pattern string
var count, uniqueIPs int
if err := rows.Scan(&pattern, &count, &uniqueIPs); err != nil {
continue
}
stats = append(stats, map[string]interface{}{
"pattern": pattern,
"count": count,
"unique_ips": uniqueIPs,
})
}
return stats, rows.Err()
}
// Cleanup removes old data
func (s *Storage) Cleanup() error {
s.mu.Lock()
defer s.mu.Unlock()
cutoff := time.Now().Add(-s.config.RetainExpired).UTC()
// Remove old unbanned entries
_, err := s.db.Exec(`
DELETE FROM bans WHERE unbanned_at IS NOT NULL AND unbanned_at < ?
`, cutoff)
if err != nil {
return fmt.Errorf("failed to cleanup old bans: %w", err)
}
// Remove old failures
_, err = s.db.Exec(`
DELETE FROM failures WHERE recorded_at < ?
`, cutoff)
if err != nil {
return fmt.Errorf("failed to cleanup old failures: %w", err)
}
// Remove old suspicious patterns
_, err = s.db.Exec(`
DELETE FROM suspicious_patterns WHERE detected_at < ?
`, cutoff)
if err != nil {
return fmt.Errorf("failed to cleanup old patterns: %w", err)
}
// Remove old enumeration attempts
_, err = s.db.Exec(`
DELETE FROM enumeration_attempts WHERE detected_at < ?
`, cutoff)
if err != nil {
return fmt.Errorf("failed to cleanup old enumeration attempts: %w", err)
}
s.logger.Debug("Storage cleanup completed", zap.Time("cutoff", cutoff))
return nil
}
// Close closes the database connection
func (s *Storage) Close() error {
close(s.done)
return s.db.Close()
}

344
webhooks.go Normal file
View File

@ -0,0 +1,344 @@
package sipguardian
import (
"bytes"
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"sync"
"time"
"go.uber.org/zap"
)
// WebhookEvent represents an event to be sent via webhook
type WebhookEvent struct {
Type string `json:"type"`
Timestamp time.Time `json:"timestamp"`
Data interface{} `json:"data"`
}
// BanEventData contains data for ban/unban events
type BanEventData struct {
IP string `json:"ip"`
Reason string `json:"reason,omitempty"`
BannedAt time.Time `json:"banned_at,omitempty"`
ExpiresAt time.Time `json:"expires_at,omitempty"`
HitCount int `json:"hit_count,omitempty"`
Duration string `json:"duration,omitempty"`
}
// SuspiciousEventData contains data for suspicious activity events
type SuspiciousEventData struct {
IP string `json:"ip"`
Pattern string `json:"pattern"`
Sample string `json:"sample,omitempty"`
FailureCount int `json:"failure_count"`
}
// EnumerationEventData contains data for enumeration detection events
type EnumerationEventData struct {
IP string `json:"ip"`
Reason string `json:"reason"`
UniqueCount int `json:"unique_count"`
Extensions []string `json:"extensions,omitempty"`
SeqStart int `json:"seq_start,omitempty"`
SeqEnd int `json:"seq_end,omitempty"`
}
// WebhookConfig holds webhook configuration
type WebhookConfig struct {
// URL to send webhook events to
URL string `json:"url"`
// Secret for HMAC signature (optional)
Secret string `json:"secret,omitempty"`
// Events to subscribe to (default: all)
// Options: "ban", "unban", "suspicious", "failure"
Events []string `json:"events,omitempty"`
// Timeout for webhook requests
Timeout time.Duration `json:"timeout,omitempty"`
// RetryCount for failed webhook deliveries
RetryCount int `json:"retry_count,omitempty"`
// Headers to include in webhook requests
Headers map[string]string `json:"headers,omitempty"`
}
// WebhookManager handles webhook dispatching
type WebhookManager struct {
configs []WebhookConfig
client *http.Client
logger *zap.Logger
mu sync.RWMutex
// Channel for async event dispatching
eventChan chan WebhookEvent
done chan struct{}
}
// Global webhook manager instance
var (
webhookManager *WebhookManager
webhookMu sync.Mutex
)
// GetWebhookManager returns the global webhook manager, creating it if necessary
func GetWebhookManager(logger *zap.Logger) *WebhookManager {
webhookMu.Lock()
defer webhookMu.Unlock()
if webhookManager == nil {
webhookManager = &WebhookManager{
configs: []WebhookConfig{},
client: &http.Client{Timeout: 10 * time.Second},
logger: logger,
eventChan: make(chan WebhookEvent, 100),
done: make(chan struct{}),
}
go webhookManager.dispatcher()
}
return webhookManager
}
// AddWebhook registers a new webhook endpoint
func (wm *WebhookManager) AddWebhook(config WebhookConfig) {
wm.mu.Lock()
defer wm.mu.Unlock()
// Set defaults
if config.Timeout == 0 {
config.Timeout = 10 * time.Second
}
if config.RetryCount == 0 {
config.RetryCount = 3
}
if len(config.Events) == 0 {
config.Events = []string{"ban", "unban", "suspicious", "failure", "enumeration"}
}
wm.configs = append(wm.configs, config)
wm.logger.Info("Webhook registered",
zap.String("url", config.URL),
zap.Strings("events", config.Events),
)
}
// ClearWebhooks removes all registered webhooks
func (wm *WebhookManager) ClearWebhooks() {
wm.mu.Lock()
defer wm.mu.Unlock()
wm.configs = []WebhookConfig{}
}
// Emit sends an event to all subscribed webhooks
func (wm *WebhookManager) Emit(eventType string, data interface{}) {
event := WebhookEvent{
Type: eventType,
Timestamp: time.Now().UTC(),
Data: data,
}
select {
case wm.eventChan <- event:
// Event queued
default:
wm.logger.Warn("Webhook event queue full, dropping event",
zap.String("type", eventType),
)
}
}
// dispatcher processes events from the channel
func (wm *WebhookManager) dispatcher() {
for {
select {
case <-wm.done:
return
case event := <-wm.eventChan:
wm.dispatch(event)
}
}
}
// dispatch sends an event to all matching webhooks
func (wm *WebhookManager) dispatch(event WebhookEvent) {
wm.mu.RLock()
configs := make([]WebhookConfig, len(wm.configs))
copy(configs, wm.configs)
wm.mu.RUnlock()
for _, config := range configs {
if wm.shouldSend(config, event.Type) {
go wm.send(config, event)
}
}
}
// shouldSend checks if an event type matches the webhook's subscriptions
func (wm *WebhookManager) shouldSend(config WebhookConfig, eventType string) bool {
for _, e := range config.Events {
if e == eventType || e == "all" {
return true
}
}
return false
}
// send delivers a webhook event with retries
func (wm *WebhookManager) send(config WebhookConfig, event WebhookEvent) {
payload, err := json.Marshal(event)
if err != nil {
wm.logger.Error("Failed to marshal webhook event",
zap.Error(err),
zap.String("type", event.Type),
)
return
}
var lastErr error
for attempt := 0; attempt <= config.RetryCount; attempt++ {
if attempt > 0 {
// Exponential backoff
time.Sleep(time.Duration(attempt*attempt) * time.Second)
}
ctx, cancel := context.WithTimeout(context.Background(), config.Timeout)
req, err := http.NewRequestWithContext(ctx, "POST", config.URL, bytes.NewReader(payload))
if err != nil {
cancel()
lastErr = err
continue
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "SIP-Guardian-Webhook/1.0")
req.Header.Set("X-SIP-Guardian-Event", event.Type)
// Add custom headers
for k, v := range config.Headers {
req.Header.Set(k, v)
}
// Add HMAC signature if secret is configured
if config.Secret != "" {
signature := computeHMAC(payload, config.Secret)
req.Header.Set("X-SIP-Guardian-Signature", signature)
}
resp, err := wm.client.Do(req)
cancel()
if err != nil {
lastErr = err
wm.logger.Debug("Webhook delivery failed, retrying",
zap.String("url", config.URL),
zap.Int("attempt", attempt+1),
zap.Error(err),
)
continue
}
resp.Body.Close()
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
wm.logger.Debug("Webhook delivered successfully",
zap.String("url", config.URL),
zap.String("type", event.Type),
zap.Int("status", resp.StatusCode),
)
return
}
lastErr = fmt.Errorf("unexpected status code: %d", resp.StatusCode)
wm.logger.Debug("Webhook returned non-success status",
zap.String("url", config.URL),
zap.Int("status", resp.StatusCode),
zap.Int("attempt", attempt+1),
)
}
wm.logger.Error("Webhook delivery failed after retries",
zap.String("url", config.URL),
zap.String("type", event.Type),
zap.Error(lastErr),
)
}
// Stop gracefully shuts down the webhook manager
func (wm *WebhookManager) Stop() {
close(wm.done)
}
// computeHMAC generates an HMAC-SHA256 signature for webhook verification
func computeHMAC(payload []byte, secret string) string {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(payload)
return hex.EncodeToString(mac.Sum(nil))
}
// Helper functions for emitting specific events
// EmitBanEvent sends a ban notification
func EmitBanEvent(logger *zap.Logger, entry *BanEntry) {
wm := GetWebhookManager(logger)
wm.Emit("ban", BanEventData{
IP: entry.IP,
Reason: entry.Reason,
BannedAt: entry.BannedAt,
ExpiresAt: entry.ExpiresAt,
HitCount: entry.HitCount,
Duration: entry.ExpiresAt.Sub(entry.BannedAt).String(),
})
}
// EmitUnbanEvent sends an unban notification
func EmitUnbanEvent(logger *zap.Logger, ip string, reason string) {
wm := GetWebhookManager(logger)
wm.Emit("unban", BanEventData{
IP: ip,
Reason: reason,
})
}
// EmitSuspiciousEvent sends a suspicious activity notification
func EmitSuspiciousEvent(logger *zap.Logger, ip, pattern, sample string, failureCount int) {
wm := GetWebhookManager(logger)
wm.Emit("suspicious", SuspiciousEventData{
IP: ip,
Pattern: pattern,
Sample: sample,
FailureCount: failureCount,
})
}
// EmitFailureEvent sends a failure notification
func EmitFailureEvent(logger *zap.Logger, ip, reason string, count int) {
wm := GetWebhookManager(logger)
wm.Emit("failure", map[string]interface{}{
"ip": ip,
"reason": reason,
"count": count,
})
}
// EmitEnumerationEvent sends an enumeration detection notification
func EmitEnumerationEvent(logger *zap.Logger, ip string, result EnumerationResult) {
wm := GetWebhookManager(logger)
wm.Emit("enumeration", EnumerationEventData{
IP: ip,
Reason: result.Reason,
UniqueCount: result.UniqueCount,
Extensions: result.Extensions,
SeqStart: result.SeqStart,
SeqEnd: result.SeqEnd,
})
}