Repository: moby/moby
File: daemon/archive_tarcopyoptions_unix.go (Go)

Code before:
//go:build !windows
// +build !windows

package daemon // import "github.com/docker/docker/daemon"

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/docker/docker/container"
	"github.com/docker/docker/pkg/archive"
	"github.com/docker/docker/pkg/idtools"
)

func (daemon *Daemon) tarCopyOptions(container *container.Container, noOverwriteDirNonDir bool) (*archive.TarOptions, error) {
	if container.Config.User == "" {
		return daemon.defaultTarCopyOptions(noOverwriteDirNonDir), nil
	}

	uid, gid, err := getUIDGID(container.Config.User)
	if err != nil {
		return nil, err
	}

	return &archive.TarOptions{
		NoOverwriteDirNonDir: noOverwriteDirNonDir,
		ChownOpts:            &idtools.Identity{UID: uid, GID: gid},
	}, nil
}

// getUIDGID resolves the UID and GID of a given user and, optionally, group.
//
// 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 getent(1)
//   username:groupname  - valid username; valid groupname from getent(1)
//   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 getent(1)
//
//  If only a username (or uid) is provided, an attempt is made to lookup the gid

Review comment (suggestion):
UID and GID should be resolved from within the container if we want to fix #34142. There's already a function which parses a user[:group] string and resolves it to UID/GID inside the container: [`getUser`](https://github.com/moby/moby/blob/83b51522df43866e39f7c757a6ab84ef4050eeb3/daemon/oci_linux.go#L184-L212). It does delegate to libcontainer/user, though. There's also the [`WithUser`](https://pkg.go.dev/github.com/containerd/containerd/oci#WithUser) function in containerd, though it would be a bit awkward to use here.

Code after:
//go:build !windows

package daemon // import "github.com/docker/docker/daemon"

import (
	"fmt"
	"math"
	"strconv"
	"strings"

	"github.com/docker/docker/container"
	"github.com/docker/docker/errdefs"
	"github.com/docker/docker/pkg/archive"
	"github.com/docker/docker/pkg/idtools"
	"github.com/moby/sys/user"
)

func (daemon *Daemon) tarCopyOptions(ctr *container.Container, noOverwriteDirNonDir bool) (*archive.TarOptions, error) {
	if ctr.Config.User == "" {
		return daemon.defaultTarCopyOptions(noOverwriteDirNonDir), nil
	}

	uid, gid, err := getUIDGID(ctr.Config.User)
	if err != nil {
		return nil, errdefs.InvalidParameter(err)
	}

	return &archive.TarOptions{
		NoOverwriteDirNonDir: noOverwriteDirNonDir,
		ChownOpts:            &idtools.Identity{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