feat(go): advance validation sidecar toward production

This commit is contained in:
Luna 2026-07-22 01:52:19 -07:00
parent 6a06eba255
commit f85c2e831a
No known key found for this signature in database
92 changed files with 8881 additions and 91 deletions

View file

@ -0,0 +1,131 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package ebpfsource
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"github.com/cilium/ebpf/link"
"github.com/cilium/ebpf/perf"
"github.com/cilium/ebpf/rlimit"
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/events"
)
// ExecSource owns the Go-native exec tracepoint and perf reader. Load errors
// are returned to the caller so startup can log and fall back to polling.
type ExecSource struct {
objects execObjects
link link.Link
reader *perf.Reader
}
// execEventRaw mirrors struct exec_event in bpf/exec.bpf.c. Keeping the wire
// layout explicit makes changes to the kernel/user-space ABI reviewable.
type execEventRaw struct {
PID uint32
PPID uint32
UID uint32
ParentComm [16]int8
Filename [128]int8
Arg1 [128]int8
Arg2 [128]int8
}
// LostSamplesError reports perf-buffer pressure without making it impossible
// for the caller to continue reading later events.
type LostSamplesError struct {
Count uint64
}
func (e LostSamplesError) Error() string {
return fmt.Sprintf("exec perf buffer lost %d samples", e.Count)
}
func OpenExecSource() (*ExecSource, error) {
// Required by older kernels. On newer memcg-accounted kernels this may fail
// in an otherwise capable service sandbox, so attempt the load regardless
// and preserve both errors only when loading also fails.
memlockErr := rlimit.RemoveMemlock()
var objects execObjects
if err := loadExecObjects(&objects, nil); err != nil {
if memlockErr != nil {
return nil, fmt.Errorf("load exec BPF objects: %w (memlock adjustment: %v)", err, memlockErr)
}
return nil, fmt.Errorf("load exec BPF objects: %w", err)
}
attached, err := link.Tracepoint("syscalls", "sys_enter_execve", objects.TraceExecve, nil)
if err != nil {
objects.Close()
return nil, fmt.Errorf("attach exec tracepoint: %w", err)
}
reader, err := perf.NewReader(objects.Events, 64*4096)
if err != nil {
attached.Close()
objects.Close()
return nil, fmt.Errorf("open exec perf reader: %w", err)
}
return &ExecSource{objects: objects, link: attached, reader: reader}, nil
}
func (s *ExecSource) Read() (events.ExecEvent, error) {
record, err := s.reader.Read()
if err != nil {
return events.ExecEvent{}, err
}
if record.LostSamples != 0 {
return events.ExecEvent{}, LostSamplesError{Count: record.LostSamples}
}
return decodeExecSample(record.RawSample)
}
func decodeExecSample(sample []byte) (events.ExecEvent, error) {
var raw execEventRaw
if len(sample) != binary.Size(raw) {
return events.ExecEvent{}, fmt.Errorf("exec event size %d, want %d", len(sample), binary.Size(raw))
}
if err := binary.Read(bytes.NewReader(sample), binary.NativeEndian, &raw); err != nil {
return events.ExecEvent{}, fmt.Errorf("decode exec event: %w", err)
}
argv := make([]string, 0, 2)
if value := cString(raw.Arg1[:]); value != "" {
argv = append(argv, value)
}
if value := cString(raw.Arg2[:]); value != "" {
argv = append(argv, value)
}
return events.ExecEvent{
PID: int(raw.PID), PPID: int(raw.PPID), UID: int(raw.UID),
ParentComm: cString(raw.ParentComm[:]), Filename: cString(raw.Filename[:]),
Argv: argv,
}, nil
}
func (s *ExecSource) Close() error {
var failures []error
if s.reader != nil {
failures = append(failures, s.reader.Close())
}
if s.link != nil {
failures = append(failures, s.link.Close())
}
failures = append(failures, s.objects.Close())
return errors.Join(failures...)
}
func cString(raw []int8) string {
value := make([]byte, 0, len(raw))
for _, char := range raw {
if char == 0 {
break
}
value = append(value, byte(char))
}
return string(value)
}
var _ io.Closer = (*ExecSource)(nil)