Skip to content

Commit

Permalink
[Heartbeat] Enable Heartbeat to run when elastic-agent container is e…
Browse files Browse the repository at this point in the history
…xecuted as root (#30869)

* Include group write permission in runtime directories, adapt umask on docker containers and restrict heartbeat setuid to containerized instances

* Add CHANGELOG entries

* Fix linter issues
  • Loading branch information
emilioalvap authored Apr 25, 2022
1 parent a21671b commit 8906734
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 12 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...main[Check the HEAD dif
*Filebeat*

*Heartbeat*

- Restrict setuid to containerized environments. {pull}30869[30869]

*Metricbeat*

Expand All @@ -42,6 +42,11 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...main[Check the HEAD dif
- Fix the ability for subcommands to be ran properly from the beats containers. {pull}30452[30452]
- Update docker/distribution dependency library to fix a security issues concerning OCI Manifest Type Confusion Issue. {pull}30462[30462]
- Fix dissect trim panics from DELETE (127)(\u007f) character {issue}30657[30657] {pull}30658[30658]
- Load data stream during setup, so users do not need extra permissions during publishing. {issue}30647[30647] {pull}31048[31048]
- Add ecs container fields {pull}31020[31020]
- Fix docs reference for syslog processor {pull}31087[31087]
- Fix AWS config initialization issue when using a role {issue}30999[30999] {pull}31014[31014]
- Fix group write permissions on runtime directories. {pull}30869[30869]

*Auditbeat*

Expand Down
26 changes: 17 additions & 9 deletions heartbeat/security/security.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import (
"strconv"
"syscall"

"github.com/elastic/go-sysinfo"

"kernel.org/pub/linux/libs/security/libcap/cap"

"github.com/elastic/beats/v7/libbeat/common/seccomp"
Expand All @@ -39,7 +41,13 @@ func init() {
// In the context of a container, where users frequently run as root, we follow BEAT_SETUID_AS to setuid/gid
// and add capabilities to make this actually run as a regular user. This also helps Node.js in synthetics, which
// does not want to run as root. It's also just generally more secure.
if localUserName := os.Getenv("BEAT_SETUID_AS"); localUserName != "" && syscall.Geteuid() == 0 {
sysInfo, err := sysinfo.Host()
isContainer := false
if err == nil && sysInfo.Info().Containerized != nil {
isContainer = *sysInfo.Info().Containerized
}

if localUserName := os.Getenv("BEAT_SETUID_AS"); isContainer && localUserName != "" && syscall.Geteuid() == 0 {
err := changeUser(localUserName)
if err != nil {
panic(err)
Expand All @@ -52,7 +60,7 @@ func init() {
// rather than relying on errors from `setcap`
_ = setCapabilities()

err := setSeccompRules()
err = setSeccompRules()
if err != nil {
panic(err)
}
Expand All @@ -63,33 +71,33 @@ func changeUser(localUserName string) error {
if err != nil {
return fmt.Errorf("could not lookup '%s': %w", localUser, err)
}
localUserUid, err := strconv.Atoi(localUser.Uid)
localUserUID, err := strconv.Atoi(localUser.Uid)
if err != nil {
return fmt.Errorf("could not parse UID '%s' as int: %w", localUser.Uid, err)
}
localUserGid, err := strconv.Atoi(localUser.Gid)
localUserGID, err := strconv.Atoi(localUser.Gid)
if err != nil {
return fmt.Errorf("could not parse GID '%s' as int: %w", localUser.Uid, err)
}
// We include the root group because the docker image contains many directories (data,logs)
// that are owned by root:root with 0775 perms. The heartbeat user is in both groups
// in the container, but we need to repeat that here.
err = syscall.Setgroups([]int{localUserGid, 0})
err = syscall.Setgroups([]int{localUserGID, 0})
if err != nil {
return fmt.Errorf("could not set groups: %w", err)
}

// Set the main group as localUserUid so new files created are owned by the user's group
err = syscall.Setgid(localUserGid)
err = syscall.Setgid(localUserGID)
if err != nil {
return fmt.Errorf("could not set gid to %d: %w", localUserGid, err)
return fmt.Errorf("could not set gid to %d: %w", localUserGID, err)
}

// Note this is not the regular SetUID! Look at the 'cap' package docs for it, it preserves
// capabilities post-SetUID, which we use to lock things down immediately
err = cap.SetUID(localUserUid)
err = cap.SetUID(localUserUID)
if err != nil {
return fmt.Errorf("could not setuid to %d: %w", localUserUid, err)
return fmt.Errorf("could not setuid to %d: %w", localUserUID, err)
}

// This may not be necessary, but is good hygiene, we do some shelling out to node/npm etc.
Expand Down
4 changes: 2 additions & 2 deletions libbeat/paths/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ func (paths *Path) InitPaths(cfg *Path) error {
}

// make sure the data path exists
err = os.MkdirAll(paths.Data, 0750)
err = os.MkdirAll(paths.Data, 0770)
if err != nil {
return fmt.Errorf("Failed to create data path %s: %v", paths.Data, err)
return fmt.Errorf("failed to create data path %s: %w", paths.Data, err)
}

return nil
Expand Down

0 comments on commit 8906734

Please sign in to comment.