feat(go): advance validation sidecar toward production
This commit is contained in:
parent
6a06eba255
commit
f85c2e831a
92 changed files with 8881 additions and 91 deletions
256
go-agent/internal/ebpfsource/syscall_test.go
Normal file
256
go-agent/internal/ebpfsource/syscall_test.go
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package ebpfsource
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func TestDecodeSyscallSample(t *testing.T) {
|
||||
raw := syscallEventRaw{
|
||||
PID: 42, PPID: 7, UID: 1000, SyscallID: syscallMprotect,
|
||||
Args: [6]uint64{0x1000, 0x2000, 0x6},
|
||||
}
|
||||
copyCString(raw.Comm[:], "dropper")
|
||||
var sample bytes.Buffer
|
||||
if err := binary.Write(&sample, binary.NativeEndian, raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
event, err := decodeSyscallSample(sample.Bytes())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if event.PID != 42 || event.PPID != 7 || event.UID != 1000 ||
|
||||
event.Comm != "dropper" || event.Syscall != "mprotect" || event.Args[2] != 0x6 {
|
||||
t.Fatalf("unexpected event: %#v", event)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeSyscallSampleRejectsUnknownIDAndSize(t *testing.T) {
|
||||
if _, err := decodeSyscallSample([]byte{1}); err == nil {
|
||||
t.Fatal("expected size error")
|
||||
}
|
||||
raw := syscallEventRaw{SyscallID: 99}
|
||||
var sample bytes.Buffer
|
||||
if err := binary.Write(&sample, binary.NativeEndian, raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := decodeSyscallSample(sample.Bytes()); err == nil {
|
||||
t.Fatal("expected unknown ID error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeSecuritySampleProducesTypedHostTransition(t *testing.T) {
|
||||
raw := syscallEventRaw{
|
||||
PID: 42, PPID: 7, UID: 1000, SyscallID: hostSetuid,
|
||||
Args: [6]uint64{0},
|
||||
}
|
||||
copyCString(raw.Comm[:], "python3")
|
||||
var sample bytes.Buffer
|
||||
if err := binary.Write(&sample, binary.NativeEndian, raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
securityEvent, err := decodeSecuritySample(sample.Bytes())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if securityEvent.Syscall != nil || securityEvent.Host == nil ||
|
||||
securityEvent.Host.Event != "setuid" || securityEvent.Host.TargetUID != 0 ||
|
||||
securityEvent.Host.TargetGID != -1 || securityEvent.Host.Comm != "python3" {
|
||||
t.Fatalf("unexpected security event: %#v", securityEvent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeSecuritySampleProducesPermissionChange(t *testing.T) {
|
||||
raw := syscallEventRaw{
|
||||
PID: 42, PPID: 7, UID: 1000, SyscallID: hostFchownat,
|
||||
Args: [6]uint64{0, 0},
|
||||
}
|
||||
copyCString(raw.Comm[:], "python3")
|
||||
copyCString(raw.Text[:], "/etc/systemd/system/backdoor.service")
|
||||
var sample bytes.Buffer
|
||||
if err := binary.Write(&sample, binary.NativeEndian, raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
securityEvent, err := decodeSecuritySample(sample.Bytes())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if securityEvent.Host == nil || securityEvent.Host.Event != "chown" ||
|
||||
securityEvent.Host.Path != "/etc/systemd/system/backdoor.service" ||
|
||||
securityEvent.Host.TargetUID != 0 || securityEvent.Host.TargetGID != 0 {
|
||||
t.Fatalf("unexpected security event: %#v", securityEvent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeSecuritySampleProducesCapabilities(t *testing.T) {
|
||||
raw := syscallEventRaw{
|
||||
PID: 42, PPID: 7, UID: 1000, SyscallID: hostCapset,
|
||||
Args: [6]uint64{(1 << 2) | (1 << 21)},
|
||||
}
|
||||
copyCString(raw.Comm[:], "python3")
|
||||
var sample bytes.Buffer
|
||||
if err := binary.Write(&sample, binary.NativeEndian, raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
securityEvent, err := decodeSecuritySample(sample.Bytes())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := []string{"CAP_DAC_READ_SEARCH", "CAP_SYS_ADMIN"}
|
||||
if securityEvent.Host == nil || securityEvent.Host.Event != "capset" ||
|
||||
!slices.Equal(securityEvent.Host.Capabilities, want) {
|
||||
t.Fatalf("unexpected security event: %#v", securityEvent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeSecuritySampleProducesModuleLoad(t *testing.T) {
|
||||
raw := syscallEventRaw{
|
||||
PID: 42, PPID: 7, UID: 0, SyscallID: hostFinitModule,
|
||||
Args: [6]uint64{5},
|
||||
}
|
||||
var sample bytes.Buffer
|
||||
if err := binary.Write(&sample, binary.NativeEndian, raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
securityEvent, err := decodeSecuritySample(sample.Bytes())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if securityEvent.Host == nil || securityEvent.Host.Event != "module_load" || securityEvent.pathDirFD != 5 {
|
||||
t.Fatalf("unexpected security event: %#v", securityEvent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeSecuritySampleProducesFileWrite(t *testing.T) {
|
||||
raw := syscallEventRaw{
|
||||
PID: 42, PPID: 7, UID: 1000, SyscallID: hostFileWrite,
|
||||
Args: [6]uint64{5, 128},
|
||||
}
|
||||
copyCString(raw.Comm[:], "python3")
|
||||
var sample bytes.Buffer
|
||||
if err := binary.Write(&sample, binary.NativeEndian, raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
securityEvent, err := decodeSecuritySample(sample.Bytes())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if securityEvent.Host == nil || securityEvent.Host.Event != "file_write" ||
|
||||
securityEvent.Host.Comm != "python3" || securityEvent.pathDirFD != 5 {
|
||||
t.Fatalf("unexpected security event: %#v", securityEvent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeSecuritySampleProducesNetworkEvents(t *testing.T) {
|
||||
raw := syscallEventRaw{
|
||||
PID: 42, PPID: 7, UID: 1000, SyscallID: hostTCPConnect,
|
||||
Args: [6]uint64{unix.AF_INET, 4444, 0, 5},
|
||||
}
|
||||
copyCString(raw.Comm[:], "python3")
|
||||
address := []byte{8, 8, 8, 8}
|
||||
raw.Args[2] = uint64(binary.NativeEndian.Uint32(address))
|
||||
var sample bytes.Buffer
|
||||
if err := binary.Write(&sample, binary.NativeEndian, raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
securityEvent, err := decodeSecuritySample(sample.Bytes())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if securityEvent.Host == nil || securityEvent.Host.Event != "tcp_connect" ||
|
||||
securityEvent.Host.PeerIP != "8.8.8.8" || securityEvent.Host.PeerPort != 4444 ||
|
||||
securityEvent.pathDirFD != 5 {
|
||||
t.Fatalf("unexpected network event: %#v", securityEvent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeSecuritySampleProducesAccept(t *testing.T) {
|
||||
raw := syscallEventRaw{
|
||||
PID: 42, PPID: 7, UID: 1000, SyscallID: hostAccept4,
|
||||
Args: [6]uint64{0, 0, 0, 6},
|
||||
}
|
||||
copyCString(raw.Comm[:], "python3")
|
||||
var sample bytes.Buffer
|
||||
if err := binary.Write(&sample, binary.NativeEndian, raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
securityEvent, err := decodeSecuritySample(sample.Bytes())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if securityEvent.Host == nil || securityEvent.Host.Event != "accept" || securityEvent.pathDirFD != 6 {
|
||||
t.Fatalf("unexpected accept event: %#v", securityEvent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMonitoredSyscallsUsesPlatformConstants(t *testing.T) {
|
||||
numbers := monitoredSyscalls()
|
||||
if len(numbers) != 26+len(legacyPermissionSyscalls())+len(legacyNetworkSyscalls()) ||
|
||||
numbers[unix.SYS_MEMFD_CREATE] != syscallMemfdCreate ||
|
||||
numbers[unix.SYS_MPROTECT] != syscallMprotect || numbers[unix.SYS_SETUID] != hostSetuid {
|
||||
t.Fatalf("unexpected syscall map: %#v", numbers)
|
||||
}
|
||||
for _, number := range []uint64{
|
||||
unix.SYS_WRITE, unix.SYS_WRITEV, unix.SYS_PWRITE64,
|
||||
unix.SYS_PWRITEV, unix.SYS_PWRITEV2,
|
||||
} {
|
||||
if numbers[number] != hostFileWrite {
|
||||
t.Fatalf("write syscall %d maps to %d", number, numbers[number])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveProcessPath(t *testing.T) {
|
||||
procRoot := t.TempDir()
|
||||
processDir := filepath.Join(procRoot, "42")
|
||||
if err := os.Mkdir(processDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Symlink("/etc/systemd", filepath.Join(processDir, "cwd")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(processDir, "fd"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Symlink("/etc", filepath.Join(processDir, "fd", "5")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Symlink("socket:[12345]", filepath.Join(processDir, "fd", "6")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(procRoot, "net"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tcpTable := "sl local remote st tx rx tr tm retr uid timeout inode\n0: 0100007F:1F90 08080808:115C 01 0 0 0 0 0 12345\n"
|
||||
if err := os.WriteFile(filepath.Join(procRoot, "net", "tcp"), []byte(tcpTable), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := resolveProcessPath(procRoot, 42, -100, "system/agent.service"); got != "/etc/systemd/system/agent.service" {
|
||||
t.Fatalf("resolved path=%q", got)
|
||||
}
|
||||
if got := resolveProcessPath(procRoot, 42, 5, "cron.d/agent"); got != "/etc/cron.d/agent" {
|
||||
t.Fatalf("dirfd-resolved path=%q", got)
|
||||
}
|
||||
if got := resolveProcessFD(procRoot, 42, 5); got != "/etc" {
|
||||
t.Fatalf("fd-resolved path=%q", got)
|
||||
}
|
||||
if !isTCPSocket(procRoot, 42, 6) || isTCPSocket(procRoot, 42, 5) {
|
||||
t.Fatal("TCP socket classification mismatch")
|
||||
}
|
||||
local, peer, ok := lookupTCPSocket(procRoot, 42, 6)
|
||||
if !ok || local.IP != "127.0.0.1" || local.Port != 8080 || peer.IP != "8.8.8.8" || peer.Port != 4444 {
|
||||
t.Fatalf("unexpected endpoints: local=%#v peer=%#v ok=%v", local, peer, ok)
|
||||
}
|
||||
if got := resolveProcessPath(procRoot, 99, -100, "relative"); got != "relative" {
|
||||
t.Fatalf("unresolved path=%q", got)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue