diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 2719d081e0ce..0d3e3c5606ba 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -74,6 +74,7 @@ https://github.com/elastic/beats/compare/v8.2.0\...main[Check the HEAD diff] - Fix bug where states.duration_ms was incorrect type. {pull}33563[33563] - Fix handling of long UDP messages in UDP input. {issue}33836[33836] {pull}33837[33837] - Fix browser monitor summary reporting as up when monitor is down. {issue}33374[33374] {pull}33819[33819] +- Fix beat capabilities on Docker image. {pull}33584[33584] *Heartbeat* diff --git a/heartbeat/security/policy_linux_386.go b/heartbeat/security/policy_linux_386.go index 74b98e8d5b46..b868891db68d 100644 --- a/heartbeat/security/policy_linux_386.go +++ b/heartbeat/security/policy_linux_386.go @@ -109,8 +109,11 @@ func init() { "sendto", "set_robust_list", "set_tid_address", + "setgid", + "setgroups", "setpriority", "setsid", + "setuid", "sigaltstack", "socket", "socketpair", diff --git a/heartbeat/security/policy_linux_amd64.go b/heartbeat/security/policy_linux_amd64.go index 478527f6c21d..9d9f0033a9a1 100644 --- a/heartbeat/security/policy_linux_amd64.go +++ b/heartbeat/security/policy_linux_amd64.go @@ -109,8 +109,11 @@ func init() { "sendto", "set_robust_list", "set_tid_address", + "setgid", + "setgroups", "setpriority", "setsid", + "setuid", "sigaltstack", "socket", "socketpair", diff --git a/heartbeat/security/security.go b/heartbeat/security/security.go index ab99ac4fc36c..b4f4f51d61e3 100644 --- a/heartbeat/security/security.go +++ b/heartbeat/security/security.go @@ -27,7 +27,7 @@ import ( "strconv" "syscall" - "github.com/elastic/go-sysinfo" + sysinfo "github.com/elastic/go-sysinfo" "kernel.org/pub/linux/libs/security/libcap/cap" ) @@ -44,7 +44,7 @@ func init() { } if localUserName := os.Getenv("BEAT_SETUID_AS"); isContainer && localUserName != "" && syscall.Geteuid() == 0 { - err := changeUser(localUserName) + err := setNodeProcAttr(localUserName) if err != nil { panic(err) } @@ -57,7 +57,8 @@ func init() { _ = setCapabilities() } -func changeUser(localUserName string) error { +func setNodeProcAttr(localUserName string) error { + localUser, err := user.Lookup(localUserName) if err != nil { return fmt.Errorf("could not lookup '%s': %w", localUser, err) @@ -70,50 +71,33 @@ func changeUser(localUserName string) error { 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}) - 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) - if err != nil { - return fmt.Errorf("could not set gid to %d: %w", localUserGID, err) + NodeChildProcCred = &syscall.Credential{ + Uid: uint32(localUserUID), + Gid: uint32(localUserGID), + Groups: []uint32{0}, + NoSetGroups: false, } - // 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) - if err != nil { - 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. - // and $HOME should reflect the user's preferences return os.Setenv("HOME", localUser.HomeDir) } func setCapabilities() error { - // Start with an empty capability set - newcaps := cap.NewSet() - // Both permitted and effective are required! Permitted makes the permmission - // possible to get, effective makes it 'active' - err := newcaps.SetFlag(cap.Permitted, true, cap.NET_RAW) - if err != nil { - return fmt.Errorf("error setting permitted setcap: %w", err) - } - err = newcaps.SetFlag(cap.Effective, true, cap.NET_RAW) + newcaps := cap.GetProc() + + // Raise all permitted caps to effective + err := newcaps.Fill(cap.Effective, cap.Permitted) if err != nil { - return fmt.Errorf("error setting effective setcap: %w", err) + return fmt.Errorf("error raising effective cap set: %w", err) } - // We do not want these capabilities to be inherited by subprocesses - err = newcaps.SetFlag(cap.Inheritable, false, cap.NET_RAW) + // Drop all inheritable caps to stop propagation to child proc + err = newcaps.ClearFlag(cap.Inheritable) if err != nil { - return fmt.Errorf("error setting inheritable setcap: %w", err) + return fmt.Errorf("error clearing inheritable cap set: %w", err) } // Apply the new capabilities to the current process (incl. all threads) diff --git a/heartbeat/security/security_unix.go b/heartbeat/security/security_unix.go new file mode 100644 index 000000000000..04d0175b2ad1 --- /dev/null +++ b/heartbeat/security/security_unix.go @@ -0,0 +1,27 @@ +// 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. + +//go:build linux || darwin +// +build linux darwin + +package security + +import ( + "syscall" +) + +var NodeChildProcCred *syscall.Credential = nil diff --git a/x-pack/heartbeat/monitors/browser/browser.go b/x-pack/heartbeat/monitors/browser/browser.go index 6411d0d3f092..4444950a6201 100644 --- a/x-pack/heartbeat/monitors/browser/browser.go +++ b/x-pack/heartbeat/monitors/browser/browser.go @@ -17,6 +17,7 @@ import ( "github.com/elastic/beats/v7/heartbeat/ecserr" "github.com/elastic/beats/v7/heartbeat/monitors/plugin" + "github.com/elastic/beats/v7/heartbeat/security" ) func init() { @@ -38,7 +39,7 @@ func create(name string, cfg *config.C) (p plugin.Plugin, err error) { }) // We do not use user.Current() which does not reflect setuid changes! - if syscall.Geteuid() == 0 { + if syscall.Geteuid() == 0 && security.NodeChildProcCred == nil { return plugin.Plugin{}, fmt.Errorf("script monitors cannot be run as root") } diff --git a/x-pack/heartbeat/monitors/browser/synthexec/synthexec_linux.go b/x-pack/heartbeat/monitors/browser/synthexec/synthexec_linux.go index 65843d727375..a1a5a0879529 100644 --- a/x-pack/heartbeat/monitors/browser/synthexec/synthexec_linux.go +++ b/x-pack/heartbeat/monitors/browser/synthexec/synthexec_linux.go @@ -1,19 +1,24 @@ // Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. -//go:build linux || darwin -// +build linux darwin +//go:build linux +// +build linux package synthexec import ( + "os" "os/exec" "golang.org/x/sys/unix" + + "github.com/elastic/beats/v7/heartbeat/security" + "github.com/elastic/elastic-agent-libs/logp" ) func init() { platformCmdMutate = func(cmd *exec.Cmd) { + logp.L().Warn("invoking node as:", security.NodeChildProcCred, " from: ", os.Getenv("HOME")) // Note that while cmd.SysProcAtr takes a syscall.SysProcAttr object // we are passing in a unix.SysProcAttr object // this is equivalent, but the unix package is not considered deprecated @@ -21,6 +26,8 @@ func init() { cmd.SysProcAttr = &unix.SysProcAttr{ // Ensure node subprocesses are killed if this process dies (linux only) Pdeathsig: unix.SIGKILL, + // Apply restricted user if available + Credential: security.NodeChildProcCred, } } }