terrahive

command module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 22, 2026 License: MPL-2.0 Imports: 5 Imported by: 0

README

terrahive

Write eBPF probes as Terraform code. Run terraform apply. They load into the running Linux kernel.

Terrahive is a Terraform provider that treats the kernel as infrastructure. You declare a BPF program, a map, or a probe as an HCL resource. apply loads and attaches it. plan reads the kernel back and shows real drift. destroy unloads it. The workflow you already use for cloud servers.

resource "ebpf_program" "trace_open" {
  object_file = "trace_open.bpf.o"
}

resource "ebpf_kprobe" "trace_open" {
  program = ebpf_program.trace_open.id
  symbol  = "do_sys_openat2"
}

That is it. apply loads the program, attaches the kprobe, and pins both so they survive after Terraform exits. destroy detaches and unloads them. You can declare the program from a precompiled object file (above), from inline C, or from inline Go: see Program sources.

It is a meme project, built to be technically sound.

The premise

Terraform providers call remote APIs and manage infrastructure that lives somewhere else. Terrahive calls the bpf() syscall on the kernel it runs inside. There is no "somewhere else". The API endpoint is /proc/self, and the blast radius is exactly one machine: the one you are standing on.

That is a bad idea, and it works anyway, because the kernel's BPF object model genuinely maps to CRUD. Create loads and pins, Read resolves the pin and reads real kernel state, Delete unpins and unloads. Read docs/ANTIPATTERNS.md for every rule this breaks and why we broke it.

Capabilities

Terrahive manages the full BPF object lifecycle through Terraform:

  • Load and pin BPF programs. One ebpf_program resource loads a program from three input modes: a precompiled object file, inline C, or inline Go. See Program sources below.
  • Create and pin BPF maps with ebpf_map, and manage individual entries with ebpf_map_entry.
  • Attach programs to kernel hooks with eight attachment resources: ebpf_kprobe, ebpf_tracepoint, ebpf_tracing, ebpf_xdp, ebpf_tcx, ebpf_cgroup, ebpf_netfilter, and ebpf_struct_ops.
  • Read real kernel state on refresh, so a plan reports actual drift.
  • Pin every object to bpffs, so links keep firing after Terraform exits.

Every resource maps to CRUD on the bpf() syscall. Create loads and pins, Read resolves the pin, Delete unpins and unloads. See docs/ANTIPATTERNS.md for the rules this breaks and why.

Flavors

Two binaries ship from one codebase:

  • terrahive (lean): takes precompiled object files and c_source. No embedded compiler. This is the flavor you should use.
  • terrahive-bumble: embeds a pinned TinyGo toolchain (which statically links LLVM), so it compiles go_source into BPF bytecode at apply time. The binary is roughly 150 MB.

Install

Both flavors publish to the Terraform Registry under maxgio92/terrahive. The resources use the ebpf_ prefix, so set a local provider name:

terraform {
  required_providers {
    ebpf = {
      source = "maxgio92/terrahive"
    }
  }
}

The two flavors are separate build artifacts, not one release chosen by a version constraint. Build the one you want from source:

# Lean flavor.
make build

# Bumble flavor. Fetches the pinned TinyGo release first (needs network).
make build-bumble

Quickstart

You need root or CAP_BPF and a bpffs mount at /sys/fs/bpf:

sudo mount -t bpf bpf /sys/fs/bpf

Load a precompiled program and attach it to a kernel symbol:

provider "ebpf" {}

resource "ebpf_program" "hello" {
  name        = "hello"
  object_file = "${path.module}/hello.bpf.o"
}

resource "ebpf_kprobe" "openat" {
  name    = "hello-openat"
  program = ebpf_program.hello.id
  symbol  = "do_sys_openat2"
}
sudo terraform apply

The probe keeps firing after Terraform exits, because the link is pinned. terraform destroy unpins the link and unloads the program. See the examples directory for hello-kprobe, xdp-drop, and go-probe.

Program sources

The ebpf_program resource takes exactly one source. The three source attributes are mutually exclusive: set one and only one. The program type comes from the ELF section name, so you rarely need to set type.

1. object_file

Point at a precompiled CO-RE object. This is the recommended path. The lean terrahive binary is enough. No compiler runs at apply. Build the object yourself first:

clang -O2 -g -target bpf -c hello.bpf.c -o hello.bpf.o

The object must contain exactly one program. Then load and attach it:

terraform {
  required_providers {
    ebpf = {
      source = "maxgio92/terrahive"
    }
  }
}

provider "ebpf" {}

resource "ebpf_program" "hello" {
  name        = "hello"
  object_file = "${path.module}/hello.bpf.o"
}

resource "ebpf_kprobe" "openat" {
  name    = "hello-openat"
  program = ebpf_program.hello.id
  symbol  = "do_sys_openat2"
}
2. c_source

Pass BPF C inline. Terrahive compiles it with the system clang at apply time, so clang must be on PATH. Both flavors support this mode. The SEC() name sets the program type:

terraform {
  required_providers {
    ebpf = {
      source = "maxgio92/terrahive"
    }
  }
}

provider "ebpf" {}

resource "ebpf_program" "openat" {
  name = "trace_openat"

  c_source = <<-EOT
    #include <linux/bpf.h>
    #include <bpf/bpf_helpers.h>

    SEC("kprobe/do_sys_openat2")
    int trace_openat(struct pt_regs *ctx) {
      return 0;
    }

    char _license[] SEC("license") = "GPL";
  EOT
}

resource "ebpf_kprobe" "openat" {
  name    = "c-openat"
  program = ebpf_program.openat.id
  symbol  = "do_sys_openat2"
}
3. go_source

Pass BPF Go inline. This mode needs the bumble flavor (terrahive-bumble), which embeds the TinyGo toolchain and compiles Go into BPF bytecode at apply time. The lean terrahive binary rejects go_source at plan time with a clear error that points you to bumble. TinyGo BPF output is experimental and often upsets the verifier, so treat this path as the showcase, not the safe default.

terraform {
  required_providers {
    ebpf = {
      source = "maxgio92/terrahive"
    }
  }
}

provider "ebpf" {}

resource "ebpf_program" "counter" {
  name = "go_counter"

  # The body below is illustrative, not a working TinyGo BPF program.
  # A real probe declares its section and helpers the way TinyGo's BPF
  # target expects; the point here is the resource shape, not the source.
  go_source = <<-EOT
    package main

    func probe() int {
      return 0
    }

    func main() {}
  EOT
}

resource "ebpf_kprobe" "counter" {
  name    = "go-counter-openat"
  program = ebpf_program.counter.id
  symbol  = "do_sys_openat2"
}

Resources

  • ebpf_program: loads a BPF program from an object file, C source, or Go source.
  • ebpf_map: creates and pins a BPF map.
  • ebpf_map_entry: manages one key/value pair in a pinned map.
  • ebpf_kprobe: attaches to kprobe, kretprobe, uprobe, uretprobe, or usdt.
  • ebpf_tracepoint: attaches to a tracepoint, raw tracepoint, or perf event.
  • ebpf_tracing: attaches a BTF tracing program (fentry, fexit, fmod_ret, lsm, iter).
  • ebpf_xdp: attaches an XDP program to a network interface.
  • ebpf_tcx: attaches a tc program via tcx (kernel 6.6).
  • ebpf_cgroup: attaches a program to a cgroup.
  • ebpf_netfilter: attaches a netfilter program to a hook (kernel 6.4).
  • ebpf_struct_ops: registers a struct_ops map.

Per-resource reference lives under docs/resources/.

Testing

  • make test: unit tests, lean and bumble.
  • make cover: unit tests under the race detector with a coverage profile (coverage.out), then a per-function coverage report.
  • make fuzz: runs each fuzz target briefly (FUZZTIME bounds each one). The targets cover the parsers that take untrusted input: the ELF loader, the stapsdt USDT note decoder, and the map-entry ID and base64 decode path.
  • make validate-examples: builds the provider and runs terraform init -backend=false plus terraform validate on each example root module, offline, through Terraform dev_overrides (see scripts/validate-examples.sh). Modules that use go_source validate against a bumble binary.
  • make e2e: a real terraform apply and terraform destroy of the built provider through the CLI, loading an object file and attaching a kprobe, asserting the pins appear and are gone after destroy. It is a script (scripts/e2e.sh) rather than a Go test: it drives the built binary through the Terraform CLI, which the in-process terraform-plugin-testing acceptance tests do not. It needs root and bpffs, so run sudo -E make e2e; it skips with a message otherwise.
  • make testacc: the in-process acceptance tests; run sudo -E TF_ACC=1 make testacc.

Caveats

Read these before you rely on terrahive:

  • Local machine only. The state file and the target kernel share fate. Reinstall the OS and your state describes a kernel that no longer exists. There is no remote state story that makes sense here.
  • Root or CAP_BPF. Loading BPF programs needs it, so terraform apply runs with kernel-level privileges. Every eBPF tool makes the same demand.
  • Kernel version matters. Broad bpf_link coverage needs 5.15 or newer. tcx needs 6.6, netfilter needs 6.4. Available program and attach types change with the kernel.
  • Program updates replace, they do not mutate. A loaded program is immutable, so a change destroys and recreates. Between detach and reattach the probe is off, and events in that window are not observed.
  • The verifier runs at apply, not plan. A plan can validate and apply can still fail with a verifier rejection. The full verifier log surfaces in the apply error.
  • c_source needs the system clang on PATH. go_source needs the bumble flavor, and TinyGo BPF output is experimental and often verifier-hostile.

If you want fleet-wide declarative BPF for real, use bpfman. Terrahive is a well-built ship in a bottle: the objection is not that it fails, but that it exists.

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
internal
hive
Package hive wraps the kernel BPF object lifecycle shared by all terrahive resources: pinning, loading, reading back, and unpinning.
Package hive wraps the kernel BPF object lifecycle shared by all terrahive resources: pinning, loading, reading back, and unpinning.
hive/tinygo
Package tinygo extracts a bundled TinyGo toolchain to a local cache directory and compiles BPF Go source with it.
Package tinygo extracts a bundled TinyGo toolchain to a local cache directory and compiles BPF Go source with it.
provider
Package provider implements the terrahive Terraform provider.
Package provider implements the terrahive Terraform provider.

Jump to

Keyboard shortcuts

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