Skip to content

Commit

Permalink
Add user information to process (#34)
Browse files Browse the repository at this point in the history
Adds a `User()` function to `Process` and implements it for all providers.

For Linux and Darwin (macOS) this retrieves the real, effective, and saved user and group IDs. For Windows, this retrieves the user and primary group SID.

Closes #10.
  • Loading branch information
Christoph Wurm authored Jan 3, 2019
1 parent 0a83ff4 commit e685522
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 0 deletions.
17 changes: 17 additions & 0 deletions providers/darwin/process_darwin_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"bytes"
"encoding/binary"
"os"
"strconv"
"time"
"unsafe"

Expand Down Expand Up @@ -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
}
Expand Down
32 changes: 32 additions & 0 deletions providers/linux/process_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"io/ioutil"
"os"
"strconv"
"strings"
"time"

"github.com/prometheus/procfs"
Expand Down Expand Up @@ -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))
Expand Down
40 changes: 40 additions & 0 deletions providers/windows/process_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"unsafe"

"github.com/pkg/errors"
syswin "golang.org/x/sys/windows"

windows "github.com/elastic/go-windows"

Expand Down Expand Up @@ -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 {
Expand Down
20 changes: 20 additions & 0 deletions system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ package sysinfo
import (
"encoding/json"
"os"
osUser "os/user"
"runtime"
"strconv"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -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() {
Expand Down
33 changes: 33 additions & 0 deletions types/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Process interface {
CPUTimer
Info() (ProcessInfo, error)
Memory() (MemoryInfo, error)
User() (UserInfo, error)
}

type ProcessInfo struct {
Expand All @@ -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)
}
Expand Down

0 comments on commit e685522

Please sign in to comment.