From 3426a24929a39da6fb0153dd00c8b9e7010ec53b Mon Sep 17 00:00:00 2001 From: Thomas Stromberg Date: Sat, 28 Mar 2020 12:10:37 -0700 Subject: [PATCH 1/5] Add support for impersonating secondary groups --- pkg/commands/run.go | 38 +++++++++++++++++++++++++--- vendor/github.com/spf13/afero/go.mod | 2 ++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/pkg/commands/run.go b/pkg/commands/run.go index b60f185b3..e4be00df7 100644 --- a/pkg/commands/run.go +++ b/pkg/commands/run.go @@ -21,6 +21,7 @@ import ( "os" "os/exec" "os/user" + "strconv" "strings" "syscall" @@ -79,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 = impersonate(userStr) if err != nil { - return err + return errors.Wrap(err, "impersonate") } - cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid} } env, err := addDefaultHOME(userStr, replacementEnvs) @@ -113,6 +113,38 @@ 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/vendor/github.com/spf13/afero/go.mod b/vendor/github.com/spf13/afero/go.mod index 086855099..216979990 100644 --- a/vendor/github.com/spf13/afero/go.mod +++ b/vendor/github.com/spf13/afero/go.mod @@ -1,3 +1,5 @@ module github.com/spf13/afero +go 1.14 + require golang.org/x/text v0.3.0 From a6504769e3d1dde831cec2a48dd6c2fe8bbef6a5 Mon Sep 17 00:00:00 2001 From: Thomas Stromberg Date: Sat, 28 Mar 2020 22:01:20 -0700 Subject: [PATCH 2/5] Add secondary group impersonation w/ !cgo support --- integration/dockerfiles/1097 | 12 ++++++ pkg/commands/run.go | 38 ++--------------- pkg/util/groupids_cgo.go | 13 ++++++ pkg/util/groupids_fallback.go | 76 +++++++++++++++++++++++++++++++++ pkg/util/syscall_credentials.go | 44 +++++++++++++++++++ 5 files changed, 148 insertions(+), 35 deletions(-) create mode 100644 integration/dockerfiles/1097 create mode 100644 pkg/util/groupids_cgo.go create mode 100644 pkg/util/groupids_fallback.go create mode 100644 pkg/util/syscall_credentials.go 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 +} From 8a7ba69efc39ea1e1c9fd28d68c1c4c7cc20f082 Mon Sep 17 00:00:00 2001 From: Thomas Stromberg Date: Sat, 28 Mar 2020 22:05:41 -0700 Subject: [PATCH 3/5] Revert go.mod update --- vendor/github.com/spf13/afero/go.mod | 2 -- 1 file changed, 2 deletions(-) diff --git a/vendor/github.com/spf13/afero/go.mod b/vendor/github.com/spf13/afero/go.mod index 216979990..086855099 100644 --- a/vendor/github.com/spf13/afero/go.mod +++ b/vendor/github.com/spf13/afero/go.mod @@ -1,5 +1,3 @@ module github.com/spf13/afero -go 1.14 - require golang.org/x/text v0.3.0 From 5cbf34b67e445378efeb8c4cc7b6b36b8b41c8f0 Mon Sep 17 00:00:00 2001 From: Tejal Desai Date: Sun, 3 May 2020 21:04:22 -0700 Subject: [PATCH 4/5] Update pkg/util/groupids_fallback.go --- pkg/util/groupids_fallback.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/util/groupids_fallback.go b/pkg/util/groupids_fallback.go index 036d67196..d38995e66 100644 --- a/pkg/util/groupids_fallback.go +++ b/pkg/util/groupids_fallback.go @@ -64,7 +64,7 @@ func localGroups(r io.Reader) []*group { continue } - // wheel:*:0:root + // wheel:*:0:root,anotherGrp parts := strings.SplitN(string(line), ":", 4) if _, err := strconv.Atoi(parts[2]); err != nil { continue From cb14b65e0844c5e5f16f36d0b98689edeaa43c39 Mon Sep 17 00:00:00 2001 From: Tejal Desai Date: Sun, 3 May 2020 21:10:55 -0700 Subject: [PATCH 5/5] fix boilerplate --- pkg/util/groupids_cgo.go | 16 ++++++++++++++++ pkg/util/groupids_fallback.go | 16 ++++++++++++++++ pkg/util/syscall_credentials.go | 16 ++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/pkg/util/groupids_cgo.go b/pkg/util/groupids_cgo.go index f97af9536..1ff80006b 100644 --- a/pkg/util/groupids_cgo.go +++ b/pkg/util/groupids_cgo.go @@ -1,6 +1,22 @@ // +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 ( diff --git a/pkg/util/groupids_fallback.go b/pkg/util/groupids_fallback.go index d38995e66..3c9868e03 100644 --- a/pkg/util/groupids_fallback.go +++ b/pkg/util/groupids_fallback.go @@ -1,6 +1,22 @@ // +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 ( diff --git a/pkg/util/syscall_credentials.go b/pkg/util/syscall_credentials.go index 4320650a1..f0a2a3406 100644 --- a/pkg/util/syscall_credentials.go +++ b/pkg/util/syscall_credentials.go @@ -1,3 +1,19 @@ +/* +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 (