Skip to content

Commit

Permalink
fim: implement ebpf backend
Browse files Browse the repository at this point in the history
  • Loading branch information
mmat11 committed Feb 8, 2024
1 parent 9db2cd1 commit 136b45c
Show file tree
Hide file tree
Showing 32 changed files with 860 additions and 140 deletions.
File renamed without changes.
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ Setting environmental variable ELASTIC_NETINFO:false in Elastic Agent pod will d
*Auditbeat*

- Add linux capabilities to processes in the system/process. {pull}37453[37453]
- Add opt-in eBPF backend for file_integrity module. {pull}37223[37223]

*Filebeat*

Expand Down
4 changes: 2 additions & 2 deletions NOTICE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12257,11 +12257,11 @@ SOFTWARE.

--------------------------------------------------------------------------------
Dependency : github.com/elastic/ebpfevents
Version: v0.3.2
Version: v0.4.0
Licence type (autodetected): Apache-2.0
--------------------------------------------------------------------------------

Contents of probable licence file $GOMODCACHE/github.com/elastic/ebpfevents@v0.3.2/LICENSE.txt:
Contents of probable licence file $GOMODCACHE/github.com/elastic/ebpfevents@v0.4.0/LICENSE.txt:

The https://github.com/elastic/ebpfevents repository contains source code under
various licenses:
Expand Down
1 change: 0 additions & 1 deletion auditbeat/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,3 @@ module/*/_meta/config.yml
/auditbeat
/auditbeat.test
/docs/html_docs

5 changes: 5 additions & 0 deletions auditbeat/auditbeat.reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ auditbeat.modules:
# Auditbeat will ignore files unless they match a pattern.
#include_files:
#- '/\.ssh($|/)'
# Select the backend which will be used to source events.
# "fsnotify" doesn't have the ability to associate user data to file events.
# Valid values: auto, fsnotify, kprobes, ebpf.
# Default: fsnotify.
backend: fsnotify

# Scan over the configured file paths at startup and send events for new or
# modified files since the last time Auditbeat was running.
Expand Down
4 changes: 4 additions & 0 deletions auditbeat/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ services:
- KIBANA_PORT=5601
volumes:
- ${PWD}/..:/go/src/github.com/elastic/beats/
- /sys:/sys
command: make
privileged: true
pid: host
cap_add:
- AUDIT_CONTROL
- BPF
- PERFMON
- SYS_RESOURCE

# This is a proxy used to block beats until all services are healthy.
# See: https://github.com/docker/compose/issues/4369
Expand Down
10 changes: 9 additions & 1 deletion auditbeat/docs/modules/file_integrity.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,13 @@ to only send events for new or modified files.

The operating system features that power this feature are as follows.

* Linux - `inotify` is used, and therefore the kernel must have inotify support.
* Linux - Multiple backends are supported: `auto`, `fsnotify`, `kprobes`, `ebpf`.
By default, `fsnotify` is used, and therefore the kernel must have inotify support.
Inotify was initially merged into the 2.6.13 Linux kernel.
The eBPF backend uses modern eBPF features and supports 5.10.16+ kernels.
FSNotify doesn't have the ability to associate user data to file events.
The preferred backend can be selected by specifying the `backend` config option.
Since eBPF and Kprobes are in technical preview, `auto` will default to `fsnotify`.
* macOS (Darwin) - Uses the `FSEvents` API, present since macOS 10.5. This API
coalesces multiple changes to a file into a single event. {beatname_uc} translates
this coalesced changes into a meaningful sequence of actions. However,
Expand Down Expand Up @@ -144,6 +149,9 @@ of this directories are watched. If `recursive` is set to `true`, the
`file_integrity` module will watch for changes on this directories and all
their subdirectories.

*`backend`*:: (*Linux only*) Select the backend which will be used to
source events. Valid values: `auto`, `fsnotify`, `kprobes`, `ebpf`. Default: `fsnotify`.

include::{docdir}/auditbeat-options.asciidoc[]


Expand Down
8 changes: 8 additions & 0 deletions auditbeat/module/file_integrity/_meta/config.yml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@
#- '/\.ssh($|/)'
{{- end }}

{{- if eq .GOOS "linux" }}
# Select the backend which will be used to source events.
# "fsnotify" doesn't have the ability to associate user data to file events.
# Valid values: auto, fsnotify, kprobes, ebpf.
# Default: fsnotify.
backend: fsnotify
{{- end }}

# Scan over the configured file paths at startup and send events for new or
# modified files since the last time Auditbeat was running.
scan_at_start: true
Expand Down
10 changes: 9 additions & 1 deletion auditbeat/module/file_integrity/_meta/docs.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,13 @@ to only send events for new or modified files.

The operating system features that power this feature are as follows.

* Linux - `inotify` is used, and therefore the kernel must have inotify support.
* Linux - Multiple backends are supported: `auto`, `fsnotify`, `kprobes`, `ebpf`.
By default, `fsnotify` is used, and therefore the kernel must have inotify support.
Inotify was initially merged into the 2.6.13 Linux kernel.
The eBPF backend uses modern eBPF features and supports 5.10.16+ kernels.
FSNotify doesn't have the ability to associate user data to file events.
The preferred backend can be selected by specifying the `backend` config option.
Since eBPF and Kprobes are in technical preview, `auto` will default to `fsnotify`.
* macOS (Darwin) - Uses the `FSEvents` API, present since macOS 10.5. This API
coalesces multiple changes to a file into a single event. {beatname_uc} translates
this coalesced changes into a meaningful sequence of actions. However,
Expand Down Expand Up @@ -137,4 +142,7 @@ of this directories are watched. If `recursive` is set to `true`, the
`file_integrity` module will watch for changes on this directories and all
their subdirectories.

*`backend`*:: (*Linux only*) Select the backend which will be used to
source events. Valid values: `auto`, `fsnotify`, `kprobes`, `ebpf`. Default: `fsnotify`.

include::{docdir}/auditbeat-options.asciidoc[]
27 changes: 27 additions & 0 deletions auditbeat/module/file_integrity/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@
package file_integrity

import (
"errors"
"fmt"
"math"
"path/filepath"
"regexp"
"runtime"
"sort"
"strings"

Expand Down Expand Up @@ -72,6 +74,25 @@ const (
XXH64 HashType = "xxh64"
)

type Backend string

const (
BackendFSNotify Backend = "fsnotify"
BackendKprobes Backend = "kprobes"
BackendEBPF Backend = "ebpf"
BackendAuto Backend = "auto"
)

func (b *Backend) Unpack(v string) error {
*b = Backend(v)
switch *b {
case BackendFSNotify, BackendKprobes, BackendEBPF, BackendAuto:
return nil
default:
return fmt.Errorf("invalid backend: %q", v)
}
}

// Config contains the configuration parameters for the file integrity
// metricset.
type Config struct {
Expand All @@ -86,6 +107,7 @@ type Config struct {
Recursive bool `config:"recursive"` // Recursive enables recursive monitoring of directories.
ExcludeFiles []match.Matcher `config:"exclude_files"`
IncludeFiles []match.Matcher `config:"include_files"`
Backend Backend `config:"backend"`
}

// Validate validates the config data and return an error explaining all the
Expand Down Expand Up @@ -160,6 +182,11 @@ nextHash:
if err != nil {
errs = append(errs, fmt.Errorf("invalid scan_rate_per_sec value: %w", err))
}

if c.Backend != "" && c.Backend != BackendAuto && runtime.GOOS != "linux" {
errs = append(errs, errors.New("backend can only be specified on linux"))
}

return errs.Err()
}

Expand Down
71 changes: 44 additions & 27 deletions auditbeat/module/file_integrity/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,14 @@ const (
// SourceFSNotify identifies events triggered by a notification from the
// file system.
SourceFSNotify
// SourceEBPF identifies events triggered by an eBPF program.
SourceEBPF
)

var sourceNames = map[Source]string{
SourceScan: "scan",
SourceFSNotify: "fsnotify",
SourceEBPF: "ebpf",
}

// Type identifies the file type (e.g. dir, file, symlink).
Expand All @@ -91,12 +94,20 @@ const (
FileType
DirType
SymlinkType
CharDeviceType
BlockDeviceType
FIFOType
SocketType
)

var typeNames = map[Type]string{
FileType: "file",
DirType: "dir",
SymlinkType: "symlink",
FileType: "file",
DirType: "dir",
SymlinkType: "symlink",
CharDeviceType: "char_device",
BlockDeviceType: "block_device",
FIFOType: "fifo",
SocketType: "socket",
}

// Digest is an output of a hash function.
Expand Down Expand Up @@ -189,36 +200,42 @@ func NewEventFromFileInfo(

switch event.Info.Type {
case FileType:
if event.Info.Size <= maxFileSize {
hashes, nbytes, err := hashFile(event.Path, maxFileSize, hashTypes...)
if err != nil {
event.errors = append(event.errors, err)
event.hashFailed = true
} else if hashes != nil {
// hashFile returns nil hashes and no error when:
// - There's no hashes configured.
// - File size at the time of hashing is larger than configured limit.
event.Hashes = hashes
event.Info.Size = nbytes
}

if len(fileParsers) != 0 && event.ParserResults == nil {
event.ParserResults = make(mapstr.M)
}
for _, p := range fileParsers {
err = p.Parse(event.ParserResults, path)
if err != nil {
event.errors = append(event.errors, err)
}
}
}
fillHashes(&event, path, maxFileSize, hashTypes, fileParsers)
case SymlinkType:
event.TargetPath, _ = filepath.EvalSymlinks(event.Path)
event.TargetPath, err = filepath.EvalSymlinks(event.Path)
if err != nil {
event.errors = append(event.errors, err)
}
}

return event
}

func fillHashes(event *Event, path string, maxFileSize uint64, hashTypes []HashType, fileParsers []FileParser) {
if event.Info.Size <= maxFileSize {
hashes, nbytes, err := hashFile(event.Path, maxFileSize, hashTypes...)
if err != nil {
event.errors = append(event.errors, err)
event.hashFailed = true
} else if hashes != nil {
// hashFile returns nil hashes and no error when:
// - There's no hashes configured.
// - File size at the time of hashing is larger than configured limit.
event.Hashes = hashes
event.Info.Size = nbytes
}

if len(fileParsers) != 0 && event.ParserResults == nil {
event.ParserResults = make(mapstr.M)
}
for _, p := range fileParsers {
if err = p.Parse(event.ParserResults, path); err != nil {
event.errors = append(event.errors, err)
}
}
}
}

// NewEvent creates a new Event. Any errors that occur are included in the
// returned Event.
func NewEvent(
Expand Down
Loading

0 comments on commit 136b45c

Please sign in to comment.