feat: add logrotate support with SIGHUP signal handling
Some checks failed
Build RPM Package / Build RPM Packages (CentOS 7, Rocky 8/9/10) (push) Has been cancelled

- Add Reopenable interface in api/types.go for log rotation support
- Add FileWriter.Reopen() method to reopen log files after rotation
- Add MultiWriter.Reopen() method to propagate rotation to all writers
- Update main.go to handle SIGHUP signal for systemctl reload
- Add logrotate configuration file (packaging/logrotate/ja4sentinel)
- Update systemd service with ExecReload for graceful rotation
- Update architecture.yml with logrotate documentation
- Update RPM spec and Dockerfile.package to include logrotate files
- Bump version to 1.1.0

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
Jacquin Antoine
2026-03-02 20:50:47 +01:00
parent 965720a183
commit 52c9f2f6f4
8 changed files with 149 additions and 19 deletions

View File

@ -35,7 +35,7 @@ COPY . .
# Build binary for Linux # Build binary for Linux
# Binary will be dynamically linked but compatible with all RHEL-based distros # Binary will be dynamically linked but compatible with all RHEL-based distros
ARG VERSION=1.0.8 ARG VERSION=1.1.0
ARG BUILD_TIME="" ARG BUILD_TIME=""
ARG GIT_COMMIT="" ARG GIT_COMMIT=""
RUN mkdir -p dist && \ RUN mkdir -p dist && \
@ -53,7 +53,7 @@ FROM rockylinux:9 AS rpm-builder
WORKDIR /package WORKDIR /package
# VERSION must be redeclared for each stage that needs it # VERSION must be redeclared for each stage that needs it
ARG VERSION=1.0.8 ARG VERSION=1.1.0
# Install rpm-build tools (Rocky Linux 9) # Install rpm-build tools (Rocky Linux 9)
RUN dnf install -y \ RUN dnf install -y \
@ -64,7 +64,8 @@ RUN dnf install -y \
&& dnf clean all && dnf clean all
# Setup rpmbuild directory structure # Setup rpmbuild directory structure
RUN mkdir -p /root/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} RUN mkdir -p /root/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} && \
mkdir -p /root/rpmbuild/SOURCES/logrotate
# Copy spec file # Copy spec file
COPY packaging/rpm/ja4sentinel.spec /root/rpmbuild/SPECS/ja4sentinel.spec COPY packaging/rpm/ja4sentinel.spec /root/rpmbuild/SPECS/ja4sentinel.spec
@ -72,11 +73,13 @@ COPY packaging/rpm/ja4sentinel.spec /root/rpmbuild/SPECS/ja4sentinel.spec
# Copy binary from Go builder and other files to SOURCES # Copy binary from Go builder and other files to SOURCES
COPY --from=builder /build/dist/ja4sentinel /root/rpmbuild/SOURCES/ja4sentinel COPY --from=builder /build/dist/ja4sentinel /root/rpmbuild/SOURCES/ja4sentinel
COPY packaging/systemd/ja4sentinel.service /root/rpmbuild/SOURCES/ja4sentinel.service COPY packaging/systemd/ja4sentinel.service /root/rpmbuild/SOURCES/ja4sentinel.service
COPY packaging/logrotate/ja4sentinel /root/rpmbuild/SOURCES/logrotate/ja4sentinel
COPY config.yml.example /root/rpmbuild/SOURCES/config.yml COPY config.yml.example /root/rpmbuild/SOURCES/config.yml
# Set permissions # Set permissions
RUN chmod 755 /root/rpmbuild/SOURCES/ja4sentinel && \ RUN chmod 755 /root/rpmbuild/SOURCES/ja4sentinel && \
chmod 644 /root/rpmbuild/SOURCES/ja4sentinel.service && \ chmod 644 /root/rpmbuild/SOURCES/ja4sentinel.service && \
chmod 644 /root/rpmbuild/SOURCES/logrotate/ja4sentinel && \
chmod 640 /root/rpmbuild/SOURCES/config.yml chmod 640 /root/rpmbuild/SOURCES/config.yml
# Build RPM for Rocky Linux 8 (el8) # Build RPM for Rocky Linux 8 (el8)

View File

@ -205,6 +205,13 @@ type Logger interface {
Error(component, message string, details map[string]string) Error(component, message string, details map[string]string)
} }
// Reopenable defines the interface for components that support log file rotation.
// Implementations must reopen their output files when receiving a SIGHUP signal.
// This is used by systemctl reload to switch to new log files after logrotate.
type Reopenable interface {
Reopen() error
}
// Helper functions for creating and converting records // Helper functions for creating and converting records
// NewLogRecord creates a flattened LogRecord from TLSClientHello and Fingerprints. // NewLogRecord creates a flattened LogRecord from TLSClientHello and Fingerprints.

View File

@ -8,6 +8,9 @@ project:
(via psanford/tlsfingerprint), enrichir avec des métadonnées IP/TCP, (via psanford/tlsfingerprint), enrichir avec des métadonnées IP/TCP,
et loguer les résultats (IP, ports, JA4, meta) vers une ou plusieurs et loguer les résultats (IP, ports, JA4, meta) vers une ou plusieurs
sorties configurables (socket UNIX, stdout, fichier, ...). sorties configurables (socket UNIX, stdout, fichier, ...).
Le service est géré par systemd avec support de rotation des logs via logrotate.
La commande `systemctl reload ja4sentinel` permet de réouvrir les fichiers de log
après rotation (signal SIGHUP).
languages: languages:
- go - go
goals: goals:
@ -122,6 +125,7 @@ modules:
- "Construire les instances des modules (capture, tlsparse, fingerprint, output, logging)." - "Construire les instances des modules (capture, tlsparse, fingerprint, output, logging)."
- "Brancher les modules entre eux selon l'architecture pipeline." - "Brancher les modules entre eux selon l'architecture pipeline."
- "Gérer les signaux système (arrêt propre)." - "Gérer les signaux système (arrêt propre)."
- "Gérer le signal SIGHUP pour la rotation des logs (systemctl reload)."
allowed_dependencies: allowed_dependencies:
- "config" - "config"
- "capture" - "capture"
@ -410,16 +414,16 @@ architecture:
flow_control: flow_control:
connection_states: connection_states:
description: "États simplifiés dun flux TCP pour minimiser la capture." description: "États simplifiés d'un flux TCP pour minimiser la capture."
states: states:
- name: "NEW" - name: "NEW"
description: "Observation dun SYN client sur un port surveillé, création dun état minimal (IP/TCP meta)." description: "Observation d'un SYN client sur un port surveillé, création d'un état minimal (IP/TCP meta)."
- name: "WAIT_CLIENT_HELLO" - name: "WAIT_CLIENT_HELLO"
description: "Accumulation des segments TCP nécessaires pour extraire un ClientHello complet." description: "Accumulation des segments TCP nécessaires pour extraire un ClientHello complet."
- name: "JA4_DONE" - name: "JA4_DONE"
description: "JA4 calculé et logué, on arrête de suivre ce flux." description: "JA4 calculé et logué, on arrête de suivre ce flux."
rules: rules:
- "Pas de tableaux imbriqués ni dobjets deeply nested." - "Pas de tableaux imbriqués ni d'objets deeply nested."
- "Toutes les métadonnées IP/TCP sont flatten sous forme de champs scalaires nommés." - "Toutes les métadonnées IP/TCP sont flatten sous forme de champs scalaires nommés."
- "Les noms de champs suivent la convention: ip_meta_*, tcp_meta_*, ja*." - "Les noms de champs suivent la convention: ip_meta_*, tcp_meta_*, ja*."
- "Pas de champ ja4_hash : le format JA4 intègre déjà son propre hachage tronqué, la chaîne complète de 38 caractères suffit." - "Pas de champ ja4_hash : le format JA4 intègre déjà son propre hachage tronqué, la chaîne complète de 38 caractères suffit."
@ -447,3 +451,23 @@ flow_control:
- "ja3" - "ja3"
- "ja3_hash" - "ja3_hash"
packaging:
rpm:
description: "Package RPM pour déploiement sur serveurs Linux."
files:
- path: "/etc/logrotate.d/ja4sentinel"
description: "Script logrotate pour la rotation des fichiers de log."
note: "Fourni par le RPM, configure la rotation quotidienne avec compression."
- path: "/etc/systemd/system/ja4sentinel.service"
description: "Unité systemd pour la gestion du service."
note: "Doit inclure Type=notify et ExecReload=/bin/kill -HUP $MAINPID pour supporter systemctl reload."
logrotate:
description: "Configuration logrotate pour la rotation des logs."
behavior:
- "Rotation quotidienne ou selon taille."
- "Compression des logs archivés."
- "Envoi du signal SIGHUP au service après rotation pour réouvrir les fichiers."
reload_mechanism:
- "systemctl reload ja4sentinel déclenche le handler SIGHUP."
- "Le service réouvre ses fichiers de log sans redémarrage complet."

View File

@ -107,9 +107,9 @@ func main() {
}() }()
} }
// Setup signal handling // Setup signal handling for shutdown and log rotation
sigChan := make(chan os.Signal, 1) sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
// Create pipeline components // Create pipeline components
captureEngine := capture.New() captureEngine := capture.New()
@ -202,19 +202,41 @@ func main() {
}() }()
// Wait for shutdown signal or capture error // Wait for shutdown signal or capture error
select { for {
case sig := <-sigChan: select {
appLogger.Info("main", "Received shutdown signal", map[string]string{ case sig := <-sigChan:
"signal": sig.String(), switch sig {
}) case syscall.SIGHUP:
case err := <-captureErrChan: // Handle log rotation - reopen output files
if err != nil { appLogger.Info("main", "Received SIGHUP, reopening log files", nil)
appLogger.Error("capture", "Capture engine failed", map[string]string{ if mw, ok := outputWriter.(api.Reopenable); ok {
"error": err.Error(), if err := mw.Reopen(); err != nil {
}) appLogger.Error("main", "Failed to reopen log files", map[string]string{
"error": err.Error(),
})
} else {
appLogger.Info("main", "Log files reopened successfully", nil)
}
} else {
appLogger.Warn("main", "Output writer does not support log rotation", nil)
}
case syscall.SIGINT, syscall.SIGTERM:
appLogger.Info("main", "Received shutdown signal", map[string]string{
"signal": sig.String(),
})
goto shutdown
}
case err := <-captureErrChan:
if err != nil {
appLogger.Error("capture", "Capture engine failed", map[string]string{
"error": err.Error(),
})
}
goto shutdown
} }
} }
shutdown:
// Graceful shutdown // Graceful shutdown
appLogger.Info("main", "Shutting down...", nil) appLogger.Info("main", "Shutting down...", nil)

View File

@ -182,6 +182,28 @@ func (w *FileWriter) Close() error {
return nil return nil
} }
// Reopen reopens the log file (for logrotate support)
func (w *FileWriter) Reopen() error {
w.mutex.Lock()
defer w.mutex.Unlock()
if err := w.file.Close(); err != nil {
return fmt.Errorf("failed to close file during reopen: %w", err)
}
// Open new file
newFile, err := os.OpenFile(w.path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
return fmt.Errorf("failed to reopen file %s: %w", w.path, err)
}
w.file = newFile
w.encoder = json.NewEncoder(newFile)
w.currentSize = 0
return nil
}
// UnixSocketWriter writes log records to a UNIX socket with reconnection logic // UnixSocketWriter writes log records to a UNIX socket with reconnection logic
type UnixSocketWriter struct { type UnixSocketWriter struct {
socketPath string socketPath string
@ -495,6 +517,23 @@ func (mw *MultiWriter) CloseAll() error {
return lastErr return lastErr
} }
// Reopen reopens all writers that support log rotation
func (mw *MultiWriter) Reopen() error {
mw.mutex.Lock()
defer mw.mutex.Unlock()
var lastErr error
for _, w := range mw.writers {
if reopenable, ok := w.(api.Reopenable); ok {
if err := reopenable.Reopen(); err != nil {
lastErr = err
}
}
}
return lastErr
}
// BuilderImpl implements the api.Builder interface // BuilderImpl implements the api.Builder interface
type BuilderImpl struct{} type BuilderImpl struct{}

View File

@ -0,0 +1,17 @@
# Logrotate configuration for ja4sentinel
# Install to: /etc/logrotate.d/ja4sentinel
/var/log/ja4sentinel/*.log {
daily
missingok
rotate 7
compress
delaycompress
notifempty
create 0600 root root
sharedscripts
postrotate
# Send SIGHUP to ja4sentinel to reopen log files
/bin/systemctl reload ja4sentinel 2>/dev/null || true
endscript
}

View File

@ -3,7 +3,7 @@
%if %{defined build_version} %if %{defined build_version}
%define spec_version %{build_version} %define spec_version %{build_version}
%else %else
%define spec_version 1.0.9 %define spec_version 1.1.0
%endif %endif
Name: ja4sentinel Name: ja4sentinel
@ -44,6 +44,7 @@ Features:
%install %install
mkdir -p %{buildroot}/usr/bin mkdir -p %{buildroot}/usr/bin
mkdir -p %{buildroot}/etc/ja4sentinel mkdir -p %{buildroot}/etc/ja4sentinel
mkdir -p %{buildroot}/etc/logrotate.d
mkdir -p %{buildroot}/var/lib/ja4sentinel mkdir -p %{buildroot}/var/lib/ja4sentinel
mkdir -p %{buildroot}/var/log/ja4sentinel mkdir -p %{buildroot}/var/log/ja4sentinel
mkdir -p %{buildroot}/var/run/logcorrelator mkdir -p %{buildroot}/var/run/logcorrelator
@ -56,6 +57,9 @@ install -m 755 %{_sourcedir}/ja4sentinel %{buildroot}/usr/bin/ja4sentinel
# Install systemd service # Install systemd service
install -m 644 %{_sourcedir}/ja4sentinel.service %{buildroot}/usr/lib/systemd/system/ja4sentinel.service install -m 644 %{_sourcedir}/ja4sentinel.service %{buildroot}/usr/lib/systemd/system/ja4sentinel.service
# Install logrotate configuration
install -m 644 %{_sourcedir}/logrotate/ja4sentinel %{buildroot}/etc/logrotate.d/ja4sentinel
# Install default config # Install default config
install -m 640 %{_sourcedir}/config.yml %{buildroot}/etc/ja4sentinel/config.yml.default install -m 640 %{_sourcedir}/config.yml %{buildroot}/etc/ja4sentinel/config.yml.default
install -m 640 %{_sourcedir}/config.yml %{buildroot}/usr/share/ja4sentinel/config.yml install -m 640 %{_sourcedir}/config.yml %{buildroot}/usr/share/ja4sentinel/config.yml
@ -109,6 +113,7 @@ fi
%files %files
/usr/bin/ja4sentinel /usr/bin/ja4sentinel
/usr/lib/systemd/system/ja4sentinel.service /usr/lib/systemd/system/ja4sentinel.service
/etc/logrotate.d/ja4sentinel
/usr/share/ja4sentinel/config.yml /usr/share/ja4sentinel/config.yml
%config(noreplace) /etc/ja4sentinel/config.yml.default %config(noreplace) /etc/ja4sentinel/config.yml.default
%dir /etc/ja4sentinel %dir /etc/ja4sentinel
@ -117,6 +122,18 @@ fi
%dir /var/run/logcorrelator %dir /var/run/logcorrelator
%changelog %changelog
* Mon Mar 02 2026 Jacquin Antoine <rpm@arkel.fr> - 1.1.0-1
- Add logrotate configuration for automatic log file rotation
- Add SIGHUP signal handling for log file reopening (systemctl reload)
- Add ExecReload to systemd service for graceful log rotation
- Add Reopenable interface for output writers supporting log rotation
- Add FileWriter.Reopen() method for log file rotation support
- Add MultiWriter.Reopen() method to propagate rotation to all writers
- Update main.go to handle SIGHUP signal for log rotation
- Add packaging/logrotate/ja4sentinel configuration file
- Update architecture.yml with logrotate and reload documentation
- Update Dockerfile.package to include logrotate file in RPM build
* Mon Mar 02 2026 Jacquin Antoine <rpm@arkel.fr> - 1.0.9-1 * Mon Mar 02 2026 Jacquin Antoine <rpm@arkel.fr> - 1.0.9-1
- Add SNI (Server Name Indication) extraction from TLS ClientHello - Add SNI (Server Name Indication) extraction from TLS ClientHello
- Add ALPN (Application-Layer Protocol Negotiation) extraction - Add ALPN (Application-Layer Protocol Negotiation) extraction

View File

@ -10,6 +10,7 @@ User=root
Group=root Group=root
WorkingDirectory=/var/lib/ja4sentinel WorkingDirectory=/var/lib/ja4sentinel
ExecStart=/usr/bin/ja4sentinel --config /etc/ja4sentinel/config.yml ExecStart=/usr/bin/ja4sentinel --config /etc/ja4sentinel/config.yml
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure Restart=on-failure
RestartSec=5 RestartSec=5
WatchdogSec=30 WatchdogSec=30