Skip to content

Commit

Permalink
[Heartbeat] Drop only inheritable cap set and defer setuid to node fo…
Browse files Browse the repository at this point in the history
…rk (#33584)

* [Heartbeat] Drop only inheritable cap set and defer setuid to node fork

* Update heartbeat/security/security.go

* Fix linter

* Fix linter, add definition for darwin

* Fix linter for darwin

* Fix linter

* Fix linter

* Add changelog

Co-authored-by: Andrew Cholakian <[email protected]>
  • Loading branch information
emilioalvap and andrewvc authored Jan 24, 2023
1 parent 88b3e96 commit a065bd0
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 37 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,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*
Expand Down
3 changes: 3 additions & 0 deletions heartbeat/security/policy_linux_386.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,11 @@ func init() {
"sendto",
"set_robust_list",
"set_tid_address",
"setgid",
"setgroups",
"setpriority",
"setsid",
"setuid",
"sigaltstack",
"socket",
"socketpair",
Expand Down
3 changes: 3 additions & 0 deletions heartbeat/security/policy_linux_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,11 @@ func init() {
"sendto",
"set_robust_list",
"set_tid_address",
"setgid",
"setgroups",
"setpriority",
"setsid",
"setuid",
"sigaltstack",
"socket",
"socketpair",
Expand Down
52 changes: 18 additions & 34 deletions heartbeat/security/security.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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)
}
Expand All @@ -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)
Expand All @@ -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)
Expand Down
27 changes: 27 additions & 0 deletions heartbeat/security/security_unix.go
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion x-pack/heartbeat/monitors/browser/browser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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")
}

Expand Down
11 changes: 9 additions & 2 deletions x-pack/heartbeat/monitors/browser/synthexec/synthexec_linux.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
// 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
// as the syscall package is
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,
}
}
}

0 comments on commit a065bd0

Please sign in to comment.