mojombo/god — pull request #159 by spraints (2014-02-26 17:01:30+00:00)
Title: Do groups more like login(1)

This solves a couple of problems.

1. Assigning `uid` but not `gid` left the `gid` as 0.
2. Spawned processes did not run with all of the `uid` user's groups.

## Assigning `uid` but not `gid`

Say you have a god watch that assigns a uid but not a gid.

```ruby
God.watch do |w|
...
w.uid = "nobody"
...
end
```

The process started by this watch will have the configured uid. Its gid and supplemental groups will include the gid of the god process, i.e. `0(root)`. Here's a ruby program that illustrates this:

```ruby
uid, file = ARGV
def r(cmd) ; puts "#{$$}> #{cmd}" ; system cmd ; end
r "id"
r "ls -l #{file}"
fork do
require 'etc'
uid = Etc.getpwnam(uid).uid
Process::Sys.setuid(uid)
r 'id'
r "cat #{file}"
end
Process.wait
```

outputs:

```
$ sudo ruby example.rb burke private
570> id
uid=0(root) gid=0(root) groups=0(root)
570> ls -l private
-r--r----- 1 root root 7 Feb 26 08:37 private
573> id
uid=1017(burke) gid=0(root) groups=1016(burke),0(root)
573> cat private
secret
```

The change in 0038204 can be demonstrated like this:

```ruby
uid, file = ARGV
def r(cmd) ; puts "#{$$}> #{cmd}" ; system cmd ; end
r "id"
r "ls -l #{file}"
fork do
require 'etc'
uid_num = Etc.getpwnam(uid).uid
gid_num = Etc.getpwnam(uid).gid
Process.groups = [gid_num]
Process::Sys.setgid(gid_num)
Process::Sys.setuid(uid_num)
r 'id'
r "cat #{file}"
end
Process.wait
```

output:

```
$ sudo ruby example.rb burke private
2469> id
uid=0(root) gid=0(root) groups=0(root)
2469> ls -l private
-r--r----- 1 root root 7 Feb 26 08:37 private
2472> id
uid=1017(burke) gid=1016(burke) groups=1016(burke)
2472> cat private
cat: private: Permission denied
```

## Supplemental groups

Now that we have a group id (either assigned in the config or found in the OS), unfortunately the spawned process won't be able to use resources that require one of `uid`'s supplemental groups. For example, say my user is in several groups:

```
$ id
uid=1000(burke) gid=1000(burke) groups=1000(burke),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),111(lpadmin),112(sambashare),999(docker)
```

Running the example program shows that the spawned process will have fewer rights than a logged in user.

```
burke$ cat private
cat: private: Permission denied
burke$ cat private2
secret
burke$ cat private3
secret

$ sudo ruby example.rb burke private private2 private3
15197> id
uid=0(root) gid=0(root) groups=0(root)
15197> ls -l private private2 private3
-r--r----- 1 root root 7 Feb 26 11:49 private
-r--r----- 1 root cdrom 7 Feb 26 11:47 private2
-r--r----- 1 root adm 7 Feb 26 11:52 private3
15200> id
uid=1000(burke) gid=1000(burke) groups=1000(burke)
15200> cat private
cat: private: Permission denied
15200> cat private2
cat: private2: Permission denied
15200> cat private3
cat: private3: Permission denied
```

Using [Process.initgroups](http://ruby-doc.org/core-1.8.7/Process.html#method-c-initgroups) (the change from f8f30f6) in the test script...

```ruby
uid, *files = ARGV
def r(cmd) ; puts "#{$$}> #{cmd}" ; system cmd ; end
r "id"
r "ls -l #{files.join(' ')}"
fork do
require 'etc'
uid_num = Etc.getpwnam(uid).uid
gid_num = Etc.getpwnam(uid).gid
Process.groups = [gid_num]
Process.initgroups(uid, gid_num)
Process::Sys.setgid(gid_num)
Process::Sys.setuid(uid_num)
r 'id'
files.each
files.each { |file| r "cat #{file}" }
end
Process.wait
```

leads to the expected (to me) output:

```
$ sudo ruby example.rb burke private private2 private3
15210> id
uid=0(root) gid=0(root) groups=0(root)
15210> ls -l private private2 private3
-r--r----- 1 root root 7 Feb 26 11:49 private
-r--r----- 1 root cdrom 7 Feb 26 11:47 private2
-r--r----- 1 root adm 7 Feb 26 11:52 private3
15213> id
uid=1000(burke) gid=1000(burke) groups=1000(burke),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),111(lpadmin),112(sambashare),999(docker)
15213> cat private
cat: private: Permission denied
15213> cat private2
secret
15213> cat private3
secret
```