Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions auditbeat/auditbeat.reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,11 @@ auditbeat.modules:
# 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
# When "auto" is used, the module will try backends in order of preference:
# - Linux: ebpf -> kprobes -> fsnotify
# - Windows: etw -> fsnotify
# Default: auto.
backend: auto

# 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
7 changes: 5 additions & 2 deletions auditbeat/module/file_integrity/_meta/config.yml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,11 @@
# 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
# When "auto" is used, the module will try backends in order of preference:
# - Linux: ebpf -> kprobes -> fsnotify
# - Windows: etw -> fsnotify
# Default: auto.
backend: auto
{{- end }}

# Scan over the configured file paths at startup and send events for new or
Expand Down
13 changes: 7 additions & 6 deletions auditbeat/module/file_integrity/_meta/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ At startup this module will perform an initial scan of the configured files and

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

* 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. The `Kprobes` backend uses tracefs and supports 3.10+ kernels. FSNotify doesnt 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`.
* Linux - Multiple backends are supported: `auto`, `fsnotify`, `kprobes`, `ebpf`. {applies_to}`stack: ga 9.3.0` By default, `auto` is used, which automatically selects the best available backend. The module tries `ebpf` first, falls back to `kprobes` if eBPF is not supported, and finally falls back to `fsnotify` if neither eBPF nor kprobes are available. The eBPF backend uses modern eBPF features and supports 5.10.16+ kernels. The `Kprobes` backend uses tracefs and supports 3.10+ kernels. The `fsnotify` backend uses inotify (merged into Linux kernel 2.6.13) but doesn't have the ability to associate user data to file events. The backend can be explicitly selected by specifying the `backend` config option.
* macOS (Darwin) - Uses the `FSEvents` API, present since macOS 10.5. This API coalesces multiple changes to a file into a single event. Auditbeat translates this coalesced changes into a meaningful sequence of actions. However, in rare situations the reported events may have a different ordering than what actually happened.
* Windows:
* `ReadDirectoryChangesW` is used.
* {applies_to}`stack: preview 9.2.0` Multiple backends are supported: `auto`, `fsnotify`, `etw`. By default, `fsnotify` is used, which utilizes the `ReadDirectoryChangesW` Windows API. The `etw` backend uses Event Tracing for Windows (ETW) to monitor file system activities at the kernel level, supporting enhanced process context information. It requires Administrator privileges.
* {applies_to}`stack: preview 9.2.0` Multiple backends are supported: `auto`, `fsnotify`, `etw`. {applies_to}`stack: ga 9.3.0` By default, `auto` is used, which automatically tries the `etw` backend first, falling back to `fsnotify` if ETW is not available or supported. The `etw` backend uses Event Tracing for Windows (ETW) to monitor file system activities at the kernel level, supporting enhanced process context information and requires Administrator privileges. The `fsnotify` backend utilizes the `ReadDirectoryChangesW` Windows API. The backend can be explicitly selected by specifying the `backend` config option.

The file integrity module should not be used to monitor paths on network file systems.

Expand Down Expand Up @@ -91,9 +90,11 @@ This module also supports the [standard configuration options](#module-standard-
**`backend`**
: Select the backend that will be used to source events. The available backends vary by operating system:

* **Linux:** `auto`, `fsnotify`, `kprobes`, `ebpf`. Default: `fsnotify`.
* {applies_to}`stack: ga 9.2.0` **Windows:** `auto`, `fsnotify`, `etw`. Default: `fsnotify`.
* {applies_to}`stack: ga 9.2.0` **macOS:** Only `auto` and `fsnotify` are supported. Default: `fsnotify`
* **Linux:** `auto`, `fsnotify`, `kprobes`, `ebpf`. {applies_to}`stack: preview 9.3.0` Default: `auto`.
* {applies_to}`stack: ga 9.2.0` **Windows:** `auto`, `fsnotify`, `etw`. {applies_to}`stack: ga 9.3.0` Default: `auto`.
* {applies_to}`stack: ga 9.2.0` **macOS:** Only `auto` and `fsnotify` are supported. {applies_to}`stack: ga 9.3.0` Default: `auto`.

{applies_to}`stack: ga 9.3.0` When `auto` is used (or when left unspecified), the module automatically selects the best available backend for the platform. See the "How it works" section for details on backend selection order.

**`flush_interval`** {applies_to}`stack: ga 9.2.0`
: (**ETW backend only**) Controls how often the ETW backend flushes event correlation groups. The ETW backend groups related file operations (like create, write, close) to provide meaningful events. This setting determines how long to wait for related events before considering an operation complete and sending the event. Setting a shorter interval will send events more quickly but may break up related operations. Setting a longer interval will provide better event correlation but may delay event delivery and impact memory footprint. This option is ignored when using other backends. Default: `1m`.
Expand Down
24 changes: 9 additions & 15 deletions auditbeat/module/file_integrity/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,25 +176,14 @@ nextHash:
errs = append(errs, fmt.Errorf("invalid scan_rate_per_sec value: %w", err))
}

if c.Backend != "" {
switch runtime.GOOS {
case "linux":
if c.Backend == BackendETW {
errs = append(errs, errors.New("backend etw is not supported on linux"))
}
case "windows":
if c.Backend != BackendETW && c.Backend != BackendAuto {
errs = append(errs, errors.New("windows only supports etw or auto backend"))
}
default:
if c.Backend != BackendAuto {
errs = append(errs, errors.New("backend can only be specified on linux or windows"))
}
if c.Backend != "auto" && c.Backend != "" {
if _, found := supportedBackends[c.Backend]; !found {
errs = append(errs, fmt.Errorf("backend %v is not supported on %v", c.Backend, runtime.GOOS))
}
}

// Validate flush_interval for ETW backend
if c.Backend == BackendETW && c.FlushInterval < time.Second {
if runtime.GOOS == "windows" && (c.Backend == BackendETW || c.Backend == "auto" || c.Backend == "") && c.FlushInterval < time.Second {
errs = append(errs, fmt.Errorf("flush_interval must be at least 1 second, got %v", c.FlushInterval))
}

Expand Down Expand Up @@ -247,4 +236,9 @@ var defaultConfig = Config{
ScanAtStart: true,
ScanRatePerSec: "50 MiB",
FlushInterval: time.Minute, // Default flush interval for ETW backend
// Backend is intentionally left empty (""), which triggers auto-selection.
// The auto-selection will choose the best available backend for the platform:
// - Linux: ebpf -> kprobes -> fsnotify
// - Windows: etw -> fsnotify
// - macOS: fsnotify
}
12 changes: 11 additions & 1 deletion auditbeat/module/file_integrity/ebpfreader_supported.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,19 @@

package file_integrity

import "github.com/elastic/elastic-agent-libs/logp"
import (
"github.com/elastic/beats/v7/libbeat/ebpf"
"github.com/elastic/elastic-agent-libs/logp"
)

func newEBPFReader(c Config, l *logp.Logger) (EventProducer, error) {
// Test if eBPF is available by trying to get the watcher
// This validates early that eBPF can actually be used, allowing fallback to work
_, err := ebpf.GetWatcher()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that newFSNotifyReader and newKProbesReader, get watchers and then close them.
The ebpf watcher doesn't need to be closed?

if err != nil {
return nil, err
}

paths := make(map[string]struct{})
for _, p := range c.Paths {
paths[p] = struct{}{}
Expand Down
80 changes: 80 additions & 0 deletions auditbeat/module/file_integrity/eventreader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package file_integrity

import (
"errors"
"fmt"

"github.com/elastic/elastic-agent-libs/logp"
)

// backendInitializer is a function that initializes a backend
type backendInitializer func(Config, *logp.Logger) (EventProducer, error)

func NewEventReader(c Config, logger *logp.Logger) (EventProducer, error) {
// Handle auto backend selection with fallback mechanism
if c.Backend == BackendAuto || c.Backend == "" {
return tryBackendsInOrder(supportedBackends, autoBackendOrder, c, logger)
}

// Handle explicit backend selection
return initBackend(supportedBackends, c.Backend, c, logger)
}

// tryBackendsInOrder attempts to initialize backends in the specified order
func tryBackendsInOrder(supportedBackends map[Backend]backendInitializer, backends []Backend, c Config, logger *logp.Logger) (EventProducer, error) {
logger.Infof("backend auto-selection enabled, trying backends in order: %v", backends)
var reader EventProducer
var lastErr error
for i, backend := range backends {
l := logger.Named(string(backend))
initializer, found := supportedBackends[backend]
if !found {
// this should never happen
l.Fatalf("backend %s not supported, this is a bug", backend)
}
if reader, lastErr = initializer(c, l); lastErr == nil {
l.Infof("selected backend: %s", backend)
return reader, nil
}

// Log fallback message unless this is the last backend
if i < len(backends)-1 {
logger.Warnf("%s backend not available: %v, falling back to %s",
backend, lastErr, backends[i+1])
}
}
return nil, fmt.Errorf("all backends failed, last error: %w", lastErr)
}

// initBackend initializes a specific backend
func initBackend(supportedBackends map[Backend]backendInitializer, backend Backend, c Config, logger *logp.Logger) (EventProducer, error) {
l := logger.Named(string(backend))
initializer, found := supportedBackends[backend]
if !found {
return nil, errors.ErrUnsupported
}

reader, err := initializer(c, l)
if err != nil {
return nil, err
}
l.Infof("selected backend: %s", backend)
return reader, nil
}
12 changes: 10 additions & 2 deletions auditbeat/module/file_integrity/eventreader_fsevents.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,16 @@ var flagNames = map[fsevents.EventFlags]string{
fsevents.ItemIsSymlink: "ItemIsSymlink",
}

// NewEventReader creates a new EventProducer backed by FSEvents macOS facility.
func NewEventReader(c Config, logger *logp.Logger) (EventProducer, error) {
var autoBackendOrder = []Backend{
BackendFSNotify,
}

var supportedBackends = map[Backend]backendInitializer{
BackendFSNotify: newFSEventsReader,
}

// newFSEventsReader creates a new EventProducer backed by FSEvents macOS facility.
func newFSEventsReader(c Config, logger *logp.Logger) (EventProducer, error) {
stream := &fsevents.EventStream{
Paths: c.Paths,
// NoDefer: Ignore Latency field and send events as fast as possible.
Expand Down
13 changes: 13 additions & 0 deletions auditbeat/module/file_integrity/eventreader_fsnotify.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@ type fsNotifyReader struct {
parsers []FileParser
}

func newFSNotifyReader(c Config, l *logp.Logger) (EventProducer, error) {
// Test if fsnotify is available by trying to create a watcher
// This validates early that fsnotify can actually be used, allowing fallback to work
testWatcher, err := monitor.New(c.Recursive, c.IsExcludedPath)
if err != nil {
return nil, err
}
// Close the test watcher immediately - we'll create a new one in Start()
testWatcher.Close()

return &fsNotifyReader{config: c, log: l, parsers: FileParsers(c)}, nil
}

func (r *fsNotifyReader) Start(done <-chan struct{}) (<-chan Event, error) {
watcher, err := monitor.New(r.config.Recursive, r.config.IsExcludedPath)
if err != nil {
Expand Down
13 changes: 11 additions & 2 deletions auditbeat/module/file_integrity/eventreader_kprobes.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,16 @@ type kProbesReader struct {
processor beat.Processor
}

func newKProbesReader(config Config, l *logp.Logger, parsers []FileParser) (*kProbesReader, error) {
func newKProbesReader(config Config, l *logp.Logger) (EventProducer, error) {
// Test if kprobes is available by trying to create a monitor
// This validates early that kprobes can actually be used, allowing fallback to work
testMonitor, err := kprobes.New(config.Recursive, l)
if err != nil {
return nil, err
}
// Close the test monitor immediately - we'll create a new one in Start()
testMonitor.Close()

processor, err := add_process_metadata.NewWithConfig(
l,
add_process_metadata.ConfigOverwriteKeys(true),
Expand All @@ -59,7 +68,7 @@ func newKProbesReader(config Config, l *logp.Logger, parsers []FileParser) (*kPr
config: config,
eventC: make(chan Event),
log: l,
parsers: parsers,
parsers: FileParsers(config),
processor: processor,
}, nil
}
Expand Down
41 changes: 9 additions & 32 deletions auditbeat/module/file_integrity/eventreader_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,37 +19,14 @@

package file_integrity

import (
"errors"

"github.com/elastic/elastic-agent-libs/logp"
)

func NewEventReader(c Config, logger *logp.Logger) (EventProducer, error) {
if c.Backend == BackendAuto || c.Backend == BackendFSNotify || c.Backend == "" {
// Auto and unset defaults to fsnotify
l := logger.Named("fsnotify")
l.Info("selected backend: fsnotify")
return &fsNotifyReader{
config: c,
log: l,
parsers: FileParsers(c),
}, nil
}

if c.Backend == BackendEBPF {
l := logger.Named("ebpf")
l.Info("selected backend: ebpf")

return newEBPFReader(c, l)
}

if c.Backend == BackendKprobes {
l := logger.Named("kprobes")
l.Info("selected backend: kprobes")
return newKProbesReader(c, l, FileParsers(c))
}
var autoBackendOrder = []Backend{
BackendEBPF,
BackendKprobes,
BackendFSNotify,
}

// unimplemented
return nil, errors.ErrUnsupported
var supportedBackends = map[Backend]backendInitializer{
BackendEBPF: newEBPFReader,
BackendKprobes: newKProbesReader,
BackendFSNotify: newFSNotifyReader,
}
14 changes: 5 additions & 9 deletions auditbeat/module/file_integrity/eventreader_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,10 @@

package file_integrity

import (
"github.com/elastic/elastic-agent-libs/logp"
)
var autoBackendOrder = []Backend{
BackendFSNotify,
}

func NewEventReader(c Config, logger *logp.Logger) (EventProducer, error) {
return &fsNotifyReader{
config: c,
log: logger.Named("fsnotify"),
parsers: FileParsers(c),
}, nil
var supportedBackends = map[Backend]backendInitializer{
BackendFSNotify: newFSNotifyReader,
}
Loading
Loading