21

Using the os/exec package, I want to run an external command on a *nix OS, on behalf of another user (of course go process run under root user of another user with su privileges)

I want avoid "su" or "bash" commands and make it purely with go.

I made an approach using syscall.Setuid but this will change the user to the main project, I just need change the user to the external child process:

func (self *Command) LoseTo(username string) {
   u, err := user.Lookup(username)
   if err != nil {
      fmt.Printf("%v", err)
   }

   uid, err := strconv.Atoi(u.Uid)
   if err := syscall.Setuid(uid); err != nil {
      fmt.Printf("%v", err)
   }
}
nemo
  • 55,207
  • 13
  • 135
  • 135
mcuadros
  • 4,098
  • 3
  • 37
  • 44

2 Answers2

41

You can add a syscall.Credential struct to Cmd.SysProcAttr

cmd := exec.Command(command, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid}
JimB
  • 104,193
  • 13
  • 262
  • 255
  • 1
    Great answer, but just wanted to note that the syscall package is not the same across OSs. From the [documentation](http://golang.org/pkg/syscall/), "The primary use of syscall is inside other packages that provide a more portable interface to the system, such as "os", "time" and "net". Use those packages rather than this one if you can". This is a good solution but will need additional code to make it platform independent. – Rick Smith Aug 11 '14 at 22:46
  • 1
    @RickSmith: of course, but the question *was* specifically for a "*nix OS" – JimB Aug 12 '14 at 16:10
  • @JimB I wasn't sure how obvious the implications of `syscall` are to future readers. I guess I've just been bitten by not realizing the nature of `syscall`. Answer used and thumbed up! – Rick Smith Aug 12 '14 at 16:23
  • this doesn't work for me on Mac OS X, anything specific to do over there ? – abourget Feb 16 '15 at 16:48
  • @abourget: It works exactly the same on osx. What are you seeing? – JimB Feb 18 '15 at 15:50
  • syscall.Credential.Uid expects an int, os.user.Lookup returns a string. I find the thought of casting troublesome. – donatJ Apr 13 '15 at 20:59
  • @donatJ: You seem to be commenting on the os/user package, which needs to be cross-platform, and not all platforms use numeric uids. The syscall package is platform specific. The is no "casting", it's reading an int value from a string, and this is the price of cross-platform code. – JimB Apr 13 '15 at 21:15
  • Well I meant I'm uncomfortable with casting what I get back myself. I want my app to be able to specify a username to exec as. After compiling I got the runtime error `user: Lookup not implemented on linux/amd64` anyway, so sigh. – donatJ Apr 13 '15 at 21:57
  • Will this code works without root privileges? – FiftiN Jan 26 '23 at 22:41
4

In case if someone is looking for a working code snippet:

u, erru := user.Lookup(LocalUserName)
if erru != nil {
    fmt.Println( erru, " for ",LocalUserName)
}
uid, err = strconv.ParseInt(u.Uid, 10, 32)
gid, err = strconv.ParseInt(u.Gid, 10, 32)

cmd := exec.Command("/bin/uname", "-a")
cmd.Stdout = &out
cmd.Stderr = &stderr
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
err = cmd.Run()
VikasPushkar
  • 392
  • 3
  • 18