Skip to content

Repository files navigation

eBPF Runtime Security Agent

A lightweight host and container security agent that hooks into Linux kernel syscalls using eBPF (CO-RE), streams events to userspace via BPF ring buffers in real-time, enriches them with process and container metadata, and evaluates them against behavioral baselines + anomaly scoring rules to emit alerts.

Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                            KERNEL SPACE (eBPF)                              │
├─────────────────────────────────────────────────────────────────────────────┤
│  Tracepoints:                                                               │
│  • sys_enter_execve / sys_enter_execveat  →  execve events (pid, ppid,     │
│    uid, cgroup_id, comm, filename)                                          │
│  • sys_enter_connect / sys_enter_accept* → network events (pid, ppid,      │
│    uid, cgroup_id, comm, dest_ip, dest_port, is_accept)                     │
│  • sys_enter_openat (filtered) → sensitive file opens only                 │
│                                                                             │
│  All events → BPF Ring Buffer (per program)                                │
└────────────────────────────────┬────────────────────────────────────────────┘
                                 │
                                 ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                           USERSPACE (Go Agent)                              │
├─────────────────────────────────────────────────────────────────────────────┤
│  1. Ring Buffer Readers (per tracepoint)                                   │
│  2. Event Consolidation Channel                                            │
│  3. Real-time Enrichment:                                                  │
│     • /proc/<pid>/cmdline → full command line                              │
│     • /proc/<pid>/cgroup   → container ID (64-char hex)                    │
│     • UID lookup           → username                                      │
│  4. Baseline Store (per-container JSON, persisted to disk)                 │
│     • Observed binaries (allowlist)                                        │
│     • Observed dest IP:port pairs                                          │
│     • Process tree (parent comm → child comm)                              │
│     • Learning phase tracking (configurable window)                        │
│  5. Rule Engine (baseline-aware scoring):                                  │
│     • root_exec_in_container          (high)                               │
│     • shell_spawned_by_non_shell_parent (medium)                          │
│     • outbound_connection_from_container (medium, baseline-aware)         │
│     • sensitive_file_read             (high, always alert)                │
│     • unseen_binary_exec              (high, baseline-aware)              │
│     • anomalous_process_tree          (high, baseline-aware)              │
│  6. Output: JSON alerts to stdout (SIEM-ready)                             │
└─────────────────────────────────────────────────────────────────────────────┘

Features

Kernel-Side Probes (C, CO-RE via libbpf)

  • execve/execveat: Captures every process execution with binary path, PID, PPID, UID, cgroup ID
  • connect/accept/accept4: Captures outbound AND inbound connections with dest IP, port, protocol
  • openat (filtered): Only emits events for sensitive paths (/etc/shadow, SSH keys, Docker socket, etc.) — reduces noise

Userspace Agent (Go, cilium/ebpf)

  • Real-time enrichment: cmdline, container ID, username
  • Behavioral Baselining: Per-container learning phase (configurable, default 10 min) builds allowlists of binaries, network destinations, process tree edges
  • Anomaly Scoring: Deviations from baseline increment a running risk score per container (exponential moving average)
  • Baseline-Aware Rules: Rules check baseline before alerting (e.g., only alert on new outbound destinations after learning)
  • Persistence: Baselines saved to baseline_data/<container_id>.json — survives agent restarts
  • Configurable: All thresholds/weights via agent-config.yaml

Detection Rules (Baseline-Aware)

Rule Severity Baseline-Aware Description
root_exec_in_container High Root exec of binary not seen during learning
shell_spawned_by_non_shell_parent Medium Shell spawned by non-shell parent; checks baseline for parent→child edge
outbound_connection_from_container Medium Outbound connection to destination not in baseline (ignores loopback, accept)
sensitive_file_read High ❌ (always) Read of /etc/shadow, SSH keys, Docker socket by non-allowed process
unseen_binary_exec High Binary execution not in baseline allowlist
anomalous_process_tree High Parent→child process relationship not seen during learning

Build Prerequisites

Linux environment (Ubuntu 22.04+/Debian 12+, kernel 5.15+) with:

  • Kernel with CONFIG_DEBUG_INFO_BTF=y (check: grep CONFIG_DEBUG_INFO_BTF /boot/config-$(uname -r))
  • LLVM/Clang ≥ 14
  • libbpf development headers
  • Go ≥ 1.21

Install on Ubuntu/Debian:

sudo apt update
sudo apt install -y clang llvm libbpf-dev linux-headers-$(uname -r) bpftool make go git

Verify BTF is available:

ls -l /sys/kernel/btf/vmlinux

How to Build and Run

1. Generate Kernel Types and Bindings

Extract kernel BTF to vmlinux.h and generate Go bindings via bpf2go:

make generate

This creates bpf/vmlinux.h and gen/*_bpf.go files.

2. Build the Agent

Compile the userspace Go agent binary (Linux target):

make build

Output: ./ebpf-runtime-agent

3. Run the Agent

Requires root (CAP_BPF, CAP_PERFMON, CAP_SYS_ADMIN):

sudo ./ebpf-runtime-agent run [config.yaml]

Default config: agent-config.yaml in working directory.

Run options:

# Run with all events logged as JSON (for SIEM ingestion)
sudo ./ebpf-runtime-agent run --log-all-events

# Run with web dashboard on port 8080
sudo ./ebpf-runtime-agent run --web --web-port 8080

# Custom config and baseline directory
sudo ./ebpf-runtime-agent run -c /etc/agent-config.yaml --baseline-dir /var/lib/ebpf-agent

4. CLI Commands (Cobra-based)

# Run the agent
sudo ./ebpf-runtime-agent run [flags]

# Show agent status
sudo ./ebpf-runtime-agent status

# Reset baseline for a container
sudo ./ebpf-runtime-agent baseline reset <container_id>

# Show version
./ebpf-runtime-agent version

# Help
./ebpf-runtime-agent --help
./ebpf-runtime-agent run --help

Testing the Detection

Quick Test (Manual)

# Terminal 1: Run agent
sudo ./ebpf-runtime-agent run

# Terminal 2: Trigger detections
docker run --rm alpine whoami                    # root_exec_in_container
docker run --rm alpine sh -c "echo hello"        # shell_spawned_by_non_shell_parent
docker run --rm alpine wget -qO- http://example.com  # outbound_connection
sudo cat /etc/shadow                              # sensitive_file_read

Full Test Suite (Automated)

The test/ directory provides a complete demo environment:

  1. Start test containers (benign workloads for baseline learning):
cd test
docker compose up -d

This starts:

  • test-web: nginx on port 8080
  • test-python-app: Python script polling httpbin.org every 30s
  • test-alpine: Idle Alpine container for manual testing
  1. Run the agent and wait for baseline learning to complete (default 10 min):
sudo ../ebpf-runtime-agent run

Watch for "Learning complete for container X" logs (or just wait 10 min).

  1. Simulate attacks against test-alpine:
./attack-sim.sh test-alpine

This triggers all 6 rule types in sequence. Observe the agent's JSON alerts and escalating risk scores.

  1. Cleanup:
docker compose down

Configuration (agent-config.yaml)

baseline_window_minutes: 10    # Learning phase duration
risk_threshold: 40.0           # Alert threshold (not currently used for gating)
rule_weights:
  root_exec: 15.0
  shell_spawned_by_non_shell_parent: 20.0
  outbound_connection_from_container: 10.0
  sensitive_file_read: 30.0
alert_on_startup: true         # Emit alerts during learning phase

Key Configuration Notes

  • baseline_window_minutes: How long the agent observes before considering the baseline "learned." During learning, anomaly scores are dampened and baseline-aware rules don't fire.
  • rule_weights: Score added to container's running risk score when a rule triggers. Higher = more severe.
  • risk_threshold: Reserved for future use (e.g., gating alerts, web dashboard coloring).

Output Format (JSON)

All alerts are emitted as structured JSON to stdout:

{
  "timestamp": "2026-09-02T15:04:05.000Z",
  "severity": "high",
  "rule": "unseen_binary_exec",
  "message": "Unseen binary execution in container a1b2c3d4: /usr/bin/curl",
  "pid": 12345,
  "ppid": 12300,
  "uid": 0,
  "username": "root",
  "comm": "curl",
  "container_id": "a1b2c3d4e5f67890...",
  "cmdline": "curl --version"
}

This is SIEM-ready (Elastic, Splunk, Loki, etc. can ingest directly).

SIEM Integration: Log All Events

Enable --log-all-events to stream every enriched event (not just alerts) as JSON lines:

sudo ./ebpf-runtime-agent run --log-all-events | jq .

Example event:

{
  "type": "EXECVE",
  "timestamp": "2026-09-02T15:04:05.123Z",
  "pid": 12345,
  "ppid": 12300,
  "uid": 0,
  "cgroup_id": 42,
  "username": "root",
  "comm": "curl",
  "container_id": "a1b2c3d4e5f67890...",
  "cmdline": "curl --version",
  "filename": "/usr/bin/curl"
}

Pipe to log shippers (Filebeat, Fluent Bit, Vector) for Elastic/Splunk/Datadog.


Web Dashboard (Optional)

Enable a minimal live dashboard:

sudo ./ebpf-runtime-agent run --web --web-port 8080

Then open http://<host>:8080 in a browser. Features:

  • Live container risk scores (auto-refreshes every 5s)
  • Learning phase status per container
  • Observed binaries/destinations counts
  • Color-coded risk levels (green/yellow/red)

The dashboard is a single embedded HTML page — no external dependencies.


Known Limitations

Limitation Details
Linux only eBPF CO-RE requires Linux kernel ≥ 5.8 with BTF. No Windows/macOS support.
Single host No multi-node/Kubernetes support. Designed for single-host Docker/containerd.
Heuristic scoring Not ML-based. Anomaly scores use explainable weighted heuristics (transparent, interview-friendly).
No enforcement Detection only. No process killing/blocking (future work).
Kernel version Requires kernel ≥ 5.8 with BTF. Older kernels need BCC (not supported here).
cgroup v1/v2 Container ID extraction works for standard Docker/containerd cgroup layouts. May need tuning for exotic runtimes.
Ring buffer loss Under extreme load, ring buffer may drop events (256KB per program). Tunable in C code.
IPv6 parsing Basic support; dest_port byte-order handled but IPv6 scope IDs not fully parsed.

Project Structure

.
├── bpf/                    # eBPF C programs (kernel-space)
│   ├── common.h           # Shared struct definitions
│   ├── execve.c           # execve/execveat tracepoints
│   ├── connect.c          # connect/accept/accept4 tracepoints
│   └── openat.c           # openat tracepoint (sensitive-only)
├── gen/                    # Generated Go bindings (bpf2go output)
│   ├── generate.go        # go:generate directives
│   └── *_bpf.go           # Generated loader code
├── internal/
│   ├── baseline/          # Baseline store + anomaly scoring
│   ├── config/            # YAML config loading
│   ├── enrich/            # PID → cmdline, container ID, username
│   ├── events/            # Event parsing + types
│   ├── loader/            # eBPF program loading + attachment
│   ├── output/            # JSON console sink (extensible)
│   └── rules/             # Detection rules + engine
├── test/                   # Demo environment
│   ├── docker-compose.yml # Benign test containers
│   └── attack-sim.sh      # Attack simulation script
├── agent-config.yaml       # Default configuration
├── main.go                 # CLI entrypoint (Cobra)
├── Makefile                # Build automation
├── setup-vm.sh             # Fresh Ubuntu VM setup
├── ebpf-runtime-agent.service # systemd service
├── .gitignore
└── go.mod / go.sum         # Go module

Development Notes

Adding a New Tracepoint

  1. Add C program in bpf/<name>.c with SEC("tracepoint/...") and ringbuf
  2. Add go:generate line in gen/generate.go
  3. Run make generate
  4. Update internal/loader/loader.go to load/attach new program
  5. Update internal/events/event.go if new fields needed

Adding a New Rule

  1. Implement Rule interface in internal/rules/rules.go
  2. Register in NewRuleEngine in internal/rules/engine.go
  3. Add weight to agent-config.yaml under rule_weights

Regenerating eBPF Bindings

After modifying .c files or common.h:

make generate
make build

Future Work (Not in Scope)

  • Kubernetes integration (pod/namespace awareness via kubelet API)
  • Active response: kill container, block network, pause cgroup
  • Web dashboard (serving live risk scores via embedded HTTP) — implemented in Phase 3
  • ML-based anomaly detection (replace heuristic scoring)
  • Syscall frequency anomaly detection (statistical models)
  • File integrity monitoring (inotify/fanotify + eBPF)
  • Encrypted traffic analysis (TLS SNI extraction via uprobe)

License

MIT License — see LICENSE file (if added).

About

eBPF Runtime Security Agent

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages