From 3d0ec54fb57903f1ed070e06e3b342a899bf7ac3 Mon Sep 17 00:00:00 2001 From: Jaime Soriano Pastor Date: Thu, 2 May 2019 17:52:16 +0200 Subject: [PATCH 01/16] Update instructions for socket metricset on Docker --- metricbeat/docs/running-on-docker.asciidoc | 5 ++ metricbeat/helper/socket/ptable.go | 82 ++++++++++++++++++++-- metricbeat/module/system/socket/socket.go | 7 +- 3 files changed, 84 insertions(+), 10 deletions(-) diff --git a/metricbeat/docs/running-on-docker.asciidoc b/metricbeat/docs/running-on-docker.asciidoc index 98e5f5d7df6..dfa55d3ef63 100644 --- a/metricbeat/docs/running-on-docker.asciidoc +++ b/metricbeat/docs/running-on-docker.asciidoc @@ -19,6 +19,7 @@ docker run \ --mount type=bind,source=/sys/fs/cgroup,target=/hostfs/sys/fs/cgroup,readonly \ <2> --mount type=bind,source=/,target=/hostfs,readonly \ <3> --net=host \ <4> + --user root --privileged \ <5> {dockerimage} -e -system.hostfs=/hostfs ---- @@ -41,6 +42,10 @@ of the container. They can be mounted at any location. to make this file contain the host's network devices is to use the `--net=host` flag. This is due to Linux namespacing; simply bind mounting the host's `/proc` to `/hostfs/proc` is not sufficient. +<5> The <> needs to +read files from `/proc` that belong to multiple users. In order to access these +files Metricbeat must be run as root in a privileged container. These options +are not needed if this metricset is not used. NOTE: The special filesystems +/proc+ and +/sys+ are only available if the host system is running Linux. Attempts to bind-mount these filesystems will diff --git a/metricbeat/helper/socket/ptable.go b/metricbeat/helper/socket/ptable.go index 84d600fbe00..abff0c5b193 100644 --- a/metricbeat/helper/socket/ptable.go +++ b/metricbeat/helper/socket/ptable.go @@ -15,15 +15,18 @@ // specific language governing permissions and limitations // under the License. +// +build linux + package socket import ( - "os" "strconv" "strings" + "unsafe" "github.com/joeshaw/multierror" "github.com/prometheus/procfs" + "golang.org/x/sys/unix" ) // process tools @@ -39,10 +42,10 @@ type Proc struct { // ProcTable contains all of the active processes (if the current user is root). type ProcTable struct { - fs procfs.FS - procs map[int]*Proc - inodes map[uint32]*Proc - euid int + fs procfs.FS + procs map[int]*Proc + inodes map[uint32]*Proc + privileged bool } // NewProcTable returns a new ProcTable that reads data from the /proc @@ -58,11 +61,17 @@ func NewProcTable(mountpoint string) (*ProcTable, error) { return nil, err } - p := &ProcTable{fs: fs, euid: os.Geteuid()} + p := &ProcTable{fs: fs, privileged: isPrivileged(mountpoint)} p.Refresh() return p, nil } +// Privileged returns true if the process has enough permissions to read +// sockets of all users +func (t *ProcTable) Privileged() bool { + return t.privileged +} + // Refresh updates the process table with new processes and removes processes // that have exited. It collects the PID, command, and socket inode information. // If running as non-root, only information from the current process will be @@ -70,7 +79,7 @@ func NewProcTable(mountpoint string) (*ProcTable, error) { func (t *ProcTable) Refresh() error { var err error var procs []procfs.Proc - if t.euid == 0 { + if t.privileged { procs, err = t.fs.AllProcs() if err != nil { return err @@ -150,3 +159,62 @@ func socketInodes(p *procfs.Proc) ([]uint32, error) { func (t *ProcTable) ProcessBySocketInode(inode uint32) *Proc { return t.inodes[inode] } + +const requiredCapabilities = uint64(0x0000000000080004) // ptrace & dac_read_search + +// isPrivileged checks if this process has privileges to read sockets +// of all users +func isPrivileged(mountpoint string) bool { + capabilities := getCapabilities() + return (capabilities.effective & requiredCapabilities) > 0 +} + +type capData64 struct { + effective uint64 + permitted uint64 + inheritable uint64 +} + +type capData32 struct { + effective uint32 + permitted uint32 + inheritable uint32 +} + +func uint32to64(a, b uint32) uint64 { + return uint64(a)<<32 | uint64(b) +} + +const ( + capabilityVersion1 = 0x19980330 // Version 1, 32-bit capabilities + capabilityVersion3 = 0x20080522 // Version 3 (replaced V2), 64-bit capabilities +) + +func getCapabilities() capData64 { + header := struct { + version uint32 + pid int32 + }{ + version: capabilityVersion3, + pid: 0, // Self + } + + // Check compatibility with version 3 + _, _, e := unix.Syscall(unix.SYS_CAPGET, uintptr(unsafe.Pointer(&header)), 0, 0) + if e != 0 { + header.version = capabilityVersion1 + } + + var data [2]capData32 + _, _, e = unix.Syscall(unix.SYS_CAPGET, uintptr(unsafe.Pointer(&header)), uintptr(unsafe.Pointer(&data)), 0) + if e != 0 { + // If this fails, there are invalid arguments + panic(unix.ErrnoName(e)) + } + + var data64 capData64 + data64.effective = uint32to64(data[1].effective, data[0].effective) + data64.permitted = uint32to64(data[1].permitted, data[0].permitted) + data64.inheritable = uint32to64(data[1].inheritable, data[0].inheritable) + return data64 +} diff --git a/metricbeat/module/system/socket/socket.go b/metricbeat/module/system/socket/socket.go index 9bb25f7f675..a45375ea0ed 100644 --- a/metricbeat/module/system/socket/socket.go +++ b/metricbeat/module/system/socket/socket.go @@ -77,9 +77,10 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { if err != nil { return nil, err } - if os.Geteuid() != 0 { - logp.Info("socket process info will only be available for " + - "metricbeat because the process is running as a non-root user") + if !ptable.Privileged() { + logp.Info("socket process info will only be available for metricbeat process " + + "because it is running without enough privileges " + + "(ptrace and dac_read_search required)") } m := &MetricSet{ From 5e8f1b781713b3de9a6d79638b0bfb5787c5759f Mon Sep 17 00:00:00 2001 From: Jaime Soriano Pastor Date: Thu, 2 May 2019 22:50:45 +0200 Subject: [PATCH 02/16] Check capabilities instead of root euid for sockets --- libbeat/common/seccomp/policy_linux_386.go | 1 + libbeat/common/seccomp/policy_linux_amd64.go | 1 + metricbeat/helper/socket/ptable.go | 2 +- metricbeat/module/system/socket/socket.go | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/libbeat/common/seccomp/policy_linux_386.go b/libbeat/common/seccomp/policy_linux_386.go index 043ccf7d8ae..66d0cd99409 100644 --- a/libbeat/common/seccomp/policy_linux_386.go +++ b/libbeat/common/seccomp/policy_linux_386.go @@ -31,6 +31,7 @@ func init() { "_llseek", "access", "brk", + "capget", "clock_gettime", "clone", "close", diff --git a/libbeat/common/seccomp/policy_linux_amd64.go b/libbeat/common/seccomp/policy_linux_amd64.go index a131e7f3c34..9d45a444642 100644 --- a/libbeat/common/seccomp/policy_linux_amd64.go +++ b/libbeat/common/seccomp/policy_linux_amd64.go @@ -34,6 +34,7 @@ func init() { "arch_prctl", "bind", "brk", + "capget", "clock_gettime", "clone", "close", diff --git a/metricbeat/helper/socket/ptable.go b/metricbeat/helper/socket/ptable.go index abff0c5b193..12d31ef22f7 100644 --- a/metricbeat/helper/socket/ptable.go +++ b/metricbeat/helper/socket/ptable.go @@ -160,7 +160,7 @@ func (t *ProcTable) ProcessBySocketInode(inode uint32) *Proc { return t.inodes[inode] } -const requiredCapabilities = uint64(0x0000000000080004) // ptrace & dac_read_search +const requiredCapabilities = uint64(0x0000000000080004) // sys_ptrace & dac_read_search // isPrivileged checks if this process has privileges to read sockets // of all users diff --git a/metricbeat/module/system/socket/socket.go b/metricbeat/module/system/socket/socket.go index a45375ea0ed..93c4103fba5 100644 --- a/metricbeat/module/system/socket/socket.go +++ b/metricbeat/module/system/socket/socket.go @@ -80,7 +80,7 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { if !ptable.Privileged() { logp.Info("socket process info will only be available for metricbeat process " + "because it is running without enough privileges " + - "(ptrace and dac_read_search required)") + "(sys_ptrace and dac_read_search capabilities required)") } m := &MetricSet{ From ed44cb5357050d380c764f1ad5282480ff0e0a27 Mon Sep 17 00:00:00 2001 From: Jaime Soriano Pastor Date: Fri, 3 May 2019 01:08:35 +0200 Subject: [PATCH 03/16] Move capabilities getter to libbeat --- libbeat/common/capabilities_linux.go | 76 ++++++++++++++++++++++++++++ metricbeat/helper/socket/ptable.go | 58 ++------------------- 2 files changed, 80 insertions(+), 54 deletions(-) create mode 100644 libbeat/common/capabilities_linux.go diff --git a/libbeat/common/capabilities_linux.go b/libbeat/common/capabilities_linux.go new file mode 100644 index 00000000000..dc15b355043 --- /dev/null +++ b/libbeat/common/capabilities_linux.go @@ -0,0 +1,76 @@ +// 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. + +// +build linux + +package common + +import ( + "unsafe" + + "golang.org/x/sys/unix" +) + +type CapData struct { + Effective uint64 + Permitted uint64 + Inheritable uint64 +} + +type capData32 struct { + effective uint32 + permitted uint32 + inheritable uint32 +} + +func uint32to64(a, b uint32) uint64 { + return uint64(a)<<32 | uint64(b) +} + +const ( + capabilityVersion1 = 0x19980330 // Version 1, 32-bit capabilities + capabilityVersion3 = 0x20080522 // Version 3 (replaced V2), 64-bit capabilities +) + +func GetCapabilities() CapData { + header := struct { + version uint32 + pid int32 + }{ + version: capabilityVersion3, + pid: 0, // Self + } + + // Check compatibility with version 3 + _, _, e := unix.Syscall(unix.SYS_CAPGET, uintptr(unsafe.Pointer(&header)), 0, 0) + if e != 0 { + header.version = capabilityVersion1 + } + + var data [2]capData32 + _, _, e = unix.Syscall(unix.SYS_CAPGET, uintptr(unsafe.Pointer(&header)), uintptr(unsafe.Pointer(&data)), 0) + if e != 0 { + // If this fails, there are invalid arguments + panic(unix.ErrnoName(e)) + } + + return CapData{ + Effective: uint32to64(data[1].effective, data[0].effective), + Permitted: uint32to64(data[1].permitted, data[0].permitted), + Inheritable: uint32to64(data[1].inheritable, data[0].inheritable), + } +} diff --git a/metricbeat/helper/socket/ptable.go b/metricbeat/helper/socket/ptable.go index 12d31ef22f7..e02196f8083 100644 --- a/metricbeat/helper/socket/ptable.go +++ b/metricbeat/helper/socket/ptable.go @@ -22,11 +22,11 @@ package socket import ( "strconv" "strings" - "unsafe" "github.com/joeshaw/multierror" "github.com/prometheus/procfs" - "golang.org/x/sys/unix" + + "github.com/elastic/beats/libbeat/common" ) // process tools @@ -165,56 +165,6 @@ const requiredCapabilities = uint64(0x0000000000080004) // sys_ptrace & dac_read // isPrivileged checks if this process has privileges to read sockets // of all users func isPrivileged(mountpoint string) bool { - capabilities := getCapabilities() - return (capabilities.effective & requiredCapabilities) > 0 -} - -type capData64 struct { - effective uint64 - permitted uint64 - inheritable uint64 -} - -type capData32 struct { - effective uint32 - permitted uint32 - inheritable uint32 -} - -func uint32to64(a, b uint32) uint64 { - return uint64(a)<<32 | uint64(b) -} - -const ( - capabilityVersion1 = 0x19980330 // Version 1, 32-bit capabilities - capabilityVersion3 = 0x20080522 // Version 3 (replaced V2), 64-bit capabilities -) - -func getCapabilities() capData64 { - header := struct { - version uint32 - pid int32 - }{ - version: capabilityVersion3, - pid: 0, // Self - } - - // Check compatibility with version 3 - _, _, e := unix.Syscall(unix.SYS_CAPGET, uintptr(unsafe.Pointer(&header)), 0, 0) - if e != 0 { - header.version = capabilityVersion1 - } - - var data [2]capData32 - _, _, e = unix.Syscall(unix.SYS_CAPGET, uintptr(unsafe.Pointer(&header)), uintptr(unsafe.Pointer(&data)), 0) - if e != 0 { - // If this fails, there are invalid arguments - panic(unix.ErrnoName(e)) - } - - var data64 capData64 - data64.effective = uint32to64(data[1].effective, data[0].effective) - data64.permitted = uint32to64(data[1].permitted, data[0].permitted) - data64.inheritable = uint32to64(data[1].inheritable, data[0].inheritable) - return data64 + capabilities := common.GetCapabilities() + return (capabilities.Effective & requiredCapabilities) > 0 } From f31754edc02a51164422f00c24804744777f6f20 Mon Sep 17 00:00:00 2001 From: Jaime Soriano Pastor Date: Fri, 3 May 2019 01:13:56 +0200 Subject: [PATCH 04/16] Change cap data abstractions --- libbeat/common/capabilities_linux.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/libbeat/common/capabilities_linux.go b/libbeat/common/capabilities_linux.go index dc15b355043..df2cc77a13e 100644 --- a/libbeat/common/capabilities_linux.go +++ b/libbeat/common/capabilities_linux.go @@ -31,12 +31,20 @@ type CapData struct { Inheritable uint64 } -type capData32 struct { +type capData32 [2]struct { effective uint32 permitted uint32 inheritable uint32 } +func (d capData32) to64() CapData { + return CapData{ + Effective: uint32to64(d[1].effective, d[0].effective), + Permitted: uint32to64(d[1].permitted, d[0].permitted), + Inheritable: uint32to64(d[1].inheritable, d[0].inheritable), + } +} + func uint32to64(a, b uint32) uint64 { return uint64(a)<<32 | uint64(b) } @@ -61,16 +69,12 @@ func GetCapabilities() CapData { header.version = capabilityVersion1 } - var data [2]capData32 + var data capData32 _, _, e = unix.Syscall(unix.SYS_CAPGET, uintptr(unsafe.Pointer(&header)), uintptr(unsafe.Pointer(&data)), 0) if e != 0 { // If this fails, there are invalid arguments panic(unix.ErrnoName(e)) } - return CapData{ - Effective: uint32to64(data[1].effective, data[0].effective), - Permitted: uint32to64(data[1].permitted, data[0].permitted), - Inheritable: uint32to64(data[1].inheritable, data[0].inheritable), - } + return data.to64() } From 342a80260457f98069901f966b9df01cdf98a2d8 Mon Sep 17 00:00:00 2001 From: Jaime Soriano Pastor Date: Fri, 3 May 2019 01:38:50 +0200 Subject: [PATCH 05/16] Document and refactor --- libbeat/common/capabilities_linux.go | 30 ++++++++++++++++++++-------- metricbeat/helper/socket/ptable.go | 2 +- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/libbeat/common/capabilities_linux.go b/libbeat/common/capabilities_linux.go index df2cc77a13e..ba0726e4a49 100644 --- a/libbeat/common/capabilities_linux.go +++ b/libbeat/common/capabilities_linux.go @@ -25,20 +25,31 @@ import ( "golang.org/x/sys/unix" ) -type CapData struct { - Effective uint64 - Permitted uint64 +// CapabilitiesData contains the capability sets of a process +type CapabilitiesData struct { + // Effective is the capability set used for permission checks + Effective uint64 + + // Permitted is the superset of effective capabilities that the thread may assume + Permitted uint64 + + // Inheritable is the set of capabilities inherited to child processes Inheritable uint64 } +// Check performs a permission check for a given capabilities set +func (d CapabilitiesData) Check(set uint64) bool { + return (d.Effective & set) > 0 +} + type capData32 [2]struct { effective uint32 permitted uint32 inheritable uint32 } -func (d capData32) to64() CapData { - return CapData{ +func (d capData32) to64() CapabilitiesData { + return CapabilitiesData{ Effective: uint32to64(d[1].effective, d[0].effective), Permitted: uint32to64(d[1].permitted, d[0].permitted), Inheritable: uint32to64(d[1].inheritable, d[0].inheritable), @@ -51,10 +62,12 @@ func uint32to64(a, b uint32) uint64 { const ( capabilityVersion1 = 0x19980330 // Version 1, 32-bit capabilities - capabilityVersion3 = 0x20080522 // Version 3 (replaced V2), 64-bit capabilities + capabilityVersion3 = 0x20080522 // Version 3, 64-bit capabilities (replaced version 2) ) -func GetCapabilities() CapData { +// GetCapabilities gets the capabilities of this process using system calls to avoid +// depending on procfs or library functions for permission checks +func GetCapabilities() CapabilitiesData { header := struct { version uint32 pid int32 @@ -72,7 +85,8 @@ func GetCapabilities() CapData { var data capData32 _, _, e = unix.Syscall(unix.SYS_CAPGET, uintptr(unsafe.Pointer(&header)), uintptr(unsafe.Pointer(&data)), 0) if e != 0 { - // If this fails, there are invalid arguments + // If this fails, there are invalid arguments, and all arguments are + // being created here. panic(unix.ErrnoName(e)) } diff --git a/metricbeat/helper/socket/ptable.go b/metricbeat/helper/socket/ptable.go index e02196f8083..f873f59a163 100644 --- a/metricbeat/helper/socket/ptable.go +++ b/metricbeat/helper/socket/ptable.go @@ -166,5 +166,5 @@ const requiredCapabilities = uint64(0x0000000000080004) // sys_ptrace & dac_read // of all users func isPrivileged(mountpoint string) bool { capabilities := common.GetCapabilities() - return (capabilities.Effective & requiredCapabilities) > 0 + return capabilities.Check(requiredCapabilities) } From e8115d77a4e693242329d6ab1984a2c3d9313fea Mon Sep 17 00:00:00 2001 From: Jaime Soriano Pastor Date: Fri, 3 May 2019 17:40:06 +0200 Subject: [PATCH 06/16] Add specific capabilities instead of full privileges --- metricbeat/docs/running-on-docker.asciidoc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/metricbeat/docs/running-on-docker.asciidoc b/metricbeat/docs/running-on-docker.asciidoc index dfa55d3ef63..43550ff8e32 100644 --- a/metricbeat/docs/running-on-docker.asciidoc +++ b/metricbeat/docs/running-on-docker.asciidoc @@ -19,7 +19,7 @@ docker run \ --mount type=bind,source=/sys/fs/cgroup,target=/hostfs/sys/fs/cgroup,readonly \ <2> --mount type=bind,source=/,target=/hostfs,readonly \ <3> --net=host \ <4> - --user root --privileged \ <5> + --user root --cap-add sys_ptrace --cap-add dac_read_search \ <5> {dockerimage} -e -system.hostfs=/hostfs ---- @@ -44,8 +44,9 @@ flag. This is due to Linux namespacing; simply bind mounting the host's `/proc` to `/hostfs/proc` is not sufficient. <5> The <> needs to read files from `/proc` that belong to multiple users. In order to access these -files Metricbeat must be run as root in a privileged container. These options -are not needed if this metricset is not used. +files Metricbeat must be run as root in a container with `sys_ptrace` and +`dac_read_search` capabilities. These options are not needed if this metricset +is not used. NOTE: The special filesystems +/proc+ and +/sys+ are only available if the host system is running Linux. Attempts to bind-mount these filesystems will From 7a356309fa72cbd41062e48889400a49b837ff45 Mon Sep 17 00:00:00 2001 From: Jaime Soriano Pastor Date: Fri, 3 May 2019 18:25:12 +0200 Subject: [PATCH 07/16] Use sysinfo instead of syscall for capabilities --- libbeat/common/capabilities_linux.go | 87 ++++++++---------------- metricbeat/helper/socket/ptable.go | 20 ++---- metricbeat/helper/socket/ptable_linux.go | 36 ++++++++++ metricbeat/helper/socket/ptable_other.go | 30 ++++++++ 4 files changed, 99 insertions(+), 74 deletions(-) create mode 100644 metricbeat/helper/socket/ptable_linux.go create mode 100644 metricbeat/helper/socket/ptable_other.go diff --git a/libbeat/common/capabilities_linux.go b/libbeat/common/capabilities_linux.go index ba0726e4a49..0dd3fc9d7fa 100644 --- a/libbeat/common/capabilities_linux.go +++ b/libbeat/common/capabilities_linux.go @@ -20,75 +20,42 @@ package common import ( - "unsafe" - - "golang.org/x/sys/unix" + "github.com/elastic/go-sysinfo" + "github.com/elastic/go-sysinfo/types" + "github.com/pkg/errors" ) -// CapabilitiesData contains the capability sets of a process -type CapabilitiesData struct { - // Effective is the capability set used for permission checks - Effective uint64 - - // Permitted is the superset of effective capabilities that the thread may assume - Permitted uint64 - - // Inheritable is the set of capabilities inherited to child processes - Inheritable uint64 -} +// Capabilities contains the capability sets of a process +type Capabilities types.CapabilityInfo // Check performs a permission check for a given capabilities set -func (d CapabilitiesData) Check(set uint64) bool { - return (d.Effective & set) > 0 -} - -type capData32 [2]struct { - effective uint32 - permitted uint32 - inheritable uint32 -} - -func (d capData32) to64() CapabilitiesData { - return CapabilitiesData{ - Effective: uint32to64(d[1].effective, d[0].effective), - Permitted: uint32to64(d[1].permitted, d[0].permitted), - Inheritable: uint32to64(d[1].inheritable, d[0].inheritable), +func (c Capabilities) Check(set []string) bool { + for _, capability := range set { + found := false + for _, effective := range c.Effective { + if capability == effective { + found = true + break + } + } + if !found { + return false + } } + return true } -func uint32to64(a, b uint32) uint64 { - return uint64(a)<<32 | uint64(b) -} - -const ( - capabilityVersion1 = 0x19980330 // Version 1, 32-bit capabilities - capabilityVersion3 = 0x20080522 // Version 3, 64-bit capabilities (replaced version 2) -) - -// GetCapabilities gets the capabilities of this process using system calls to avoid -// depending on procfs or library functions for permission checks -func GetCapabilities() CapabilitiesData { - header := struct { - version uint32 - pid int32 - }{ - version: capabilityVersion3, - pid: 0, // Self - } - - // Check compatibility with version 3 - _, _, e := unix.Syscall(unix.SYS_CAPGET, uintptr(unsafe.Pointer(&header)), 0, 0) - if e != 0 { - header.version = capabilityVersion1 +// GetCapabilities gets the capabilities of this process +func GetCapabilities() (Capabilities, error) { + p, err := sysinfo.Self() + if err != nil { + return Capabilities{}, errors.Wrap(err, "failed to read self process information") } - var data capData32 - _, _, e = unix.Syscall(unix.SYS_CAPGET, uintptr(unsafe.Pointer(&header)), uintptr(unsafe.Pointer(&data)), 0) - if e != 0 { - // If this fails, there are invalid arguments, and all arguments are - // being created here. - panic(unix.ErrnoName(e)) + if c, ok := p.(types.Capabilities); ok { + capabilities, err := c.Capabilities() + return Capabilities(*capabilities), errors.Wrap(err, "failed to read process capabilities") } - return data.to64() + return Capabilities{}, errors.New("capabilities not available") } diff --git a/metricbeat/helper/socket/ptable.go b/metricbeat/helper/socket/ptable.go index f873f59a163..5463deed917 100644 --- a/metricbeat/helper/socket/ptable.go +++ b/metricbeat/helper/socket/ptable.go @@ -15,8 +15,6 @@ // specific language governing permissions and limitations // under the License. -// +build linux - package socket import ( @@ -25,8 +23,6 @@ import ( "github.com/joeshaw/multierror" "github.com/prometheus/procfs" - - "github.com/elastic/beats/libbeat/common" ) // process tools @@ -61,7 +57,12 @@ func NewProcTable(mountpoint string) (*ProcTable, error) { return nil, err } - p := &ProcTable{fs: fs, privileged: isPrivileged(mountpoint)} + privileged, err := isPrivileged() + if err != nil { + return nil, err + } + + p := &ProcTable{fs: fs, privileged: privileged} p.Refresh() return p, nil } @@ -159,12 +160,3 @@ func socketInodes(p *procfs.Proc) ([]uint32, error) { func (t *ProcTable) ProcessBySocketInode(inode uint32) *Proc { return t.inodes[inode] } - -const requiredCapabilities = uint64(0x0000000000080004) // sys_ptrace & dac_read_search - -// isPrivileged checks if this process has privileges to read sockets -// of all users -func isPrivileged(mountpoint string) bool { - capabilities := common.GetCapabilities() - return capabilities.Check(requiredCapabilities) -} diff --git a/metricbeat/helper/socket/ptable_linux.go b/metricbeat/helper/socket/ptable_linux.go new file mode 100644 index 00000000000..091efe60bfe --- /dev/null +++ b/metricbeat/helper/socket/ptable_linux.go @@ -0,0 +1,36 @@ +// 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. + +// +build linux + +package socket + +import ( + "github.com/elastic/beats/libbeat/common" +) + +var requiredCapabilities = []string{"sys_ptrace", "dac_read_search"} + +// isPrivileged checks if this process has privileges to read sockets +// of all users +func isPrivileged() (bool, error) { + capabilities, err := common.GetCapabilities() + if err != nil { + return false, err + } + return capabilities.Check(requiredCapabilities), nil +} diff --git a/metricbeat/helper/socket/ptable_other.go b/metricbeat/helper/socket/ptable_other.go new file mode 100644 index 00000000000..ee91e10345e --- /dev/null +++ b/metricbeat/helper/socket/ptable_other.go @@ -0,0 +1,30 @@ +// 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. + +// +build !linux + +package socket + +import ( + "os" +) + +// isPrivileged checks if this process has privileges to read sockets +// of all users +func isPrivileged() (bool, error) { + return os.Geteuid() == 0, nil +} From 8c81f67ac6f8252b3facc22e9e70dc1979544d36 Mon Sep 17 00:00:00 2001 From: Jaime Soriano Pastor Date: Fri, 3 May 2019 19:02:13 +0200 Subject: [PATCH 08/16] When running non-privileged, get info from all processes owned by current user --- metricbeat/helper/socket/ptable.go | 49 ++++++++++++++++------- metricbeat/module/system/socket/socket.go | 7 ++-- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/metricbeat/helper/socket/ptable.go b/metricbeat/helper/socket/ptable.go index 5463deed917..3f8cd2bba11 100644 --- a/metricbeat/helper/socket/ptable.go +++ b/metricbeat/helper/socket/ptable.go @@ -1,5 +1,3 @@ -// 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 @@ -18,8 +16,10 @@ package socket import ( + "os" "strconv" "strings" + "syscall" "github.com/joeshaw/multierror" "github.com/prometheus/procfs" @@ -78,19 +78,9 @@ func (t *ProcTable) Privileged() bool { // If running as non-root, only information from the current process will be // collected. func (t *ProcTable) Refresh() error { - var err error - var procs []procfs.Proc - if t.privileged { - procs, err = t.fs.AllProcs() - if err != nil { - return err - } - } else { - proc, err := t.fs.Self() - if err != nil { - return err - } - procs = append(procs, proc) + procs, err := t.accessibleProcs() + if err != nil { + return err } var errs multierror.Errors @@ -134,6 +124,35 @@ func (t *ProcTable) Refresh() error { return errs.Err() } +func (t *ProcTable) accessibleProcs() ([]procfs.Proc, error) { + procs, err := t.fs.AllProcs() + if err != nil { + return nil, err + } + if t.privileged { + return procs, nil + } + + // Filter out not owned processes + k := 0 + euid := uint32(os.Geteuid()) + for i := 0; i < len(procs); i++ { + p := t.fs.Path(strconv.Itoa(procs[i].PID)) + info, err := os.Stat(p) + if err != nil { + continue + } + stat, ok := info.Sys().(*syscall.Stat_t) + if !ok || stat.Uid != euid { + continue + } + procs[k] = procs[i] + k++ + } + + return procs[:k], nil +} + func socketInodes(p *procfs.Proc) ([]uint32, error) { fds, err := p.FileDescriptorTargets() if err != nil { diff --git a/metricbeat/module/system/socket/socket.go b/metricbeat/module/system/socket/socket.go index 93c4103fba5..fe90231683c 100644 --- a/metricbeat/module/system/socket/socket.go +++ b/metricbeat/module/system/socket/socket.go @@ -78,9 +78,8 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { return nil, err } if !ptable.Privileged() { - logp.Info("socket process info will only be available for metricbeat process " + - "because it is running without enough privileges " + - "(sys_ptrace and dac_read_search capabilities required)") + logp.Info("socket process info will only be available for processes owned by the %v user "+ + "because this Beat is not running with enough privileges", os.Geteuid()) } m := &MetricSet{ @@ -213,7 +212,7 @@ func (m *MetricSet) enrichConnectionData(c *connection) { c.Command = proc.Command c.CmdLine = proc.CmdLine c.Args = proc.Args - } else if m.euid == 0 { + } else if m.ptable.Privileged() { if c.Inode == 0 { c.ProcessError = fmt.Errorf("process has exited. inode=%v, tcp_state=%v", c.Inode, c.State) From fd291bbf1772291050a1b171256c5e0571151bdc Mon Sep 17 00:00:00 2001 From: Jaime Soriano Pastor Date: Fri, 3 May 2019 19:05:12 +0200 Subject: [PATCH 09/16] Add changelog --- CHANGELOG.next.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 11c0a4f98e0..d7c07db9689 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -104,6 +104,8 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Change diskio metrics retrieval method (only for Windows) from wmi query to DeviceIOControl function using the IOCTL_DISK_PERFORMANCE control code {pull}11635[11635] - Call GetMetricData api per region instead of per instance. {issue}11820[11820] {pull}11882[11882] - Update documentation with cloudwatch:ListMetrics permission. {pull}11987[11987] +- Check permissions in system socket metricset based on capabilities. {pull}12039[12039] +- Get process information from sockets owned by current user when system socket metricset is run without privileges. {pull}12039[12039] *Packetbeat* From 35989a494e13bedb5efb7b5cab4e48ddef770d2c Mon Sep 17 00:00:00 2001 From: Jaime Soriano Pastor Date: Fri, 3 May 2019 19:06:55 +0200 Subject: [PATCH 10/16] Revert unneeded changes to seccomp --- libbeat/common/seccomp/policy_linux_386.go | 1 - libbeat/common/seccomp/policy_linux_amd64.go | 1 - 2 files changed, 2 deletions(-) diff --git a/libbeat/common/seccomp/policy_linux_386.go b/libbeat/common/seccomp/policy_linux_386.go index 66d0cd99409..043ccf7d8ae 100644 --- a/libbeat/common/seccomp/policy_linux_386.go +++ b/libbeat/common/seccomp/policy_linux_386.go @@ -31,7 +31,6 @@ func init() { "_llseek", "access", "brk", - "capget", "clock_gettime", "clone", "close", diff --git a/libbeat/common/seccomp/policy_linux_amd64.go b/libbeat/common/seccomp/policy_linux_amd64.go index 9d45a444642..a131e7f3c34 100644 --- a/libbeat/common/seccomp/policy_linux_amd64.go +++ b/libbeat/common/seccomp/policy_linux_amd64.go @@ -34,7 +34,6 @@ func init() { "arch_prctl", "bind", "brk", - "capget", "clock_gettime", "clone", "close", From b50265a3d7deb4ac0cedcb69749f1595c1af2121 Mon Sep 17 00:00:00 2001 From: Jaime Soriano Pastor Date: Fri, 3 May 2019 19:12:33 +0200 Subject: [PATCH 11/16] Add lines removed by mistake --- metricbeat/helper/socket/ptable.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/metricbeat/helper/socket/ptable.go b/metricbeat/helper/socket/ptable.go index 3f8cd2bba11..f00afc6ba49 100644 --- a/metricbeat/helper/socket/ptable.go +++ b/metricbeat/helper/socket/ptable.go @@ -1,3 +1,5 @@ +// 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 From f3ff33874250870c66532ec9aea3d98abad55829 Mon Sep 17 00:00:00 2001 From: Jaime Soriano Pastor Date: Fri, 3 May 2019 19:18:58 +0200 Subject: [PATCH 12/16] Update metricset description too --- metricbeat/module/system/socket/_meta/docs.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metricbeat/module/system/socket/_meta/docs.asciidoc b/metricbeat/module/system/socket/_meta/docs.asciidoc index 7dd1697ba41..791701e6dae 100644 --- a/metricbeat/module/system/socket/_meta/docs.asciidoc +++ b/metricbeat/module/system/socket/_meta/docs.asciidoc @@ -20,8 +20,8 @@ metricbeat.modules: `period` value than the other metricsets. The metricset reports the process that has the socket open. In order to provide -this information, Metricbeat must be running as root. Root access is also -required to read the file descriptor information of other processes. +this information for all processes in the system, Metricbeat must be run with +`sys_ptrace` and `dac_read_search` Linux capabilities or with full privileges. [float] === Configuration From ebdb20e9ddfc5d5607607026aacf1a2d5e535b37 Mon Sep 17 00:00:00 2001 From: Jaime Soriano Pastor Date: Fri, 3 May 2019 19:28:58 +0200 Subject: [PATCH 13/16] Fix goimports --- libbeat/common/capabilities_linux.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libbeat/common/capabilities_linux.go b/libbeat/common/capabilities_linux.go index 0dd3fc9d7fa..e05cf99fb72 100644 --- a/libbeat/common/capabilities_linux.go +++ b/libbeat/common/capabilities_linux.go @@ -20,9 +20,10 @@ package common import ( + "github.com/pkg/errors" + "github.com/elastic/go-sysinfo" "github.com/elastic/go-sysinfo/types" - "github.com/pkg/errors" ) // Capabilities contains the capability sets of a process From 7ac29c50ab3ecfee525143559836a94061b69df1 Mon Sep 17 00:00:00 2001 From: Jaime Soriano Pastor Date: Mon, 6 May 2019 16:49:49 +0200 Subject: [PATCH 14/16] Rephrase documentation changes after review feedback --- metricbeat/docs/running-on-docker.asciidoc | 19 +++++++++++++------ .../module/system/socket/_meta/docs.asciidoc | 8 +++++--- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/metricbeat/docs/running-on-docker.asciidoc b/metricbeat/docs/running-on-docker.asciidoc index 43550ff8e32..97736a319c4 100644 --- a/metricbeat/docs/running-on-docker.asciidoc +++ b/metricbeat/docs/running-on-docker.asciidoc @@ -19,7 +19,6 @@ docker run \ --mount type=bind,source=/sys/fs/cgroup,target=/hostfs/sys/fs/cgroup,readonly \ <2> --mount type=bind,source=/,target=/hostfs,readonly \ <3> --net=host \ <4> - --user root --cap-add sys_ptrace --cap-add dac_read_search \ <5> {dockerimage} -e -system.hostfs=/hostfs ---- @@ -42,17 +41,25 @@ of the container. They can be mounted at any location. to make this file contain the host's network devices is to use the `--net=host` flag. This is due to Linux namespacing; simply bind mounting the host's `/proc` to `/hostfs/proc` is not sufficient. -<5> The <> needs to -read files from `/proc` that belong to multiple users. In order to access these -files Metricbeat must be run as root in a container with `sys_ptrace` and -`dac_read_search` capabilities. These options are not needed if this metricset -is not used. NOTE: The special filesystems +/proc+ and +/sys+ are only available if the host system is running Linux. Attempts to bind-mount these filesystems will fail on Windows and MacOS. + +If the <> +is being used on Linux, more privileges will need to be granted to Metricbeat. +This metricset reads files from `/proc` that are an interface to internal +objects owned by other users. The capabilities needed to read all these files +(`sys_ptrace` and `dac_read_search`) are disabled by default on Docker. To +grant these permissions these flags are needed too: + +["source","sh",subs="attributes"] +---- +--user root --cap-add sys_ptrace --cap-add dac_read_search +---- [float] + [[monitoring-service]] ==== Monitor a service in another container diff --git a/metricbeat/module/system/socket/_meta/docs.asciidoc b/metricbeat/module/system/socket/_meta/docs.asciidoc index 791701e6dae..e1c8a483bce 100644 --- a/metricbeat/module/system/socket/_meta/docs.asciidoc +++ b/metricbeat/module/system/socket/_meta/docs.asciidoc @@ -19,9 +19,11 @@ metricbeat.modules: <1> You can configure the `socket` metricset separately to specify a different `period` value than the other metricsets. -The metricset reports the process that has the socket open. In order to provide -this information for all processes in the system, Metricbeat must be run with -`sys_ptrace` and `dac_read_search` Linux capabilities or with full privileges. +The metricset reports the process that has the socket open. To provide this +information on Linux for all processes, Metricbeat must be run with +`sys_ptrace` and `dac_read_search` capabilities. These permissions are usually +granted when running as root, but they can and may need to be explictly enabled +when running metricbeat inside a container. [float] === Configuration From 3f29501d6574cde57eff63719308b2384a83e0ab Mon Sep 17 00:00:00 2001 From: Jaime Soriano Pastor Date: Mon, 6 May 2019 16:52:54 +0200 Subject: [PATCH 15/16] Typos --- metricbeat/module/system/socket/_meta/docs.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metricbeat/module/system/socket/_meta/docs.asciidoc b/metricbeat/module/system/socket/_meta/docs.asciidoc index e1c8a483bce..e705b8ea1b8 100644 --- a/metricbeat/module/system/socket/_meta/docs.asciidoc +++ b/metricbeat/module/system/socket/_meta/docs.asciidoc @@ -22,8 +22,8 @@ metricbeat.modules: The metricset reports the process that has the socket open. To provide this information on Linux for all processes, Metricbeat must be run with `sys_ptrace` and `dac_read_search` capabilities. These permissions are usually -granted when running as root, but they can and may need to be explictly enabled -when running metricbeat inside a container. +granted when running as root, but they can and may need to be explictly added +when running Metricbeat inside a container. [float] === Configuration From 0cccca3fde204cded02089cfdc16367ab15cee67 Mon Sep 17 00:00:00 2001 From: Jaime Soriano Pastor Date: Tue, 7 May 2019 11:07:38 +0200 Subject: [PATCH 16/16] Add build restrictions for windows --- metricbeat/helper/socket/ptable.go | 2 ++ metricbeat/helper/socket/ptable_other.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/metricbeat/helper/socket/ptable.go b/metricbeat/helper/socket/ptable.go index f00afc6ba49..af22156849d 100644 --- a/metricbeat/helper/socket/ptable.go +++ b/metricbeat/helper/socket/ptable.go @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +// +build !windows + package socket import ( diff --git a/metricbeat/helper/socket/ptable_other.go b/metricbeat/helper/socket/ptable_other.go index ee91e10345e..447ba24448c 100644 --- a/metricbeat/helper/socket/ptable_other.go +++ b/metricbeat/helper/socket/ptable_other.go @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -// +build !linux +// +build !linux,!windows package socket