fix: renforcer limites TLS, timeouts socket et validation config
Some checks failed
Build RPM Package / Build RPM Packages (CentOS 7, Rocky 8/9/10) (push) Has been cancelled

Co-authored-by: aider (openrouter/openai/gpt-5.3-codex) <aider@aider.chat>
This commit is contained in:
Jacquin Antoine
2026-02-28 20:01:39 +01:00
parent b15c20b4cc
commit c7e8fe874f
7 changed files with 618 additions and 64 deletions

View File

@ -8,6 +8,7 @@ import (
"net"
"os"
"sync"
"time"
"ja4sentinel/api"
)
@ -76,25 +77,27 @@ func (w *FileWriter) Close() error {
// UnixSocketWriter writes log records to a UNIX socket
type UnixSocketWriter struct {
socketPath string
conn net.Conn
mutex sync.Mutex
socketPath string
conn net.Conn
mutex sync.Mutex
dialTimeout time.Duration
writeTimeout time.Duration
}
// NewUnixSocketWriter creates a new UNIX socket writer
func NewUnixSocketWriter(socketPath string) (*UnixSocketWriter, error) {
w := &UnixSocketWriter{
socketPath: socketPath,
socketPath: socketPath,
dialTimeout: 2 * time.Second,
writeTimeout: 2 * time.Second,
}
// Try to connect (socket may not exist yet)
conn, err := net.Dial("unix", socketPath)
if err != nil {
// Socket doesn't exist yet, we'll try to connect on first write
return w, nil
conn, err := net.DialTimeout("unix", socketPath, w.dialTimeout)
if err == nil {
w.conn = conn
}
w.conn = conn
return w, nil
}
@ -107,7 +110,7 @@ func (w *UnixSocketWriter) Write(rec api.LogRecord) error {
if w.conn != nil {
return nil
}
conn, err := net.Dial("unix", w.socketPath)
conn, err := net.DialTimeout("unix", w.socketPath, w.dialTimeout)
if err != nil {
return fmt.Errorf("failed to connect to socket %s: %w", w.socketPath, err)
}
@ -123,22 +126,32 @@ func (w *UnixSocketWriter) Write(rec api.LogRecord) error {
if err != nil {
return fmt.Errorf("failed to marshal record: %w", err)
}
// Add newline for line-based protocols
data = append(data, '\n')
if _, err = w.conn.Write(data); err != nil {
if err := w.conn.SetWriteDeadline(time.Now().Add(w.writeTimeout)); err != nil {
return fmt.Errorf("failed to set write deadline: %w", err)
}
if _, err = w.conn.Write(data); err == nil {
return nil
}
_ = w.conn.Close()
w.conn = nil
if errConn := ensureConn(); errConn != nil {
return fmt.Errorf("failed to write to socket and reconnect failed: %w", errConn)
}
if errDeadline := w.conn.SetWriteDeadline(time.Now().Add(w.writeTimeout)); errDeadline != nil {
_ = w.conn.Close()
w.conn = nil
return fmt.Errorf("failed to set write deadline after reconnect: %w", errDeadline)
}
if err2 := ensureConn(); err2 != nil {
return fmt.Errorf("failed to write to socket and reconnect failed: %w", err2)
}
if _, err2 := w.conn.Write(data); err2 != nil {
_ = w.conn.Close()
w.conn = nil
return fmt.Errorf("failed to write to socket after reconnect: %w", err2)
}
if _, errRetry := w.conn.Write(data); errRetry != nil {
_ = w.conn.Close()
w.conn = nil
return fmt.Errorf("failed to write to socket after reconnect: %w", errRetry)
}
return nil

View File

@ -4,9 +4,11 @@ import (
"bufio"
"bytes"
"encoding/json"
"errors"
"net"
"os"
"path/filepath"
"strings"
"sync"
"testing"
"time"
@ -238,6 +240,112 @@ func TestUnixSocketWriter(t *testing.T) {
writer.Close()
}
func TestUnixSocketWriter_Write_NonexistentSocket_ReturnsQuickly(t *testing.T) {
socketPath := filepath.Join(t.TempDir(), "ja4sentinel_missing.sock")
writer, err := NewUnixSocketWriter(socketPath)
if err != nil {
t.Fatalf("NewUnixSocketWriter() error = %v", err)
}
defer writer.Close()
start := time.Now()
err = writer.Write(api.LogRecord{
SrcIP: "192.168.1.10",
SrcPort: 44444,
DstIP: "10.0.0.10",
DstPort: 443,
})
elapsed := time.Since(start)
if err == nil {
t.Fatal("Write() should fail for non-existent socket")
}
if elapsed >= 3*time.Second {
t.Fatalf("Write() took too long: %v (expected < 3s)", elapsed)
}
}
type timeoutError struct{}
func (timeoutError) Error() string { return "i/o timeout" }
func (timeoutError) Timeout() bool { return true }
func (timeoutError) Temporary() bool { return true }
type mockAddr string
func (a mockAddr) Network() string { return "unix" }
func (a mockAddr) String() string { return string(a) }
type mockConn struct {
writeCalls int
closeCalled bool
setWriteDeadlineCalled bool
setReadDeadlineCalled bool
setAnyDeadlineWasCalled bool
}
func (m *mockConn) Read(_ []byte) (int, error) { return 0, errors.New("not implemented") }
func (m *mockConn) Write(_ []byte) (int, error) {
m.writeCalls++
return 0, timeoutError{}
}
func (m *mockConn) Close() error {
m.closeCalled = true
return nil
}
func (m *mockConn) LocalAddr() net.Addr { return mockAddr("local") }
func (m *mockConn) RemoteAddr() net.Addr { return mockAddr("remote") }
func (m *mockConn) SetDeadline(_ time.Time) error {
m.setAnyDeadlineWasCalled = true
return nil
}
func (m *mockConn) SetReadDeadline(_ time.Time) error {
m.setReadDeadlineCalled = true
return nil
}
func (m *mockConn) SetWriteDeadline(_ time.Time) error {
m.setWriteDeadlineCalled = true
return nil
}
func TestUnixSocketWriter_Write_UsesWriteDeadline(t *testing.T) {
mc := &mockConn{}
writer := &UnixSocketWriter{
socketPath: filepath.Join(t.TempDir(), "missing.sock"),
conn: mc,
dialTimeout: 100 * time.Millisecond,
writeTimeout: 100 * time.Millisecond,
}
err := writer.Write(api.LogRecord{
SrcIP: "192.168.1.20",
SrcPort: 55555,
DstIP: "10.0.0.20",
DstPort: 443,
})
if err == nil {
t.Fatal("Write() should fail because reconnect target does not exist")
}
if !mc.setWriteDeadlineCalled {
t.Fatal("expected SetWriteDeadline to be called before write")
}
if !mc.closeCalled {
t.Fatal("expected connection to be closed after first write failure")
}
if mc.writeCalls != 1 {
t.Fatalf("expected exactly 1 write on initial conn, got %d", mc.writeCalls)
}
if !strings.Contains(err.Error(), "reconnect failed") {
t.Fatalf("expected reconnect failure error, got: %v", err)
}
}
type unixTestServer struct {
listener net.Listener
received chan string