// 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) } }