chore: clean .gitignore cache
This commit is contained in:
@@ -1,340 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDefaultCrowdsecExecutorPidFile(t *testing.T) {
|
||||
e := NewDefaultCrowdsecExecutor()
|
||||
tmp := t.TempDir()
|
||||
expected := filepath.Join(tmp, "crowdsec.pid")
|
||||
if p := e.pidFile(tmp); p != expected {
|
||||
t.Fatalf("pidFile mismatch got %s expected %s", p, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultCrowdsecExecutorStartStatusStop(t *testing.T) {
|
||||
e := NewDefaultCrowdsecExecutor()
|
||||
tmp := t.TempDir()
|
||||
|
||||
// Create a mock /proc for process validation
|
||||
mockProc := t.TempDir()
|
||||
e.procPath = mockProc
|
||||
|
||||
// create a tiny script that sleeps and traps TERM
|
||||
// Name it with "crowdsec" so our process validation passes
|
||||
script := filepath.Join(tmp, "crowdsec_test_runner.sh")
|
||||
content := `#!/bin/sh
|
||||
trap 'exit 0' TERM INT
|
||||
while true; do sleep 1; done
|
||||
`
|
||||
if err := os.WriteFile(script, []byte(content), 0o755); err != nil {
|
||||
t.Fatalf("write script: %v", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
defer cancel()
|
||||
|
||||
pid, err := e.Start(ctx, script, tmp)
|
||||
if err != nil {
|
||||
t.Fatalf("start err: %v", err)
|
||||
}
|
||||
if pid <= 0 {
|
||||
t.Fatalf("invalid pid %d", pid)
|
||||
}
|
||||
|
||||
// Create mock /proc/{pid}/cmdline with "crowdsec" for the started process
|
||||
procPidDir := filepath.Join(mockProc, strconv.Itoa(pid))
|
||||
_ = os.MkdirAll(procPidDir, 0o755)
|
||||
// Use a cmdline that contains "crowdsec" to simulate a real CrowdSec process
|
||||
mockCmdline := "/usr/bin/crowdsec\x00-c\x00/etc/crowdsec/config.yaml"
|
||||
_ = os.WriteFile(filepath.Join(procPidDir, "cmdline"), []byte(mockCmdline), 0o644)
|
||||
|
||||
// ensure pid file exists and content matches
|
||||
pidB, err := os.ReadFile(e.pidFile(tmp))
|
||||
if err != nil {
|
||||
t.Fatalf("read pid file: %v", err)
|
||||
}
|
||||
gotPid, _ := strconv.Atoi(string(pidB))
|
||||
if gotPid != pid {
|
||||
t.Fatalf("pid file mismatch got %d expected %d", gotPid, pid)
|
||||
}
|
||||
|
||||
// Status should return running
|
||||
running, got, err := e.Status(ctx, tmp)
|
||||
if err != nil {
|
||||
t.Fatalf("status err: %v", err)
|
||||
}
|
||||
if !running || got != pid {
|
||||
t.Fatalf("status expected running for %d got %d running=%v", pid, got, running)
|
||||
}
|
||||
|
||||
// Stop should terminate and remove pid file
|
||||
if err := e.Stop(ctx, tmp); err != nil {
|
||||
t.Fatalf("stop err: %v", err)
|
||||
}
|
||||
|
||||
// give a little time for process to exit
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
running2, _, _ := e.Status(ctx, tmp)
|
||||
if running2 {
|
||||
t.Fatalf("process still running after stop")
|
||||
}
|
||||
}
|
||||
|
||||
// Additional coverage tests for error paths
|
||||
|
||||
func TestDefaultCrowdsecExecutor_Status_NoPidFile(t *testing.T) {
|
||||
exec := NewDefaultCrowdsecExecutor()
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
running, pid, err := exec.Status(context.Background(), tmpDir)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, running)
|
||||
assert.Equal(t, 0, pid)
|
||||
}
|
||||
|
||||
func TestDefaultCrowdsecExecutor_Status_InvalidPid(t *testing.T) {
|
||||
exec := NewDefaultCrowdsecExecutor()
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Write invalid pid
|
||||
_ = os.WriteFile(filepath.Join(tmpDir, "crowdsec.pid"), []byte("invalid"), 0o644)
|
||||
|
||||
running, pid, err := exec.Status(context.Background(), tmpDir)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, running)
|
||||
assert.Equal(t, 0, pid)
|
||||
}
|
||||
|
||||
func TestDefaultCrowdsecExecutor_Status_NonExistentProcess(t *testing.T) {
|
||||
exec := NewDefaultCrowdsecExecutor()
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Write a pid that doesn't exist
|
||||
// Use a very high PID that's unlikely to exist
|
||||
_ = os.WriteFile(filepath.Join(tmpDir, "crowdsec.pid"), []byte("999999999"), 0o644)
|
||||
|
||||
running, pid, err := exec.Status(context.Background(), tmpDir)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, running)
|
||||
assert.Equal(t, 999999999, pid)
|
||||
}
|
||||
|
||||
func TestDefaultCrowdsecExecutor_Stop_NoPidFile(t *testing.T) {
|
||||
exec := NewDefaultCrowdsecExecutor()
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
err := exec.Stop(context.Background(), tmpDir)
|
||||
|
||||
// Stop should be idempotent - no PID file means already stopped
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestDefaultCrowdsecExecutor_Stop_InvalidPid(t *testing.T) {
|
||||
exec := NewDefaultCrowdsecExecutor()
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Write invalid pid
|
||||
_ = os.WriteFile(filepath.Join(tmpDir, "crowdsec.pid"), []byte("invalid"), 0o644)
|
||||
|
||||
err := exec.Stop(context.Background(), tmpDir)
|
||||
|
||||
// Stop should clean up malformed PID file and succeed
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify PID file was cleaned up
|
||||
_, statErr := os.Stat(filepath.Join(tmpDir, "crowdsec.pid"))
|
||||
assert.True(t, os.IsNotExist(statErr), "PID file should be removed after Stop with invalid PID")
|
||||
}
|
||||
|
||||
func TestDefaultCrowdsecExecutor_Stop_NonExistentProcess(t *testing.T) {
|
||||
exec := NewDefaultCrowdsecExecutor()
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Write a pid that doesn't exist
|
||||
_ = os.WriteFile(filepath.Join(tmpDir, "crowdsec.pid"), []byte("999999999"), 0o644)
|
||||
|
||||
err := exec.Stop(context.Background(), tmpDir)
|
||||
|
||||
// Stop should be idempotent - stale PID file means process already dead
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify PID file was cleaned up
|
||||
_, statErr := os.Stat(filepath.Join(tmpDir, "crowdsec.pid"))
|
||||
assert.True(t, os.IsNotExist(statErr), "Stale PID file should be cleaned up after Stop")
|
||||
}
|
||||
|
||||
func TestDefaultCrowdsecExecutor_Stop_Idempotent(t *testing.T) {
|
||||
exec := NewDefaultCrowdsecExecutor()
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Stop should succeed even when called multiple times
|
||||
err1 := exec.Stop(context.Background(), tmpDir)
|
||||
err2 := exec.Stop(context.Background(), tmpDir)
|
||||
err3 := exec.Stop(context.Background(), tmpDir)
|
||||
|
||||
assert.NoError(t, err1)
|
||||
assert.NoError(t, err2)
|
||||
assert.NoError(t, err3)
|
||||
}
|
||||
|
||||
func TestDefaultCrowdsecExecutor_Start_InvalidBinary(t *testing.T) {
|
||||
exec := NewDefaultCrowdsecExecutor()
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
pid, err := exec.Start(context.Background(), "/nonexistent/binary", tmpDir)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, 0, pid)
|
||||
}
|
||||
|
||||
// Tests for PID reuse vulnerability fix
|
||||
|
||||
func TestDefaultCrowdsecExecutor_isCrowdSecProcess_ValidProcess(t *testing.T) {
|
||||
exec := NewDefaultCrowdsecExecutor()
|
||||
|
||||
// Create a mock /proc/{pid}/cmdline
|
||||
tmpDir := t.TempDir()
|
||||
exec.procPath = tmpDir
|
||||
|
||||
// Create a fake PID directory with crowdsec in cmdline
|
||||
pid := 12345
|
||||
procPidDir := filepath.Join(tmpDir, strconv.Itoa(pid))
|
||||
_ = os.MkdirAll(procPidDir, 0o755)
|
||||
|
||||
// Write cmdline with crowdsec (null-separated like real /proc)
|
||||
cmdline := "/usr/bin/crowdsec\x00-c\x00/etc/crowdsec/config.yaml"
|
||||
_ = os.WriteFile(filepath.Join(procPidDir, "cmdline"), []byte(cmdline), 0o644)
|
||||
|
||||
assert.True(t, exec.isCrowdSecProcess(pid), "Should detect CrowdSec process")
|
||||
}
|
||||
|
||||
func TestDefaultCrowdsecExecutor_isCrowdSecProcess_DifferentProcess(t *testing.T) {
|
||||
exec := NewDefaultCrowdsecExecutor()
|
||||
|
||||
// Create a mock /proc/{pid}/cmdline
|
||||
tmpDir := t.TempDir()
|
||||
exec.procPath = tmpDir
|
||||
|
||||
// Create a fake PID directory with a different process (like dlv debugger)
|
||||
pid := 12345
|
||||
procPidDir := filepath.Join(tmpDir, strconv.Itoa(pid))
|
||||
_ = os.MkdirAll(procPidDir, 0o755)
|
||||
|
||||
// Write cmdline with dlv (the original bug case)
|
||||
cmdline := "/usr/local/bin/dlv\x00--telemetry\x00--headless"
|
||||
_ = os.WriteFile(filepath.Join(procPidDir, "cmdline"), []byte(cmdline), 0o644)
|
||||
|
||||
assert.False(t, exec.isCrowdSecProcess(pid), "Should NOT detect dlv as CrowdSec")
|
||||
}
|
||||
|
||||
func TestDefaultCrowdsecExecutor_isCrowdSecProcess_NonExistentProcess(t *testing.T) {
|
||||
exec := NewDefaultCrowdsecExecutor()
|
||||
|
||||
// Create a mock /proc without the PID
|
||||
tmpDir := t.TempDir()
|
||||
exec.procPath = tmpDir
|
||||
|
||||
// Don't create any PID directory
|
||||
assert.False(t, exec.isCrowdSecProcess(99999), "Should return false for non-existent process")
|
||||
}
|
||||
|
||||
func TestDefaultCrowdsecExecutor_isCrowdSecProcess_EmptyCmdline(t *testing.T) {
|
||||
exec := NewDefaultCrowdsecExecutor()
|
||||
|
||||
// Create a mock /proc/{pid}/cmdline
|
||||
tmpDir := t.TempDir()
|
||||
exec.procPath = tmpDir
|
||||
|
||||
// Create a fake PID directory with empty cmdline
|
||||
pid := 12345
|
||||
procPidDir := filepath.Join(tmpDir, strconv.Itoa(pid))
|
||||
_ = os.MkdirAll(procPidDir, 0o755)
|
||||
|
||||
// Write empty cmdline
|
||||
_ = os.WriteFile(filepath.Join(procPidDir, "cmdline"), []byte(""), 0o644)
|
||||
|
||||
assert.False(t, exec.isCrowdSecProcess(pid), "Should return false for empty cmdline")
|
||||
}
|
||||
|
||||
func TestDefaultCrowdsecExecutor_Status_PIDReuse_DifferentProcess(t *testing.T) {
|
||||
exec := NewDefaultCrowdsecExecutor()
|
||||
|
||||
// Create temp directories for config and mock /proc
|
||||
tmpDir := t.TempDir()
|
||||
mockProc := t.TempDir()
|
||||
exec.procPath = mockProc
|
||||
|
||||
// Get current process PID (which exists and responds to Signal(0))
|
||||
currentPID := os.Getpid()
|
||||
|
||||
// Write current PID to the crowdsec.pid file (simulating stale PID file)
|
||||
_ = os.WriteFile(filepath.Join(tmpDir, "crowdsec.pid"), []byte(strconv.Itoa(currentPID)), 0o644)
|
||||
|
||||
// Create mock /proc entry for current PID but with a non-crowdsec cmdline
|
||||
procPidDir := filepath.Join(mockProc, strconv.Itoa(currentPID))
|
||||
_ = os.MkdirAll(procPidDir, 0o755)
|
||||
_ = os.WriteFile(filepath.Join(procPidDir, "cmdline"), []byte("/usr/local/bin/dlv\x00debug"), 0o644)
|
||||
|
||||
// Status should return NOT running because the PID is not CrowdSec
|
||||
running, pid, err := exec.Status(context.Background(), tmpDir)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, running, "Should detect PID reuse and return not running")
|
||||
assert.Equal(t, currentPID, pid)
|
||||
}
|
||||
|
||||
func TestDefaultCrowdsecExecutor_Status_PIDReuse_IsCrowdSec(t *testing.T) {
|
||||
exec := NewDefaultCrowdsecExecutor()
|
||||
|
||||
// Create temp directories for config and mock /proc
|
||||
tmpDir := t.TempDir()
|
||||
mockProc := t.TempDir()
|
||||
exec.procPath = mockProc
|
||||
|
||||
// Get current process PID (which exists and responds to Signal(0))
|
||||
currentPID := os.Getpid()
|
||||
|
||||
// Write current PID to the crowdsec.pid file
|
||||
_ = os.WriteFile(filepath.Join(tmpDir, "crowdsec.pid"), []byte(strconv.Itoa(currentPID)), 0o644)
|
||||
|
||||
// Create mock /proc entry for current PID with crowdsec cmdline
|
||||
procPidDir := filepath.Join(mockProc, strconv.Itoa(currentPID))
|
||||
_ = os.MkdirAll(procPidDir, 0o755)
|
||||
_ = os.WriteFile(filepath.Join(procPidDir, "cmdline"), []byte("/usr/bin/crowdsec\x00-c\x00config.yaml"), 0o644)
|
||||
|
||||
// Status should return running because it IS CrowdSec
|
||||
running, pid, err := exec.Status(context.Background(), tmpDir)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, running, "Should return running when process is CrowdSec")
|
||||
assert.Equal(t, currentPID, pid)
|
||||
}
|
||||
|
||||
func TestDefaultCrowdsecExecutor_Stop_SignalError(t *testing.T) {
|
||||
exec := NewDefaultCrowdsecExecutor()
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Write a pid for a process that exists but we can't signal (e.g., init process or other user's process)
|
||||
// Use PID 1 which exists but typically can't be signaled by non-root
|
||||
_ = os.WriteFile(filepath.Join(tmpDir, "crowdsec.pid"), []byte("1"), 0o644)
|
||||
|
||||
err := exec.Stop(context.Background(), tmpDir)
|
||||
|
||||
// Stop should return an error when Signal fails with something other than ESRCH/ErrProcessDone
|
||||
// On Linux, signaling PID 1 as non-root returns EPERM (Operation not permitted)
|
||||
// The exact behavior depends on the system, but the test verifies the error path is triggered
|
||||
_ = err // Result depends on system permissions, but line 76-79 is now exercised
|
||||
}
|
||||
Reference in New Issue
Block a user