groundtruth

package
v0.8.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 30, 2026 License: Apache-2.0 Imports: 6 Imported by: 0

Documentation

Overview

Package groundtruth maps kernel-level eBPF (Tetragon) observations into Wardyn's audit vocabulary (types.AuditEvent): the tamper-proof ground-truth counterpart to the agent's self-report and PTY replay. A host-scoped sidecar (cmd/wardyn-tetragon-ingest) correlates each kernel event to a run via the `wardyn.run-id` container label, maps it here, and POSTs append-only batches keyed on run_id with a `kernel.*` action prefix and data.stream="ebpf" (Tetragon's JSON export, not gRPC, to keep go.mod light).

HONESTY: this stream is detection, not prevention — the ld-linux/mmap dynamic-linker bypass of execve hooks is real and surfaced via data.loader=true rather than hidden; host eBPF is also blind inside CC3/Kata microVM guests, so callers emit a one-time kernel.sensor.bypass event.

Index

Constants

View Source
const (
	ActionProcessExec    = "kernel.process.exec"    // process execve observed by the kernel sensor
	ActionNetworkConnect = "kernel.network.connect" // outbound TCP connect observed by the sensor
	ActionFileWrite      = "kernel.file.write"      // write to a sensitive path observed by the sensor

	// ActionSensorHeartbeat is the periodic liveness beat the ingest sidecar
	// emits (run_id NULL); /healthz keys ebpf_groundtruth state off the most
	// recent one within a TTL, so "healthy" requires events actually arriving.
	ActionSensorHeartbeat = "kernel.sensor.ping"
	// ActionSensorBlind is the one-time event for a run the host eBPF sensor
	// can't see into (CC3/Kata microVM guest); blindness is made visible, not silent.
	ActionSensorBlind = "kernel.sensor.bypass"
)

Kernel-event action namespace: every event carries one of these as types.AuditEvent.Action. The `kernel.` prefix is enforced by the ingest endpoint (rejected if absent) and is what SIEM rules key on alongside data.stream="ebpf".

View Source
const KernelActionPrefix = "kernel."

KernelActionPrefix is the required prefix for every ground-truth action; the control-plane endpoint fail-closed rejects any action lacking it.

View Source
const SensorActor = "wardyn-tetragon-ingest"

SensorActor is the fixed audit actor for sensor-originated events. SECURITY: the control plane forces actor=SensorActor + actor_type=system on ingest, so a compromised sensor can't impersonate a human or an agent run.

View Source
const Stream = "ebpf"

Stream is data.stream on every event here; SIEM rules and the UI discriminate ground-truth from agent self-report on it, without parsing the action.

Variables

This section is empty.

Functions

func BlindEvent

func BlindEvent(runID uuid.UUID, reason string) types.AuditEvent

BlindEvent builds the one-time event for a run the host eBPF sensor can't see into (CC3/Kata), bound to the run so the gap is visible, not silent.

func HeartbeatEventWithDropped

func HeartbeatEventWithDropped(droppedTotal, observedTotal, droppedUnmapped uint64, observedByKind map[string]uint64) types.AuditEvent

HeartbeatEventWithDropped builds the periodic sensor liveness beat. run_id is NULL (host-scoped); /healthz keys ebpf_groundtruth state off the most recent one within a TTL.

droppedTotal merges two loss causes (full POST buffer; oversized/ unterminated export line) since both answer the same operator question; a log line distinguishes them. observedTotal is the real-kernel-event count mapped — together they show "alive but idle" vs "events flowing". observedByKind splits it per event kind (nil on older builds), since the aggregate can't tell all kinds arriving from one masking others gone blind. droppedUnmapped counts events refused for correlating to no run, so "saw nothing" isn't conflated with "saw plenty, correlated none".

func IsDynamicLinker

func IsDynamicLinker(binary string) bool

IsDynamicLinker reports whether binary is a dynamic linker / loader (the ld-linux/mmap bypass surface, flagged not prevented), including as the first argv token (`ld-linux.so ./payload`).

func IsSensitivePath

func IsSensitivePath(path string) bool

IsSensitivePath reports whether a written file path is in the allowlist of paths we record on the file_write subtype. Empty paths are not sensitive.

Types

type Correlation

type Correlation string

Correlation records whether an event was bound to a known Wardyn run.

const (
	CorrelationMapped Correlation = "mapped" // the event's container/cgroup resolved to a run
	// CorrelationUnmapped means it did not; never silently dropped, but
	// emitted with run_id NULL so an unmapped kernel event near a run is a visible signal.
	CorrelationUnmapped Correlation = "unmapped"
)

type Correlator

type Correlator interface {
	// RunForContainer returns the run id for a container id (any prefix
	// length Tetragon emits); ok is false for unknown / non-Wardyn containers.
	RunForContainer(containerID string) (runID uuid.UUID, ok bool)
}

Correlator resolves a container id to a Wardyn run. The ingest sidecar implements it by indexing docker containers labelled wardyn.managed=true (fed by `docker events`, reconciled by `docker ps -a`; no cgroup-id index, since this export shape carries none) — the only correlation knowledge this package has, keeping the mapper target-agnostic and unit-testable.

type EventData

type EventData struct {
	Stream      string   `json:"stream"`                 // always Stream ("ebpf")
	Subtype     Subtype  `json:"subtype"`                // fine-grained kernel-event kind
	CgroupID    uint64   `json:"cgroup_id,omitempty"`    // kernel cgroup id of the observed process (0 if unknown)
	ContainerID string   `json:"container_id,omitempty"` // (truncated) container id the event was attributed to
	Argv        []string `json:"argv,omitempty"`         // full process command line for exec events
	Dst         string   `json:"dst,omitempty"`          // "ip:port" for network_connect events
	Path        string   `json:"path,omitempty"`         // written file path for file_write events
	// Loader is true when the exec'd binary is a dynamic linker (ld-linux /
	// ld-musl): such an exec can load+run an arbitrary ELF the execve hook
	// never named. Flagged, not blocked.
	Loader      bool        `json:"loader,omitempty"`
	Correlation Correlation `json:"correlation"`      // "mapped" or "unmapped"
	Reason      string      `json:"reason,omitempty"` // free-form explanation for sensor.blind / failure cases
}

EventData is the JSON shape stored in audit_events.data for every event here (audit_events.data is JSONB, so this needs no schema change); deliberately small and stable so SIEM rules can rely on it.

type Mapper

type Mapper struct {
	// contains filtered or unexported fields
}

Mapper converts a Tetragon JSON event into a Wardyn AuditEvent. Stateless apart from the injected Correlator; clock-free (the sidecar stamps time). Safe for concurrent use when the Correlator is.

func NewMapper

func NewMapper(corr Correlator) *Mapper

NewMapper builds a Mapper over a Correlator. A nil Correlator treats every event as unmapped — never a panic, so a mis-wired sidecar degrades to visible blindness rather than crashing.

func (*Mapper) Map

func (m *Mapper) Map(ev TetragonEvent) (types.AuditEvent, bool)

Map converts ev into an AuditEvent. ok is false for an unrecorded event kind or a non-sensitive file write (filtered noise). When ok is true, the event has Action with the kernel. prefix and Data with stream="ebpf".

Events that can't be bound to a run are still returned (ok=true) with RunID nil and correlation="unmapped" — blindness must be visible, never a silent drop.

func (*Mapper) MapLine

func (m *Mapper) MapLine(line []byte) (types.AuditEvent, bool)

MapLine parses one JSON line of the Tetragon export and maps it. ok is false on a parse error or an unrecorded/filtered event.

type Subtype

type Subtype string

Subtype is the fine-grained kernel-event kind carried in data.subtype: stable, machine-readable, one-to-one with the action where applicable.

const (
	SubtypeProcessExec    Subtype = "process_exec"
	SubtypeNetworkConnect Subtype = "network_connect"
	SubtypeFileWrite      Subtype = "file_write"
)

type TetragonArg

type TetragonArg struct {
	FileArg   *TetragonFileArg `json:"file_arg,omitempty"`
	PathArg   *TetragonFileArg `json:"path_arg,omitempty"`
	StringArg string           `json:"string_arg,omitempty"`
	SockArg   *TetragonSockArg `json:"sock_arg,omitempty"`
}

TetragonArg is one kprobe argument: path-bearing shapes (file_arg.path, path_arg.path, string_arg) and the socket shape (sock_arg) used for connect detection.

type TetragonEvent

type TetragonEvent struct {
	ProcessExec   *TetragonProcessExec   `json:"process_exec,omitempty"`
	ProcessKprobe *TetragonProcessKprobe `json:"process_kprobe,omitempty"`
}

TetragonEvent is one line of the Tetragon JSON export; exactly one event-kind field is set. process_exec and process_kprobe are mapped; the kprobe handler multiplexes file-writes and network connects by function name + argument shape. Other kinds are ignored (ok=false).

type TetragonFileArg

type TetragonFileArg struct {
	Path string `json:"path,omitempty"`
}

TetragonFileArg carries a path-bearing argument.

type TetragonProcess

type TetragonProcess struct {
	Binary    string `json:"binary,omitempty"`
	Arguments string `json:"arguments,omitempty"`
	CgroupID  uint64 `json:"cgroup_id,string,omitempty"`
	Docker    string `json:"docker,omitempty"` // container id (truncated)
}

TetragonProcess is the common process descriptor across event kinds: binary, arguments, and container/cgroup correlation handles.

type TetragonProcessExec

type TetragonProcessExec struct {
	Process *TetragonProcess `json:"process,omitempty"`
}

TetragonProcessExec carries a process execve.

type TetragonProcessKprobe

type TetragonProcessKprobe struct {
	Process      *TetragonProcess `json:"process,omitempty"`
	FunctionName string           `json:"function_name,omitempty"`
	Args         []TetragonArg    `json:"args,omitempty"`
}

TetragonProcessKprobe carries a kprobe hit — the workhorse event kind for everything other than exec. FunctionName identifies the hooked kernel function; Args carry the typed argument objects, used for sensitive file writes (file_arg/path_arg/string_arg) and outbound connects (sock_arg).

type TetragonSockArg

type TetragonSockArg struct {
	DAddr string `json:"daddr,omitempty"`
	DPort int    `json:"dport,omitempty"`
}

TetragonSockArg is Tetragon's KprobeSock, emitted by a connect kprobe; only the destination tuple (daddr/dport) is read.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL