1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Merge pull request #9023 from cyphar/libcontainer-update-to-new-interface

Update vendor'd `libcontainer` and update to use new `user` interface.
This commit is contained in:
unclejack 2014-11-08 16:17:28 +02:00
commit b18efeeb7a
11 changed files with 833 additions and 71 deletions

View file

@ -1370,7 +1370,11 @@ func ServeFd(addr string, handle http.Handler) error {
} }
func lookupGidByName(nameOrGid string) (int, error) { func lookupGidByName(nameOrGid string) (int, error) {
groups, err := user.ParseGroupFilter(func(g *user.Group) bool { groupFile, err := user.GetGroupFile()
if err != nil {
return -1, err
}
groups, err := user.ParseGroupFileFilter(groupFile, func(g user.Group) bool {
return g.Name == nameOrGid || strconv.Itoa(g.Gid) == nameOrGid return g.Name == nameOrGid || strconv.Itoa(g.Gid) == nameOrGid
}) })
if err != nil { if err != nil {

View file

@ -66,7 +66,7 @@ if [ "$1" = '--go' ]; then
mv tmp-tar src/code.google.com/p/go/src/pkg/archive/tar mv tmp-tar src/code.google.com/p/go/src/pkg/archive/tar
fi fi
clone git github.com/docker/libcontainer fd6df76562137aa3b18e44b790cb484fe2b6fa0b clone git github.com/docker/libcontainer 4ae31b6ceb2c2557c9f05f42da61b0b808faa5a4
# see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file) # see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file)
rm -rf src/github.com/docker/libcontainer/vendor rm -rf src/github.com/docker/libcontainer/vendor
eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli')" eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli')"

View file

@ -167,26 +167,43 @@ func RestoreParentDeathSignal(old int) error {
// SetupUser changes the groups, gid, and uid for the user inside the container // SetupUser changes the groups, gid, and uid for the user inside the container
func SetupUser(u string) error { func SetupUser(u string) error {
uid, gid, suppGids, home, err := user.GetUserGroupSupplementaryHome(u, syscall.Getuid(), syscall.Getgid(), "/") // Set up defaults.
defaultExecUser := user.ExecUser{
Uid: syscall.Getuid(),
Gid: syscall.Getgid(),
Home: "/",
}
passwdFile, err := user.GetPasswdFile()
if err != nil {
return err
}
groupFile, err := user.GetGroupFile()
if err != nil {
return err
}
execUser, err := user.GetExecUserFile(u, &defaultExecUser, passwdFile, groupFile)
if err != nil { if err != nil {
return fmt.Errorf("get supplementary groups %s", err) return fmt.Errorf("get supplementary groups %s", err)
} }
if err := syscall.Setgroups(suppGids); err != nil { if err := syscall.Setgroups(execUser.Sgids); err != nil {
return fmt.Errorf("setgroups %s", err) return fmt.Errorf("setgroups %s", err)
} }
if err := system.Setgid(gid); err != nil { if err := system.Setgid(execUser.Gid); err != nil {
return fmt.Errorf("setgid %s", err) return fmt.Errorf("setgid %s", err)
} }
if err := system.Setuid(uid); err != nil { if err := system.Setuid(execUser.Uid); err != nil {
return fmt.Errorf("setuid %s", err) return fmt.Errorf("setuid %s", err)
} }
// if we didn't get HOME already, set it based on the user's HOME // if we didn't get HOME already, set it based on the user's HOME
if envHome := os.Getenv("HOME"); envHome == "" { if envHome := os.Getenv("HOME"); envHome == "" {
if err := os.Setenv("HOME", home); err != nil { if err := os.Setenv("HOME", execUser.Home); err != nil {
return fmt.Errorf("set HOME %s", err) return fmt.Errorf("set HOME %s", err)
} }
} }

View file

@ -1003,28 +1003,23 @@ func AddRoute(destination, source, gateway, device string) error {
} }
if source != "" { if source != "" {
srcIP, srcNet, err := net.ParseCIDR(source) srcIP := net.ParseIP(source)
if err != nil { if err != nil {
return fmt.Errorf("source CIDR %s couldn't be parsed", source) return fmt.Errorf("source IP %s couldn't be parsed", source)
} }
srcFamily := getIpFamily(srcIP) srcFamily := getIpFamily(srcIP)
if currentFamily != -1 && currentFamily != srcFamily { if currentFamily != -1 && currentFamily != srcFamily {
return fmt.Errorf("source and destination ip were not the same IP family") return fmt.Errorf("source and destination ip were not the same IP family")
} }
currentFamily = srcFamily currentFamily = srcFamily
srcLen, bits := srcNet.Mask.Size()
if srcLen == 0 && bits == 0 {
return fmt.Errorf("source CIDR %s generated a non-canonical Mask", source)
}
msg.Family = uint8(srcFamily) msg.Family = uint8(srcFamily)
msg.Src_len = uint8(srcLen)
var srcData []byte var srcData []byte
if srcFamily == syscall.AF_INET { if srcFamily == syscall.AF_INET {
srcData = srcIP.To4() srcData = srcIP.To4()
} else { } else {
srcData = srcIP.To16() srcData = srcIP.To16()
} }
rtAttrs = append(rtAttrs, newRtAttr(syscall.RTA_SRC, srcData)) rtAttrs = append(rtAttrs, newRtAttr(syscall.RTA_PREFSRC, srcData))
} }
if gateway != "" { if gateway != "" {

View file

@ -280,6 +280,34 @@ func TestAddDelNetworkIp(t *testing.T) {
} }
} }
func TestAddRouteSourceSelection(t *testing.T) {
tstIp := "127.1.1.1"
tl := testLink{name: "tstEth", linkType: "dummy"}
addLink(t, tl.name, tl.linkType)
defer deleteLink(t, tl.name)
ip := net.ParseIP(tstIp)
mask := net.IPv4Mask(255, 255, 255, 255)
ipNet := &net.IPNet{IP: ip, Mask: mask}
iface, err := net.InterfaceByName(tl.name)
if err != nil {
t.Fatalf("Lost created link %#v", tl)
}
if err := NetworkLinkAddIp(iface, ip, ipNet); err != nil {
t.Fatalf("Could not add IP address %s to interface %#v: %s", ip.String(), iface, err)
}
upLink(t, tl.name)
defer downLink(t, tl.name)
if err := AddRoute("127.0.0.0/8", tstIp, "", tl.name); err != nil {
t.Fatalf("Failed to add route with source address")
}
}
func TestCreateVethPair(t *testing.T) { func TestCreateVethPair(t *testing.T) {
if testing.Short() { if testing.Short() {
return return

View file

@ -0,0 +1,209 @@
{
"capabilities": [
"CHOWN",
"DAC_OVERRIDE",
"FOWNER",
"MKNOD",
"NET_RAW",
"SETGID",
"SETUID",
"SETFCAP",
"SETPCAP",
"NET_BIND_SERVICE",
"SYS_CHROOT",
"KILL"
],
"cgroups": {
"allowed_devices": [
{
"cgroup_permissions": "m",
"major_number": -1,
"minor_number": -1,
"type": 99
},
{
"cgroup_permissions": "m",
"major_number": -1,
"minor_number": -1,
"type": 98
},
{
"cgroup_permissions": "rwm",
"major_number": 5,
"minor_number": 1,
"path": "/dev/console",
"type": 99
},
{
"cgroup_permissions": "rwm",
"major_number": 4,
"path": "/dev/tty0",
"type": 99
},
{
"cgroup_permissions": "rwm",
"major_number": 4,
"minor_number": 1,
"path": "/dev/tty1",
"type": 99
},
{
"cgroup_permissions": "rwm",
"major_number": 136,
"minor_number": -1,
"type": 99
},
{
"cgroup_permissions": "rwm",
"major_number": 5,
"minor_number": 2,
"type": 99
},
{
"cgroup_permissions": "rwm",
"major_number": 10,
"minor_number": 200,
"type": 99
},
{
"cgroup_permissions": "rwm",
"file_mode": 438,
"major_number": 1,
"minor_number": 3,
"path": "/dev/null",
"type": 99
},
{
"cgroup_permissions": "rwm",
"file_mode": 438,
"major_number": 1,
"minor_number": 5,
"path": "/dev/zero",
"type": 99
},
{
"cgroup_permissions": "rwm",
"file_mode": 438,
"major_number": 1,
"minor_number": 7,
"path": "/dev/full",
"type": 99
},
{
"cgroup_permissions": "rwm",
"file_mode": 438,
"major_number": 5,
"path": "/dev/tty",
"type": 99
},
{
"cgroup_permissions": "rwm",
"file_mode": 438,
"major_number": 1,
"minor_number": 9,
"path": "/dev/urandom",
"type": 99
},
{
"cgroup_permissions": "rwm",
"file_mode": 438,
"major_number": 1,
"minor_number": 8,
"path": "/dev/random",
"type": 99
}
],
"name": "docker-koye",
"parent": "docker"
},
"restrict_sys": true,
"mount_config": {
"device_nodes": [
{
"cgroup_permissions": "rwm",
"file_mode": 438,
"major_number": 1,
"minor_number": 3,
"path": "/dev/null",
"type": 99
},
{
"cgroup_permissions": "rwm",
"file_mode": 438,
"major_number": 1,
"minor_number": 5,
"path": "/dev/zero",
"type": 99
},
{
"cgroup_permissions": "rwm",
"file_mode": 438,
"major_number": 1,
"minor_number": 7,
"path": "/dev/full",
"type": 99
},
{
"cgroup_permissions": "rwm",
"file_mode": 438,
"major_number": 5,
"path": "/dev/tty",
"type": 99
},
{
"cgroup_permissions": "rwm",
"file_mode": 438,
"major_number": 1,
"minor_number": 9,
"path": "/dev/urandom",
"type": 99
},
{
"cgroup_permissions": "rwm",
"file_mode": 438,
"major_number": 1,
"minor_number": 8,
"path": "/dev/random",
"type": 99
}
]
},
"environment": [
"HOME=/",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"HOSTNAME=koye",
"TERM=xterm"
],
"hostname": "koye",
"namespaces": {
"NEWIPC": true,
"NEWNET": true,
"NEWNS": true,
"NEWPID": true,
"NEWUTS": true
},
"networks": [
{
"address": "127.0.0.1/0",
"gateway": "localhost",
"mtu": 1500,
"type": "loopback"
},
{
"address": "172.17.0.101/16",
"bridge": "docker0",
"veth_prefix": "veth",
"mtu": 1500,
"type": "veth"
}
],
"routes": [
{
"destination": "0.0.0.0/0",
"source": "172.17.0.101",
"gateway": "172.17.42.1",
"interface_name": "eth0"
}
],
"tty": true
}

View file

@ -0,0 +1,108 @@
package user
import (
"errors"
"fmt"
"syscall"
)
var (
// The current operating system does not provide the required data for user lookups.
ErrUnsupported = errors.New("user lookup: operating system does not provide passwd-formatted data")
)
func lookupUser(filter func(u User) bool) (User, error) {
// Get operating system-specific passwd reader-closer.
passwd, err := GetPasswd()
if err != nil {
return User{}, err
}
defer passwd.Close()
// Get the users.
users, err := ParsePasswdFilter(passwd, filter)
if err != nil {
return User{}, err
}
// No user entries found.
if len(users) == 0 {
return User{}, fmt.Errorf("no matching entries in passwd file")
}
// Assume the first entry is the "correct" one.
return users[0], nil
}
// CurrentUser looks up the current user by their user id in /etc/passwd. If the
// user cannot be found (or there is no /etc/passwd file on the filesystem),
// then CurrentUser returns an error.
func CurrentUser() (User, error) {
return LookupUid(syscall.Getuid())
}
// LookupUser looks up a user by their username in /etc/passwd. If the user
// cannot be found (or there is no /etc/passwd file on the filesystem), then
// LookupUser returns an error.
func LookupUser(username string) (User, error) {
return lookupUser(func(u User) bool {
return u.Name == username
})
}
// LookupUid looks up a user by their user id in /etc/passwd. If the user cannot
// be found (or there is no /etc/passwd file on the filesystem), then LookupId
// returns an error.
func LookupUid(uid int) (User, error) {
return lookupUser(func(u User) bool {
return u.Uid == uid
})
}
func lookupGroup(filter func(g Group) bool) (Group, error) {
// Get operating system-specific group reader-closer.
group, err := GetGroup()
if err != nil {
return Group{}, err
}
defer group.Close()
// Get the users.
groups, err := ParseGroupFilter(group, filter)
if err != nil {
return Group{}, err
}
// No user entries found.
if len(groups) == 0 {
return Group{}, fmt.Errorf("no matching entries in group file")
}
// Assume the first entry is the "correct" one.
return groups[0], nil
}
// CurrentGroup looks up the current user's group by their primary group id's
// entry in /etc/passwd. If the group cannot be found (or there is no
// /etc/group file on the filesystem), then CurrentGroup returns an error.
func CurrentGroup() (Group, error) {
return LookupGid(syscall.Getgid())
}
// LookupGroup looks up a group by its name in /etc/group. If the group cannot
// be found (or there is no /etc/group file on the filesystem), then LookupGroup
// returns an error.
func LookupGroup(groupname string) (Group, error) {
return lookupGroup(func(g Group) bool {
return g.Name == groupname
})
}
// LookupGid looks up a group by its group id in /etc/group. If the group cannot
// be found (or there is no /etc/group file on the filesystem), then LookupGid
// returns an error.
func LookupGid(gid int) (Group, error) {
return lookupGroup(func(g Group) bool {
return g.Gid == gid
})
}

View file

@ -0,0 +1,30 @@
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
package user
import (
"io"
"os"
)
// Unix-specific path to the passwd and group formatted files.
const (
unixPasswdFile = "/etc/passwd"
unixGroupFile = "/etc/group"
)
func GetPasswdFile() (string, error) {
return unixPasswdFile, nil
}
func GetPasswd() (io.ReadCloser, error) {
return os.Open(unixPasswdFile)
}
func GetGroupFile() (string, error) {
return unixGroupFile, nil
}
func GetGroup() (io.ReadCloser, error) {
return os.Open(unixGroupFile)
}

View file

@ -0,0 +1,21 @@
// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris
package user
import "io"
func GetPasswdFile() (string, error) {
return "", ErrUnsupported
}
func GetPasswd() (io.ReadCloser, error) {
return nil, ErrUnsupported
}
func GetGroupFile() (string, error) {
return "", ErrUnsupported
}
func GetGroup() (io.ReadCloser, error) {
return nil, ErrUnsupported
}

View file

@ -69,23 +69,36 @@ func parseLine(line string, v ...interface{}) {
} }
} }
func ParsePasswd() ([]*User, error) { func ParsePasswdFile(path string) ([]User, error) {
return ParsePasswdFilter(nil) passwd, err := os.Open(path)
}
func ParsePasswdFilter(filter func(*User) bool) ([]*User, error) {
f, err := os.Open("/etc/passwd")
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer f.Close() defer passwd.Close()
return parsePasswdFile(f, filter) return ParsePasswd(passwd)
} }
func parsePasswdFile(r io.Reader, filter func(*User) bool) ([]*User, error) { func ParsePasswd(passwd io.Reader) ([]User, error) {
return ParsePasswdFilter(passwd, nil)
}
func ParsePasswdFileFilter(path string, filter func(User) bool) ([]User, error) {
passwd, err := os.Open(path)
if err != nil {
return nil, err
}
defer passwd.Close()
return ParsePasswdFilter(passwd, filter)
}
func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) {
if r == nil {
return nil, fmt.Errorf("nil source for passwd-formatted data")
}
var ( var (
s = bufio.NewScanner(r) s = bufio.NewScanner(r)
out = []*User{} out = []User{}
) )
for s.Scan() { for s.Scan() {
@ -103,7 +116,7 @@ func parsePasswdFile(r io.Reader, filter func(*User) bool) ([]*User, error) {
// Name:Pass:Uid:Gid:Gecos:Home:Shell // Name:Pass:Uid:Gid:Gecos:Home:Shell
// root:x:0:0:root:/root:/bin/bash // root:x:0:0:root:/root:/bin/bash
// adm:x:3:4:adm:/var/adm:/bin/false // adm:x:3:4:adm:/var/adm:/bin/false
p := &User{} p := User{}
parseLine( parseLine(
text, text,
&p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell, &p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell,
@ -117,23 +130,36 @@ func parsePasswdFile(r io.Reader, filter func(*User) bool) ([]*User, error) {
return out, nil return out, nil
} }
func ParseGroup() ([]*Group, error) { func ParseGroupFile(path string) ([]Group, error) {
return ParseGroupFilter(nil) group, err := os.Open(path)
}
func ParseGroupFilter(filter func(*Group) bool) ([]*Group, error) {
f, err := os.Open("/etc/group")
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer f.Close() defer group.Close()
return parseGroupFile(f, filter) return ParseGroup(group)
} }
func parseGroupFile(r io.Reader, filter func(*Group) bool) ([]*Group, error) { func ParseGroup(group io.Reader) ([]Group, error) {
return ParseGroupFilter(group, nil)
}
func ParseGroupFileFilter(path string, filter func(Group) bool) ([]Group, error) {
group, err := os.Open(path)
if err != nil {
return nil, err
}
defer group.Close()
return ParseGroupFilter(group, filter)
}
func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) {
if r == nil {
return nil, fmt.Errorf("nil source for group-formatted data")
}
var ( var (
s = bufio.NewScanner(r) s = bufio.NewScanner(r)
out = []*Group{} out = []Group{}
) )
for s.Scan() { for s.Scan() {
@ -151,7 +177,7 @@ func parseGroupFile(r io.Reader, filter func(*Group) bool) ([]*Group, error) {
// Name:Pass:Gid:List // Name:Pass:Gid:List
// root:x:0:root // root:x:0:root
// adm:x:4:root,adm,daemon // adm:x:4:root,adm,daemon
p := &Group{} p := Group{}
parseLine( parseLine(
text, text,
&p.Name, &p.Pass, &p.Gid, &p.List, &p.Name, &p.Pass, &p.Gid, &p.List,
@ -165,94 +191,160 @@ func parseGroupFile(r io.Reader, filter func(*Group) bool) ([]*Group, error) {
return out, nil return out, nil
} }
// Given a string like "user", "1000", "user:group", "1000:1000", returns the uid, gid, list of supplementary group IDs, and home directory, if available and/or applicable. type ExecUser struct {
func GetUserGroupSupplementaryHome(userSpec string, defaultUid, defaultGid int, defaultHome string) (int, int, []int, string, error) { Uid, Gid int
var ( Sgids []int
uid = defaultUid Home string
gid = defaultGid }
suppGids = []int{}
home = defaultHome
// GetExecUserFile is a wrapper for GetExecUser. It reads data from each of the
// given file paths and uses that data as the arguments to GetExecUser. If the
// files cannot be opened for any reason, the error is ignored and a nil
// io.Reader is passed instead.
func GetExecUserFile(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) {
passwd, err := os.Open(passwdPath)
if err != nil {
passwd = nil
} else {
defer passwd.Close()
}
group, err := os.Open(groupPath)
if err != nil {
group = nil
} else {
defer group.Close()
}
return GetExecUser(userSpec, defaults, passwd, group)
}
// GetExecUser parses a user specification string (using the passwd and group
// readers as sources for /etc/passwd and /etc/group data, respectively). In
// the case of blank fields or missing data from the sources, the values in
// defaults is used.
//
// GetExecUser will return an error if a user or group literal could not be
// found in any entry in passwd and group respectively.
//
// Examples of valid user specifications are:
// * ""
// * "user"
// * "uid"
// * "user:group"
// * "uid:gid
// * "user:gid"
// * "uid:group"
func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (*ExecUser, error) {
var (
userArg, groupArg string userArg, groupArg string
name string
) )
if defaults == nil {
defaults = new(ExecUser)
}
// Copy over defaults.
user := &ExecUser{
Uid: defaults.Uid,
Gid: defaults.Gid,
Sgids: defaults.Sgids,
Home: defaults.Home,
}
// Sgids slice *cannot* be nil.
if user.Sgids == nil {
user.Sgids = []int{}
}
// allow for userArg to have either "user" syntax, or optionally "user:group" syntax // allow for userArg to have either "user" syntax, or optionally "user:group" syntax
parseLine(userSpec, &userArg, &groupArg) parseLine(userSpec, &userArg, &groupArg)
users, err := ParsePasswdFilter(func(u *User) bool { users, err := ParsePasswdFilter(passwd, func(u User) bool {
if userArg == "" { if userArg == "" {
return u.Uid == uid return u.Uid == user.Uid
} }
return u.Name == userArg || strconv.Itoa(u.Uid) == userArg return u.Name == userArg || strconv.Itoa(u.Uid) == userArg
}) })
if err != nil && !os.IsNotExist(err) { if err != nil && passwd != nil {
if userArg == "" { if userArg == "" {
userArg = strconv.Itoa(uid) userArg = strconv.Itoa(user.Uid)
} }
return 0, 0, nil, "", fmt.Errorf("Unable to find user %v: %v", userArg, err) return nil, fmt.Errorf("Unable to find user %v: %v", userArg, err)
} }
haveUser := users != nil && len(users) > 0 haveUser := users != nil && len(users) > 0
if haveUser { if haveUser {
// if we found any user entries that matched our filter, let's take the first one as "correct" // if we found any user entries that matched our filter, let's take the first one as "correct"
uid = users[0].Uid name = users[0].Name
gid = users[0].Gid user.Uid = users[0].Uid
home = users[0].Home user.Gid = users[0].Gid
user.Home = users[0].Home
} else if userArg != "" { } else if userArg != "" {
// we asked for a user but didn't find them... let's check to see if we wanted a numeric user // we asked for a user but didn't find them... let's check to see if we wanted a numeric user
uid, err = strconv.Atoi(userArg) user.Uid, err = strconv.Atoi(userArg)
if err != nil { if err != nil {
// not numeric - we have to bail // not numeric - we have to bail
return 0, 0, nil, "", fmt.Errorf("Unable to find user %v", userArg) return nil, fmt.Errorf("Unable to find user %v", userArg)
} }
if uid < minId || uid > maxId {
return 0, 0, nil, "", ErrRange // Must be inside valid uid range.
if user.Uid < minId || user.Uid > maxId {
return nil, ErrRange
} }
// if userArg couldn't be found in /etc/passwd but is numeric, just roll with it - this is legit // if userArg couldn't be found in /etc/passwd but is numeric, just roll with it - this is legit
} }
if groupArg != "" || (haveUser && users[0].Name != "") { if groupArg != "" || name != "" {
groups, err := ParseGroupFilter(func(g *Group) bool { groups, err := ParseGroupFilter(group, func(g Group) bool {
// Explicit group format takes precedence.
if groupArg != "" { if groupArg != "" {
return g.Name == groupArg || strconv.Itoa(g.Gid) == groupArg return g.Name == groupArg || strconv.Itoa(g.Gid) == groupArg
} }
// Check if user is a member.
for _, u := range g.List { for _, u := range g.List {
if u == users[0].Name { if u == name {
return true return true
} }
} }
return false return false
}) })
if err != nil && !os.IsNotExist(err) { if err != nil && group != nil {
return 0, 0, nil, "", fmt.Errorf("Unable to find groups for user %v: %v", users[0].Name, err) return nil, fmt.Errorf("Unable to find groups for user %v: %v", users[0].Name, err)
} }
haveGroup := groups != nil && len(groups) > 0 haveGroup := groups != nil && len(groups) > 0
if groupArg != "" { if groupArg != "" {
if haveGroup { if haveGroup {
// if we found any group entries that matched our filter, let's take the first one as "correct" // if we found any group entries that matched our filter, let's take the first one as "correct"
gid = groups[0].Gid user.Gid = groups[0].Gid
} else { } else {
// we asked for a group but didn't find id... let's check to see if we wanted a numeric group // we asked for a group but didn't find id... let's check to see if we wanted a numeric group
gid, err = strconv.Atoi(groupArg) user.Gid, err = strconv.Atoi(groupArg)
if err != nil { if err != nil {
// not numeric - we have to bail // not numeric - we have to bail
return 0, 0, nil, "", fmt.Errorf("Unable to find group %v", groupArg) return nil, fmt.Errorf("Unable to find group %v", groupArg)
} }
if gid < minId || gid > maxId {
return 0, 0, nil, "", ErrRange // Ensure gid is inside gid range.
if user.Gid < minId || user.Gid > maxId {
return nil, ErrRange
} }
// if groupArg couldn't be found in /etc/group but is numeric, just roll with it - this is legit // if groupArg couldn't be found in /etc/group but is numeric, just roll with it - this is legit
} }
} else if haveGroup { } else if haveGroup {
suppGids = make([]int, len(groups)) // If implicit group format, fill supplementary gids.
user.Sgids = make([]int, len(groups))
for i, group := range groups { for i, group := range groups {
suppGids[i] = group.Gid user.Sgids[i] = group.Gid
} }
} }
} }
return uid, gid, suppGids, home, nil return user, nil
} }

View file

@ -1,6 +1,8 @@
package user package user
import ( import (
"io"
"reflect"
"strings" "strings"
"testing" "testing"
) )
@ -54,7 +56,7 @@ func TestUserParseLine(t *testing.T) {
} }
func TestUserParsePasswd(t *testing.T) { func TestUserParsePasswd(t *testing.T) {
users, err := parsePasswdFile(strings.NewReader(` users, err := ParsePasswdFilter(strings.NewReader(`
root:x:0:0:root:/root:/bin/bash root:x:0:0:root:/root:/bin/bash
adm:x:3:4:adm:/var/adm:/bin/false adm:x:3:4:adm:/var/adm:/bin/false
this is just some garbage data this is just some garbage data
@ -74,7 +76,7 @@ this is just some garbage data
} }
func TestUserParseGroup(t *testing.T) { func TestUserParseGroup(t *testing.T) {
groups, err := parseGroupFile(strings.NewReader(` groups, err := ParseGroupFilter(strings.NewReader(`
root:x:0:root root:x:0:root
adm:x:4:root,adm,daemon adm:x:4:root,adm,daemon
this is just some garbage data this is just some garbage data
@ -92,3 +94,259 @@ this is just some garbage data
t.Fatalf("Expected groups[1] to be 4 - adm - 3 members, got %v - %v - %v", groups[1].Gid, groups[1].Name, len(groups[1].List)) t.Fatalf("Expected groups[1] to be 4 - adm - 3 members, got %v - %v - %v", groups[1].Gid, groups[1].Name, len(groups[1].List))
} }
} }
func TestValidGetExecUser(t *testing.T) {
const passwdContent = `
root:x:0:0:root user:/root:/bin/bash
adm:x:42:43:adm:/var/adm:/bin/false
this is just some garbage data
`
const groupContent = `
root:x:0:root
adm:x:43:
grp:x:1234:root,adm
this is just some garbage data
`
defaultExecUser := ExecUser{
Uid: 8888,
Gid: 8888,
Sgids: []int{8888},
Home: "/8888",
}
tests := []struct {
ref string
expected ExecUser
}{
{
ref: "root",
expected: ExecUser{
Uid: 0,
Gid: 0,
Sgids: []int{0, 1234},
Home: "/root",
},
},
{
ref: "adm",
expected: ExecUser{
Uid: 42,
Gid: 43,
Sgids: []int{1234},
Home: "/var/adm",
},
},
{
ref: "root:adm",
expected: ExecUser{
Uid: 0,
Gid: 43,
Sgids: defaultExecUser.Sgids,
Home: "/root",
},
},
{
ref: "adm:1234",
expected: ExecUser{
Uid: 42,
Gid: 1234,
Sgids: defaultExecUser.Sgids,
Home: "/var/adm",
},
},
{
ref: "42:1234",
expected: ExecUser{
Uid: 42,
Gid: 1234,
Sgids: defaultExecUser.Sgids,
Home: "/var/adm",
},
},
{
ref: "1337:1234",
expected: ExecUser{
Uid: 1337,
Gid: 1234,
Sgids: defaultExecUser.Sgids,
Home: defaultExecUser.Home,
},
},
{
ref: "1337",
expected: ExecUser{
Uid: 1337,
Gid: defaultExecUser.Gid,
Sgids: defaultExecUser.Sgids,
Home: defaultExecUser.Home,
},
},
{
ref: "",
expected: ExecUser{
Uid: defaultExecUser.Uid,
Gid: defaultExecUser.Gid,
Sgids: defaultExecUser.Sgids,
Home: defaultExecUser.Home,
},
},
}
for _, test := range tests {
passwd := strings.NewReader(passwdContent)
group := strings.NewReader(groupContent)
execUser, err := GetExecUser(test.ref, &defaultExecUser, passwd, group)
if err != nil {
t.Logf("got unexpected error when parsing '%s': %s", test.ref, err.Error())
t.Fail()
continue
}
if !reflect.DeepEqual(test.expected, *execUser) {
t.Logf("got: %#v", execUser)
t.Logf("expected: %#v", test.expected)
t.Fail()
continue
}
}
}
func TestInvalidGetExecUser(t *testing.T) {
const passwdContent = `
root:x:0:0:root user:/root:/bin/bash
adm:x:42:43:adm:/var/adm:/bin/false
this is just some garbage data
`
const groupContent = `
root:x:0:root
adm:x:43:
grp:x:1234:root,adm
this is just some garbage data
`
tests := []string{
// No such user/group.
"notuser",
"notuser:notgroup",
"root:notgroup",
"notuser:adm",
"8888:notgroup",
"notuser:8888",
// Invalid user/group values.
"-1:0",
"0:-3",
"-5:-2",
}
for _, test := range tests {
passwd := strings.NewReader(passwdContent)
group := strings.NewReader(groupContent)
execUser, err := GetExecUser(test, nil, passwd, group)
if err == nil {
t.Logf("got unexpected success when parsing '%s': %#v", test, execUser)
t.Fail()
continue
}
}
}
func TestGetExecUserNilSources(t *testing.T) {
const passwdContent = `
root:x:0:0:root user:/root:/bin/bash
adm:x:42:43:adm:/var/adm:/bin/false
this is just some garbage data
`
const groupContent = `
root:x:0:root
adm:x:43:
grp:x:1234:root,adm
this is just some garbage data
`
defaultExecUser := ExecUser{
Uid: 8888,
Gid: 8888,
Sgids: []int{8888},
Home: "/8888",
}
tests := []struct {
ref string
passwd, group bool
expected ExecUser
}{
{
ref: "",
passwd: false,
group: false,
expected: ExecUser{
Uid: 8888,
Gid: 8888,
Sgids: []int{8888},
Home: "/8888",
},
},
{
ref: "root",
passwd: true,
group: false,
expected: ExecUser{
Uid: 0,
Gid: 0,
Sgids: []int{8888},
Home: "/root",
},
},
{
ref: "0",
passwd: false,
group: false,
expected: ExecUser{
Uid: 0,
Gid: 8888,
Sgids: []int{8888},
Home: "/8888",
},
},
{
ref: "0:0",
passwd: false,
group: false,
expected: ExecUser{
Uid: 0,
Gid: 0,
Sgids: []int{8888},
Home: "/8888",
},
},
}
for _, test := range tests {
var passwd, group io.Reader
if test.passwd {
passwd = strings.NewReader(passwdContent)
}
if test.group {
group = strings.NewReader(groupContent)
}
execUser, err := GetExecUser(test.ref, &defaultExecUser, passwd, group)
if err != nil {
t.Logf("got unexpected error when parsing '%s': %s", test.ref, err.Error())
t.Fail()
continue
}
if !reflect.DeepEqual(test.expected, *execUser) {
t.Logf("got: %#v", execUser)
t.Logf("expected: %#v", test.expected)
t.Fail()
continue
}
}
}