Files
caddy-waf/types.go
2025-12-06 23:08:54 +01:00

203 lines
6.2 KiB
Go

package caddywaf
import (
"regexp"
"sync"
"time"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/oschwald/maxminddb-golang"
"github.com/phemmer/go-iptrie"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// Package caddywaf is a Caddy module providing web application firewall functionality.
// ==================== Constants and Globals ====================
var (
_ caddy.Module = (*Middleware)(nil)
_ caddy.Provisioner = (*Middleware)(nil)
_ caddyhttp.MiddlewareHandler = (*Middleware)(nil)
_ caddyfile.Unmarshaler = (*Middleware)(nil)
_ caddy.Validator = (*Middleware)(nil)
)
// Define custom types for rule hits
type (
RuleID string
HitCount int
)
// RuleCache caches compiled regex patterns for rules.
type RuleCache struct {
mu sync.RWMutex
rules map[string]*regexp.Regexp
}
// CountryAccessFilter struct
type CountryAccessFilter struct {
Enabled bool `json:"enabled"`
CountryList []string `json:"country_list"`
GeoIPDBPath string `json:"geoip_db_path"`
geoIP *maxminddb.Reader `json:"-"` // Explicitly mark as not serialized
}
// ASNAccessFilter struct
type ASNAccessFilter struct {
Enabled bool `json:"enabled"`
BlockedASNs []string `json:"blocked_asns"`
GeoIPDBPath string `json:"geoip_db_path"`
geoIP *maxminddb.Reader `json:"-"` // Explicitly mark as not serialized
}
// GeoIPRecord struct
type GeoIPRecord struct {
Country struct {
ISOCode string `maxminddb:"iso_code"`
} `maxminddb:"country"`
}
// ASNRecord struct
type ASNRecord struct {
AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"`
AutonomousSystemNumber uint `maxminddb:"autonomous_system_number"`
}
// Rule struct
type Rule struct {
ID string `json:"id"`
Phase int `json:"phase"`
Pattern string `json:"pattern"`
Targets []string `json:"targets"`
Severity string `json:"severity"` // Used for logging only
Score int `json:"score"`
Action string `json:"mode"` // CRITICAL FIX: This should map to the "mode" field in JSON
Description string `json:"description"`
regex *regexp.Regexp
Priority int // New field for rule priority
}
// CustomBlockResponse struct
type CustomBlockResponse struct {
StatusCode int
Headers map[string]string
Body string
}
// WAFState struct
type WAFState struct {
TotalScore int
Blocked bool
StatusCode int
ResponseWritten bool
}
// Middleware is the main WAF middleware struct that implements Caddy's
// Module, Provisioner, Validator, and MiddlewareHandler interfaces.
//
// It provides comprehensive web application firewall functionality including:
// - Rule-based request filtering
// - IP and DNS blacklisting
// - Geographic access control
// - Rate limiting
// - Anomaly detection
// - Custom response handling
// - Real-time metrics and monitoring
//
// The middleware can be configured via Caddyfile or JSON and integrates
// seamlessly into Caddy's request processing pipeline.
type Middleware struct {
mu sync.RWMutex
RuleFiles []string `json:"rule_files"`
IPBlacklistFile string `json:"ip_blacklist_file"`
DNSBlacklistFile string `json:"dns_blacklist_file"`
AnomalyThreshold int `json:"anomaly_threshold"`
CountryBlacklist CountryAccessFilter `json:"country_blacklist"`
CountryWhitelist CountryAccessFilter `json:"country_whitelist"`
BlockASNs ASNAccessFilter `json:"block_asns"`
Rules map[int][]Rule `json:"-"`
ipBlacklist *iptrie.Trie `json:"-"`
dnsBlacklist map[string]struct{} `json:"-"` // Changed to map[string]struct{}
logger *zap.Logger
LogSeverity string `json:"log_severity,omitempty"`
LogJSON bool `json:"log_json,omitempty"`
logLevel zapcore.Level
isShuttingDown bool
geoIPCacheTTL time.Duration
geoIPLookupFallbackBehavior string
CustomResponses map[int]CustomBlockResponse `json:"custom_responses,omitempty"`
LogFilePath string
LogBuffer int `json:"log_buffer,omitempty"` // Add the LogBuffer field
RedactSensitiveData bool `json:"redact_sensitive_data,omitempty"`
MaxRequestBodySize int64 `json:"max_request_body_size,omitempty"`
GeoIPFailOpen bool `json:"geoip_fail_open,omitempty"`
ruleHits sync.Map `json:"-"`
MetricsEndpoint string `json:"metrics_endpoint,omitempty"`
configLoader *ConfigLoader
blacklistLoader *BlacklistLoader
geoIPHandler *GeoIPHandler
requestValueExtractor *RequestValueExtractor
RateLimit RateLimit
rateLimiter *RateLimiter
totalRequests int64
blockedRequests int64
allowedRequests int64
ruleHitsByPhase map[int]int64
geoIPStats map[string]int64 // Key: country code, Value: count
muMetrics sync.RWMutex // Mutex for metrics synchronization
rateLimiterBlockedRequests int64 // Add rate limiter blocked requests metric
muRateLimiterMetrics sync.RWMutex // Mutex to protect rate limiter metrics
geoIPBlocked int
Tor TorConfig `json:"tor,omitempty"`
logChan chan LogEntry // Buffered channel for log entries
logDone chan struct{} // Signal to stop the logging worker
ruleCache *RuleCache // New field for RuleCache
IPBlacklistBlockCount int64 `json:"ip_blacklist_hits"`
muIPBlacklistMetrics sync.Mutex
DNSBlacklistBlockCount int64 `json:"dns_blacklist_hits"`
muDNSBlacklistMetrics sync.Mutex
}
// ==================== Constructors (New functions) ====================
// NewRuleCache creates a new RuleCache.
func NewRuleCache() *RuleCache {
return &RuleCache{
rules: make(map[string]*regexp.Regexp),
}
}
// ==================== RuleCache Methods ====================
// Get retrieves a compiled regex pattern from the cache.
func (rc *RuleCache) Get(ruleID string) (*regexp.Regexp, bool) {
rc.mu.RLock()
defer rc.mu.RUnlock()
regex, exists := rc.rules[ruleID]
return regex, exists
}
// Set stores a compiled regex pattern in the cache.
func (rc *RuleCache) Set(ruleID string, regex *regexp.Regexp) {
rc.mu.Lock()
defer rc.mu.Unlock()
rc.rules[ruleID] = regex
}