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
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:
@ -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)
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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 d’un 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 d’un SYN client sur un port surveillé, création d’un é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 d’objets 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."
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
for {
|
||||||
select {
|
select {
|
||||||
case sig := <-sigChan:
|
case sig := <-sigChan:
|
||||||
|
switch sig {
|
||||||
|
case syscall.SIGHUP:
|
||||||
|
// Handle log rotation - reopen output files
|
||||||
|
appLogger.Info("main", "Received SIGHUP, reopening log files", nil)
|
||||||
|
if mw, ok := outputWriter.(api.Reopenable); ok {
|
||||||
|
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{
|
appLogger.Info("main", "Received shutdown signal", map[string]string{
|
||||||
"signal": sig.String(),
|
"signal": sig.String(),
|
||||||
})
|
})
|
||||||
|
goto shutdown
|
||||||
|
}
|
||||||
case err := <-captureErrChan:
|
case err := <-captureErrChan:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
appLogger.Error("capture", "Capture engine failed", map[string]string{
|
appLogger.Error("capture", "Capture engine failed", map[string]string{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
goto shutdown
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shutdown:
|
||||||
// Graceful shutdown
|
// Graceful shutdown
|
||||||
appLogger.Info("main", "Shutting down...", nil)
|
appLogger.Info("main", "Shutting down...", nil)
|
||||||
|
|
||||||
|
|||||||
@ -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{}
|
||||||
|
|
||||||
|
|||||||
17
packaging/logrotate/ja4sentinel
Normal file
17
packaging/logrotate/ja4sentinel
Normal 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
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user