| //go:build !windows |
| |
| package daemon |
| |
| import ( |
| "fmt" |
| "math" |
| "strconv" |
| "strings" |
| |
| "github.com/moby/go-archive" |
| "github.com/moby/moby/v2/daemon/container" |
| "github.com/moby/moby/v2/errdefs" |
| "github.com/moby/sys/user" |
| ) |
| |
| func (daemon *Daemon) tarCopyOptions(ctr *container.Container, allowOverwriteDirWithFile bool) (*archive.TarOptions, error) { |
| if ctr.Config.User == "" { |
| return daemon.defaultTarCopyOptions(allowOverwriteDirWithFile), nil |
| } |
| |
| uid, gid, err := getUIDGID(ctr.Config.User) |
| if err != nil { |
| return nil, errdefs.InvalidParameter(err) |
| } |
| |
| return &archive.TarOptions{ |
| NoOverwriteDirNonDir: !allowOverwriteDirWithFile, |
| ChownOpts: &archive.ChownOpts{UID: uid, GID: gid}, |
| }, nil |
| } |
| |
| // getUIDGID resolves the UID and GID of a given container's Config.User, |
| // which can contain a user name (or ID) and, optionally, group (or ID). |
| // |
| // usergrp is a username or uid, and optional group, in the format `user[:group]`. |
| // Both `user` and `group` can be provided as an `uid` / `gid`, so the following |
| // formats are supported: |
| // |
| // - username - valid username from /etc/passwd |
| // - username:groupname - valid username; valid groupname from /etc/passwd, /etc/group |
| // - uid - 32-bit unsigned int valid Linux UID value |
| // - uid:gid - uid value; 32-bit unsigned int Linux GID value |
| // - username:gid - valid username from getent(1), gid value; 32-bit unsigned int Linux GID value |
| // - uid:groupname - 32-bit unsigned int valid Linux UID value, valid groupname from /etc/group |
| // |
| // If only a username (or uid) is provided, an attempt is made to look up the gid |
| // for that username using /etc/passwd |
| func getUIDGID(ctrUser string) (uid int, gid int, _ error) { |
| userNameOrID, groupNameOrID, _ := strings.Cut(ctrUser, ":") |
| |
| // Align with behavior of docker run, which treats an empty username |
| // or groupname as default (0 (root)). |
| // |
| // docker run --rm --user ":33" alpine id |
| // uid=0(root) gid=33 groups=33 |
| // |
| // docker run --rm --user "33:" alpine id |
| // uid=33 gid=0(root) groups=0(root) |
| if userNameOrID != "" { |
| var err error |
| uid, gid, err = lookupUser(userNameOrID) |
| if err != nil { |
| return 0, 0, err |
| } |
| } |
| if groupNameOrID != "" { |
| var err error |
| gid, err = lookupGID(groupNameOrID) |
| if err != nil { |
| return 0, 0, err |
| } |
| } |
| return uid, gid, nil |
| } |
| |
| // getIDOrName checks whether nameOrID is a ID (integer) or a Name. |
| // It assumes nameOrID is a name when failing to parse as integer, |
| // in which case a non-empty name is returned. |
| func getIDOrName(nameOrID string) (id int, name string) { |
| if uid, err := strconv.ParseUint(nameOrID, 10, 32); err == nil && uid <= math.MaxInt32 { |
| // uid provided |
| return int(uid), "" |
| } |
| // not an id, assume name |
| return 0, nameOrID |
| } |
| |
| func lookupUser(nameOrID string) (uid, gid int, _ error) { |
| userID, userName := getIDOrName(nameOrID) |
| if userName != "" { |
| u, err := user.LookupUser(userName) |
| if err != nil { |
| return 0, 0, fmt.Errorf("failed to look up user %q in container: %w", userName, err) |
| } |
| return u.Uid, u.Gid, nil |
| } |
| |
| u, err := user.LookupUid(userID) |
| if err != nil { |
| // Match behavior of "docker run": when using a UID for the |
| // user, resolving the user and its primary group is best-effort. |
| // If a user with the given UID is found, we use its primary |
| // group, otherwise use it as-is and use the default (0) as |
| // GID. |
| // |
| // docker run --rm --user 12345 ubuntu id |
| // uid=12345 gid=0(root) groups=0(root) |
| // |
| // docker run --rm ubuntu cat /etc/passwd | grep www-data |
| // www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin |
| // |
| // docker run --rm --user 33 ubuntu id |
| // uid=33(www-data) gid=33(www-data) groups=33(www-data) |
| return userID, 0, nil |
| } |
| return u.Uid, u.Gid, nil |
| } |
| |
| func lookupGID(nameOrID string) (int, error) { |
| groupID, groupName := getIDOrName(nameOrID) |
| if groupName == "" { |
| // GID is passed, no need to look up |
| return groupID, nil |
| } |
| group, err := user.LookupGroup(groupName) |
| if err != nil { |
| return 0, fmt.Errorf("failed to look up group %q in container: %w", nameOrID, err) |
| } |
| return group.Gid, nil |
| } |