From 8906734a2f62d65f8fadc4c0cc1600d1dfafc280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Alvarez=20Pi=C3=B1eiro?= <95703246+emilioalvap@users.noreply.github.com> Date: Mon, 25 Apr 2022 18:34:31 +0200 Subject: [PATCH] [Heartbeat] Enable Heartbeat to run when elastic-agent container is executed 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 --- CHANGELOG.next.asciidoc | 7 ++++++- heartbeat/security/security.go | 26 +++++++++++++++++--------- libbeat/paths/paths.go | 4 ++-- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 27cdbe57c073..b10040ad7294 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -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* @@ -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* diff --git a/heartbeat/security/security.go b/heartbeat/security/security.go index 56ab56e79f62..da69eb380b91 100644 --- a/heartbeat/security/security.go +++ b/heartbeat/security/security.go @@ -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" @@ -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) @@ -52,7 +60,7 @@ func init() { // rather than relying on errors from `setcap` _ = setCapabilities() - err := setSeccompRules() + err = setSeccompRules() if err != nil { panic(err) } @@ -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. diff --git a/libbeat/paths/paths.go b/libbeat/paths/paths.go index f73c0e34e97b..c9df846d1293 100644 --- a/libbeat/paths/paths.go +++ b/libbeat/paths/paths.go @@ -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