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

@ -38,7 +38,7 @@ func (l *LoaderImpl) Load() (api.AppConfig, error) {
fileConfig, err := l.loadFromFile(path)
if err == nil {
config = mergeConfigs(config, fileConfig)
} else if !( !explicit && errors.Is(err, os.ErrNotExist)) {
} else if !(!explicit && errors.Is(err, os.ErrNotExist)) {
return config, fmt.Errorf("failed to load config file: %w", err)
}
@ -115,6 +115,7 @@ func parsePorts(s string) []uint16 {
parts := strings.Split(s, ",")
ports := make([]uint16, 0, len(parts))
seen := make(map[uint16]struct{}, len(parts))
for _, part := range parts {
part = strings.TrimSpace(part)
@ -123,9 +124,19 @@ func parsePorts(s string) []uint16 {
}
port, err := strconv.ParseUint(part, 10, 16)
if err == nil {
ports = append(ports, uint16(port))
if err != nil {
continue
}
p := uint16(port)
if p == 0 {
continue
}
if _, exists := seen[p]; exists {
continue
}
seen[p] = struct{}{}
ports = append(ports, p)
}
return ports
@ -164,19 +175,53 @@ func mergeConfigs(base, override api.AppConfig) api.AppConfig {
// validate checks if the configuration is valid
func (l *LoaderImpl) validate(config api.AppConfig) error {
if config.Core.Interface == "" {
if strings.TrimSpace(config.Core.Interface) == "" {
return fmt.Errorf("interface cannot be empty")
}
if len(config.Core.ListenPorts) == 0 {
return fmt.Errorf("at least one listen port is required")
}
for _, p := range config.Core.ListenPorts {
if p == 0 {
return fmt.Errorf("listen port 0 is invalid")
}
}
if config.Core.FlowTimeoutSec <= 0 || config.Core.FlowTimeoutSec > 300 {
return fmt.Errorf("flow_timeout_sec must be between 1 and 300")
}
if config.Core.PacketBufferSize <= 0 || config.Core.PacketBufferSize > 1_000_000 {
return fmt.Errorf("packet_buffer_size must be between 1 and 1000000")
}
allowedTypes := map[string]struct{}{
"stdout": {},
"file": {},
"unix_socket": {},
}
// Validate outputs
for i, output := range config.Outputs {
if output.Type == "" {
outputType := strings.TrimSpace(output.Type)
if outputType == "" {
return fmt.Errorf("output[%d]: type cannot be empty", i)
}
if _, ok := allowedTypes[outputType]; !ok {
return fmt.Errorf("output[%d]: unknown type %q", i, outputType)
}
switch outputType {
case "file":
if strings.TrimSpace(output.Params["path"]) == "" {
return fmt.Errorf("output[%d]: file output requires non-empty path", i)
}
case "unix_socket":
if strings.TrimSpace(output.Params["socket_path"]) == "" {
return fmt.Errorf("output[%d]: unix_socket output requires non-empty socket_path", i)
}
}
}
return nil

View File

@ -58,21 +58,39 @@ func TestParsePorts(t *testing.T) {
}
}
func TestParsePorts_DeduplicateAndIgnoreZero(t *testing.T) {
got := parsePorts("443, 0, 443, 8443")
want := []uint16{443, 8443}
if len(got) != len(want) {
t.Fatalf("parsePorts() length = %d, want %d (got: %v)", len(got), len(want), got)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("parsePorts()[%d] = %d, want %d", i, got[i], want[i])
}
}
}
func TestMergeConfigs(t *testing.T) {
base := api.AppConfig{
Core: api.Config{
Interface: "eth0",
ListenPorts: []uint16{443},
BPFFilter: "",
Interface: "eth0",
ListenPorts: []uint16{443},
BPFFilter: "",
FlowTimeoutSec: 30,
PacketBufferSize: 1000,
},
Outputs: []api.OutputConfig{},
}
override := api.AppConfig{
Core: api.Config{
Interface: "lo",
ListenPorts: []uint16{8443},
BPFFilter: "tcp",
Interface: "lo",
ListenPorts: []uint16{8443},
BPFFilter: "tcp",
FlowTimeoutSec: 60,
PacketBufferSize: 2000,
},
Outputs: []api.OutputConfig{
{Type: "stdout", Enabled: true},
@ -93,6 +111,12 @@ func TestMergeConfigs(t *testing.T) {
if len(result.Outputs) != 1 {
t.Errorf("Outputs length = %v, want 1", len(result.Outputs))
}
if result.Core.FlowTimeoutSec != 60 {
t.Errorf("FlowTimeoutSec = %v, want 60", result.Core.FlowTimeoutSec)
}
if result.Core.PacketBufferSize != 2000 {
t.Errorf("PacketBufferSize = %v, want 2000", result.Core.PacketBufferSize)
}
}
func TestValidate(t *testing.T) {
@ -107,8 +131,10 @@ func TestValidate(t *testing.T) {
name: "valid config",
config: api.AppConfig{
Core: api.Config{
Interface: "eth0",
ListenPorts: []uint16{443},
Interface: "eth0",
ListenPorts: []uint16{443},
FlowTimeoutSec: 30,
PacketBufferSize: 1000,
},
Outputs: []api.OutputConfig{
{Type: "stdout", Enabled: true},
@ -120,8 +146,10 @@ func TestValidate(t *testing.T) {
name: "empty interface",
config: api.AppConfig{
Core: api.Config{
Interface: "",
ListenPorts: []uint16{443},
Interface: "",
ListenPorts: []uint16{443},
FlowTimeoutSec: 30,
PacketBufferSize: 1000,
},
},
wantErr: true,
@ -130,8 +158,10 @@ func TestValidate(t *testing.T) {
name: "no listen ports",
config: api.AppConfig{
Core: api.Config{
Interface: "eth0",
ListenPorts: []uint16{},
Interface: "eth0",
ListenPorts: []uint16{},
FlowTimeoutSec: 30,
PacketBufferSize: 1000,
},
},
wantErr: true,
@ -140,8 +170,10 @@ func TestValidate(t *testing.T) {
name: "output with empty type",
config: api.AppConfig{
Core: api.Config{
Interface: "eth0",
ListenPorts: []uint16{443},
Interface: "eth0",
ListenPorts: []uint16{443},
FlowTimeoutSec: 30,
PacketBufferSize: 1000,
},
Outputs: []api.OutputConfig{
{Type: "", Enabled: true},
@ -149,6 +181,18 @@ func TestValidate(t *testing.T) {
},
wantErr: true,
},
{
name: "listen port zero",
config: api.AppConfig{
Core: api.Config{
Interface: "eth0",
ListenPorts: []uint16{0},
FlowTimeoutSec: 30,
PacketBufferSize: 1000,
},
},
wantErr: true,
},
}
for _, tt := range tests {
@ -161,6 +205,162 @@ func TestValidate(t *testing.T) {
}
}
func TestValidate_InvalidCoreBounds(t *testing.T) {
loader := &LoaderImpl{}
tests := []struct {
name string
cfg api.AppConfig
hasErr bool
}{
{
name: "timeout zero",
cfg: api.AppConfig{
Core: api.Config{
Interface: "eth0",
ListenPorts: []uint16{443},
FlowTimeoutSec: 0,
PacketBufferSize: 1000,
},
},
hasErr: true,
},
{
name: "timeout too high",
cfg: api.AppConfig{
Core: api.Config{
Interface: "eth0",
ListenPorts: []uint16{443},
FlowTimeoutSec: 301,
PacketBufferSize: 1000,
},
},
hasErr: true,
},
{
name: "buffer zero",
cfg: api.AppConfig{
Core: api.Config{
Interface: "eth0",
ListenPorts: []uint16{443},
FlowTimeoutSec: 30,
PacketBufferSize: 0,
},
},
hasErr: true,
},
{
name: "buffer too high",
cfg: api.AppConfig{
Core: api.Config{
Interface: "eth0",
ListenPorts: []uint16{443},
FlowTimeoutSec: 30,
PacketBufferSize: 1_000_001,
},
},
hasErr: true,
},
{
name: "valid bounds",
cfg: api.AppConfig{
Core: api.Config{
Interface: "eth0",
ListenPorts: []uint16{443},
FlowTimeoutSec: 30,
PacketBufferSize: 1000,
},
Outputs: []api.OutputConfig{
{Type: "stdout", Enabled: true},
},
},
hasErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := loader.validate(tt.cfg)
if (err != nil) != tt.hasErr {
t.Fatalf("validate() error = %v, wantErr %v", err, tt.hasErr)
}
})
}
}
func TestValidate_InvalidOutputs(t *testing.T) {
loader := &LoaderImpl{}
baseCore := api.Config{
Interface: "eth0",
ListenPorts: []uint16{443},
FlowTimeoutSec: 30,
PacketBufferSize: 1000,
}
tests := []struct {
name string
outputs []api.OutputConfig
wantErr bool
}{
{
name: "unknown output type",
outputs: []api.OutputConfig{
{Type: "unknown", Enabled: true},
},
wantErr: true,
},
{
name: "file without path",
outputs: []api.OutputConfig{
{Type: "file", Enabled: true, Params: map[string]string{}},
},
wantErr: true,
},
{
name: "unix socket without socket_path",
outputs: []api.OutputConfig{
{Type: "unix_socket", Enabled: true, Params: map[string]string{}},
},
wantErr: true,
},
{
name: "valid file output",
outputs: []api.OutputConfig{
{Type: "file", Enabled: true, Params: map[string]string{"path": "/tmp/x.log"}},
},
wantErr: false,
},
{
name: "valid unix socket output",
outputs: []api.OutputConfig{
{Type: "unix_socket", Enabled: true, Params: map[string]string{"socket_path": "/tmp/x.sock"}},
},
wantErr: false,
},
{
name: "valid stdout output",
outputs: []api.OutputConfig{
{Type: "stdout", Enabled: true},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := api.AppConfig{
Core: baseCore,
Outputs: tt.outputs,
}
err := loader.validate(cfg)
if (err != nil) != tt.wantErr {
t.Fatalf("validate() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestLoadFromEnv(t *testing.T) {
// Save original env vars
origInterface := os.Getenv("JA4SENTINEL_INTERFACE")
@ -195,9 +395,11 @@ func TestLoadFromEnv(t *testing.T) {
func TestToJSON(t *testing.T) {
config := api.AppConfig{
Core: api.Config{
Interface: "eth0",
ListenPorts: []uint16{443, 8443},
BPFFilter: "tcp",
Interface: "eth0",
ListenPorts: []uint16{443, 8443},
BPFFilter: "tcp",
FlowTimeoutSec: 30,
PacketBufferSize: 1000,
},
Outputs: []api.OutputConfig{
{Type: "stdout", Enabled: true, Params: map[string]string{}},