fix: getUIDandGID is able to resolve non-existing users and groups (#2106)
* fix: getUIDandGID is able to resolve non-existing users and groups A common pattern in dockerfiles is to provide a plain uid and gid number, which doesn't neccesarily exist inside the os. Signed-off-by: Höhl, Lukas <lukas.hoehl@accso.de> * test: add chown dockerfile Signed-off-by: Höhl, Lukas <lukas.hoehl@accso.de> * chore: format Signed-off-by: Höhl, Lukas <lukas.hoehl@accso.de> * chore: add comment Signed-off-by: Höhl, Lukas <lukas.hoehl@accso.de> * tests: fix chown dockerfile Signed-off-by: Höhl, Lukas <lukas.hoehl@accso.de> * refactor: split up getIdsFromUsernameAndGroup func Signed-off-by: Höhl, Lukas <lukas.hoehl@accso.de> * fix: implement raw uid logic for LookupUser Signed-off-by: Höhl, Lukas <lukas.hoehl@accso.de> * test: add dockerfiles for integration test * fix: lookup user error message * test: add dockerfiles for non-existing user testcase * fix: forgot error check * tests: fix syscall credentials test * chore: add debug output for copy command * tests: set specific gid for integration dockerfile * tests: fix syscall credentials test github runner had the exact uid that i was testing on, so the groups were not empty Signed-off-by: Höhl, Lukas <lukas.hoehl@accso.de> * tests: fix test script Signed-off-by: Höhl, Lukas <lukas.hoehl@accso.de> * chore: apply golangci lint checks Signed-off-by: Höhl, Lukas <lukas.hoehl@accso.de> * fix: reset file ownership in createFile if not root owned Signed-off-by: Höhl, Lukas <lukas.hoehl@accso.de> * chore: logrus.Debugf missed format variable Signed-off-by: Höhl, Lukas <lukas.hoehl@accso.de> * chore(test-script): remove go html coverage Signed-off-by: Höhl, Lukas <lukas.hoehl@accso.de> * test(k8s): increase wait timeout Signed-off-by: Höhl, Lukas <lukas.hoehl@accso.de>
This commit is contained in:
parent
8710ce3311
commit
aad03dc285
4
Makefile
4
Makefile
|
|
@ -66,6 +66,10 @@ minikube-setup:
|
||||||
test: out/executor
|
test: out/executor
|
||||||
@ ./scripts/test.sh
|
@ ./scripts/test.sh
|
||||||
|
|
||||||
|
test-with-coverage: test
|
||||||
|
go tool cover -html=out/coverage.out
|
||||||
|
|
||||||
|
|
||||||
.PHONY: integration-test
|
.PHONY: integration-test
|
||||||
integration-test:
|
integration-test:
|
||||||
@ ./scripts/integration-test.sh
|
@ ./scripts/integration-test.sh
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Copyright 2018 Google, Inc. All rights reserved.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
FROM debian:9.11
|
||||||
|
RUN echo "hey" > /tmp/foo
|
||||||
|
|
||||||
|
FROM debian:9.11
|
||||||
|
RUN groupadd --gid 5000 testgroup
|
||||||
|
COPY --from=0 --chown=1001:1001 /tmp/foo /tmp/baz
|
||||||
|
# with existing group
|
||||||
|
COPY --from=0 --chown=1001:testgroup /tmp/foo /tmp/baz
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Copyright 2018 Google, Inc. All rights reserved.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
FROM debian:9.11
|
||||||
|
USER 1001:1001
|
||||||
|
RUN echo "hey2" > /tmp/foo
|
||||||
|
USER 1001
|
||||||
|
RUN echo "hello" > /tmp/foobar
|
||||||
|
|
||||||
|
# With existing group
|
||||||
|
USER root
|
||||||
|
RUN groupadd testgroup
|
||||||
|
USER 1001:testgroup
|
||||||
|
RUN echo "hello" > /tmp/foobar
|
||||||
|
|
@ -89,7 +89,7 @@ func TestK8s(t *testing.T) {
|
||||||
t.Logf("Waiting for K8s kaniko build job to finish: %s\n",
|
t.Logf("Waiting for K8s kaniko build job to finish: %s\n",
|
||||||
"job/kaniko-test-"+job.Name)
|
"job/kaniko-test-"+job.Name)
|
||||||
|
|
||||||
kubeWaitCmd := exec.Command("kubectl", "wait", "--for=condition=complete", "--timeout=1m",
|
kubeWaitCmd := exec.Command("kubectl", "wait", "--for=condition=complete", "--timeout=2m",
|
||||||
"job/kaniko-test-"+job.Name)
|
"job/kaniko-test-"+job.Name)
|
||||||
if out, errR := RunCommandWithoutTest(kubeWaitCmd); errR != nil {
|
if out, errR := RunCommandWithoutTest(kubeWaitCmd); errR != nil {
|
||||||
t.Log(kubeWaitCmd.Args)
|
t.Log(kubeWaitCmd.Args)
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ func (c *CopyCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.Bu
|
||||||
|
|
||||||
replacementEnvs := buildArgs.ReplacementEnvs(config.Env)
|
replacementEnvs := buildArgs.ReplacementEnvs(config.Env)
|
||||||
uid, gid, err := getUserGroup(c.cmd.Chown, replacementEnvs)
|
uid, gid, err := getUserGroup(c.cmd.Chown, replacementEnvs)
|
||||||
|
logrus.Debugf("found uid %v and gid %v for chown string %v", uid, gid, c.cmd.Chown)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "getting user group from chown")
|
return errors.Wrap(err, "getting user group from chown")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
|
@ -41,8 +40,7 @@ type RunCommand struct {
|
||||||
|
|
||||||
// for testing
|
// for testing
|
||||||
var (
|
var (
|
||||||
userLookup = user.Lookup
|
userLookup = util.LookupUser
|
||||||
userLookupID = user.LookupId
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *RunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
|
func (r *RunCommand) ExecuteCommand(config *v1.Config, buildArgs *dockerfile.BuildArgs) error {
|
||||||
|
|
@ -151,11 +149,7 @@ func addDefaultHOME(u string, envs []string) ([]string, error) {
|
||||||
// Otherwise the user is set to uid and HOME is /
|
// Otherwise the user is set to uid and HOME is /
|
||||||
userObj, err := userLookup(u)
|
userObj, err := userLookup(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if uo, e := userLookupID(u); e == nil {
|
return nil, fmt.Errorf("lookup user %v: %w", u, err)
|
||||||
userObj = uo
|
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return append(envs, fmt.Sprintf("%s=%s", constants.HOME, userObj.HomeDir)), nil
|
return append(envs, fmt.Sprintf("%s=%s", constants.HOME, userObj.HomeDir)), nil
|
||||||
|
|
@ -256,6 +250,7 @@ func (cr *CachingRunCommand) MetadataOnly() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: this should create the workdir if it doesn't exist, atleast this is what docker does
|
||||||
func setWorkDirIfExists(workdir string) string {
|
func setWorkDirIfExists(workdir string) string {
|
||||||
if _, err := os.Lstat(workdir); err == nil {
|
if _, err := os.Lstat(workdir); err == nil {
|
||||||
return workdir
|
return workdir
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ package commands
|
||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
|
@ -38,7 +37,6 @@ func Test_addDefaultHOME(t *testing.T) {
|
||||||
user string
|
user string
|
||||||
mockUser *user.User
|
mockUser *user.User
|
||||||
lookupError error
|
lookupError error
|
||||||
mockUserID *user.User
|
|
||||||
initial []string
|
initial []string
|
||||||
expected []string
|
expected []string
|
||||||
}{
|
}{
|
||||||
|
|
@ -81,19 +79,18 @@ func Test_addDefaultHOME(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "USER is set using the UID",
|
name: "USER is set using the UID",
|
||||||
user: "newuser",
|
user: "1000",
|
||||||
lookupError: errors.New("User not found"),
|
mockUser: &user.User{
|
||||||
mockUserID: &user.User{
|
Username: "1000",
|
||||||
Username: "user",
|
HomeDir: "/",
|
||||||
HomeDir: "/home/user",
|
|
||||||
},
|
},
|
||||||
initial: []string{
|
initial: []string{
|
||||||
"PATH=/something/else",
|
"PATH=/something/else",
|
||||||
},
|
},
|
||||||
expected: []string{
|
expected: []string{
|
||||||
"PATH=/something/else",
|
"PATH=/something/else",
|
||||||
"HOME=/home/user",
|
"HOME=/",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -113,11 +110,10 @@ func Test_addDefaultHOME(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
original := userLookup
|
||||||
userLookup = func(username string) (*user.User, error) { return test.mockUser, test.lookupError }
|
userLookup = func(username string) (*user.User, error) { return test.mockUser, test.lookupError }
|
||||||
userLookupID = func(username string) (*user.User, error) { return test.mockUserID, nil }
|
|
||||||
defer func() {
|
defer func() {
|
||||||
userLookup = user.Lookup
|
userLookup = original
|
||||||
userLookupID = user.LookupId
|
|
||||||
}()
|
}()
|
||||||
actual, err := addDefaultHOME(test.user, test.initial)
|
actual, err := addDefaultHOME(test.user, test.initial)
|
||||||
testutil.CheckErrorAndDeepEqual(t, false, err, test.expected, actual)
|
testutil.CheckErrorAndDeepEqual(t, false, err, test.expected, actual)
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,6 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// for testing
|
|
||||||
var (
|
|
||||||
Lookup = util.Lookup
|
|
||||||
)
|
|
||||||
|
|
||||||
type UserCommand struct {
|
type UserCommand struct {
|
||||||
BaseCommand
|
BaseCommand
|
||||||
cmd *instructions.UserCommand
|
cmd *instructions.UserCommand
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,10 @@ limitations under the License.
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os/user"
|
"os/user"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
"github.com/GoogleContainerTools/kaniko/pkg/dockerfile"
|
||||||
"github.com/GoogleContainerTools/kaniko/pkg/util"
|
|
||||||
|
|
||||||
"github.com/GoogleContainerTools/kaniko/testutil"
|
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
|
|
@ -109,13 +107,6 @@ func TestUpdateUser(t *testing.T) {
|
||||||
User: test.user,
|
User: test.user,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
Lookup = func(_ string) (*user.User, error) {
|
|
||||||
if test.userObj != nil {
|
|
||||||
return test.userObj, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("error while looking up user")
|
|
||||||
}
|
|
||||||
defer func() { Lookup = util.Lookup }()
|
|
||||||
buildArgs := dockerfile.NewBuildArgs([]string{})
|
buildArgs := dockerfile.NewBuildArgs([]string{})
|
||||||
err := cmd.ExecuteCommand(cfg, buildArgs)
|
err := cmd.ExecuteCommand(cfg, buildArgs)
|
||||||
testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedUID, cfg.User)
|
testutil.CheckErrorAndDeepEqual(t, false, err, test.expectedUID, cfg.User)
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
reflect "reflect"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
|
@ -39,7 +38,7 @@ import (
|
||||||
|
|
||||||
// for testing
|
// for testing
|
||||||
var (
|
var (
|
||||||
getUIDAndGID = GetUIDAndGIDFromString
|
getUIDAndGIDFunc = getUIDAndGID
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -353,7 +352,7 @@ func GetUserGroup(chownStr string, env []string) (int64, int64, error) {
|
||||||
return -1, -1, err
|
return -1, -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
uid32, gid32, err := getUIDAndGID(chown, true)
|
uid32, gid32, err := getUIDAndGIDFromString(chown, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, -1, err
|
return -1, -1, err
|
||||||
}
|
}
|
||||||
|
|
@ -364,86 +363,112 @@ func GetUserGroup(chownStr string, env []string) (int64, int64, error) {
|
||||||
// Extract user and group id from a string formatted 'user:group'.
|
// Extract user and group id from a string formatted 'user:group'.
|
||||||
// If fallbackToUID is set, the gid is equal to uid if the group is not specified
|
// If fallbackToUID is set, the gid is equal to uid if the group is not specified
|
||||||
// otherwise gid is set to zero.
|
// otherwise gid is set to zero.
|
||||||
func GetUIDAndGIDFromString(userGroupString string, fallbackToUID bool) (uint32, uint32, error) {
|
// UserID and GroupID don't need to be present on the system.
|
||||||
|
func getUIDAndGIDFromString(userGroupString string, fallbackToUID bool) (uint32, uint32, error) {
|
||||||
userAndGroup := strings.Split(userGroupString, ":")
|
userAndGroup := strings.Split(userGroupString, ":")
|
||||||
userStr := userAndGroup[0]
|
userStr := userAndGroup[0]
|
||||||
var groupStr string
|
var groupStr string
|
||||||
if len(userAndGroup) > 1 {
|
if len(userAndGroup) > 1 {
|
||||||
groupStr = userAndGroup[1]
|
groupStr = userAndGroup[1]
|
||||||
}
|
}
|
||||||
|
return getUIDAndGIDFunc(userStr, groupStr, fallbackToUID)
|
||||||
if reflect.TypeOf(userStr).String() == "int" {
|
|
||||||
return 0, 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
uidStr, gidStr, err := GetUserFromUsername(userStr, groupStr, fallbackToUID)
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// uid and gid need to be fit into uint32
|
|
||||||
uid64, err := strconv.ParseUint(uidStr, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
gid64, err := strconv.ParseUint(gidStr, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return uint32(uid64), uint32(gid64), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUserFromUsername(userStr string, groupStr string, fallbackToUID bool) (string, string, error) {
|
func getUIDAndGID(userStr string, groupStr string, fallbackToUID bool) (uint32, uint32, error) {
|
||||||
// Lookup by username
|
user, err := LookupUser(userStr)
|
||||||
userObj, err := Lookup(userStr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
uid32, err := getUID(user.Uid)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Same dance with groups
|
gid, err := getGIDFromName(groupStr, fallbackToUID)
|
||||||
var group *user.Group
|
if err != nil {
|
||||||
if groupStr != "" {
|
if errors.Is(err, fallbackToUIDError) {
|
||||||
group, err = user.LookupGroup(groupStr)
|
return uid32, uid32, nil
|
||||||
|
}
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
return uid32, gid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getGID tries to parse the gid or falls back to getGroupFromName if it's not an id
|
||||||
|
func getGID(groupStr string, fallbackToUID bool) (uint32, error) {
|
||||||
|
gid, err := strconv.ParseUint(groupStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fallbackToUIDOrError(err, fallbackToUID)
|
||||||
|
}
|
||||||
|
return uint32(gid), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getGIDFromName tries to parse the groupStr into an existing group.
|
||||||
|
// if the group doesn't exist, fallback to getGID to parse non-existing valid GIDs.
|
||||||
|
func getGIDFromName(groupStr string, fallbackToUID bool) (uint32, error) {
|
||||||
|
group, err := user.LookupGroup(groupStr)
|
||||||
|
if err != nil {
|
||||||
|
// unknown group error could relate to a non existing group
|
||||||
|
var groupErr *user.UnknownGroupError
|
||||||
|
if errors.Is(err, groupErr) {
|
||||||
|
return getGID(groupStr, fallbackToUID)
|
||||||
|
}
|
||||||
|
group, err = user.LookupGroupId(groupStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(user.UnknownGroupError); !ok {
|
return getGID(groupStr, fallbackToUID)
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
group, err = user.LookupGroupId(groupStr)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return getGID(group.Gid, fallbackToUID)
|
||||||
uid := userObj.Uid
|
|
||||||
gid := "0"
|
|
||||||
if fallbackToUID {
|
|
||||||
gid = userObj.Gid
|
|
||||||
}
|
|
||||||
if group != nil {
|
|
||||||
gid = group.Gid
|
|
||||||
}
|
|
||||||
|
|
||||||
return uid, gid, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Lookup(userStr string) (*user.User, error) {
|
var fallbackToUIDError = new(fallbackToUIDErrorType)
|
||||||
|
|
||||||
|
type fallbackToUIDErrorType struct{}
|
||||||
|
|
||||||
|
func (e fallbackToUIDErrorType) Error() string {
|
||||||
|
return "fallback to uid"
|
||||||
|
}
|
||||||
|
|
||||||
|
func fallbackToUIDOrError(err error, fallbackToUID bool) error {
|
||||||
|
if fallbackToUID {
|
||||||
|
return fallbackToUIDError
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupUser will try to lookup the userStr inside the passwd file.
|
||||||
|
// If the user does not exists, the function will fallback to parsing the userStr as an uid.
|
||||||
|
func LookupUser(userStr string) (*user.User, error) {
|
||||||
userObj, err := user.Lookup(userStr)
|
userObj, err := user.Lookup(userStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(user.UnknownUserError); !ok {
|
unknownUserErr := new(user.UnknownUserError)
|
||||||
|
// only return if it's not an unknown user error
|
||||||
|
if !errors.As(err, unknownUserErr) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookup by id
|
// Lookup by id
|
||||||
u, e := user.LookupId(userStr)
|
userObj, err = user.LookupId(userStr)
|
||||||
if e != nil {
|
if err != nil {
|
||||||
return nil, err
|
uid, err := getUID(userStr)
|
||||||
|
if err != nil {
|
||||||
|
// at this point, the user does not exist and the userStr is not a valid number.
|
||||||
|
return nil, fmt.Errorf("user %v is not a uid and does not exist on the system", userStr)
|
||||||
|
}
|
||||||
|
userObj = &user.User{
|
||||||
|
Uid: fmt.Sprint(uid),
|
||||||
|
HomeDir: "/",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
userObj = u
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return userObj, nil
|
return userObj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getUID(userStr string) (uint32, error) {
|
||||||
|
// checkif userStr is a valid id
|
||||||
|
uid, err := strconv.ParseUint(userStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return uint32(uid), nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -555,28 +555,32 @@ func Test_RemoteUrls(t *testing.T) {
|
||||||
|
|
||||||
func TestGetUserGroup(t *testing.T) {
|
func TestGetUserGroup(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
description string
|
description string
|
||||||
chown string
|
chown string
|
||||||
env []string
|
env []string
|
||||||
mock func(string, bool) (uint32, uint32, error)
|
mockIDGetter func(userStr string, groupStr string, fallbackToUID bool) (uint32, uint32, error)
|
||||||
expectedU int64
|
// needed, in case uid is a valid number, but group is a name
|
||||||
expectedG int64
|
mockGroupIDGetter func(groupStr string) (*user.Group, error)
|
||||||
shdErr bool
|
expectedU int64
|
||||||
|
expectedG int64
|
||||||
|
shdErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
description: "non empty chown",
|
description: "non empty chown",
|
||||||
chown: "some:some",
|
chown: "some:some",
|
||||||
env: []string{},
|
env: []string{},
|
||||||
mock: func(string, bool) (uint32, uint32, error) { return 100, 1000, nil },
|
mockIDGetter: func(string, string, bool) (uint32, uint32, error) {
|
||||||
expectedU: 100,
|
return 100, 1000, nil
|
||||||
expectedG: 1000,
|
},
|
||||||
|
expectedU: 100,
|
||||||
|
expectedG: 1000,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "non empty chown with env replacement",
|
description: "non empty chown with env replacement",
|
||||||
chown: "some:$foo",
|
chown: "some:$foo",
|
||||||
env: []string{"foo=key"},
|
env: []string{"foo=key"},
|
||||||
mock: func(c string, t bool) (uint32, uint32, error) {
|
mockIDGetter: func(userStr string, groupStr string, fallbackToUID bool) (uint32, uint32, error) {
|
||||||
if c == "some:key" {
|
if userStr == "some" && groupStr == "key" {
|
||||||
return 10, 100, nil
|
return 10, 100, nil
|
||||||
}
|
}
|
||||||
return 0, 0, fmt.Errorf("did not resolve environment variable")
|
return 0, 0, fmt.Errorf("did not resolve environment variable")
|
||||||
|
|
@ -586,7 +590,7 @@ func TestGetUserGroup(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "empty chown string",
|
description: "empty chown string",
|
||||||
mock: func(c string, t bool) (uint32, uint32, error) {
|
mockIDGetter: func(string, string, bool) (uint32, uint32, error) {
|
||||||
return 0, 0, fmt.Errorf("should not be called")
|
return 0, 0, fmt.Errorf("should not be called")
|
||||||
},
|
},
|
||||||
expectedU: -1,
|
expectedU: -1,
|
||||||
|
|
@ -595,9 +599,11 @@ func TestGetUserGroup(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
original := getUIDAndGID
|
originalIDGetter := getUIDAndGIDFunc
|
||||||
defer func() { getUIDAndGID = original }()
|
defer func() {
|
||||||
getUIDAndGID = tc.mock
|
getUIDAndGIDFunc = originalIDGetter
|
||||||
|
}()
|
||||||
|
getUIDAndGIDFunc = tc.mockIDGetter
|
||||||
uid, gid, err := GetUserGroup(tc.chown, tc.env)
|
uid, gid, err := GetUserGroup(tc.chown, tc.env)
|
||||||
testutil.CheckErrorAndDeepEqual(t, tc.shdErr, err, uid, tc.expectedU)
|
testutil.CheckErrorAndDeepEqual(t, tc.shdErr, err, uid, tc.expectedU)
|
||||||
testutil.CheckErrorAndDeepEqual(t, tc.shdErr, err, gid, tc.expectedG)
|
testutil.CheckErrorAndDeepEqual(t, tc.shdErr, err, gid, tc.expectedG)
|
||||||
|
|
@ -661,33 +667,191 @@ func TestResolveEnvironmentReplacementList(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_GetUIDAndGIDFromString(t *testing.T) {
|
func Test_GetUIDAndGIDFromString(t *testing.T) {
|
||||||
currentUser, err := user.Current()
|
currentUser := testutil.GetCurrentUser(t)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Cannot get current user: %s", err)
|
|
||||||
}
|
|
||||||
groups, err := currentUser.GroupIds()
|
|
||||||
if err != nil || len(groups) == 0 {
|
|
||||||
t.Fatalf("Cannot get groups for current user: %s", err)
|
|
||||||
}
|
|
||||||
primaryGroupObj, err := user.LookupGroupId(groups[0])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Could not lookup name of group %s: %s", groups[0], err)
|
|
||||||
}
|
|
||||||
primaryGroup := primaryGroupObj.Name
|
|
||||||
|
|
||||||
testCases := []string{
|
type args struct {
|
||||||
fmt.Sprintf("%s:%s", currentUser.Uid, currentUser.Gid),
|
userGroupStr string
|
||||||
fmt.Sprintf("%s:%s", currentUser.Username, currentUser.Gid),
|
fallbackToUID bool
|
||||||
fmt.Sprintf("%s:%s", currentUser.Uid, primaryGroup),
|
}
|
||||||
fmt.Sprintf("%s:%s", currentUser.Username, primaryGroup),
|
|
||||||
|
type expected struct {
|
||||||
|
userID uint32
|
||||||
|
groupID uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
currentUserUID, _ := strconv.ParseUint(currentUser.Uid, 10, 32)
|
||||||
|
currentUserGID, _ := strconv.ParseUint(currentUser.Gid, 10, 32)
|
||||||
|
expectedCurrentUser := expected{
|
||||||
|
userID: uint32(currentUserUID),
|
||||||
|
groupID: uint32(currentUserGID),
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
testname string
|
||||||
|
args args
|
||||||
|
expected expected
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testname: "current user uid and gid",
|
||||||
|
args: args{
|
||||||
|
userGroupStr: fmt.Sprintf("%d:%d", currentUserUID, currentUserGID),
|
||||||
|
},
|
||||||
|
expected: expectedCurrentUser,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testname: "current user username and gid",
|
||||||
|
args: args{
|
||||||
|
userGroupStr: fmt.Sprintf("%s:%d", currentUser.Username, currentUserGID),
|
||||||
|
},
|
||||||
|
expected: expectedCurrentUser,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testname: "current user username and primary group",
|
||||||
|
args: args{
|
||||||
|
userGroupStr: fmt.Sprintf("%s:%s", currentUser.Username, currentUser.PrimaryGroup),
|
||||||
|
},
|
||||||
|
expected: expectedCurrentUser,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testname: "current user uid and primary group",
|
||||||
|
args: args{
|
||||||
|
userGroupStr: fmt.Sprintf("%d:%s", currentUserUID, currentUser.PrimaryGroup),
|
||||||
|
},
|
||||||
|
expected: expectedCurrentUser,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testname: "non-existing valid uid and gid",
|
||||||
|
args: args{
|
||||||
|
userGroupStr: fmt.Sprintf("%d:%d", 1001, 50000),
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
userID: 1001,
|
||||||
|
groupID: 50000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testname: "uid and existing group",
|
||||||
|
args: args{
|
||||||
|
userGroupStr: fmt.Sprintf("%d:%s", 1001, currentUser.PrimaryGroup),
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
userID: 1001,
|
||||||
|
groupID: uint32(currentUserGID),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testname: "uid and non existing group-name with fallbackToUID",
|
||||||
|
args: args{
|
||||||
|
userGroupStr: fmt.Sprintf("%d:%s", 1001, "hello-world-group"),
|
||||||
|
fallbackToUID: true,
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
userID: 1001,
|
||||||
|
groupID: 1001,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testname: "uid and non existing group-name",
|
||||||
|
args: args{
|
||||||
|
userGroupStr: fmt.Sprintf("%d:%s", 1001, "hello-world-group"),
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testname: "name and non existing gid",
|
||||||
|
args: args{
|
||||||
|
userGroupStr: fmt.Sprintf("%s:%d", currentUser.Username, 50000),
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
userID: expectedCurrentUser.userID,
|
||||||
|
groupID: 50000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testname: "only uid and fallback is false",
|
||||||
|
args: args{
|
||||||
|
userGroupStr: fmt.Sprintf("%d", currentUserUID),
|
||||||
|
fallbackToUID: false,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testname: "only uid and fallback is true",
|
||||||
|
args: args{
|
||||||
|
userGroupStr: fmt.Sprintf("%d", currentUserUID),
|
||||||
|
fallbackToUID: true,
|
||||||
|
},
|
||||||
|
expected: expected{
|
||||||
|
userID: expectedCurrentUser.userID,
|
||||||
|
groupID: expectedCurrentUser.userID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testname: "non-existing user without group",
|
||||||
|
args: args{
|
||||||
|
userGroupStr: "helloworlduser",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
expectedU, _ := strconv.ParseUint(currentUser.Uid, 10, 32)
|
|
||||||
expectedG, _ := strconv.ParseUint(currentUser.Gid, 10, 32)
|
|
||||||
for _, tt := range testCases {
|
for _, tt := range testCases {
|
||||||
uid, gid, err := GetUIDAndGIDFromString(tt, false)
|
uid, gid, err := getUIDAndGIDFromString(tt.args.userGroupStr, tt.args.fallbackToUID)
|
||||||
if uid != uint32(expectedU) || gid != uint32(expectedG) || err != nil {
|
testutil.CheckError(t, tt.wantErr, err)
|
||||||
t.Errorf("Could not correctly decode %s to uid/gid %d:%d. Result: %d:%d", tt, expectedU, expectedG,
|
if uid != tt.expected.userID || gid != tt.expected.groupID {
|
||||||
|
t.Errorf("%v failed. Could not correctly decode %s to uid/gid %d:%d. Result: %d:%d",
|
||||||
|
tt.testname,
|
||||||
|
tt.args.userGroupStr,
|
||||||
|
tt.expected.userID, tt.expected.groupID,
|
||||||
uid, gid)
|
uid, gid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLookupUser(t *testing.T) {
|
||||||
|
currentUser := testutil.GetCurrentUser(t)
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
userStr string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
testname string
|
||||||
|
args args
|
||||||
|
expected *user.User
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testname: "non-existing user",
|
||||||
|
args: args{
|
||||||
|
userStr: "foobazbar",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testname: "uid",
|
||||||
|
args: args{
|
||||||
|
userStr: "30000",
|
||||||
|
},
|
||||||
|
expected: &user.User{
|
||||||
|
Uid: "30000",
|
||||||
|
HomeDir: "/",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testname: "current user",
|
||||||
|
args: args{
|
||||||
|
userStr: currentUser.Username,
|
||||||
|
},
|
||||||
|
expected: currentUser.User,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.testname, func(t *testing.T) {
|
||||||
|
got, err := LookupUser(tt.args.userStr)
|
||||||
|
testutil.CheckErrorAndDeepEqual(t, tt.wantErr, err, tt.expected, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,10 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DoNotChangeUID = -1
|
const (
|
||||||
const DoNotChangeGID = -1
|
DoNotChangeUID = -1
|
||||||
|
DoNotChangeGID = -1
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
snapshotTimeout = "SNAPSHOT_TIMEOUT_DURATION"
|
snapshotTimeout = "SNAPSHOT_TIMEOUT_DURATION"
|
||||||
|
|
@ -539,6 +541,26 @@ func FilepathExists(path string) bool {
|
||||||
return !os.IsNotExist(err)
|
return !os.IsNotExist(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resetFileOwnershipIfNotMatching function changes ownership of the file at path to newUID and newGID.
|
||||||
|
// If the ownership already matches, chown is not executed.
|
||||||
|
func resetFileOwnershipIfNotMatching(path string, newUID, newGID uint32) error {
|
||||||
|
fsInfo, err := os.Lstat(path)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "getting stat of present file")
|
||||||
|
}
|
||||||
|
stat, ok := fsInfo.Sys().(*syscall.Stat_t)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("can't convert fs.FileInfo of %v to linux syscall.Stat_t", path)
|
||||||
|
}
|
||||||
|
if stat.Uid != newUID && stat.Gid != newGID {
|
||||||
|
err = os.Chown(path, int(newUID), int(newGID))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "reseting file ownership to root")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// CreateFile creates a file at path and copies over contents from the reader
|
// CreateFile creates a file at path and copies over contents from the reader
|
||||||
func CreateFile(path string, reader io.Reader, perm os.FileMode, uid uint32, gid uint32) error {
|
func CreateFile(path string, reader io.Reader, perm os.FileMode, uid uint32, gid uint32) error {
|
||||||
// Create directory path if it doesn't exist
|
// Create directory path if it doesn't exist
|
||||||
|
|
@ -546,6 +568,15 @@ func CreateFile(path string, reader io.Reader, perm os.FileMode, uid uint32, gid
|
||||||
return errors.Wrap(err, "creating parent dir")
|
return errors.Wrap(err, "creating parent dir")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the file is already created with ownership other than root, reset the ownership
|
||||||
|
if FilepathExists(path) {
|
||||||
|
logrus.Debugf("file at %v already exists, resetting file ownership to root", path)
|
||||||
|
err := resetFileOwnershipIfNotMatching(path, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "reseting file ownership")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dest, err := os.Create(path)
|
dest, err := os.Create(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "creating file")
|
return errors.Wrap(err, "creating file")
|
||||||
|
|
@ -793,7 +824,12 @@ func mkdirAllWithPermissions(path string, mode os.FileMode, uid, gid int64) erro
|
||||||
}
|
}
|
||||||
if uid > math.MaxUint32 || gid > math.MaxUint32 {
|
if uid > math.MaxUint32 || gid > math.MaxUint32 {
|
||||||
// due to https://github.com/golang/go/issues/8537
|
// due to https://github.com/golang/go/issues/8537
|
||||||
return errors.New(fmt.Sprintf("Numeric User-ID or Group-ID greater than %v are not properly supported.", uint64(math.MaxUint32)))
|
return errors.New(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"Numeric User-ID or Group-ID greater than %v are not properly supported.",
|
||||||
|
uint64(math.MaxUint32),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if err := os.Chown(path, int(uid), int(gid)); err != nil {
|
if err := os.Chown(path, int(uid), int(gid)); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -851,7 +887,6 @@ func CreateTargetTarfile(tarpath string) (*os.File, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return os.Create(tarpath)
|
return os.Create(tarpath)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if a file is a symlink
|
// Returns true if a file is a symlink
|
||||||
|
|
@ -1009,8 +1044,8 @@ type walkFSResult struct {
|
||||||
func WalkFS(
|
func WalkFS(
|
||||||
dir string,
|
dir string,
|
||||||
existingPaths map[string]struct{},
|
existingPaths map[string]struct{},
|
||||||
changeFunc func(string) (bool, error)) ([]string, map[string]struct{}) {
|
changeFunc func(string) (bool, error),
|
||||||
|
) ([]string, map[string]struct{}) {
|
||||||
timeOutStr := os.Getenv(snapshotTimeout)
|
timeOutStr := os.Getenv(snapshotTimeout)
|
||||||
if timeOutStr == "" {
|
if timeOutStr == "" {
|
||||||
logrus.Tracef("Environment '%s' not set. Using default snapshot timeout '%s'", snapshotTimeout, defaultTimeout)
|
logrus.Tracef("Environment '%s' not set. Using default snapshot timeout '%s'", snapshotTimeout, defaultTimeout)
|
||||||
|
|
@ -1102,7 +1137,6 @@ func GetFSInfoMap(dir string, existing map[string]os.FileInfo) (map[string]os.Fi
|
||||||
fileMap[path] = fi
|
fileMap[path] = fi
|
||||||
foundPaths = append(foundPaths, path)
|
foundPaths = append(foundPaths, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -26,5 +26,9 @@ import (
|
||||||
|
|
||||||
// groupIDs returns all of the group ID's a user is a member of
|
// groupIDs returns all of the group ID's a user is a member of
|
||||||
func groupIDs(u *user.User) ([]string, error) {
|
func groupIDs(u *user.User) ([]string, error) {
|
||||||
|
// user can have no gid if it's a non existing user
|
||||||
|
if u.Gid == "" {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
return u.GroupIds()
|
return u.GroupIds()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,11 @@ type group struct {
|
||||||
func groupIDs(u *user.User) ([]string, error) {
|
func groupIDs(u *user.User) ([]string, error) {
|
||||||
logrus.Infof("Performing slow lookup of group ids for %s", u.Username)
|
logrus.Infof("Performing slow lookup of group ids for %s", u.Username)
|
||||||
|
|
||||||
|
// user can have no gid if it's a non existing user
|
||||||
|
if u.Gid == "" {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
f, err := os.Open(groupFile)
|
f, err := os.Open(groupFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "open")
|
return nil, errors.Wrap(err, "open")
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
|
@ -25,18 +26,19 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func SyscallCredentials(userStr string) (*syscall.Credential, error) {
|
func SyscallCredentials(userStr string) (*syscall.Credential, error) {
|
||||||
uid, gid, err := GetUIDAndGIDFromString(userStr, true)
|
uid, gid, err := getUIDAndGIDFromString(userStr, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "get uid/gid")
|
return nil, errors.Wrap(err, "get uid/gid")
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := Lookup(userStr)
|
u, err := LookupUser(fmt.Sprint(uid))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "lookup")
|
return nil, errors.Wrap(err, "lookup")
|
||||||
}
|
}
|
||||||
logrus.Infof("Util.Lookup returned: %+v", u)
|
logrus.Infof("Util.Lookup returned: %+v", u)
|
||||||
|
|
||||||
var groups []uint32
|
// initiliaze empty
|
||||||
|
groups := []uint32{}
|
||||||
|
|
||||||
gidStr, err := groupIDs(u)
|
gidStr, err := groupIDs(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/GoogleContainerTools/kaniko/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSyscallCredentials(t *testing.T) {
|
||||||
|
currentUser := testutil.GetCurrentUser(t)
|
||||||
|
uid, _ := strconv.ParseUint(currentUser.Uid, 10, 32)
|
||||||
|
currentUserUID32 := uint32(uid)
|
||||||
|
gid, _ := strconv.ParseUint(currentUser.Gid, 10, 32)
|
||||||
|
currentUserGID32 := uint32(gid)
|
||||||
|
|
||||||
|
currentUserGroupIDsU32 := []uint32{}
|
||||||
|
currentUserGroupIDs, _ := currentUser.GroupIds()
|
||||||
|
for _, id := range currentUserGroupIDs {
|
||||||
|
id32, _ := strconv.ParseUint(id, 10, 32)
|
||||||
|
currentUserGroupIDsU32 = append(currentUserGroupIDsU32, uint32(id32))
|
||||||
|
}
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
userStr string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want *syscall.Credential
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "non-existing user without group",
|
||||||
|
args: args{
|
||||||
|
userStr: "helloworld-user",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-existing uid without group",
|
||||||
|
args: args{
|
||||||
|
userStr: "50000",
|
||||||
|
},
|
||||||
|
want: &syscall.Credential{
|
||||||
|
Uid: 50000,
|
||||||
|
// because fallback is enabled
|
||||||
|
Gid: 50000,
|
||||||
|
Groups: []uint32{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-existing uid with existing gid",
|
||||||
|
args: args{
|
||||||
|
userStr: fmt.Sprintf("50000:%d", currentUserGID32),
|
||||||
|
},
|
||||||
|
want: &syscall.Credential{
|
||||||
|
Uid: 50000,
|
||||||
|
Gid: currentUserGID32,
|
||||||
|
Groups: []uint32{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "existing username with non-existing gid",
|
||||||
|
args: args{
|
||||||
|
userStr: fmt.Sprintf("%s:50000", currentUser.Username),
|
||||||
|
},
|
||||||
|
want: &syscall.Credential{
|
||||||
|
Uid: currentUserUID32,
|
||||||
|
Gid: 50000,
|
||||||
|
Groups: currentUserGroupIDsU32,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := SyscallCredentials(tt.args.userStr)
|
||||||
|
testutil.CheckErrorAndDeepEqual(t, tt.wantErr, err, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,14 +14,16 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
set -e
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
|
||||||
|
#set -e
|
||||||
|
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
RESET='\033[0m'
|
RESET='\033[0m'
|
||||||
|
|
||||||
echo "Running go tests..."
|
echo "Running go tests..."
|
||||||
go test -cover -v -timeout 60s `go list ./... | grep -v vendor | grep -v integration` | sed ''/PASS/s//$(printf "${GREEN}PASS${RESET}")/'' | sed ''/FAIL/s//$(printf "${RED}FAIL${RESET}")/''
|
go test -cover -coverprofile=out/coverage.out -v -timeout 60s `go list ./... | grep -v vendor | grep -v integration` | sed ''/PASS/s//$(printf "${GREEN}PASS${RESET}")/'' | sed ''/FAIL/s//$(printf "${RED}FAIL${RESET}")/''
|
||||||
GO_TEST_EXIT_CODE=${PIPESTATUS[0]}
|
GO_TEST_EXIT_CODE=${PIPESTATUS[0]}
|
||||||
if [[ $GO_TEST_EXIT_CODE -ne 0 ]]; then
|
if [[ $GO_TEST_EXIT_CODE -ne 0 ]]; then
|
||||||
exit $GO_TEST_EXIT_CODE
|
exit $GO_TEST_EXIT_CODE
|
||||||
|
|
@ -29,15 +31,15 @@ fi
|
||||||
|
|
||||||
echo "Running validation scripts..."
|
echo "Running validation scripts..."
|
||||||
scripts=(
|
scripts=(
|
||||||
"hack/boilerplate.sh"
|
"$DIR/../hack/boilerplate.sh"
|
||||||
"hack/gofmt.sh"
|
"$DIR/../hack/gofmt.sh"
|
||||||
"hack/linter.sh"
|
"$DIR/../hack/linter.sh"
|
||||||
)
|
)
|
||||||
fail=0
|
fail=0
|
||||||
for s in "${scripts[@]}"
|
for s in "${scripts[@]}"
|
||||||
do
|
do
|
||||||
echo "RUN ${s}"
|
echo "RUN ${s}"
|
||||||
if "./${s}"; then
|
if "${s}"; then
|
||||||
echo -e "${GREEN}PASSED${RESET} ${s}"
|
echo -e "${GREEN}PASSED${RESET} ${s}"
|
||||||
else
|
else
|
||||||
echo -e "${RED}FAILED${RESET} ${s}"
|
echo -e "${RED}FAILED${RESET} ${s}"
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -41,6 +42,33 @@ func SetupFiles(path string, files map[string]string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CurrentUser struct {
|
||||||
|
*user.User
|
||||||
|
|
||||||
|
PrimaryGroup string
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCurrentUser(t *testing.T) CurrentUser {
|
||||||
|
currentUser, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Cannot get current user: %s", err)
|
||||||
|
}
|
||||||
|
groups, err := currentUser.GroupIds()
|
||||||
|
if err != nil || len(groups) == 0 {
|
||||||
|
t.Fatalf("Cannot get groups for current user: %s", err)
|
||||||
|
}
|
||||||
|
primaryGroupObj, err := user.LookupGroupId(groups[0])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not lookup name of group %s: %s", groups[0], err)
|
||||||
|
}
|
||||||
|
primaryGroup := primaryGroupObj.Name
|
||||||
|
|
||||||
|
return CurrentUser{
|
||||||
|
User: currentUser,
|
||||||
|
PrimaryGroup: primaryGroup,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func CheckDeepEqual(t *testing.T, expected, actual interface{}) {
|
func CheckDeepEqual(t *testing.T, expected, actual interface{}) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if diff := cmp.Diff(actual, expected); diff != "" {
|
if diff := cmp.Diff(actual, expected); diff != "" {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue