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:
Lukas
2022-07-12 10:21:37 -04:00
committed by GitHub
parent 8710ce3311
commit aad03dc285
18 changed files with 542 additions and 150 deletions
+85 -60
View File
@@ -23,7 +23,6 @@ import (
"os"
"os/user"
"path/filepath"
reflect "reflect"
"strconv"
"strings"
@@ -39,7 +38,7 @@ import (
// for testing
var (
getUIDAndGID = GetUIDAndGIDFromString
getUIDAndGIDFunc = getUIDAndGID
)
const (
@@ -353,7 +352,7 @@ func GetUserGroup(chownStr string, env []string) (int64, int64, error) {
return -1, -1, err
}
uid32, gid32, err := getUIDAndGID(chown, true)
uid32, gid32, err := getUIDAndGIDFromString(chown, true)
if err != nil {
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'.
// If fallbackToUID is set, the gid is equal to uid if the group is not specified
// 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, ":")
userStr := userAndGroup[0]
var groupStr string
if len(userAndGroup) > 1 {
groupStr = userAndGroup[1]
}
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
return getUIDAndGIDFunc(userStr, groupStr, fallbackToUID)
}
func GetUserFromUsername(userStr string, groupStr string, fallbackToUID bool) (string, string, error) {
// Lookup by username
userObj, err := Lookup(userStr)
func getUIDAndGID(userStr string, groupStr string, fallbackToUID bool) (uint32, uint32, error) {
user, err := LookupUser(userStr)
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
var group *user.Group
if groupStr != "" {
group, err = user.LookupGroup(groupStr)
gid, err := getGIDFromName(groupStr, fallbackToUID)
if err != nil {
if errors.Is(err, fallbackToUIDError) {
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 _, ok := err.(user.UnknownGroupError); !ok {
return "", "", err
}
group, err = user.LookupGroupId(groupStr)
if err != nil {
return "", "", err
}
return getGID(groupStr, fallbackToUID)
}
}
uid := userObj.Uid
gid := "0"
if fallbackToUID {
gid = userObj.Gid
}
if group != nil {
gid = group.Gid
}
return uid, gid, nil
return getGID(group.Gid, fallbackToUID)
}
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)
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
}
// Lookup by id
u, e := user.LookupId(userStr)
if e != nil {
return nil, err
userObj, err = user.LookupId(userStr)
if err != nil {
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
}
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
}