Skip to content

Commit

Permalink
Support for setting userid/groupid for process execution
Browse files Browse the repository at this point in the history
  • Loading branch information
vladimirvivien committed Nov 9, 2024
1 parent e2c675e commit f257fd9
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 0 deletions.
43 changes: 43 additions & 0 deletions examples/setuserid/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package main

import (
"fmt"
"os"
"runtime"

"github.com/vladimirvivien/gexe"
)

// This examples demonstrates how to use gexe to run
// subprocesses with different userid/groupid.
// Note: setting userid/gid for subprocesses requires
// elevated privilege such as using sudo.

func main() {
p := gexe.NewProc(`echo "Hello World!`)
var uid string

switch runtime.GOOS {
case "windows":
case "darwin":
uid = gexe.Run(`id -u`)
case "linux":
uid = gexe.Run(`id -l`)
}

if uid != "" {
if err := p.SetUserid(uid).Err(); err != nil {
fmt.Println("Failed to set userid: ", err)
os.Exit(1)
}
}

result := p.Run()
if err := result.Err(); err != nil {
fmt.Println("Error: ", err)
os.Exit(1)
}

fmt.Println("Running process with userid:", uid)
fmt.Println(result.Result())
}
117 changes: 117 additions & 0 deletions exec/proc.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import (
"io"
"os"
osexec "os/exec"
"os/user"
"strconv"
"strings"
"syscall"
"time"

"github.com/vladimirvivien/gexe/vars"
Expand All @@ -16,6 +19,8 @@ import (
type Proc struct {
id int
err error
userid *int
groupid *int
state *os.ProcessState
result *bytes.Buffer
outputPipe io.ReadCloser
Expand All @@ -36,6 +41,7 @@ func NewProc(cmdStr string) *Proc {
}

command := osexec.Command(words[0], words[1:]...)

return &Proc{
cmd: command,
result: new(bytes.Buffer),
Expand Down Expand Up @@ -132,6 +138,26 @@ func (p *Proc) Start() *Proc {
p.cmd.Stderr = p.result
}

// apply user id and user grp
var procCred *syscall.Credential
if p.userid != nil {
procCred = &syscall.Credential{
Uid: uint32(*p.userid),
}
}
if p.groupid != nil {
if procCred == nil {
procCred = new(syscall.Credential)
}
procCred.Uid = uint32(*p.groupid)
}
if procCred != nil {
if p.cmd.SysProcAttr == nil {
p.cmd.SysProcAttr = new(syscall.SysProcAttr)
}
p.cmd.SysProcAttr.Credential = procCred
}

if err := p.cmd.Start(); err != nil {
p.err = err
return p
Expand All @@ -155,6 +181,41 @@ func (p *Proc) Command() *osexec.Cmd {
return p.cmd
}

// SetUserid looks up the user by a numerical id or
// by a name to be used for the process when launched.
func (p *Proc) SetUserid(user string) *Proc {
if p.err != nil {
return p
}
uid, err := lookupUserID(user)
if err != nil {
p.err = err
return p
}

p.userid = &uid

return p
}

// SetGroupid looks up the group by a numerical id or
// by a name to be used for the process when launched.
func (p *Proc) SetGroupid(grp string) *Proc {
if p.err != nil {
return p
}

gid, err := lookupGroupID(grp)
if err != nil {
p.err = err
return p
}

p.groupid = &gid

return p
}

// Peek attempts to read process state information
func (p *Proc) Peek() *Proc {
p.state = p.cmd.ProcessState
Expand Down Expand Up @@ -338,3 +399,59 @@ func (p *Proc) hasStarted() bool {
func Parse(cmd string) ([]string, error) {
return parse(cmd)
}

func lookupUserID(userid string) (int, error) {
var uid int
var usr *user.User
var err error

// assume userid is a valid numerical user id
usr, err = user.LookupId(userid)
if err == nil {
uid, _ = strconv.Atoi(usr.Uid)
return uid, nil
}

// if not numercal id, lookup by username
usr, err = user.Lookup(userid)
if err == nil {
uid, _ = strconv.Atoi(usr.Uid)
return uid, nil
}

return 0, err
}

func lookupGroupID(grpid string) (int, error) {
var gid int
var grp *user.Group
var err error

// assume grpid is a valid numerical group id
grp, err = user.LookupGroupId(grpid)
if err == nil {
gid, _ = strconv.Atoi(grp.Gid)
return gid, nil
}

// if not numercal id, lookup by groupname
grp, err = user.LookupGroup(grpid)
if err == nil {
gid, _ = strconv.Atoi(grp.Gid)
return gid, nil
}

return 0, err
}

func getDefaultUserIDs() (int, int, error) {

Check failure on line 447 in exec/proc.go

View workflow job for this annotation

GitHub Actions / golangci-lint

func `getDefaultUserIDs` is unused (unused)
usr, err := user.Current()
if err != nil {
return 0, 0, err
}

uid, _ := strconv.Atoi(usr.Uid)
gid, _ := strconv.Atoi(usr.Gid)

return uid, gid, nil
}

0 comments on commit f257fd9

Please sign in to comment.