Skip to content
Open
Show file tree
Hide file tree
Changes from 20 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
10 changes: 5 additions & 5 deletions NOTICE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10941,11 +10941,11 @@ SOFTWARE

--------------------------------------------------------------------------------
Dependency : github.com/elastic/elastic-agent-libs
Version: v0.26.2
Version: v0.28.0
Licence type (autodetected): Apache-2.0
--------------------------------------------------------------------------------

Contents of probable licence file $GOMODCACHE/github.com/elastic/elastic-agent-libs@v0.26.2/LICENSE:
Contents of probable licence file $GOMODCACHE/github.com/elastic/elastic-agent-libs@v0.28.0/LICENSE:

Apache License
Version 2.0, January 2004
Expand Down Expand Up @@ -28994,13 +28994,13 @@ Contents of probable licence file $GOMODCACHE/go.uber.org/[email protected]/LICENSE:

--------------------------------------------------------------------------------
Dependency : go.uber.org/zap
Version: v1.27.0
Version: v1.27.1
Licence type (autodetected): MIT
--------------------------------------------------------------------------------

Contents of probable licence file $GOMODCACHE/go.uber.org/[email protected].0/LICENSE:
Contents of probable licence file $GOMODCACHE/go.uber.org/[email protected].1/LICENSE:

Copyright (c) 2016-2017 Uber Technologies, Inc.
Copyright (c) 2016-2024 Uber Technologies, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
32 changes: 32 additions & 0 deletions changelog/fragments/1765318668-Support-chroot-in-journald.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Kind can be one of:
# - breaking-change: a change to previously-documented behavior
# - deprecation: functionality that is being removed in a later release
# - bug-fix: fixes a problem in a previous version
# - enhancement: extends functionality but does not break or fix existing behavior
# - feature: new functionality
# - known-issue: problems that we are aware of in a given version
# - security: impacts on the security of a product or a user’s deployment.
# - upgrade: important information for someone upgrading from a prior version
# - other: does not fit into any of the other categories
kind: feature

# Change summary; a 80ish characters long description of the change.
summary: The Journald input now supports setting a chroot to use when calling the journalctl binary

# Long description; in case the summary is not enough to describe the change
# this field accommodate a description without length limits.
# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment.
#description:

# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc.
component: filebeat

# PR URL; optional; the PR number that added the changeset.
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
# Please provide it if you are adding a fragment for a different PR.
pr: https://github.com/elastic/beats/pull/48008

# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
# If not present is automatically filled by the tooling with the issue linked to the PR number.
issue: https://github.com/elastic/beats/issues/47164
40 changes: 29 additions & 11 deletions docs/reference/filebeat/filebeat-input-journald.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,18 @@ The Wolfi-based Docker image does not contain the `journalctl` binary and the `j
:::

:::{important}
When using the Journald input from a Docker container, make sure the
`journalctl` binary in the container is compatible with your
Systemd/journal version. To get the version of the `journalctl` binary
in Filebeat's image run the following, adjusting the image name/tag
according to the version that you are running:


```sh
docker run --rm -it --entrypoint "journalctl" docker.elastic.co/beats/filebeat-wolfi:<VERSION> --version
```
When using the Journald input from a Docker container, make sure that
either:
- {applies_to}`stack: ga 9.3.0` [`chroot`](#filebeat-input-journald-chroot), [`journalct_path`](#filebeat-input-journald-journalctl-path) are set, or
- The `journalctl` binary in the container is compatible with your
Copy link
Member

Choose a reason for hiding this comment

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

List the container variants that include journald by default.

Systemd/journal version. To get the version of the `journalctl` binary
in Filebeat's image run the following, adjusting the image name/tag
according to the version that you are running:


```sh
docker run --rm -it --entrypoint "journalctl" docker.elastic.co/beats/filebeat-wolfi:<VERSION> --version
```
:::

If the `journalctl` process exits unexpectedly the journald input will terminate with an error and Filebeat will need to be restarted to start reading from the journal again.
Expand Down Expand Up @@ -110,8 +112,24 @@ input will only ingest the journal files found when started. New
files will not be ingested.
:::

### `chroot` [filebeat-input-journald-chroot]
```{applies_to}
stack: ga 9.3.0
```
A folder to be used as chroot when calling `journalctl`. This allows
Filebeat to call the host's `journalctl` directly. If using this
option, {{filebeat}} must be run as root and
Copy link
Member

Choose a reason for hiding this comment

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

The wolfi containers are not root by default, you will need examples of how to give the capabilities needed to do this.

You potentially only need CAP_SYS_CHROOT and not all the root capabilities, we should identify what the least privileged way for this to execute is.

Copy link
Member

Choose a reason for hiding this comment

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

If we can say that you just need CAP_SYS_CHROOT without actually being uid 0 that would help considerably so we are also not executing potentially arbitrary commands as root.

[`journalct_path`](#filebeat-input-journald-journalctl-path) must be
set.


### `journalct_path` [filebeat-input-journald-journalctl-path]
```{applies_to}
stack: ga 9.3.0
```
The absolute path for the `journalctl` binary. If not set {{filebeat}}
will look for `journalctl` in `PATH`. When using
[`chroot`](#filebeat-input-journald-chroot), `journalct_path` must be
an absolute path from within the chroot directory.


### `merge` [filebeat-input-journald-merge]
Expand Down
9 changes: 9 additions & 0 deletions filebeat/_meta/config/filebeat.inputs.reference.yml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,15 @@ filebeat.inputs:
#paths:
#- /var/log/custom.journal

# Specify a folder to be used as chroot when calling the journalct binary
#chroot:

# The absolute path for the `journalctl` binary. If not set Filebeat
# will look for `journalctl` in `PATH`. When using
Comment on lines +823 to +824
Copy link
Member

Choose a reason for hiding this comment

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

Do we want to allow executing whatever thing called journalctl we happen find?

That seems less secure than requiring the absolute path to the binary, as there is only a single file system path that can be exploited now.

Copy link
Member

Choose a reason for hiding this comment

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

Especially if we execute journalctl as root, we should narrow the scope of what we will execute as much as possible.

Copy link
Member

Choose a reason for hiding this comment

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

The only reason to just look for journalctl is if the path to it isn't consistent all hosts, where finding it then becomes really annoying.

Copy link
Member

Choose a reason for hiding this comment

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

On an ubuntu24 box I see it as the following which is certainly not hard to find.

ubuntu@ubuntu24:~$ which journalctl
/usr/bin/journalctl

If we think using PATH is necessary we could make it optional and default to off.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do we want to allow executing whatever thing called journalctl we happen find?

That is the current (before this PR) behaviour. Since we migrated to calling journalctl we just start journalctl and let it be resolved by the PATH.

The only reason to just look for journalctl is if the path to it isn't consistent all hosts, where finding it then becomes really annoying.

I don't know for sure if the journalctl binary is at the same place in all distros we support, I checked Debian 12 and Arch Linux, they're both at /usr/bin/journalctl. I can do a more in-depth research tomorrow, however changing this behaviour could be considered a breaking change.

Btw, the OpenTelemetry Collector behaves in the same way:

# `chroot`, `journalct_path` must be
# an absolute path from within the chroot directory.
#journalctl_path:

# When enabled, log entries will be ingested interleaved from all
# available journals, including remote ones.
#merge: false
Expand Down
9 changes: 9 additions & 0 deletions filebeat/filebeat.reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,15 @@ filebeat.inputs:
#paths:
#- /var/log/custom.journal

# Specify a folder to be used as chroot when calling the journalct binary
#chroot:

# The absolute path for the `journalctl` binary. If not set Filebeat
# will look for `journalctl` in `PATH`. When using
# `chroot`, `journalct_path` must be
# an absolute path from within the chroot directory.
#journalctl_path:

# When enabled, log entries will be ingested interleaved from all
# available journals, including remote ones.
#merge: false
Expand Down
15 changes: 15 additions & 0 deletions filebeat/input/journald/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
// specific language governing permissions and limitations
// under the License.

// This file was contributed to by generative AI

//go:build linux

package journald
Expand Down Expand Up @@ -76,6 +78,18 @@ type config struct {
// Allow ingesting log entries interleaved from all available journals,
// including remote ones.
Merge bool `config:"merge"`

// Chroot is the chroot folder used to call journalctl
Chroot string `config:"chroot"`

// JournalctlPath specifies the path to the `journalctl` binary.
// This field is required only if the Chroot option is set, as the
// input needs to locate the binary within the chroot environment.
// If Chroot is set, JournalctlPath must be an absolute path within
// the chroot environment. If JournalctlPath is not explicitly set,
// it defaults to `journalctl`, which assumes that the `journalctl`
// binary is available in the system's `PATH` environment variable.
JournalctlPath string `config:"journalctl_path"`
}

// bwcIncludeMatches is a wrapper that accepts include_matches configuration
Expand Down Expand Up @@ -107,5 +121,6 @@ func defaultConfig() config {
return config{
Seek: journalctl.SeekHead,
SaveRemoteHostname: false,
JournalctlPath: "journalctl",
}
}
26 changes: 23 additions & 3 deletions filebeat/input/journald/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import (
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
"time"

Expand Down Expand Up @@ -57,8 +59,9 @@
Facilities []int
SaveRemoteHostname bool
Parsers parser.Config
Journalctl bool
Merge bool
Chroot string
JournalctlPath string
}

type checkpoint struct {
Expand Down Expand Up @@ -112,6 +115,21 @@
sources[i] = pathSource(p)
}

if config.Chroot != "" {
chrootStat, err := os.Stat(config.Chroot)
if err != nil {
return nil, nil, fmt.Errorf("cannot stat chroot: %w", err)
}
if !chrootStat.IsDir() {
return nil, nil, fmt.Errorf("provided chroot (%s) is not a directory", config.Chroot)
}

fullPath := filepath.Join(config.Chroot, config.JournalctlPath)
Copy link
Member

Choose a reason for hiding this comment

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

We may want to verify the ownership of the file, maybe that it is only writable by root so it can't be replaced by something from an arbitrary user, especially if we are going to execute it as root.

Speaking of executing it as a root, if we can execute this as a lower privileged user that would help the security boundary here too.

if _, err := os.Stat(fullPath); err != nil {
return nil, nil, fmt.Errorf("cannot stat journalctl binary in chroot: %w", err)
}
}

return sources, &journald{
ID: config.ID,
Since: config.Since,
Expand All @@ -124,6 +142,8 @@
SaveRemoteHostname: config.SaveRemoteHostname,
Parsers: config.Parsers,
Merge: config.Merge,
Chroot: config.Chroot,
JournalctlPath: config.JournalctlPath,
}, nil
}

Expand All @@ -143,7 +163,7 @@
inp.Since,
src.Name(),
inp.Merge,
journalctl.Factory,
journalctl.NewFactory(inp.Chroot, inp.JournalctlPath),
)
if err != nil {
return err
Expand Down Expand Up @@ -179,7 +199,7 @@
inp.Since,
src.Name(),
inp.Merge,
journalctl.Factory,
journalctl.NewFactory(inp.Chroot, inp.JournalctlPath),
)
if err != nil {
wrappedErr := fmt.Errorf("could not start journal reader: %w", err)
Expand Down Expand Up @@ -347,7 +367,7 @@
}

m := reader.Message{
Ts: time.UnixMicro(int64(data.RealtimeTimestamp)),

Check failure on line 370 in filebeat/input/journald/input.go

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

G115: integer overflow conversion uint64 -> int64 (gosec)
Content: content,
Bytes: len(content),
Fields: fields,
Expand Down
Loading
Loading