v1.1.11: Fix exclude_source_ips config loading and debug logging

Major fixes:
- Add exclude_source_ips to mergeConfigs() - config file values now properly loaded
- Add validation for exclude_source_ips (IP/CIDR format validation)
- Remove JA4SENTINEL_LOG_LEVEL env var from systemd service
- Config file log_level now respected without env override

Debug logging improvements:
- Log IP filter entries at startup (debug mode)
- Track filtered packet count with atomic counter
- Display filter statistics at shutdown via GetFilterStats()
- New debug logs in tlsparse component

Testing:
- Add 6 new unit tests for exclude_source_ips and log_level config loading
- Test mergeConfigs() behavior with empty/override values
- Test validation of invalid IPs and CIDR ranges

Documentation:
- Update architecture.yml with ipfilter module
- Document config loading priority and notes
- Update api.Config fields (LocalIPs, ExcludeSourceIPs, LogLevel)

Files changed:
- internal/config/loader.go (merge, validation, helpers)
- internal/config/loader_test.go (6 new tests)
- internal/tlsparse/parser.go (GetFilterStats, counter)
- cmd/ja4sentinel/main.go (debug logging)
- packaging/systemd/ja4sentinel.service (remove env var)
- architecture.yml (ipfilter module, config_loading section)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
toto
2026-03-04 15:55:00 +01:00
parent 952701d4da
commit bd45344d19
7 changed files with 426 additions and 7 deletions

View File

@ -175,6 +175,11 @@ func mergeConfigs(base, override api.AppConfig) api.AppConfig {
result.Core.LogLevel = override.Core.LogLevel
}
// Merge exclude_source_ips (override takes precedence)
if len(override.Core.ExcludeSourceIPs) > 0 {
result.Core.ExcludeSourceIPs = override.Core.ExcludeSourceIPs
}
if len(override.Outputs) > 0 {
result.Outputs = override.Outputs
}
@ -218,6 +223,27 @@ func (l *LoaderImpl) validate(config api.AppConfig) error {
}
}
// Validate exclude_source_ips (if provided)
if len(config.Core.ExcludeSourceIPs) > 0 {
for i, ip := range config.Core.ExcludeSourceIPs {
if ip == "" {
return fmt.Errorf("exclude_source_ips[%d]: entry cannot be empty", i)
}
// Basic validation: check if it looks like an IP or CIDR
if !strings.Contains(ip, "/") {
// Single IP - basic check
if !isValidIP(ip) {
return fmt.Errorf("exclude_source_ips[%d]: invalid IP address %q", i, ip)
}
} else {
// CIDR - basic check
if !isValidCIDR(ip) {
return fmt.Errorf("exclude_source_ips[%d]: invalid CIDR %q", i, ip)
}
}
}
}
allowedTypes := map[string]struct{}{
"stdout": {},
"file": {},
@ -257,3 +283,47 @@ func ToJSON(config api.AppConfig) string {
}
return string(data)
}
// isValidIP checks if a string is a valid IP address
func isValidIP(ip string) bool {
if ip == "" {
return false
}
// Simple validation: check if it contains only valid IP characters
for _, ch := range ip {
if !((ch >= '0' && ch <= '9') || ch == '.') {
// Could be IPv6
if ch == ':' {
return true // Accept IPv6 without detailed validation
}
return false
}
}
return true
}
// isValidCIDR checks if a string is a valid CIDR notation
func isValidCIDR(cidr string) bool {
if cidr == "" {
return false
}
parts := strings.Split(cidr, "/")
if len(parts) != 2 {
return false
}
// Check IP part
if !isValidIP(parts[0]) {
return false
}
// Check prefix length
prefix, err := strconv.Atoi(parts[1])
if err != nil {
return false
}
if strings.Contains(parts[0], ":") {
// IPv6
return prefix >= 0 && prefix <= 128
}
// IPv4
return prefix >= 0 && prefix <= 32
}