diff --git a/README.md b/README.md index e8406ca9..a2b8b132 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,42 @@ with your own config baked in. See [benchmark](benchmark) directory to get an idea of how low ebpf overhead is. +## Required capabilities + +While you can run `ebpf_exporter` as `root`, it is not strictly necessary. +Only the following two capabilities are necessary for normal operation: + +* `CAP_BPF`: required for privileged bpf operations and for reading memory +* `CAP_PERFMON`: required to attach bpf programs to kprobes and tracepoints + +If you are using `systemd`, you can use the following configuration to run +as on otherwise unprivileged dynamic user with the needed capabilities: + +```ini +DynamicUser=true +AmbientCapabilities=CAP_BPF CAP_PERFMON +CapabilityBoundingSet=CAP_BPF CAP_PERFMON +``` + +Prior to Linux v5.8 there was no dedicated `CAP_BPF` and `CAP_PERFMON`, +but you can use `CAP_SYS_ADMIN` instead of your kernel is older. + +If you pass `--capabilities.keep=none` flag to `ebpf_expoter`, then it drops +all capabilities after attaching the probes, leaving it fully unprivileged. + +The following additional capabilities might be needed: + +* `CAP_SYSLOG`: if you use `ksym` decoder to have access to `/proc/kallsyms`. + Note that you must keep this capability: `--capabilities.keep=cap_syslog`. + See: https://elixir.bootlin.com/linux/v6.4/source/kernel/kallsyms.c#L982 +* `CAP_IPC_LOCK`: if you use `perf_event_array` for reading from the kernel. + Note that you must keep it: `--capabilities.keep=cap_perfmon,cap_ipc_lock`. +* `CAP_NET_ADMIN`: if you use net admin related programs like xdp. + See: https://elixir.bootlin.com/linux/v6.4/source/kernel/bpf/syscall.c#L2450 +* `CAP_SYS_RESOURCE`: if you run an older kernel without memcg accounting for + bpf memory. Upstream Linux kernel added support for this in v5.11. + See: https://github.com/libbpf/libbpf/blob/v1.2.0/src/bpf.c#L98-L106 + ## Supported scenarios Currently the only supported way of getting data out of the kernel is via maps. diff --git a/cmd/ebpf_exporter/main.go b/cmd/ebpf_exporter/main.go index a9458798..10bbf9ed 100644 --- a/cmd/ebpf_exporter/main.go +++ b/cmd/ebpf_exporter/main.go @@ -16,6 +16,7 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/common/version" "gopkg.in/alecthomas/kingpin.v2" + "kernel.org/pub/linux/libs/security/libcap/cap" ) func main() { @@ -24,6 +25,7 @@ func main() { debug := kingpin.Flag("debug", "Enable debug.").Bool() listenAddress := kingpin.Flag("web.listen-address", "The address to listen on for HTTP requests (fd://0 for systemd activation).").Default(":9435").String() metricsPath := kingpin.Flag("web.telemetry-path", "Path under which to expose metrics.").Default("/metrics").String() + capabilities := kingpin.Flag("capabilities.keep", "Comma separated list of capabilities to keep (cap_syslog, cap_bpf, etc.), 'all' or 'none'").Default("all").String() kingpin.Version(version.Print("ebpf_exporter")) kingpin.HelpFlag.Short('h') kingpin.Parse() @@ -54,6 +56,11 @@ func main() { log.Fatalf("Error attaching exporter: %s", err) } + err = ensureCapabilities(*capabilities) + if err != nil { + log.Fatalf("Error dropping capabilities: %s", err) + } + log.Printf("Started with %d programs found in the config in %dms", len(configs), time.Since(started).Milliseconds()) err = prometheus.Register(version.NewCollector("ebpf_exporter")) @@ -115,6 +122,49 @@ func listen(addr string) error { } +func ensureCapabilities(keep string) error { + existing := cap.GetProc() + log.Printf("Started with capabilities: %q", existing) + + if keep == "all" { + log.Printf("Retaining all existing capabilities") + return nil + } + + ensure := cap.NewSet() + + values := []cap.Value{} + if keep != "none" { + for _, name := range strings.Split(keep, ",") { + value, err := cap.FromName(name) + if err != nil { + return fmt.Errorf("error parsing capability %q: %v", name, err) + } + + values = append(values, value) + } + } + + err := ensure.SetFlag(cap.Permitted, true, values...) + if err != nil { + return fmt.Errorf("error setting permitted capabilities: %v", err) + } + + err = ensure.SetFlag(cap.Effective, true, values...) + if err != nil { + return fmt.Errorf("error setting effective capabilities: %v", err) + } + + err = ensure.SetProc() + if err != nil { + return fmt.Errorf("failed to drop capabilities: %q -> %q: %v", existing, ensure, err) + } + + log.Printf("Dropped capabilities to %q", ensure) + + return nil +} + func libbpfLogCallback(level int, msg string) { levelName := "unknown" switch level { diff --git a/exporter/exporter.go b/exporter/exporter.go index 815f5edb..e0d4ff48 100644 --- a/exporter/exporter.go +++ b/exporter/exporter.go @@ -112,7 +112,10 @@ func (e *Exporter) Attach() error { return fmt.Errorf("multiple configs with name %q", cfg.Name) } - module, err := libbpfgo.NewModuleFromFile(cfg.BPFPath) + module, err := libbpfgo.NewModuleFromFileArgs(libbpfgo.NewModuleArgs{ + BPFObjPath: cfg.BPFPath, + SkipMemlockBump: true, // Let libbpf itself decide whether it is needed + }) if err != nil { return fmt.Errorf("error creating module from %q for config %q: %v", cfg.BPFPath, cfg.Name, err) } diff --git a/go.mod b/go.mod index 8223f347..19d04203 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( golang.org/x/sys v0.9.0 gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/yaml.v2 v2.4.0 + kernel.org/pub/linux/libs/security/libcap/cap v1.2.69 ) require ( @@ -25,4 +26,7 @@ require ( github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect google.golang.org/protobuf v1.30.0 // indirect + kernel.org/pub/linux/libs/security/libcap/psx v1.2.69 // indirect ) + +replace github.com/aquasecurity/libbpfgo => github.com/bobrik/libbpfgo v0.0.0-20230704220959-d2cc6f8a97da diff --git a/go.sum b/go.sum index ccb2ce9a..dace63e2 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,10 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= -github.com/aquasecurity/libbpfgo v0.4.9-libbpf-1.2.0 h1:pk9L7I6wF1nTfO42+jjXhA8ozRjvtj2ZvHV/i/YC0dE= -github.com/aquasecurity/libbpfgo v0.4.9-libbpf-1.2.0/go.mod h1:UD3Mfr+JZ/ASK2VMucI/zAdEhb35LtvYXvAUdrdqE9s= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bobrik/libbpfgo v0.0.0-20230704220959-d2cc6f8a97da h1:ccRuROY275uHluzDCosqhiKacMivXro66+S54QMm6B0= +github.com/bobrik/libbpfgo v0.0.0-20230704220959-d2cc6f8a97da/go.mod h1:UD3Mfr+JZ/ASK2VMucI/zAdEhb35LtvYXvAUdrdqE9s= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= @@ -58,3 +58,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +kernel.org/pub/linux/libs/security/libcap/cap v1.2.69 h1:N0m3tKYbkRMmDobh/47ngz+AWeV7PcfXMDi8xu3Vrag= +kernel.org/pub/linux/libs/security/libcap/cap v1.2.69/go.mod h1:Tk5Ip2TuxaWGpccL7//rAsLRH6RQ/jfqTGxuN/+i/FQ= +kernel.org/pub/linux/libs/security/libcap/psx v1.2.69 h1:IdrOs1ZgwGw5CI+BH6GgVVlOt+LAXoPyh7enr8lfaXs= +kernel.org/pub/linux/libs/security/libcap/psx v1.2.69/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=