diff --git a/Dockerfile.package b/Dockerfile.package index a86373c..d85637e 100644 --- a/Dockerfile.package +++ b/Dockerfile.package @@ -35,7 +35,7 @@ COPY . . # Build binary for Linux # Binary will be dynamically linked but compatible with all RHEL-based distros -ARG VERSION=1.0.7 +ARG VERSION=1.0.8 ARG BUILD_TIME="" ARG GIT_COMMIT="" RUN mkdir -p dist && \ @@ -53,7 +53,7 @@ FROM rockylinux:9 AS rpm-builder WORKDIR /package # VERSION must be redeclared for each stage that needs it -ARG VERSION=1.0.7 +ARG VERSION=1.0.8 # Install rpm-build tools (Rocky Linux 9) RUN dnf install -y \ @@ -72,7 +72,7 @@ COPY packaging/rpm/ja4sentinel.spec /root/rpmbuild/SPECS/ja4sentinel.spec # Copy binary from Go builder and other files to SOURCES COPY --from=builder /build/dist/ja4sentinel /root/rpmbuild/SOURCES/ja4sentinel COPY packaging/systemd/ja4sentinel.service /root/rpmbuild/SOURCES/ja4sentinel.service -COPY packaging/systemd/config.yml /root/rpmbuild/SOURCES/config.yml +COPY config.yml.example /root/rpmbuild/SOURCES/config.yml # Set permissions RUN chmod 755 /root/rpmbuild/SOURCES/ja4sentinel && \ diff --git a/api/types.go b/api/types.go index 4da76a6..4a19970 100644 --- a/api/types.go +++ b/api/types.go @@ -23,6 +23,7 @@ type Config struct { BPFFilter string `json:"bpf_filter,omitempty"` FlowTimeoutSec int `json:"flow_timeout_sec,omitempty"` // Timeout for TLS handshake extraction (default: 30) PacketBufferSize int `json:"packet_buffer_size,omitempty"` // Buffer size for packet channel (default: 1000) + LogLevel string `json:"log_level,omitempty"` // Log level: debug, info, warn, error (default: info) } // IPMeta contains IP metadata for stack fingerprinting @@ -241,6 +242,7 @@ const ( DefaultBPFFilter = "" DefaultFlowTimeout = 30 // seconds DefaultPacketBuffer = 1000 // packet channel buffer size + DefaultLogLevel = "info" ) // DefaultConfig returns an AppConfig with sensible default values. @@ -256,6 +258,7 @@ func DefaultConfig() AppConfig { BPFFilter: DefaultBPFFilter, FlowTimeoutSec: DefaultFlowTimeout, PacketBufferSize: DefaultPacketBuffer, + LogLevel: DefaultLogLevel, }, Outputs: []OutputConfig{}, } diff --git a/cmd/ja4sentinel/main.go b/cmd/ja4sentinel/main.go index 4268aeb..dbe8686 100644 --- a/cmd/ja4sentinel/main.go +++ b/cmd/ja4sentinel/main.go @@ -22,7 +22,7 @@ import ( var ( // Version information (set via ldflags) - Version = "1.0.7" + Version = "1.0.8" BuildTime = "unknown" GitCommit = "unknown" ) @@ -38,9 +38,22 @@ func main() { os.Exit(0) } - // Create logger factory + // Load configuration + cfgLoader := config.NewLoader(*configPath) + appConfig, err := cfgLoader.Load() + if err != nil { + // Create logger with default level for error reporting + loggerFactory := &logging.LoggerFactory{} + appLogger := loggerFactory.NewDefaultLogger() + appLogger.Error("main", "Failed to load configuration", map[string]string{ + "error": err.Error(), + }) + os.Exit(1) + } + + // Create logger factory with configured log level loggerFactory := &logging.LoggerFactory{} - appLogger := loggerFactory.NewDefaultLogger() + appLogger := loggerFactory.NewLogger(appConfig.Core.LogLevel) appLogger.Info("main", "Starting ja4sentinel", map[string]string{ "version": Version, @@ -48,19 +61,10 @@ func main() { "git_commit": GitCommit, }) - // Load configuration - cfgLoader := config.NewLoader(*configPath) - appConfig, err := cfgLoader.Load() - if err != nil { - appLogger.Error("main", "Failed to load configuration", map[string]string{ - "error": err.Error(), - }) - os.Exit(1) - } - appLogger.Info("main", "Configuration loaded", map[string]string{ "interface": appConfig.Core.Interface, "listen_ports": formatPorts(appConfig.Core.ListenPorts), + "log_level": appConfig.Core.LogLevel, }) // Create context with cancellation for graceful shutdown diff --git a/config.yml.example b/config.yml.example index 5205096..d6e76fa 100644 --- a/config.yml.example +++ b/config.yml.example @@ -19,6 +19,10 @@ core: # Buffer size for packet channel (default: 1000, increase for high-traffic environments) packet_buffer_size: 1000 + # Log level: debug, info, warn, error (default: info) + # Can be overridden by JA4SENTINEL_LOG_LEVEL environment variable + log_level: info + outputs: # Output to stdout (JSON lines) - type: stdout diff --git a/internal/config/loader.go b/internal/config/loader.go index e345d3e..40845bd 100644 --- a/internal/config/loader.go +++ b/internal/config/loader.go @@ -104,6 +104,11 @@ func (l *LoaderImpl) loadFromEnv(config api.AppConfig) api.AppConfig { } } + // JA4SENTINEL_LOG_LEVEL + if val := os.Getenv("JA4SENTINEL_LOG_LEVEL"); val != "" { + config.Core.LogLevel = val + } + return config } @@ -166,6 +171,10 @@ func mergeConfigs(base, override api.AppConfig) api.AppConfig { result.Core.PacketBufferSize = override.Core.PacketBufferSize } + if override.Core.LogLevel != "" { + result.Core.LogLevel = override.Core.LogLevel + } + if len(override.Outputs) > 0 { result.Outputs = override.Outputs } @@ -196,6 +205,19 @@ func (l *LoaderImpl) validate(config api.AppConfig) error { return fmt.Errorf("packet_buffer_size must be between 1 and 1000000") } + // Validate log level + validLogLevels := map[string]struct{}{ + "debug": {}, + "info": {}, + "warn": {}, + "error": {}, + } + if config.Core.LogLevel != "" { + if _, ok := validLogLevels[config.Core.LogLevel]; !ok { + return fmt.Errorf("log_level must be one of: debug, info, warn, error") + } + } + allowedTypes := map[string]struct{}{ "stdout": {}, "file": {}, diff --git a/packaging/rpm/ja4sentinel.spec b/packaging/rpm/ja4sentinel.spec index bdd110c..06e74ed 100644 --- a/packaging/rpm/ja4sentinel.spec +++ b/packaging/rpm/ja4sentinel.spec @@ -3,7 +3,7 @@ %if %{defined build_version} %define spec_version %{build_version} %else -%define spec_version 1.0.7 +%define spec_version 1.0.8 %endif Name: ja4sentinel @@ -117,6 +117,12 @@ fi %dir /var/run/logcorrelator %changelog +* Sun Mar 01 2026 Jacquin Antoine - 1.0.8-1 +- Add configurable log level (debug, info, warn, error) via config.yml +- Add JA4SENTINEL_LOG_LEVEL environment variable support +- Set TimeoutStopSec=2 for immediate service stop on restart/stop +- Consolidate config files into single example (config.yml.example) + * Sat Feb 28 2026 Jacquin Antoine - 1.0.4-1 - Add systemd sdnotify support (READY, WATCHDOG, STOPPING signals) - Enable systemd watchdog with 30s timeout diff --git a/packaging/systemd/config.yml b/packaging/systemd/config.yml deleted file mode 100644 index a248b46..0000000 --- a/packaging/systemd/config.yml +++ /dev/null @@ -1,39 +0,0 @@ -# Default configuration file for ja4sentinel -# This file is installed as /etc/ja4sentinel/config.yml.default - -core: - # Network interface to capture traffic from - # Will be overridden by JA4SENTINEL_INTERFACE env var if set - interface: eth0 - - # TCP ports to monitor for TLS handshakes - listen_ports: - - 443 - - 8443 - - # Optional BPF filter (leave empty for auto-generated filter based on listen_ports) - bpf_filter: "" - - # Timeout in seconds for TLS handshake extraction (default: 30) - flow_timeout_sec: 30 - - # Buffer size for packet channel (default: 1000, increase for high-traffic environments) - packet_buffer_size: 1000 - -outputs: - # Output to stdout (JSON lines) - disabled by default for production - - type: stdout - enabled: false - params: {} - - # Output to file - - type: file - enabled: true - params: - path: /var/log/ja4sentinel/ja4.log - - # Output to UNIX socket (for systemd/journald or other consumers) - - type: unix_socket - enabled: true - params: - socket_path: /var/run/ja4sentinel.sock diff --git a/packaging/systemd/ja4sentinel.service b/packaging/systemd/ja4sentinel.service index 92d8732..7967163 100644 --- a/packaging/systemd/ja4sentinel.service +++ b/packaging/systemd/ja4sentinel.service @@ -13,6 +13,7 @@ ExecStart=/usr/bin/ja4sentinel --config /etc/ja4sentinel/config.yml Restart=on-failure RestartSec=5 WatchdogSec=30 +TimeoutStopSec=2 NotifyAccess=main Environment=JA4SENTINEL_LOG_LEVEL=info