package lookup import ( "os" "strconv" "github.com/cyphar/filepath-securejoin" "github.com/opencontainers/runc/libcontainer/user" "github.com/sirupsen/logrus" ) const ( etcpasswd = "/etc/passwd" etcgroup = "/etc/group" ) // Overrides allows you to override defaults in GetUserGroupInfo type Overrides struct { DefaultUser *user.ExecUser ContainerEtcPasswdPath string ContainerEtcGroupPath string } // GetUserGroupInfo takes string forms of the the container's mount path and the container user and // returns a ExecUser with uid, gid, sgids, and home. And override can be provided for defaults. func GetUserGroupInfo(containerMount, containerUser string, override *Overrides) (*user.ExecUser, error) { var ( passwdDest, groupDest string defaultExecUser *user.ExecUser err error ) passwdPath := etcpasswd groupPath := etcgroup if override != nil { // Check for an override /etc/passwd path if override.ContainerEtcPasswdPath != "" { passwdPath = override.ContainerEtcPasswdPath } // Check for an override for /etc/group path if override.ContainerEtcGroupPath != "" { groupPath = override.ContainerEtcGroupPath } } // Check for an override default user if override != nil && override.DefaultUser != nil { defaultExecUser = override.DefaultUser } else { // Define a default container user //defaultExecUser = &user.ExecUser{ // Uid: 0, // Gid: 0, // Home: "/", defaultExecUser = nil } // Make sure the /etc/group and /etc/passwd destinations are not a symlink to something naughty if passwdDest, err = securejoin.SecureJoin(containerMount, passwdPath); err != nil { logrus.Debug(err) return nil, err } if groupDest, err = securejoin.SecureJoin(containerMount, groupPath); err != nil { logrus.Debug(err) return nil, err } return user.GetExecUserPath(containerUser, defaultExecUser, passwdDest, groupDest) } // GetContainerGroups uses securejoin to get a list of numerical groupids from a container. Per the runc // function it calls: If a group name cannot be found, an error will be returned. If a group id cannot be found, // or the given group data is nil, the id will be returned as-is provided it is in the legal range. func GetContainerGroups(groups []string, containerMount string, override *Overrides) ([]uint32, error) { var ( groupDest string err error uintgids []uint32 ) groupPath := etcgroup if override != nil && override.ContainerEtcGroupPath != "" { groupPath = override.ContainerEtcGroupPath } if groupDest, err = securejoin.SecureJoin(containerMount, groupPath); err != nil { logrus.Debug(err) return nil, err } gids, err := user.GetAdditionalGroupsPath(groups, groupDest) if err != nil { return nil, err } // For libpod, we want []uint32s for _, gid := range gids { uintgids = append(uintgids, uint32(gid)) } return uintgids, nil } // GetUser takes a containermount path and user name or ID and returns // a matching User structure from /etc/passwd. If it cannot locate a user // with the provided information, an ErrNoPasswdEntries is returned. // When the provided user name was an ID, a User structure with Uid // set is returned along with ErrNoPasswdEntries. func GetUser(containerMount, userIDorName string) (*user.User, error) { var inputIsName bool uid, err := strconv.Atoi(userIDorName) if err != nil { inputIsName = true } passwdDest, err := securejoin.SecureJoin(containerMount, etcpasswd) if err != nil { return nil, err } users, err := user.ParsePasswdFileFilter(passwdDest, func(u user.User) bool { if inputIsName { return u.Name == userIDorName } return u.Uid == uid }) if err != nil && !os.IsNotExist(err) { return nil, err } if len(users) > 0 { return &users[0], nil } if !inputIsName { return &user.User{Uid: uid}, user.ErrNoPasswdEntries } return nil, user.ErrNoPasswdEntries } // GetGroup takes a containermount path and a group name or ID and returns // a match Group struct from /etc/group. If it cannot locate a group, // an ErrNoGroupEntries error is returned. When the provided group name // was an ID, a Group structure with Gid set is returned along with // ErrNoGroupEntries. func GetGroup(containerMount, groupIDorName string) (*user.Group, error) { var inputIsName bool gid, err := strconv.Atoi(groupIDorName) if err != nil { inputIsName = true } groupDest, err := securejoin.SecureJoin(containerMount, etcgroup) if err != nil { return nil, err } groups, err := user.ParseGroupFileFilter(groupDest, func(g user.Group) bool { if inputIsName { return g.Name == groupIDorName } return g.Gid == gid }) if err != nil && !os.IsNotExist(err) { return nil, err } if len(groups) > 0 { return &groups[0], nil } if !inputIsName { return &user.Group{Gid: gid}, user.ErrNoGroupEntries } return nil, user.ErrNoGroupEntries }