fix(waf): Engine nur bei Konfigurationsänderung neu bauen — v1.2.75
Manager.Reload() hat bisher bei jedem 30s-Tick alle Engines neu gebaut (BuildEngine mit CRS = 2-5s). Fix: configKey (enabled, mode, paranoia_level, updatedAt) cachen — Engine wird nur neu gebaut wenn sich der Key ändert. Spart CPU und verhindert sporadische Latenzen im SPOE-Handling. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,14 +17,24 @@ type DomainEngine struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Manager holds per-domain Coraza engine instances. Engines are
|
// Manager holds per-domain Coraza engine instances. Engines are
|
||||||
// created lazily on first Reload() and cached until the next reload.
|
// rebuilt only when their configuration changes (UpdatedAt differs).
|
||||||
// All public methods are safe for concurrent use.
|
// All public methods are safe for concurrent use.
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
engines map[string]*DomainEngine // hostname → engine (nil entry = disabled)
|
engines map[string]*DomainEngine // hostname → engine (nil entry = disabled)
|
||||||
|
configKeys map[string]configKey // hostname → last-seen config fingerprint
|
||||||
crsDir string
|
crsDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// configKey identifies a specific WAF config snapshot so we only
|
||||||
|
// rebuild the engine when something actually changed.
|
||||||
|
type configKey struct {
|
||||||
|
enabled bool
|
||||||
|
mode string
|
||||||
|
paranoiaLevel int
|
||||||
|
updatedAt int64 // unix nano
|
||||||
|
}
|
||||||
|
|
||||||
// NewManager creates an empty Manager with the given CRS directory.
|
// NewManager creates an empty Manager with the given CRS directory.
|
||||||
func NewManager(crsDir string) *Manager {
|
func NewManager(crsDir string) *Manager {
|
||||||
if crsDir == "" {
|
if crsDir == "" {
|
||||||
@@ -32,6 +42,7 @@ func NewManager(crsDir string) *Manager {
|
|||||||
}
|
}
|
||||||
return &Manager{
|
return &Manager{
|
||||||
engines: make(map[string]*DomainEngine),
|
engines: make(map[string]*DomainEngine),
|
||||||
|
configKeys: make(map[string]configKey),
|
||||||
crsDir: crsDir,
|
crsDir: crsDir,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,30 +53,55 @@ type DomainConfig struct {
|
|||||||
Config models.WafConfig
|
Config models.WafConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload rebuilds all engine instances from the given list. Domains
|
// Reload refreshes engines from the given list, rebuilding only when
|
||||||
// that are disabled get a nil entry so GetForHost returns quickly
|
// the config has actually changed since the last call.
|
||||||
// without looking up a missing key.
|
|
||||||
func (m *Manager) Reload(domains []DomainConfig) error {
|
func (m *Manager) Reload(domains []DomainConfig) error {
|
||||||
engines := make(map[string]*DomainEngine, len(domains))
|
m.mu.RLock()
|
||||||
|
prevEngines := m.engines
|
||||||
|
prevKeys := m.configKeys
|
||||||
|
m.mu.RUnlock()
|
||||||
|
|
||||||
|
newEngines := make(map[string]*DomainEngine, len(domains))
|
||||||
|
newKeys := make(map[string]configKey, len(domains))
|
||||||
|
|
||||||
for _, dc := range domains {
|
for _, dc := range domains {
|
||||||
|
ck := configKey{
|
||||||
|
enabled: dc.Config.Enabled,
|
||||||
|
mode: dc.Config.Mode,
|
||||||
|
paranoiaLevel: dc.Config.ParanoiaLevel,
|
||||||
|
updatedAt: dc.Config.UpdatedAt.UnixNano(),
|
||||||
|
}
|
||||||
|
newKeys[dc.Hostname] = ck
|
||||||
|
|
||||||
if !dc.Config.Enabled {
|
if !dc.Config.Enabled {
|
||||||
engines[dc.Hostname] = nil
|
newEngines[dc.Hostname] = nil
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reuse existing engine if config hasn't changed.
|
||||||
|
if prev, ok := prevKeys[dc.Hostname]; ok && prev == ck {
|
||||||
|
if existing := prevEngines[dc.Hostname]; existing != nil {
|
||||||
|
newEngines[dc.Hostname] = existing
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
waf, err := BuildEngine(dc.Config, m.crsDir)
|
waf, err := BuildEngine(dc.Config, m.crsDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("waf: build engine for %s: %w", dc.Hostname, err)
|
return fmt.Errorf("waf: build engine for %s: %w", dc.Hostname, err)
|
||||||
}
|
}
|
||||||
engines[dc.Hostname] = &DomainEngine{WAF: waf, Mode: dc.Config.Mode}
|
newEngines[dc.Hostname] = &DomainEngine{WAF: waf, Mode: dc.Config.Mode}
|
||||||
slog.Info("waf: engine loaded",
|
slog.Info("waf: engine (re)loaded",
|
||||||
"host", dc.Hostname,
|
"host", dc.Hostname,
|
||||||
"mode", dc.Config.Mode,
|
"mode", dc.Config.Mode,
|
||||||
"paranoia_level", dc.Config.ParanoiaLevel,
|
"paranoia_level", dc.Config.ParanoiaLevel,
|
||||||
"crs", crsAvailable(m.crsDir),
|
"crs", crsAvailable(m.crsDir),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
m.engines = engines
|
m.engines = newEngines
|
||||||
|
m.configKeys = newKeys
|
||||||
m.mu.Unlock()
|
m.mu.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user