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
- func BlindEvent(runID uuid.UUID, reason string) types.AuditEvent
- func HeartbeatEventWithDropped(droppedTotal, observedTotal, droppedUnmapped uint64, ...) types.AuditEvent
- func IsDynamicLinker(binary string) bool
- func IsSensitivePath(path string) bool
- type Correlation
- type Correlator
- type EventData
- type Mapper
- type Subtype
- type TetragonArg
- type TetragonEvent
- type TetragonFileArg
- type TetragonProcess
- type TetragonProcessExec
- type TetragonProcessKprobe
- type TetragonSockArg
Constants ¶
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".
const KernelActionPrefix = "kernel."
KernelActionPrefix is the required prefix for every ground-truth action; the control-plane endpoint fail-closed rejects any action lacking it.
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.
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 ¶
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 ¶
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.
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.
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.