build(deps): bump github.com/moby/sys/user from 0.4.1 to 0.5.0

Bumps [github.com/moby/sys/user](https://github.com/moby/sys) from 0.4.1 to 0.5.0.
- [Release notes](https://github.com/moby/sys/releases)
- [Commits](https://github.com/moby/sys/compare/user/v0.4.1...user/v0.5.0)

---
updated-dependencies:
- dependency-name: github.com/moby/sys/user
  dependency-version: 0.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot] authored and GitHub committed 2026-11-04 05:25:55 +00:00
1 parent acc3f393b3
commit 9425301505
8 files changed
+155 -129

No files matched your search

+1 -1
View File
@@ -12,7 +12,7 @@ require (
github.com/godbus/dbus/v5 v5.2.4
github.com/moby/sys/capability v0.5.0
github.com/moby/sys/mountinfo v0.7.3
github.com/moby/sys/user v0.4.1
github.com/moby/sys/user v0.5.0
github.com/moby/sys/userns v0.2.1
github.com/mrunalp/fileutils v0.5.1
github.com/opencontainers/cgroups v0.1.0
+2 -2
View File
@@ -28,8 +28,8 @@ github.com/moby/sys/capability v0.5.0 h1:Bm4oD9xGCtZguPnaoQy1/jvKTSwZHMvM0FrnxNY
github.com/moby/sys/capability v0.5.0/go.mod h1:i+0cXvNSriGrL3C6sxnmlmGL5HVU0OdxocizXdC8A+g=
github.com/moby/sys/mountinfo v0.7.3 h1:e0lin8d05kLgtUES/4cS/F70KlbZX/G3aZjLsV7WAgA=
github.com/moby/sys/mountinfo v0.7.3/go.mod h1:dEJRr+H+hx0lNG0BE+Smz95u/FDzjJv4/RjA2I456Iw=
github.com/moby/sys/user v0.4.1 h1:RgjRlaDKi/Xmyrz4t8lyzXT6v2ooFeO/7xtchmhVWE0=
github.com/moby/sys/user v0.4.1/go.mod h1:E9QsW5WRe1kUAf7kW8hXKwu1uhsZEAdPLYHYSDudF4Y=
github.com/moby/sys/user v0.5.0 h1:5+S4lyoavGyX1DGs68NW8xpx48T9+a7ogSUc0KjZg8Y=
github.com/moby/sys/user v0.5.0/go.mod h1:4mQzP5aTmaLjDLp/dc870oNkik6E9vjRtWYwI05gvow=
github.com/moby/sys/userns v0.2.1 h1:4OvdM7BcPkASbuouHsbW3aeMJSFlYDldBRnXVZhaRk8=
github.com/moby/sys/userns v0.2.1/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/mrunalp/fileutils v0.5.1 h1:F+S7ZlNKnrwHfSwdlgNSkKo67ReVf8o9fel6C3dkm/Q=
+31 -37
View File
@@ -12,37 +12,39 @@ type mkdirOptions struct {
onlyNew bool
}
// WithOnlyNew is an option for MkdirAllAndChown that will only change ownership and permissions
// on newly created directories. If the directory already exists, it will not be modified
// WithOnlyNew configures [MkdirAllAndChown] and [MkdirAndChown] to leave
// the requested directory unchanged if it already exists. Newly created
// directories are still assigned the requested ownership and permissions.
func WithOnlyNew(o *mkdirOptions) {
o.onlyNew = true
}
// MkdirAllAndChown creates a directory (include any along the path) and then modifies
// ownership to the requested uid/gid. By default, if the directory already exists, this
// function will still change ownership and permissions. If WithOnlyNew is passed as an
// option, then only the newly created directories will have ownership and permissions changed.
// MkdirAllAndChown creates a directory named path, along with any necessary
// parents, and applies the requested ownership and permission bits to all
// directories it creates.
//
// If path already exists as a directory, its ownership and permission bits
// are updated unless [WithOnlyNew] is specified. Existing parent directories
// are not modified.
//
// MkdirAllAndChown is similar to [os.MkdirAll], but also applies ownership and
// reapplies the requested permission bits after creation, so the resulting
// permissions are not affected by the process umask.
func MkdirAllAndChown(path string, mode os.FileMode, uid, gid int, opts ...MkdirOpt) error {
var options mkdirOptions
for _, opt := range opts {
opt(&options)
}
return mkdirAs(path, mode, uid, gid, true, options.onlyNew)
return mkdirAs(path, mode, uid, gid, true, opts...)
}
// MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid.
// By default, if the directory already exists, this function still changes ownership and permissions.
// If WithOnlyNew is passed as an option, then only the newly created directory will have ownership
// and permissions changed.
// Note that unlike os.Mkdir(), this function does not return IsExist error
// in case path already exists.
// MkdirAndChown creates a directory named path and applies the requested
// ownership and permission bits.
//
// If path already exists as a directory, its ownership and permission bits
// are updated unless [WithOnlyNew] is specified.
//
// MkdirAndChown is similar to [os.Mkdir], but returns nil if path already
// exists as a directory. The resulting permission bits are not affected by
// the process umask.
func MkdirAndChown(path string, mode os.FileMode, uid, gid int, opts ...MkdirOpt) error {
var options mkdirOptions
for _, opt := range opts {
opt(&options)
}
return mkdirAs(path, mode, uid, gid, false, options.onlyNew)
return mkdirAs(path, mode, uid, gid, false, opts...)
}
// getRootUIDGID retrieves the remapped root uid/gid pair from the set of maps.
@@ -106,22 +108,14 @@ func (i IdentityMapping) RootPair() (int, int) {
return uid, gid
}
// ToHost returns the host UID and GID for the container uid, gid.
// Remapping is only performed if the ids aren't already the remapped root ids
// ToHost returns the host UID and GID for the container uid and gid.
// An empty mapping is treated as identity.
func (i IdentityMapping) ToHost(uid, gid int) (int, int, error) {
var err error
ruid, rgid := i.RootPair()
if uid != ruid {
ruid, err = toHost(uid, i.UIDMaps)
if err != nil {
return ruid, rgid, err
}
}
if gid != rgid {
rgid, err = toHost(gid, i.GIDMaps)
ruid, err := toHost(uid, i.UIDMaps)
if err != nil {
return -1, -1, err
}
rgid, err := toHost(gid, i.GIDMaps)
return ruid, rgid, err
}
+7 -2
View File
@@ -10,7 +10,12 @@ import (
"syscall"
)
func mkdirAs(path string, mode os.FileMode, uid, gid int, mkAll, onlyNew bool) error {
func mkdirAs(path string, mode os.FileMode, uid, gid int, mkAll bool, opts ...MkdirOpt) error {
var options mkdirOptions
for _, opt := range opts {
opt(&options)
}
path, err := filepath.Abs(path)
if err != nil {
return err
@@ -21,7 +26,7 @@ func mkdirAs(path string, mode os.FileMode, uid, gid int, mkAll, onlyNew bool) e
if !stat.IsDir() {
return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
}
if onlyNew {
if options.onlyNew {
return nil
}
+19 -6
View File
@@ -2,12 +2,25 @@ package user
import (
"os"
"syscall"
)
// This is currently a wrapper around [os.MkdirAll] since currently
// permissions aren't set through this path, the identity isn't utilized.
// Ownership is handled elsewhere, but in the future could be support here
// too.
func mkdirAs(path string, _ os.FileMode, _, _ int, _, _ bool) error {
return os.MkdirAll(path, 0)
// mkdirAs creates path, optionally creating any missing parent directories.
//
// On Windows this is currently a thin wrapper around os.Mkdir and
// os.MkdirAll. Unlike the Unix implementation, ownership and permission
// bits are not applied.
func mkdirAs(path string, _ os.FileMode, _, _ int, mkAll bool, _ ...MkdirOpt) error {
if mkAll {
return os.MkdirAll(path, 0)
}
stat, err := os.Stat(path)
if err == nil {
if !stat.IsDir() {
return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
}
return nil
}
return os.Mkdir(path, 0)
}
+31 -79
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"os"
"slices"
"strconv"
"strings"
)
@@ -17,9 +18,9 @@ const (
)
var (
// ErrNoPasswdEntries is returned if no matching entries were found in /etc/group.
// ErrNoPasswdEntries is returned if no matching entries were found in /etc/passwd.
ErrNoPasswdEntries = errors.New("no matching entries in passwd file")
// ErrNoGroupEntries is returned if no matching entries were found in /etc/passwd.
// ErrNoGroupEntries is returned if no matching entries were found in /etc/group.
ErrNoGroupEntries = errors.New("no matching entries in group file")
// ErrRange is returned if a UID or GID is outside of the valid range.
ErrRange = fmt.Errorf("uids and gids must be in range %d-%d", minID, maxID)
@@ -243,9 +244,6 @@ func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath
//
// See https://github.com/containerd/containerd/commit/de1341c201ffb0effebbf51d00376181968c8779
func parseNumeric(val string) (int, bool, error) {
if val == "" {
return 0, false, nil
}
id, err := strconv.Atoi(val)
if err != nil {
if errors.Is(err, strconv.ErrSyntax) {
@@ -296,51 +294,40 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
user := &ExecUser{
Uid: defaults.Uid,
Gid: defaults.Gid,
Sgids: defaults.Sgids,
Sgids: append([]int{}, defaults.Sgids...), // Sgids slice *cannot* be nil.
Home: defaults.Home,
}
// Sgids slice *cannot* be nil.
if user.Sgids == nil {
user.Sgids = []int{}
}
// Allow for userSpec to have either "user", or optionally "user:group" syntax.
usr, grp, _ := strings.Cut(userSpec, ":")
// Allow for userArg to have either "user" syntax, or optionally "user:group" syntax
var userArg, groupArg string
parseLine([]byte(userSpec), &userArg, &groupArg)
// Convert userArg and groupArg to be numeric, so we don't have to execute
// Convert usr and grp to be numeric, so we don't have to execute
// Atoi *twice* for each iteration over lines.
uidArg, isUID, err := parseNumeric(userArg)
userArg, err := parseUserArg(usr)
if err != nil {
return nil, err
}
gidArg, isGID, err := parseNumeric(groupArg)
groupArg, err := parseGroupArg(grp)
if err != nil {
return nil, err
}
// Find the matching user.
users, err := ParsePasswdFilter(passwd, func(u User) bool {
if userArg == "" {
if userArg == nil {
// Default to current state of the user.
return u.Uid == user.Uid
}
if isUID {
// If the userArg is a valid numeric value, always treat it as a UID.
return uidArg == u.Uid
}
return u.Name == userArg
return userArg.matches(u)
})
// If we can't find the user, we have to bail.
if err != nil && passwd != nil {
if userArg == "" {
userArg = strconv.Itoa(user.Uid)
name := usr
if userArg == nil {
name = strconv.Itoa(user.Uid)
}
return nil, fmt.Errorf("unable to find user %s: %w", userArg, err)
return nil, fmt.Errorf("unable to find user %s: %w", name, err)
}
var matchedUserName string
@@ -350,57 +337,42 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
user.Uid = users[0].Uid
user.Gid = users[0].Gid
user.Home = users[0].Home
} else if userArg != "" {
} else if userArg != nil {
// If we can't find a user with the given username, the only other valid
// option is if it's a numeric username with no associated entry in passwd.
if !isUID {
// Not numeric.
return nil, fmt.Errorf("unable to find user %s: %w", userArg, ErrNoPasswdEntries)
if !userArg.isNumeric {
return nil, fmt.Errorf("unable to find user %s: %w", userArg.name, ErrNoPasswdEntries)
}
user.Uid = uidArg
user.Uid = userArg.uid
}
// On to the groups. If we matched a username, we need to do this because of
// the supplementary group IDs.
if groupArg != "" || matchedUserName != "" {
if groupArg != nil || matchedUserName != "" {
groups, err := ParseGroupFilter(group, func(g Group) bool {
// If the group argument isn't explicit, we'll just search for it.
if groupArg == "" {
if groupArg == nil {
// Check if user is a member of this group.
for _, u := range g.List {
if u == matchedUserName {
return true
}
}
return false
return slices.Contains(g.List, matchedUserName)
}
if isGID {
// If the groupArg is numeric, always treat it as a GID.
return gidArg == g.Gid
}
return g.Name == groupArg
return groupArg.matches(g)
})
if err != nil && group != nil {
return nil, fmt.Errorf("unable to find groups for spec %v: %w", matchedUserName, err)
}
// Only start modifying user.Gid if it is in explicit form.
if groupArg != "" {
if groupArg != nil {
if len(groups) > 0 {
// First match wins, even if there's more than one matching entry.
user.Gid = groups[0].Gid
} else {
// If we can't find a group with the given name, the only other valid
// option is if it's a numeric group name with no associated entry in group.
if !isGID {
// Not numeric.
return nil, fmt.Errorf("unable to find group %s: %w", groupArg, ErrNoGroupEntries)
if !groupArg.isNumeric {
return nil, fmt.Errorf("unable to find group %s: %w", groupArg.name, ErrNoGroupEntries)
}
user.Gid = gidArg
user.Gid = groupArg.gid
}
} else if len(groups) > 0 {
// Supplementary group ids only make sense if in the implicit form.
@@ -414,39 +386,19 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
return user, nil
}
// groupArg is a parsed group argument for [GetAdditionalGroups].
type groupArg struct {
name string
gid int
isNumeric bool
}
// matches reports whether group g satisfies the argument. Numeric arguments
// are matched by GID only, others by name.
func (ag groupArg) matches(g Group) bool {
if ag.isNumeric {
return g.Gid == ag.gid
}
return g.Name == ag.name
}
// GetAdditionalGroups looks up a list of groups by name or group id
// against the given /etc/group formatted data. 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 GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, error) {
addtlGroups := make([]groupArg, len(additionalGroups))
for i, ag := range additionalGroups {
gid, ok, err := parseNumeric(ag)
addtlGroups := make([]groupArg, 0, len(additionalGroups))
for _, ag := range additionalGroups {
groupArg, err := parseGroupArg(ag)
if err != nil {
return nil, err
}
addtlGroups[i] = groupArg{
name: ag,
gid: gid,
isNumeric: ok,
}
addtlGroups = append(addtlGroups, *groupArg)
}
groups := []Group{}
+62
View File
@@ -62,3 +62,65 @@ func (l *limitedFile) Read(p []byte) (int, error) {
}
return n, err
}
// userArg is a parsed user argument for [GetExecUser].
type userArg struct {
name string
uid int
isNumeric bool
}
// parseUserArg parses a user argument as either a user name or UID.
//
// If name is empty, parseUserArg returns "nil, nil" to indicate that no user
// argument was specified.
func parseUserArg(name string) (*userArg, error) {
if name == "" {
return nil, nil
}
uid, isNumeric, err := parseNumeric(name)
if err != nil {
return nil, err
}
return &userArg{name: name, uid: uid, isNumeric: isNumeric}, nil
}
// matches reports whether user u satisfies the argument. Numeric arguments
// are matched by UID only, others by name.
func (ua *userArg) matches(u User) bool {
if ua.isNumeric {
return u.Uid == ua.uid
}
return u.Name == ua.name
}
// groupArg is a parsed group argument for [GetAdditionalGroups] or [GetExecUser].
type groupArg struct {
name string
gid int
isNumeric bool
}
// parseGroupArg parses a group argument as either a group name or GID.
//
// If name is empty, parseGroupArg returns "nil, nil" to indicate that no group
// argument was specified.
func parseGroupArg(name string) (*groupArg, error) {
if name == "" {
return nil, nil
}
gid, isNumeric, err := parseNumeric(name)
if err != nil {
return nil, err
}
return &groupArg{name: name, gid: gid, isNumeric: isNumeric}, nil
}
// matches reports whether group g satisfies the argument. Numeric arguments
// are matched by GID only, others by name.
func (ag groupArg) matches(g Group) bool {
if ag.isNumeric {
return g.Gid == ag.gid
}
return g.Name == ag.name
}
+2 -2
View File
@@ -52,8 +52,8 @@ github.com/moby/sys/capability
# github.com/moby/sys/mountinfo v0.7.3
## explicit; go 1.18
github.com/moby/sys/mountinfo
# github.com/moby/sys/user v0.4.1
## explicit; go 1.18
# github.com/moby/sys/user v0.5.0
## explicit; go 1.24
github.com/moby/sys/user
# github.com/moby/sys/userns v0.2.1
## explicit; go 1.21