Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Auditbeat] Add user information to processes #9963

Merged
merged 14 commits into from
Jan 29, 2019
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d
- Add system module. {pull}9546[9546]
- Add `user.id` (UID) and `user.name` for ECS. {pull}10195[10195]
- Add `group.id` (GID) and `group.name` for ECS. {pull}10195[10195]
- System module `process` dataset: Add user information to processes. {pull}9963[9963]

*Filebeat*

Expand Down
35 changes: 35 additions & 0 deletions auditbeat/_meta/fields.common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,38 @@
type: keyword
example: s0
description: The object's SELinux level.

- name: user
type: group
description: User information.
fields:

- name: effective
type: group
description: Effective user information.
fields:
- name: id
type: keyword
description: Effective user ID.
- name: group
type: group
description: Effective group information.
fields:
- name: id
type: keyword
description: Effective group ID.

- name: saved
type: group
description: Saved user information.
fields:
- name: id
type: keyword
description: Saved user ID.
- name: group
type: group
description: Saved group information.
fields:
- name: id
type: keyword
description: Saved group ID.
66 changes: 66 additions & 0 deletions auditbeat/docs/fields.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -2671,6 +2671,72 @@ The object's SELinux level.

--

[float]
== user fields

User information.


[float]
== effective fields

Effective user information.


*`user.effective.id`*::
+
--
type: keyword

Effective user ID.

--

[float]
== group fields

Effective group information.


*`user.effective.group.id`*::
+
--
type: keyword

Effective group ID.

--

[float]
== saved fields

Saved user information.


*`user.saved.id`*::
+
--
type: keyword

Saved user ID.

--

[float]
== group fields

Saved group information.


*`user.saved.group.id`*::
+
--
type: keyword

Saved group ID.

--

[[exported-fields-docker-processor]]
== Docker fields

Expand Down
2 changes: 1 addition & 1 deletion auditbeat/include/fields.go

Large diffs are not rendered by default.

39 changes: 29 additions & 10 deletions x-pack/auditbeat/module/system/process/_meta/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,44 @@
"name": "host.example.com"
},
"event": {
"action": "existing_process",
"action": "process_started",
"dataset": "process",
"id": "5795d53b-f7c2-463c-9c04-f316ae876d51",
"module": "system",
"kind": "state"
"kind": "event",
"module": "system"
},
"message": "Process zsh (PID: 2363) is RUNNING",
"message": "Process zsh (PID: 12936) by user elastic STARTED",
"process": {
"args": [
"/usr/bin/zsh"
"zsh"
],
"executable": "/bin/zsh",
"name": "zsh",
"pid": 2363,
"ppid": 2362,
"start": "2018-12-10T16:36:25.21Z",
"working_directory": "/home/elastic"
"pid": 12936,
"ppid": 3858,
"start": "2019-01-21T15:01:54.782288Z",
"working_directory": "/Users/elastic"
},
"service": {
"type": "system"
},
"user": {
"effective": {
"group": {
"id": "1000"
},
"id": "1000"
},
"group": {
"id": "1000",
"name": "elastic"
},
"id": "1000",
"name": "elastic",
"saved": {
"group": {
"id": "1000"
},
"id": "1000"
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

}
}
144 changes: 82 additions & 62 deletions x-pack/auditbeat/module/system/process/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ package process
import (
"fmt"
"os"
"runtime"
"os/user"
"strconv"
"time"

Expand All @@ -19,7 +19,6 @@ import (
"github.com/elastic/beats/libbeat/common"
"github.com/elastic/beats/libbeat/common/cfgwarn"
"github.com/elastic/beats/libbeat/logp"
"github.com/elastic/beats/libbeat/metric/system/process"
"github.com/elastic/beats/metricbeat/mb"
"github.com/elastic/beats/x-pack/auditbeat/cache"
"github.com/elastic/go-sysinfo"
Expand Down Expand Up @@ -84,8 +83,11 @@ type MetricSet struct {

// Process represents information about a process.
type Process struct {
Info types.ProcessInfo
Error error
Info types.ProcessInfo
UserInfo *types.UserInfo
User *user.User
Group *user.Group
Error error
}

// Hash creates a hash for Process.
Expand Down Expand Up @@ -273,13 +275,42 @@ func processEvent(process *Process, eventType string, action eventAction) mb.Eve
},
}

if process.UserInfo != nil {
putIfNotEmpty(&event.RootFields, "user.id", process.UserInfo.UID)
putIfNotEmpty(&event.RootFields, "user.group.id", process.UserInfo.GID)

putIfNotEmpty(&event.RootFields, "user.effective.id", process.UserInfo.EUID)
putIfNotEmpty(&event.RootFields, "user.effective.group.id", process.UserInfo.EGID)

putIfNotEmpty(&event.RootFields, "user.saved.id", process.UserInfo.SUID)
putIfNotEmpty(&event.RootFields, "user.saved.group.id", process.UserInfo.SGID)
}

if process.User != nil {
if process.User.Username != "" {
event.RootFields.Put("user.name", process.User.Username)
} else if process.User.Name != "" {
event.RootFields.Put("user.name", process.User.Name)
}
}

if process.Group != nil {
event.RootFields.Put("user.group.name", process.Group.Name)
}

if process.Error != nil {
event.RootFields.Put("error.message", process.Error.Error())
}

return event
}

func putIfNotEmpty(mapstr *common.MapStr, key string, value string) {
if value != "" {
mapstr.Put(key, value)
}
}

func processMessage(process *Process, action eventAction) string {
if process.Error != nil {
return fmt.Sprintf("ERROR for PID %d: %v", process.Info.PID, process.Error)
Expand All @@ -295,8 +326,13 @@ func processMessage(process *Process, action eventAction) string {
actionString = "is RUNNING"
}

return fmt.Sprintf("Process %v (PID: %d) %v",
process.Info.Name, process.Info.PID, actionString)
var userString string
if process.User != nil {
userString = fmt.Sprintf(" by user %v", process.User.Username)
}

return fmt.Sprintf("Process %v (PID: %d)%v %v",
process.Info.Name, process.Info.PID, userString, actionString)
}

func convertToCacheable(processes []*Process) []cache.Cacheable {
Expand All @@ -310,82 +346,66 @@ func convertToCacheable(processes []*Process) []cache.Cacheable {
}

func (ms *MetricSet) getProcesses() ([]*Process, error) {
// TODO: Implement Processes() in go-sysinfo
// e.g. https://github.com/elastic/go-sysinfo/blob/master/providers/darwin/process_darwin_amd64.go#L41
pids, err := process.Pids()
var processes []*Process

sysinfoProcs, err := sysinfo.Processes()
if err != nil {
return nil, errors.Wrap(err, "failed to fetch the list of PIDs")
return nil, errors.Wrap(err, "failed to fetch processes")
}

var processes []*Process
for _, pid := range pids {
for _, sysinfoProc := range sysinfoProcs {
var process *Process

sysinfoProc, err := sysinfo.Process(pid)
pInfo, err := sysinfoProc.Info()
if err != nil {
if os.IsNotExist(err) {
// Skip - process probably just terminated since our call
// to Pids()
// Skip - process probably just terminated since our call to Processes().
continue
}

if runtime.GOOS == "windows" && (pid == 0 || os.IsPermission(err)) {
// On Windows, the call to Process() can fail if Auditbeat does not have
// the necessary access rights, while trying to open the System Process (PID: 0)
// will always fail.
if os.Geteuid() != 0 && os.IsPermission(err) {
// Running as non-root, permission issues when trying to access
// other user's private process information are expected.

if !ms.suppressPermissionWarnings {
ms.log.Warnf("Failed to load process information for PID %d as non-root user. "+
"Will suppress further errors of this kind. Error: %v", sysinfoProc.PID(), err)

// Only warn once at the start of Auditbeat.
ms.suppressPermissionWarnings = true
}

continue
}

// Record what we can and continue
process = &Process{
Info: types.ProcessInfo{
PID: pid,
},
Error: errors.Wrapf(err, "failed to load process with PID %d", pid),
Info: pInfo,
Error: errors.Wrapf(err, "failed to load process information for PID %d", sysinfoProc.PID()),
}
process.Info.PID = sysinfoProc.PID() // in case pInfo did not contain it
} else {
pInfo, err := sysinfoProc.Info()
if err == nil {
process = &Process{
Info: pInfo,
}
} else {
if os.IsNotExist(err) {
// Skip - process probably just terminated since our call
// to Pids()
continue
}

if os.Geteuid() != 0 {
if os.IsPermission(err) || runtime.GOOS == "darwin" {
/*
Running as non-root, permission issues when trying to access other user's private
process information are expected.

Unfortunately, for darwin os.IsPermission() does not
work because it is a custom error created using errors.New() in
getProcTaskAllInfo() in go-sysinfo/providers/darwin/process_darwin_amd64.go

TODO: Fix go-sysinfo to have better error for darwin.
*/
if !ms.suppressPermissionWarnings {
ms.log.Warnf("Failed to load process information for PID %d as non-root user. "+
"Will suppress further errors of this kind. Error: %v", pid, err)
process = &Process{
Info: pInfo,
}
}

// Only warn once at the start of Auditbeat.
ms.suppressPermissionWarnings = true
}
userInfo, err := sysinfoProc.User()
if err != nil {
if process.Error == nil {
process.Error = errors.Wrapf(err, "failed to load user for PID %d", sysinfoProc.PID())
}
} else {
process.UserInfo = &userInfo

//continue
}
}
goUser, err := user.LookupId(userInfo.UID)
if err == nil {
process.User = goUser
}

// Record what we can and continue
process = &Process{
Info: pInfo,
Error: errors.Wrapf(err, "failed to load process information for PID %d", pid),
}
process.Info.PID = pid // in case pInfo did not contain it
group, err := user.LookupGroupId(userInfo.GID)
if err == nil {
process.Group = group
}
}

Expand Down
Loading