135 lines
4.6 KiB
Go
135 lines
4.6 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestNPMPlusTraffic(t *testing.T) {
|
|
for _, ip := range []string{"8.8.8.8", "2001:4860:4860::8888"} {
|
|
line := `[14/Oct/2026:16:24:17 +0000] example.test:8443 ` + ip + ` 0.001 "GET /health?token=secret HTTP/3.0" 200 123 "-" "test" "-" "text/html" TLSv1.3 - - -`
|
|
e, ok := parseNPMTraffic(line)
|
|
if !ok || e.IP != ip || e.Domain != "example.test" || e.Path != "/health" || e.Status != 200 {
|
|
t.Fatalf("%+v %v", e, ok)
|
|
}
|
|
}
|
|
if _, ok := parseNPMTraffic(`[14/Oct/2026:16:24:17 +0000] 443 8.8.8.8 UDP - 200 10 20 0.001`); ok {
|
|
t.Fatal("stream counted as HTTP")
|
|
}
|
|
}
|
|
func TestDockerTrafficDiscovery(t *testing.T) {
|
|
var containers []trafficDockerContainer
|
|
json.Unmarshal([]byte(`[{"Id":"aaa","Image":"ghcr.io/zoeyvid/npmplus:latest","Mounts":[{"Source":"/var/lib/docker/volumes/npm/_data","Destination":"/data"},{"Source":"/srv/logs","Destination":"/data/nginx/logs"}]},{"Id":"bbb","Image":"crowdsecurity/crowdsec:latest"}]`), &containers)
|
|
d := trafficSources(containers, nil)
|
|
if d.CrowdContainer != "bbb" || d.Error != "" || strings.Join(d.Dirs, ",") != "/var/lib/docker/volumes/npm/_data/logs,/srv/logs,/var/lib/docker/volumes/npm/_data/nginx" {
|
|
t.Fatalf("%+v", d)
|
|
}
|
|
containers = append(containers, containers[1])
|
|
d = trafficSources(containers, nil)
|
|
if d.CrowdContainer != "" || d.Error == "" {
|
|
t.Fatal("ambiguous CrowdSec silently chosen")
|
|
}
|
|
}
|
|
func TestDockerTrafficDemux(t *testing.T) {
|
|
var b bytes.Buffer
|
|
for _, frame := range []struct {
|
|
stream byte
|
|
text string
|
|
}{{2, "diagnostic"}, {1, "["}, {1, "]"}} {
|
|
h := []byte{frame.stream, 0, 0, 0, 0, 0, 0, 0}
|
|
binary.BigEndian.PutUint32(h[4:], uint32(len(frame.text)))
|
|
b.Write(h)
|
|
b.WriteString(frame.text)
|
|
}
|
|
out, err := readDockerTrafficOutput(&b)
|
|
if err != nil || string(out) != "[]" {
|
|
t.Fatalf("%q %v", out, err)
|
|
}
|
|
if _, err := readDockerTrafficOutput(bytes.NewReader([]byte{1, 0, 0})); err == nil {
|
|
t.Fatal("truncation accepted")
|
|
}
|
|
if _, err := readDockerTrafficOutput(bytes.NewReader([]byte{1, 0, 0, 0, 255, 255, 255, 255})); err == nil {
|
|
t.Fatal("oversized frame accepted")
|
|
}
|
|
}
|
|
func TestTrafficMultipleDirectoriesAndNPMPlusRotation(t *testing.T) {
|
|
a, b := t.TempDir(), t.TempDir()
|
|
p := filepath.Join(b, "access.log")
|
|
os.WriteFile(filepath.Join(a, "proxy-host-1_access.log"), nil, 0600)
|
|
os.WriteFile(p, nil, 0600)
|
|
s := trafficAgentState{Cursors: map[string]trafficCursor{}}
|
|
if n, err := s.readLogs(a, b, a); err != nil || n != 2 {
|
|
t.Fatalf("files %d: %v", n, err)
|
|
}
|
|
line := "[" + time.Now().UTC().Format("02/Jan/2006:15:04:05 -0700") + `] example.test 8.8.8.8 0.1 "GET / HTTP/2.0" 200 123 "-"` + "\n"
|
|
os.WriteFile(p, []byte(line), 0600)
|
|
s.readLogs(a, b)
|
|
if len(s.Pending) != 1 {
|
|
t.Fatal("NPM+ event missing")
|
|
}
|
|
s.Pending = nil
|
|
os.Rename(p, p+".1")
|
|
os.WriteFile(p, []byte(line), 0600)
|
|
s.readLogs(a, b)
|
|
if len(s.Pending) != 1 {
|
|
t.Fatalf("rotation duplicated/lost events: %d", len(s.Pending))
|
|
}
|
|
}
|
|
|
|
type trafficRoundTrip func(*http.Request) (*http.Response, error)
|
|
|
|
func (f trafficRoundTrip) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
|
|
func TestDockerCrowdExec(t *testing.T) {
|
|
for _, exitCode := range []int{0, 1} {
|
|
calls := 0
|
|
client := &http.Client{Transport: trafficRoundTrip(func(r *http.Request) (*http.Response, error) {
|
|
calls++
|
|
body := ""
|
|
status := 200
|
|
switch calls {
|
|
case 1:
|
|
if r.Method != "POST" || r.URL.Path != "/containers/crowdsec/exec" {
|
|
t.Fatalf("unexpected %s %s", r.Method, r.URL)
|
|
}
|
|
var payload struct {
|
|
Cmd []string
|
|
Tty bool
|
|
}
|
|
json.NewDecoder(r.Body).Decode(&payload)
|
|
if strings.Join(payload.Cmd, " ") != "cscli alerts list --since 24h --limit 50 -o json" || payload.Tty {
|
|
t.Fatalf("unexpected command %+v", payload)
|
|
}
|
|
status = 201
|
|
body = `{"Id":"exec123"}`
|
|
case 2:
|
|
if r.URL.Path != "/exec/exec123/start" {
|
|
t.Fatal(r.URL)
|
|
}
|
|
body = string([]byte{1, 0, 0, 0, 0, 0, 0, 2}) + "[]"
|
|
case 3:
|
|
if r.Method != "GET" || r.URL.Path != "/exec/exec123/json" {
|
|
t.Fatal(r.URL)
|
|
}
|
|
body = fmt.Sprintf(`{"Running":false,"ExitCode":%d}`, exitCode)
|
|
default:
|
|
t.Fatal("extra Docker request")
|
|
}
|
|
return &http.Response{StatusCode: status, Body: io.NopCloser(strings.NewReader(body)), Header: make(http.Header)}, nil
|
|
})}
|
|
out, err := dockerCrowdWithClient(context.Background(), "crowdsec", client)
|
|
if calls != 3 || (exitCode == 0 && (err != nil || string(out) != "[]")) || (exitCode != 0 && err == nil) {
|
|
t.Fatalf("calls=%d output=%s err=%v", calls, out, err)
|
|
}
|
|
}
|
|
}
|