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 b5096c5d8..c7f50f6fb 100644 --- a/pkg/commands/run.go +++ b/pkg/commands/run.go @@ -80,11 +80,10 @@ func (r *RunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bui // If specified, run the command as a specific user if userStr != "" { - uid, gid, err := util.GetUIDAndGIDFromString(userStr, true) + cmd.SysProcAttr.Credential, err = util.SyscallCredentials(userStr) if err != nil { - return err + return errors.Wrap(err, "credentials") } - cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid} } env, err := addDefaultHOME(userStr, replacementEnvs) @@ -94,6 +93,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") } diff --git a/pkg/util/groupids_cgo.go b/pkg/util/groupids_cgo.go new file mode 100644 index 000000000..1ff80006b --- /dev/null +++ b/pkg/util/groupids_cgo.go @@ -0,0 +1,29 @@ +// +build linux darwin +// +build cgo + +/* +Copyright 2020 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +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..3c9868e03 --- /dev/null +++ b/pkg/util/groupids_fallback.go @@ -0,0 +1,92 @@ +// +build linux darwin +// +build !cgo + +/* +Copyright 2020 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +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,anotherGrp + 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..f0a2a3406 --- /dev/null +++ b/pkg/util/syscall_credentials.go @@ -0,0 +1,60 @@ +/* +Copyright 2020 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +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 +}