(cherry picked from commit de1341c201ffb0effebbf51d00376181968c8779)
This commit is contained in:
parent
a7f1ff94d7
commit
07a0b5419c
@ -22,6 +22,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -593,6 +594,20 @@ func WithUser(userstr string) SpecOpts {
|
|||||||
defer ensureAdditionalGids(s)
|
defer ensureAdditionalGids(s)
|
||||||
setProcess(s)
|
setProcess(s)
|
||||||
s.Process.User.AdditionalGids = nil
|
s.Process.User.AdditionalGids = nil
|
||||||
|
// While the Linux kernel allows the max UID to be MaxUint32 - 2,
|
||||||
|
// and the OCI Runtime Spec has no definition about the max UID,
|
||||||
|
// the runc implementation is known to require the UID to be <= MaxInt32.
|
||||||
|
//
|
||||||
|
// containerd follows runc's limitation here.
|
||||||
|
//
|
||||||
|
// In future we may relax this limitation to allow MaxUint32 - 2,
|
||||||
|
// or, amend the OCI Runtime Spec to codify the implementation limitation.
|
||||||
|
const (
|
||||||
|
minUserID = 0
|
||||||
|
maxUserID = math.MaxInt32
|
||||||
|
minGroupID = 0
|
||||||
|
maxGroupID = math.MaxInt32
|
||||||
|
)
|
||||||
|
|
||||||
// For LCOW it's a bit harder to confirm that the user actually exists on the host as a rootfs isn't
|
// For LCOW it's a bit harder to confirm that the user actually exists on the host as a rootfs isn't
|
||||||
// mounted on the host and shared into the guest, but rather the rootfs is constructed entirely in the
|
// mounted on the host and shared into the guest, but rather the rootfs is constructed entirely in the
|
||||||
@ -611,8 +626,8 @@ func WithUser(userstr string) SpecOpts {
|
|||||||
switch len(parts) {
|
switch len(parts) {
|
||||||
case 1:
|
case 1:
|
||||||
v, err := strconv.Atoi(parts[0])
|
v, err := strconv.Atoi(parts[0])
|
||||||
if err != nil {
|
if err != nil || v < minUserID || v > maxUserID {
|
||||||
// if we cannot parse as a uint they try to see if it is a username
|
// if we cannot parse as an int32 then try to see if it is a username
|
||||||
return WithUsername(userstr)(ctx, client, c, s)
|
return WithUsername(userstr)(ctx, client, c, s)
|
||||||
}
|
}
|
||||||
return WithUserID(uint32(v))(ctx, client, c, s)
|
return WithUserID(uint32(v))(ctx, client, c, s)
|
||||||
@ -623,12 +638,13 @@ func WithUser(userstr string) SpecOpts {
|
|||||||
)
|
)
|
||||||
var uid, gid uint32
|
var uid, gid uint32
|
||||||
v, err := strconv.Atoi(parts[0])
|
v, err := strconv.Atoi(parts[0])
|
||||||
if err != nil {
|
if err != nil || v < minUserID || v > maxUserID {
|
||||||
username = parts[0]
|
username = parts[0]
|
||||||
} else {
|
} else {
|
||||||
uid = uint32(v)
|
uid = uint32(v)
|
||||||
}
|
}
|
||||||
if v, err = strconv.Atoi(parts[1]); err != nil {
|
v, err = strconv.Atoi(parts[1])
|
||||||
|
if err != nil || v < minGroupID || v > maxGroupID {
|
||||||
groupname = parts[1]
|
groupname = parts[1]
|
||||||
} else {
|
} else {
|
||||||
gid = uint32(v)
|
gid = uint32(v)
|
||||||
|
@ -33,6 +33,98 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//nolint:gosec
|
||||||
|
func TestWithUser(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
expectedPasswd := `root:x:0:0:root:/root:/bin/ash
|
||||||
|
guest:x:405:100:guest:/dev/null:/sbin/nologin
|
||||||
|
`
|
||||||
|
expectedGroup := `root:x:0:root
|
||||||
|
bin:x:1:root,bin,daemon
|
||||||
|
daemon:x:2:root,bin,daemon
|
||||||
|
sys:x:3:root,bin,adm
|
||||||
|
guest:x:100:guest
|
||||||
|
`
|
||||||
|
td := t.TempDir()
|
||||||
|
apply := fstest.Apply(
|
||||||
|
fstest.CreateDir("/etc", 0777),
|
||||||
|
fstest.CreateFile("/etc/passwd", []byte(expectedPasswd), 0777),
|
||||||
|
fstest.CreateFile("/etc/group", []byte(expectedGroup), 0777),
|
||||||
|
)
|
||||||
|
if err := apply.Apply(td); err != nil {
|
||||||
|
t.Fatalf("failed to apply: %v", err)
|
||||||
|
}
|
||||||
|
c := containers.Container{ID: t.Name()}
|
||||||
|
testCases := []struct {
|
||||||
|
user string
|
||||||
|
expectedUID uint32
|
||||||
|
expectedGID uint32
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
user: "0",
|
||||||
|
expectedUID: 0,
|
||||||
|
expectedGID: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
user: "root:root",
|
||||||
|
expectedUID: 0,
|
||||||
|
expectedGID: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
user: "guest",
|
||||||
|
expectedUID: 405,
|
||||||
|
expectedGID: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
user: "guest:guest",
|
||||||
|
expectedUID: 405,
|
||||||
|
expectedGID: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
user: "guest:nobody",
|
||||||
|
err: "no groups found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
user: "405:100",
|
||||||
|
expectedUID: 405,
|
||||||
|
expectedGID: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
user: "405:2147483648",
|
||||||
|
err: "no groups found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
user: "-1000",
|
||||||
|
err: "no users found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
user: "2147483648",
|
||||||
|
err: "no users found",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(testCase.user, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
s := Spec{
|
||||||
|
Version: specs.Version,
|
||||||
|
Root: &specs.Root{
|
||||||
|
Path: td,
|
||||||
|
},
|
||||||
|
Linux: &specs.Linux{},
|
||||||
|
}
|
||||||
|
err := WithUser(testCase.user)(context.Background(), nil, &c, &s)
|
||||||
|
if err != nil {
|
||||||
|
assert.EqualError(t, err, testCase.err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, testCase.expectedUID, s.Process.User.UID)
|
||||||
|
assert.Equal(t, testCase.expectedGID, s.Process.User.GID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//nolint:gosec
|
//nolint:gosec
|
||||||
func TestWithUserID(t *testing.T) {
|
func TestWithUserID(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
Loading…
Reference in New Issue
Block a user