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
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:
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user