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
131
go-agent/internal/ebpfsource/exec.go
Normal file
131
go-agent/internal/ebpfsource/exec.go
Normal 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue