enodia-sentinal/go-agent/internal/events/host_rules_test.go

90 lines
3.6 KiB
Go

// SPDX-License-Identifier: GPL-3.0-or-later
package events
import (
"encoding/json"
"strings"
"testing"
)
func TestHostEventCompatibilityAliases(t *testing.T) {
var event HostEvent
raw := []byte(`{"type":"accept","pid":"42","remote_ip":"8.8.8.8","remote_port":"51515","bind_ip":"0.0.0.0","bind_port":"4444","target_uid":"0","mode":"0o777","capability":"cap_sys_admin","name":"evil"}`)
if err := json.Unmarshal(raw, &event); err != nil {
t.Fatal(err)
}
if event.Event != "accept" || event.PID != 42 || event.PeerIP != "8.8.8.8" ||
event.PeerPort != 51515 || event.LocalPort != 4444 || event.TargetUID != 0 ||
event.TargetGID != -1 || event.Mode != 0o777 || event.Capabilities[0] != "CAP_SYS_ADMIN" ||
event.ModuleName != "evil" {
t.Fatalf("compatibility decode drifted: %#v", event)
}
}
func TestDefaultHostRulesMatchAllBuiltins(t *testing.T) {
engine := DefaultHostRuleEngine()
tests := []struct {
event HostEvent
sid int
}{
{HostEvent{Event: "tcp_connect", PID: 1, Comm: "python3", PeerIP: "8.8.8.8", PeerPort: 4444, TargetUID: -1, TargetGID: -1}, 100067},
{HostEvent{Event: "listen", PID: 2, Comm: "python3", LocalPort: 4444, TargetUID: -1, TargetGID: -1}, 100068},
{HostEvent{Event: "bind", PID: 3, Comm: "python3", LocalPort: 4444, TargetUID: -1, TargetGID: -1}, 100073},
{HostEvent{Event: "file_write", PID: 4, Comm: "python3", Path: "/etc/systemd/system/evil.service", TargetUID: -1, TargetGID: -1}, 100069},
{HostEvent{Event: "chmod", PID: 5, Comm: "chmod", Path: "/etc/cron.d/evil", Mode: 0o777, TargetUID: -1, TargetGID: -1}, 100070},
{HostEvent{Event: "setuid", PID: 6, Comm: "python3", TargetUID: 0, TargetGID: -1}, 100071},
{HostEvent{Event: "setgid", PID: 7, Comm: "python3", TargetUID: -1, TargetGID: 0}, 100072},
{HostEvent{Event: "accept", PID: 8, Comm: "python3", PeerIP: "8.8.8.8", LocalPort: 4444, TargetUID: -1, TargetGID: -1}, 100076},
{HostEvent{Event: "capset", PID: 9, Comm: "python3", Capabilities: []string{"cap_sys_admin"}, TargetUID: -1, TargetGID: -1}, 100077},
{HostEvent{Event: "module_load", PID: 10, Comm: "insmod", Path: "/tmp/evil.ko", TargetUID: -1, TargetGID: -1}, 100078},
}
for _, test := range tests {
found := false
for _, alert := range engine.Match(test.event) {
if alert.SID == test.sid {
found = true
}
}
if !found {
t.Fatalf("sid %d did not match %#v", test.sid, test.event)
}
}
}
func TestHostInterpreterCommsReturnsCopy(t *testing.T) {
first := HostInterpreterComms()
if len(first) == 0 {
t.Fatal("interpreter list must not be empty")
}
original := first[0]
first[0] = "mutated"
if HostInterpreterComms()[0] != original {
t.Fatal("caller mutated shared interpreter list")
}
}
func TestHostRulesRejectCommonOrPrivateNetworkShapes(t *testing.T) {
engine := DefaultHostRuleEngine()
for _, event := range []HostEvent{
{Event: "tcp_connect", Comm: "python3", PeerIP: "8.8.8.8", PeerPort: 443},
{Event: "tcp_connect", Comm: "python3", PeerIP: "10.0.0.2", PeerPort: 4444},
{Event: "listen", Comm: "python3", LocalPort: 80},
} {
if alerts := engine.Match(event); len(alerts) != 0 {
t.Fatalf("benign network shape matched: %#v", alerts)
}
}
}
func TestHostAlertContract(t *testing.T) {
event := HostEvent{
Event: "chmod", PID: 42, PPID: 1, UID: 0, Comm: "chmod",
Path: "/etc/cron.d/evil", TargetUID: -1, TargetGID: -1, Mode: 0o777,
}
alert := DefaultHostRuleEngine().Match(event)[0]
if alert.Key != "host:100070:chmod:42::0::0:/etc/cron.d/evil:-1:-1:511::" ||
!strings.Contains(alert.Detail, "mode=0o777 capabilities=- module=-") {
t.Fatalf("unexpected host alert: %#v", alert)
}
}