Files
ProxmoxDash/agent_traffic_discovery_test.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)
}
}
}