diff --git a/integration/dockerfiles/1097 b/integration/dockerfiles/1097 new file mode 100644 index 000000000..a046d764d --- /dev/null +++ b/integration/dockerfiles/1097 @@ -0,0 +1,12 @@ +FROM ubuntu:latest + +RUN groupadd -g 20000 bar +RUN groupadd -g 10000 foo + +RUN useradd -c "Foo user" -u 10000 -g 10000 -G bar -m foo + +RUN id foo + +USER foo + +RUN id diff --git a/pkg/commands/run.go b/pkg/commands/run.go index e4be00df7..d78c3d0fa 100644 --- a/pkg/commands/run.go +++ b/pkg/commands/run.go @@ -21,7 +21,6 @@ import ( "os" "os/exec" "os/user" - "strconv" "strings" "syscall" @@ -80,9 +79,9 @@ func (r *RunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bui // If specified, run the command as a specific user if userStr != "" { - cmd.SysProcAttr.Credential, err = impersonate(userStr) + cmd.SysProcAttr.Credential, err = util.SyscallCredentials(userStr) if err != nil { - return errors.Wrap(err, "impersonate") + return errors.Wrap(err, "credentials") } } @@ -93,6 +92,7 @@ func (r *RunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bui cmd.Env = env + logrus.Infof("Running: %s", cmd.Args) if err := cmd.Start(); err != nil { return errors.Wrap(err, "starting command") } @@ -113,38 +113,6 @@ func (r *RunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bui return nil } -func impersonate(userStr string) (*syscall.Credential, error) { - uid, gid, err := util.GetUIDAndGIDFromString(userStr, true) - if err != nil { - return nil, errors.Wrap(err, "get uid/gid") - } - - u, err := util.Lookup(userStr) - if err != nil { - return nil, errors.Wrap(err, "lookup") - } - logrus.Infof("user: %+v", u) - - // Handle the case of secondary groups - groups := []uint32{41} - gidStr, err := u.GroupIds() - logrus.Infof("groupstr: %s", gidStr) - - for _, g := range gidStr { - i, err := strconv.ParseUint(g, 10, 32) - if err != nil { - return nil, errors.Wrap(err, "parseuint") - } - groups = append(groups, uint32(i)) - } - - return &syscall.Credential{ - Uid: uid, - Gid: gid, - Groups: groups, - }, nil -} - // addDefaultHOME adds the default value for HOME if it isn't already set func addDefaultHOME(u string, envs []string) ([]string, error) { for _, env := range envs { diff --git a/pkg/util/groupids_cgo.go b/pkg/util/groupids_cgo.go new file mode 100644 index 000000000..f97af9536 --- /dev/null +++ b/pkg/util/groupids_cgo.go @@ -0,0 +1,13 @@ +// +build linux darwin +// +build cgo + +package util + +import ( + "os/user" +) + +// groupIDs returns all of the group ID's a user is a member of +func groupIDs(u *user.User) ([]string, error) { + return u.GroupIds() +} diff --git a/pkg/util/groupids_fallback.go b/pkg/util/groupids_fallback.go new file mode 100644 index 000000000..036d67196 --- /dev/null +++ b/pkg/util/groupids_fallback.go @@ -0,0 +1,76 @@ +// +build linux darwin +// +build !cgo + +package util + +import ( + "bufio" + "bytes" + "io" + "os" + "os/user" + "strconv" + "strings" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +var groupFile = "/etc/group" + +type group struct { + id string // group ID + name string // group name + members []string // secondary group ids +} + +// groupIDs returns all of the group ID's a user is a member of +func groupIDs(u *user.User) ([]string, error) { + logrus.Infof("performing slow lookup of group ids for %s", u.Username) + + f, err := os.Open(groupFile) + if err != nil { + return nil, errors.Wrap(err, "open") + } + defer f.Close() + + gids := []string{u.Gid} + + for _, g := range localGroups(f) { + for _, m := range g.members { + if m == u.Username { + gids = append(gids, g.id) + } + } + } + + return gids, nil +} + +// localGroups parses a reader in /etc/group form, returning parsed group data +// based on src/os/user/lookup_unix.go - but extended to include secondary groups +func localGroups(r io.Reader) []*group { + var groups []*group + + bs := bufio.NewScanner(r) + for bs.Scan() { + line := bs.Bytes() + + // There's no spec for /etc/passwd or /etc/group, but we try to follow + // the same rules as the glibc parser, which allows comments and blank + // space at the beginning of a line. + line = bytes.TrimSpace(line) + if len(line) == 0 || line[0] == '#' { + continue + } + + // wheel:*:0:root + parts := strings.SplitN(string(line), ":", 4) + if _, err := strconv.Atoi(parts[2]); err != nil { + continue + } + + groups = append(groups, &group{name: parts[0], id: parts[2], members: strings.Split(parts[3], ",")}) + } + return groups +} diff --git a/pkg/util/syscall_credentials.go b/pkg/util/syscall_credentials.go new file mode 100644 index 000000000..4320650a1 --- /dev/null +++ b/pkg/util/syscall_credentials.go @@ -0,0 +1,44 @@ +package util + +import ( + "strconv" + "syscall" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +func SyscallCredentials(userStr string) (*syscall.Credential, error) { + uid, gid, err := GetUIDAndGIDFromString(userStr, true) + if err != nil { + return nil, errors.Wrap(err, "get uid/gid") + } + + u, err := Lookup(userStr) + if err != nil { + return nil, errors.Wrap(err, "lookup") + } + logrus.Infof("util.Lookup returned: %+v", u) + + var groups []uint32 + + gidStr, err := groupIDs(u) + if err != nil { + return nil, errors.Wrap(err, "group ids for user") + } + + for _, g := range gidStr { + i, err := strconv.ParseUint(g, 10, 32) + if err != nil { + return nil, errors.Wrap(err, "parseuint") + } + + groups = append(groups, uint32(i)) + } + + return &syscall.Credential{ + Uid: uid, + Gid: gid, + Groups: groups, + }, nil +}