feat(serviceradar-agent): siper XDP firewall integration #1000

Open
opened 2026-03-28 04:30:41 +00:00 by mfreeman451 · 3 comments
Owner

Imported from GitHub.

Original GitHub issue: #2778
Original author: @mfreeman451
Original URL: https://github.com/carverauto/serviceradar/issues/2778
Original created: 2026-02-10T20:50:08Z


PRD: ServiceRadar "Fortress" (eBPF Shield & Sentinel)

Target Component: pkg/ebpf/fortress
Target Platform: Linux 5.10+ (LSM and CO-RE support required)
Security Level: Hardened / Self-Defending

1. Executive Summary

ServiceRadar Fortress is an integrated eBPF security suite for the serviceradar-agent. It provides two primary defense layers:

  1. Shield (Network): An XDP-based firewall that drops malicious traffic at the NIC driver level.
  2. Sentinel (Host): An LSM-based integrity monitor that prevents unauthorized modification of the agent binary, configurations, and BPF state.

By embedding these capabilities directly into the signed agent binary, Fortress creates a "kernel-fortified" process that is resistant to tampering even by root-level attackers.


2. Architecture

2.1 The Fortress Umbrella

Fortress operates as a unified service within the Go agent, managing multiple eBPF program types:

  • XDP (eXpress Data Path): High-speed packet filtering (Shield).
  • LSM (Linux Security Module): Policy enforcement for files and processes (Sentinel).

2.2 Control Plane (Go)

  • Signed Configuration: Receives CIDR blacklists and FIM policies from the Gateway via a signed ControlStream.
  • Instruction Audit: Inspects eBPF bytecode for dangerous helper functions before loading.
  • State Persistence: Pins BPF programs to /sys/fs/bpf/serviceradar to ensure defenses survive an agent restart or crash.

3. The "Airtight" Security Model

Layer 1: Immutable Provenance (No-Object-Files)

  • Requirement: Bytecode must be embedded in the Go binary.
  • Enforcement: Use bpf2go to generate Go packages that embed the ELF bytecode via //go:embed.
  • Defense: Attackers cannot "swap" eBPF logic by replacing a file on disk.

Layer 2: Instruction Sanitization (The Sandbox)

  • Requirement: The Agent must audit the BPF instruction set before loading.
  • Enforcement: Fortress must iterate through BPF_CALL instructions and cross-reference them against an Allowlist.
  • Restricted Helpers: Helpers like bpf_probe_write_user and bpf_override_return are strictly prohibited.

Layer 3: Shield LSM (Process Isolation)

  • Requirement: Shield BPF Maps (IP Blacklists) are "Agent-Only" zones.
  • Enforcement: A BPF LSM hook on bpf_map_update_elem checks the calling process identity.
  • Defense: Only the serviceradar-agent PID can modify rules. Even root using bpftool will be denied access to Shield maps.

Layer 4: Sentinel LSM (File Self-Defense)

  • Requirement: The Agent binary and configuration must be immutable in the kernel.
  • Enforcement: LSM hooks on inode_permission, inode_rename, and inode_unlink.
  • Policies:
    • Binary Protection: Deny WRITE access to the serviceradar-agent executable for all processes except the system update manager.
    • Config Protection: Deny WRITE access to /etc/serviceradar/*.json to any process other than the Agent.
  • Defense: Prevents "Live Patching" or configuration hijacking.

Layer 5: Management Safety Valve (The Golden Rule)

  • Requirement: The firewall must never block the ServiceRadar Gateway.
  • Enforcement: The XDP program includes a hardcoded bypass for the Gateway IP.
  • Security Value: Eliminates the risk of "bricking" an edge device via a bad firewall policy.

4. Functional Requirements

4.1 Shield (Network Firewall)

  • Match Logic: LPM_TRIE (Longest Prefix Match) for IPv4/IPv6.
  • Action: XDP_DROP for matches, XDP_PASS for misses.
  • Dry Run: Configurable "Log-Only" mode for testing rules.
  • Capacity: Support for 100,000+ CIDRs.

4.2 Sentinel (Integrity & Monitoring)

  • Real-time FIM: Monitor open, write, and unlink on sensitive paths.
  • Event Pipeline: Critical filesystem violations are sent to the Agent via a BPF PerfEventArray or RingBuf for immediate transmission to the Gateway.
  • Kernel Pining: Defenders persist in the kernel even if the userspace agent is killed.

5. Technical Specifications

5.1 Standards & Tooling

  • Library: github.com/cilium/ebpf (Pure Go loader).
  • Portability: BPF CO-RE (Compile Once – Run Everywhere) using vmlinux.h.
  • Kernel: Minimum 5.10 (LSM support).

5.2 Performance Targets

  • Network Latency: < 10ns per packet lookup.
  • FIM Overhead: Negligible impact on filesystem IOPS.
  • Memory: Total BPF map footprint < 64MB (configurable based on edge device RAM).

6. Implementation Rules for Contributors

  1. No Standalone ELF Files: All BPF code must be baked into the Go binary.
  2. No CGO: Loader logic must be written in pure Go.
  3. Atomic Safety: All map updates must be atomic to prevent race conditions in the firewall.
  4. Audit Logs: Every eBPF load, pin, or map-update must generate a local audit log.
  5. Fail-Open / Fail-Closed:
    • Shield (Network) should Fail Open (allow traffic) if the program cannot load.
    • Sentinel (Security) should Fail Closed (block access/alert) if integrity checks fail.

7. Success Metrics

  • Attack Simulation: A root user attempts to rm -rf /etc/serviceradar/sweep.json and is blocked by the kernel.
  • Visibility: The Gateway dashboard shows real-time drop counts for specific CIDRs.
  • Persistence: Defensive maps remain intact after an agent.service restart.
Imported from GitHub. Original GitHub issue: #2778 Original author: @mfreeman451 Original URL: https://github.com/carverauto/serviceradar/issues/2778 Original created: 2026-02-10T20:50:08Z --- # PRD: ServiceRadar "Fortress" (eBPF Shield & Sentinel) **Target Component:** `pkg/ebpf/fortress` **Target Platform:** Linux 5.10+ (LSM and CO-RE support required) **Security Level:** Hardened / Self-Defending ## 1. Executive Summary ServiceRadar **Fortress** is an integrated eBPF security suite for the `serviceradar-agent`. It provides two primary defense layers: 1. **Shield (Network):** An XDP-based firewall that drops malicious traffic at the NIC driver level. 2. **Sentinel (Host):** An LSM-based integrity monitor that prevents unauthorized modification of the agent binary, configurations, and BPF state. By embedding these capabilities directly into the signed agent binary, Fortress creates a "kernel-fortified" process that is resistant to tampering even by root-level attackers. --- ## 2. Architecture ### 2.1 The Fortress Umbrella Fortress operates as a unified service within the Go agent, managing multiple eBPF program types: * **XDP (eXpress Data Path):** High-speed packet filtering (Shield). * **LSM (Linux Security Module):** Policy enforcement for files and processes (Sentinel). ### 2.2 Control Plane (Go) * **Signed Configuration:** Receives CIDR blacklists and FIM policies from the Gateway via a signed `ControlStream`. * **Instruction Audit:** Inspects eBPF bytecode for dangerous helper functions before loading. * **State Persistence:** Pins BPF programs to `/sys/fs/bpf/serviceradar` to ensure defenses survive an agent restart or crash. --- ## 3. The "Airtight" Security Model ### Layer 1: Immutable Provenance (No-Object-Files) * **Requirement:** Bytecode must be embedded in the Go binary. * **Enforcement:** Use `bpf2go` to generate Go packages that embed the ELF bytecode via `//go:embed`. * **Defense:** Attackers cannot "swap" eBPF logic by replacing a file on disk. ### Layer 2: Instruction Sanitization (The Sandbox) * **Requirement:** The Agent must audit the BPF instruction set before loading. * **Enforcement:** Fortress must iterate through `BPF_CALL` instructions and cross-reference them against an **Allowlist**. * **Restricted Helpers:** Helpers like `bpf_probe_write_user` and `bpf_override_return` are strictly prohibited. ### Layer 3: Shield LSM (Process Isolation) * **Requirement:** Shield BPF Maps (IP Blacklists) are "Agent-Only" zones. * **Enforcement:** A BPF LSM hook on `bpf_map_update_elem` checks the calling process identity. * **Defense:** Only the `serviceradar-agent` PID can modify rules. Even `root` using `bpftool` will be denied access to Shield maps. ### Layer 4: Sentinel LSM (File Self-Defense) * **Requirement:** The Agent binary and configuration must be immutable in the kernel. * **Enforcement:** LSM hooks on `inode_permission`, `inode_rename`, and `inode_unlink`. * **Policies:** * **Binary Protection:** Deny `WRITE` access to the `serviceradar-agent` executable for all processes except the system update manager. * **Config Protection:** Deny `WRITE` access to `/etc/serviceradar/*.json` to any process other than the Agent. * **Defense:** Prevents "Live Patching" or configuration hijacking. ### Layer 5: Management Safety Valve (The Golden Rule) * **Requirement:** The firewall must never block the ServiceRadar Gateway. * **Enforcement:** The XDP program includes a hardcoded bypass for the Gateway IP. * **Security Value:** Eliminates the risk of "bricking" an edge device via a bad firewall policy. --- ## 4. Functional Requirements ### 4.1 Shield (Network Firewall) * **Match Logic:** `LPM_TRIE` (Longest Prefix Match) for IPv4/IPv6. * **Action:** `XDP_DROP` for matches, `XDP_PASS` for misses. * **Dry Run:** Configurable "Log-Only" mode for testing rules. * **Capacity:** Support for 100,000+ CIDRs. ### 4.2 Sentinel (Integrity & Monitoring) * **Real-time FIM:** Monitor `open`, `write`, and `unlink` on sensitive paths. * **Event Pipeline:** Critical filesystem violations are sent to the Agent via a BPF `PerfEventArray` or `RingBuf` for immediate transmission to the Gateway. * **Kernel Pining:** Defenders persist in the kernel even if the userspace agent is killed. --- ## 5. Technical Specifications ### 5.1 Standards & Tooling * **Library:** `github.com/cilium/ebpf` (Pure Go loader). * **Portability:** BPF CO-RE (Compile Once – Run Everywhere) using `vmlinux.h`. * **Kernel:** Minimum 5.10 (LSM support). ### 5.2 Performance Targets * **Network Latency:** < 10ns per packet lookup. * **FIM Overhead:** Negligible impact on filesystem IOPS. * **Memory:** Total BPF map footprint < 64MB (configurable based on edge device RAM). --- ## 6. Implementation Rules for Contributors 1. **No Standalone ELF Files:** All BPF code must be baked into the Go binary. 2. **No CGO:** Loader logic must be written in pure Go. 3. **Atomic Safety:** All map updates must be atomic to prevent race conditions in the firewall. 4. **Audit Logs:** Every eBPF load, pin, or map-update must generate a local audit log. 5. **Fail-Open / Fail-Closed:** * Shield (Network) should **Fail Open** (allow traffic) if the program cannot load. * Sentinel (Security) should **Fail Closed** (block access/alert) if integrity checks fail. --- ## 7. Success Metrics * **Attack Simulation:** A root user attempts to `rm -rf /etc/serviceradar/sweep.json` and is blocked by the kernel. * **Visibility:** The Gateway dashboard shows real-time drop counts for specific CIDRs. * **Persistence:** Defensive maps remain intact after an `agent.service` restart.
mfreeman451 added this to the 1.1.1 milestone 2026-03-28 04:30:41 +00:00
Author
Owner

Imported GitHub comment.

Original author: @mfreeman451
Original URL: https://github.com/carverauto/serviceradar/issues/2778#issuecomment-3880808082
Original created: 2026-02-10T21:28:50Z


siper is gplv3

Imported GitHub comment. Original author: @mfreeman451 Original URL: https://github.com/carverauto/serviceradar/issues/2778#issuecomment-3880808082 Original created: 2026-02-10T21:28:50Z --- ~~siper is gplv3~~
Author
Owner

Imported GitHub comment.

Original author: @mfreeman451
Original URL: https://github.com/carverauto/serviceradar/issues/2778#issuecomment-3882017568
Original created: 2026-02-11T04:11:13Z


https://github.com/harshavmb/fim-ebpf

Imported GitHub comment. Original author: @mfreeman451 Original URL: https://github.com/carverauto/serviceradar/issues/2778#issuecomment-3882017568 Original created: 2026-02-11T04:11:13Z --- https://github.com/harshavmb/fim-ebpf
Author
Owner

Imported GitHub comment.

Original author: @mfreeman451
Original URL: https://github.com/carverauto/serviceradar/issues/2778#issuecomment-3902746513
Original created: 2026-02-14T22:58:56Z


Implementing the Fortress spec requires a tight marriage between Go's user-space management and eBPF's kernel-space enforcement.

To solve your primary dilemma—how to reliably identify the "Agent" process in an LSM hook without relying on unstable PIDs—there are three "industry-standard" patterns used in modern eBPF security tools (like Tetragon or Falco).


1. Solution: The "Binary Inode & Device" Identity

Instead of PIDs, identify the process by the Inode of its executable file. Since Layer 4 of your PRD already makes the agent binary immutable, the Inode becomes a "Kernel-level Fingerprint" that cannot be spoofed.

How it works:

  1. Go Side: On startup, the agent calls stat("/usr/bin/serviceradar-agent") to get the Inode and Device ID.
  2. Go Side: It pushes these values into a pinned BPF Map (e.g., map_config).
  3. BPF Side (LSM): In the lsm/bpf_map_update_elem hook, you helper-call to get the current task's executable file and compare its Inode.
// BPF Snippet (LSM Hook)
SEC("lsm/bpf_map_update_elem")
int BPF_PROG(restrict_map_updates, struct bpf_map *map, void *key, void *value, u64 flags) {
    struct task_struct *task = (struct task_struct *)bpf_get_current_task_btf();
    
    // Get the inode of the process's executable
    struct file *exe_file = task->mm->exe_file;
    u32 current_inode = exe_file->f_inode->i_ino;

    // Look up the "Golden Inode" from our config map
    u32 *golden_inode = bpf_map_lookup_elem(&fortress_config, &KEY_AGENT_INODE);
    
    if (golden_inode && current_inode != *golden_inode) {
        // Not the agent! Block the update.
        return -EPERM;
    }
    return 0;
}

Note: This works perfectly in Docker/K8s because the kernel sees the underlying overlayfs Inode.


2. Implementation: Layer 2 (Instruction Sanitizer)

Using cilium/ebpf, you can inspect the bytecode after it's embedded but before it's loaded into the kernel. This prevents "Helper Hijacking."

// Go Snippet (pkg/ebpf/fortress/audit.go)
func AuditProgram(spec *ebpf.ProgramSpec) error {
	for _, ins := range spec.Instructions {
		// Check if the instruction is a Helper Call
		if ins.OpCode.Class().IsJump() && ins.OpCode.JumpOp() == asm.Call {
			helperID := ins.Constant
			if !isAllowedHelper(helperID) {
				return fmt.Errorf("illegal BPF helper detected: ID %d", helperID)
			}
		}
	}
	return nil
}

func isAllowedHelper(id int64) bool {
    // List from PRD: Disallow bpf_probe_write_user (0x24), etc.
    allowlist := map[int64]bool{
        1: true, // bpf_map_lookup_elem
        2: true, // bpf_map_update_elem
        // ... add others
    }
    return allowlist[id]
}

3. Implementation: Layer 3 & 4 (The LSM "Sentinel")

To protect the files and the BPF maps themselves, you should use the lsm/inode_permission hook. This is more performant than hooking every open() or write() syscall.

Logic for Sentinel:

  • Path: /etc/serviceradar/
  • Logic: If (mask & MAY_WRITE) AND (process_inode != agent_inode), then return -EPERM.

The "Atomic Safety" Rule:
When updating the Shield (XDP) CIDR maps, use BPF_ANY flags or BatchUpdate. Since the XDP program is just reading from the LPM_TRIE, user-space can swap entries atomically without the XDP program ever seeing a "partial" IP match.


4. Handling Containers (Docker/K8s)

You mentioned concerns about the agent running in different environments.

  1. Cgroup Identity: If you use Systemd, the agent will always be in /system.slice/serviceradar.service. In the LSM hook, you can use bpf_get_current_cgroup_id() and compare it to a value the agent learned at boot.
  2. The "Handshake" Pattern (Most Secure):
    • The Agent loads the BPF programs.
    • The Agent performs a "Magic Write" to a custom BPF map.
    • The BPF program sees this, grabs the current->tgid, and marks that specific Process ID as "Authenticated" in a Task Local Storage map.
    • Task Local Storage is cleaned up automatically by the kernel when the process exits, preventing PID-reuse attacks.
Step Task Tool
1 Generate Go bindings for XDP/LSM headers. bpf2go
2 Implement the Inode-based identity in the LSM Sentinel. vmlinux.h + LSM Hooks
3 Create the ControlStream to sync the LPM_TRIE map. Go + GRPC/Unix Socket
4 Add the Instruction Audit check to the Loader. cilium/ebpf
5 Hardcode the Gateway IP into the XDP C code as a const volatile. BPF_CORE_READ

A Quick Warning on Layer 5 (The Safety Valve)

The PRD mentions a "hardcoded bypass for the Gateway IP."
Implementation Tip: Use a BPF Global Variable for this. This allows you to update the Gateway IP via the Go agent if the Gateway moves, without re-compiling the BPF code, while still keeping the bypass logic inside the high-speed XDP path.

// BPF Side
static volatile const __u32 GATEWAY_IP = 0x08080808; // Placeholder

SEC("xdp")
int xdp_shield(struct xdp_md *ctx) {
    // ... parse IP header ...
    if (iph->saddr == GATEWAY_IP) return XDP_PASS; 
    // ... rest of logic
}

Does the Inode-based identification strategy satisfy your concerns about robustness in Docker/K8s? It's generally considered the "Gold Standard" for self-defending agents because the kernel's internal file pointers are very difficult for a root user to manipulate compared to PIDs or Cgroups.

Imported GitHub comment. Original author: @mfreeman451 Original URL: https://github.com/carverauto/serviceradar/issues/2778#issuecomment-3902746513 Original created: 2026-02-14T22:58:56Z --- Implementing the **Fortress** spec requires a tight marriage between Go's user-space management and eBPF's kernel-space enforcement. To solve your primary dilemma—**how to reliably identify the "Agent" process in an LSM hook without relying on unstable PIDs**—there are three "industry-standard" patterns used in modern eBPF security tools (like Tetragon or Falco). --- ### 1. Solution: The "Binary Inode & Device" Identity Instead of PIDs, identify the process by the **Inode** of its executable file. Since Layer 4 of your PRD already makes the agent binary immutable, the Inode becomes a "Kernel-level Fingerprint" that cannot be spoofed. **How it works:** 1. **Go Side:** On startup, the agent calls `stat("/usr/bin/serviceradar-agent")` to get the Inode and Device ID. 2. **Go Side:** It pushes these values into a pinned BPF Map (e.g., `map_config`). 3. **BPF Side (LSM):** In the `lsm/bpf_map_update_elem` hook, you helper-call to get the current task's executable file and compare its Inode. ```c // BPF Snippet (LSM Hook) SEC("lsm/bpf_map_update_elem") int BPF_PROG(restrict_map_updates, struct bpf_map *map, void *key, void *value, u64 flags) { struct task_struct *task = (struct task_struct *)bpf_get_current_task_btf(); // Get the inode of the process's executable struct file *exe_file = task->mm->exe_file; u32 current_inode = exe_file->f_inode->i_ino; // Look up the "Golden Inode" from our config map u32 *golden_inode = bpf_map_lookup_elem(&fortress_config, &KEY_AGENT_INODE); if (golden_inode && current_inode != *golden_inode) { // Not the agent! Block the update. return -EPERM; } return 0; } ``` *Note: This works perfectly in Docker/K8s because the kernel sees the underlying overlayfs Inode.* --- ### 2. Implementation: Layer 2 (Instruction Sanitizer) Using `cilium/ebpf`, you can inspect the bytecode after it's embedded but **before** it's loaded into the kernel. This prevents "Helper Hijacking." ```go // Go Snippet (pkg/ebpf/fortress/audit.go) func AuditProgram(spec *ebpf.ProgramSpec) error { for _, ins := range spec.Instructions { // Check if the instruction is a Helper Call if ins.OpCode.Class().IsJump() && ins.OpCode.JumpOp() == asm.Call { helperID := ins.Constant if !isAllowedHelper(helperID) { return fmt.Errorf("illegal BPF helper detected: ID %d", helperID) } } } return nil } func isAllowedHelper(id int64) bool { // List from PRD: Disallow bpf_probe_write_user (0x24), etc. allowlist := map[int64]bool{ 1: true, // bpf_map_lookup_elem 2: true, // bpf_map_update_elem // ... add others } return allowlist[id] } ``` --- ### 3. Implementation: Layer 3 & 4 (The LSM "Sentinel") To protect the files and the BPF maps themselves, you should use the `lsm/inode_permission` hook. This is more performant than hooking every `open()` or `write()` syscall. **Logic for Sentinel:** * **Path:** `/etc/serviceradar/` * **Logic:** If `(mask & MAY_WRITE)` AND `(process_inode != agent_inode)`, then `return -EPERM`. **The "Atomic Safety" Rule:** When updating the Shield (XDP) CIDR maps, use `BPF_ANY` flags or `BatchUpdate`. Since the XDP program is just reading from the `LPM_TRIE`, user-space can swap entries atomically without the XDP program ever seeing a "partial" IP match. --- ### 4. Handling Containers (Docker/K8s) You mentioned concerns about the agent running in different environments. 1. **Cgroup Identity:** If you use Systemd, the agent will always be in `/system.slice/serviceradar.service`. In the LSM hook, you can use `bpf_get_current_cgroup_id()` and compare it to a value the agent learned at boot. 2. **The "Handshake" Pattern (Most Secure):** * The Agent loads the BPF programs. * The Agent performs a "Magic Write" to a custom BPF map. * The BPF program sees this, grabs the `current->tgid`, and marks that specific Process ID as "Authenticated" in a **Task Local Storage** map. * Task Local Storage is cleaned up automatically by the kernel when the process exits, preventing PID-reuse attacks. ### Recommended Implementation Plan | Step | Task | Tool | | :--- | :--- | :--- | | **1** | Generate Go bindings for XDP/LSM headers. | `bpf2go` | | **2** | Implement the Inode-based identity in the LSM Sentinel. | `vmlinux.h` + LSM Hooks | | **3** | Create the `ControlStream` to sync the `LPM_TRIE` map. | Go + GRPC/Unix Socket | | **4** | Add the Instruction Audit check to the Loader. | `cilium/ebpf` | | **5** | Hardcode the Gateway IP into the XDP C code as a `const volatile`. | `BPF_CORE_READ` | ### A Quick Warning on Layer 5 (The Safety Valve) The PRD mentions a "hardcoded bypass for the Gateway IP." **Implementation Tip:** Use a BPF `Global Variable` for this. This allows you to update the Gateway IP via the Go agent if the Gateway moves, without re-compiling the BPF code, while still keeping the bypass logic inside the high-speed XDP path. ```c // BPF Side static volatile const __u32 GATEWAY_IP = 0x08080808; // Placeholder SEC("xdp") int xdp_shield(struct xdp_md *ctx) { // ... parse IP header ... if (iph->saddr == GATEWAY_IP) return XDP_PASS; // ... rest of logic } ``` **Does the Inode-based identification strategy satisfy your concerns about robustness in Docker/K8s?** It's generally considered the "Gold Standard" for self-defending agents because the kernel's internal `file` pointers are very difficult for a root user to manipulate compared to PIDs or Cgroups.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
carverauto/serviceradar#1000
No description provided.