diff --git a/providers/darwin/process_darwin_amd64.go b/providers/darwin/process_darwin_amd64.go index 24e3c32f..66dec86a 100644 --- a/providers/darwin/process_darwin_amd64.go +++ b/providers/darwin/process_darwin_amd64.go @@ -28,6 +28,7 @@ import ( "bytes" "encoding/binary" "os" + "strconv" "time" "unsafe" @@ -87,6 +88,22 @@ func (p *process) Info() (types.ProcessInfo, error) { }, nil } +func (p *process) User() (types.UserInfo, error) { + var task procTaskAllInfo + if err := getProcTaskAllInfo(p.pid, &task); err != nil { + return types.UserInfo{}, err + } + + return types.UserInfo{ + UID: strconv.Itoa(int(task.Pbsd.Pbi_ruid)), + EUID: strconv.Itoa(int(task.Pbsd.Pbi_uid)), + SUID: strconv.Itoa(int(task.Pbsd.Pbi_svuid)), + GID: strconv.Itoa(int(task.Pbsd.Pbi_rgid)), + EGID: strconv.Itoa(int(task.Pbsd.Pbi_gid)), + SGID: strconv.Itoa(int(task.Pbsd.Pbi_svgid)), + }, nil +} + func (p *process) Environment() (map[string]string, error) { return p.env, nil } diff --git a/providers/linux/process_linux.go b/providers/linux/process_linux.go index 4c6c713b..efe04f5e 100644 --- a/providers/linux/process_linux.go +++ b/providers/linux/process_linux.go @@ -22,6 +22,7 @@ import ( "io/ioutil" "os" "strconv" + "strings" "time" "github.com/prometheus/procfs" @@ -204,6 +205,37 @@ func (p *process) Capabilities() (*types.CapabilityInfo, error) { return readCapabilities(content) } +func (p *process) User() (types.UserInfo, error) { + content, err := ioutil.ReadFile(p.path("status")) + if err != nil { + return types.UserInfo{}, err + } + + var user types.UserInfo + err = parseKeyValue(content, ":", func(key, value []byte) error { + // See proc(5) for the format of /proc/[pid]/status + switch string(key) { + case "Uid": + ids := strings.Split(string(value), "\t") + if len(ids) >= 3 { + user.UID = ids[0] + user.EUID = ids[1] + user.SUID = ids[2] + } + case "Gid": + ids := strings.Split(string(value), "\t") + if len(ids) >= 3 { + user.GID = ids[0] + user.EGID = ids[1] + user.SGID = ids[2] + } + } + return nil + }) + + return user, nil +} + func ticksToDuration(ticks uint64) time.Duration { seconds := float64(ticks) / float64(userHz) * float64(time.Second) return time.Duration(int64(seconds)) diff --git a/providers/windows/process_windows.go b/providers/windows/process_windows.go index 4597d9a5..d4765a94 100644 --- a/providers/windows/process_windows.go +++ b/providers/windows/process_windows.go @@ -26,6 +26,7 @@ import ( "unsafe" "github.com/pkg/errors" + syswin "golang.org/x/sys/windows" windows "github.com/elastic/go-windows" @@ -241,6 +242,45 @@ func (p *process) Info() (types.ProcessInfo, error) { return p.info, nil } +func (p *process) User() (types.UserInfo, error) { + handle, err := p.open() + if err != nil { + return types.UserInfo{}, errors.Wrap(err, "OpenProcess failed") + } + defer syscall.CloseHandle(handle) + + var accessToken syswin.Token + err = syswin.OpenProcessToken(syswin.Handle(handle), syscall.TOKEN_QUERY, &accessToken) + if err != nil { + return types.UserInfo{}, errors.Wrap(err, "OpenProcessToken failed") + } + defer accessToken.Close() + + tokenUser, err := accessToken.GetTokenUser() + if err != nil { + return types.UserInfo{}, errors.Wrap(err, "GetTokenUser failed") + } + + sid, err := tokenUser.User.Sid.String() + if err != nil { + return types.UserInfo{}, errors.Wrap(err, "failed to look up user SID") + } + + tokenGroup, err := accessToken.GetTokenPrimaryGroup() + if err != nil { + return types.UserInfo{}, errors.Wrap(err, "GetTokenPrimaryGroup failed") + } + gsid, err := tokenGroup.PrimaryGroup.String() + if err != nil { + return types.UserInfo{}, errors.Wrap(err, "failed to look up primary group SID") + } + + return types.UserInfo{ + UID: sid, + GID: gsid, + }, nil +} + func (p *process) Memory() (types.MemoryInfo, error) { handle, err := p.open() if err != nil { diff --git a/system_test.go b/system_test.go index 31fed9a7..7b135b22 100644 --- a/system_test.go +++ b/system_test.go @@ -20,7 +20,9 @@ package sysinfo import ( "encoding/json" "os" + osUser "os/user" "runtime" + "strconv" "strings" "testing" "time" @@ -130,6 +132,24 @@ func TestSelf(t *testing.T) { } assert.WithinDuration(t, info.StartTime, time.Now(), 10*time.Second) + user, err := process.User() + if err != nil { + t.Fatal(err) + } + output["process.user"] = user + + currentUser, err := osUser.Current() + if err != nil { + t.Fatal(err) + } + assert.EqualValues(t, currentUser.Uid, user.UID) + assert.EqualValues(t, currentUser.Gid, user.GID) + + if runtime.GOOS != "windows" { + assert.EqualValues(t, strconv.Itoa(os.Geteuid()), user.EUID) + assert.EqualValues(t, strconv.Itoa(os.Getegid()), user.EGID) + } + if v, ok := process.(types.Environment); ok { expectedEnv := map[string]string{} for _, keyValue := range os.Environ() { diff --git a/types/process.go b/types/process.go index fce67bad..a12b344a 100644 --- a/types/process.go +++ b/types/process.go @@ -23,6 +23,7 @@ type Process interface { CPUTimer Info() (ProcessInfo, error) Memory() (MemoryInfo, error) + User() (UserInfo, error) } type ProcessInfo struct { @@ -35,6 +36,38 @@ type ProcessInfo struct { StartTime time.Time `json:"start_time"` } +// UserInfo contains information about the UID and GID +// values of a process. +type UserInfo struct { + // UID is the user ID. + // On Linux and Darwin (macOS) this is the real user ID. + // On Windows, this is the security identifier (SID) of the + // user account of the process access token. + UID string `json:"uid"` + + // On Linux and Darwin (macOS) this is the effective user ID. + // On Windows, this is empty. + EUID string `json:"euid"` + + // On Linux and Darwin (macOS) this is the saved user ID. + // On Windows, this is empty. + SUID string `json:"suid"` + + // GID is the primary group ID. + // On Linux and Darwin (macOS) this is the real group ID. + // On Windows, this is the security identifier (SID) of the + // primary group of the process access token. + GID string `json:"gid"` + + // On Linux and Darwin (macOS) this is the effective group ID. + // On Windows, this is empty. + EGID string `json:"egid"` + + // On Linux and Darwin (macOS) this is the saved group ID. + // On Windows, this is empty. + SGID string `json:"sgid"` +} + type Environment interface { Environment() (map[string]string, error) }