Merge pull request #10307 from henry118/uidmap

Support multiple uid/gid mappings [1/2]
This commit is contained in:
Fu Wei 2024-09-23 12:25:05 +00:00 committed by GitHub
commit 906c23218c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 557 additions and 72 deletions

View File

@ -27,32 +27,55 @@ import (
"github.com/containerd/containerd/v2/core/containers"
"github.com/containerd/containerd/v2/core/mount"
"github.com/containerd/containerd/v2/internal/userns"
"github.com/containerd/errdefs"
"github.com/opencontainers/image-spec/identity"
"github.com/opencontainers/runtime-spec/specs-go"
)
// WithRemappedSnapshot creates a new snapshot and remaps the uid/gid for the
// filesystem to be used by a container with user namespaces
func WithRemappedSnapshot(id string, i Image, uid, gid uint32) NewContainerOpts {
return withRemappedSnapshotBase(id, i, uid, gid, false)
uidmaps := []specs.LinuxIDMapping{{ContainerID: 0, HostID: uid, Size: 65536}}
gidmaps := []specs.LinuxIDMapping{{ContainerID: 0, HostID: gid, Size: 65536}}
return withRemappedSnapshotBase(id, i, uidmaps, gidmaps, false)
}
// WithUserNSRemappedSnapshot creates a new snapshot and remaps the uid/gid for the
// filesystem to be used by a container with user namespaces
func WithUserNSRemappedSnapshot(id string, i Image, uidmaps, gidmaps []specs.LinuxIDMapping) NewContainerOpts {
return withRemappedSnapshotBase(id, i, uidmaps, gidmaps, false)
}
// WithRemappedSnapshotView is similar to WithRemappedSnapshot but rootfs is mounted as read-only.
func WithRemappedSnapshotView(id string, i Image, uid, gid uint32) NewContainerOpts {
return withRemappedSnapshotBase(id, i, uid, gid, true)
uidmaps := []specs.LinuxIDMapping{{ContainerID: 0, HostID: uid, Size: 65536}}
gidmaps := []specs.LinuxIDMapping{{ContainerID: 0, HostID: gid, Size: 65536}}
return withRemappedSnapshotBase(id, i, uidmaps, gidmaps, true)
}
func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool) NewContainerOpts {
// WithUserNSRemappedSnapshotView is similar to WithUserNSRemappedSnapshot but rootfs is mounted as read-only.
func WithUserNSRemappedSnapshotView(id string, i Image, uidmaps, gidmaps []specs.LinuxIDMapping) NewContainerOpts {
return withRemappedSnapshotBase(id, i, uidmaps, gidmaps, true)
}
func withRemappedSnapshotBase(id string, i Image, uidmaps, gidmaps []specs.LinuxIDMapping, readonly bool) NewContainerOpts {
return func(ctx context.Context, client *Client, c *containers.Container) error {
diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), client.platform)
if err != nil {
return err
}
var (
parent = identity.ChainID(diffIDs).String()
usernsID = fmt.Sprintf("%s-%d-%d", parent, uid, gid)
)
rsn := remappedSnapshot{
Parent: identity.ChainID(diffIDs).String(),
IDMap: userns.IDMap{UidMap: uidmaps, GidMap: gidmaps},
}
usernsID, err := rsn.ID()
if err != nil {
return fmt.Errorf("failed to remap snapshot: %w", err)
}
c.Snapshotter, err = client.resolveSnapshotterName(ctx, c.Snapshotter)
if err != nil {
return err
@ -70,11 +93,11 @@ func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool
return err
}
}
mounts, err := snapshotter.Prepare(ctx, usernsID+"-remap", parent)
mounts, err := snapshotter.Prepare(ctx, usernsID+"-remap", rsn.Parent)
if err != nil {
return err
}
if err := remapRootFS(ctx, mounts, uid, gid); err != nil {
if err := remapRootFS(ctx, mounts, rsn.IDMap); err != nil {
snapshotter.Remove(ctx, usernsID)
return err
}
@ -95,22 +118,30 @@ func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool
}
}
func remapRootFS(ctx context.Context, mounts []mount.Mount, uid, gid uint32) error {
func remapRootFS(ctx context.Context, mounts []mount.Mount, idMap userns.IDMap) error {
return mount.WithTempMount(ctx, mounts, func(root string) error {
return filepath.Walk(root, incrementFS(root, uid, gid))
return filepath.Walk(root, chown(root, idMap))
})
}
func incrementFS(root string, uidInc, gidInc uint32) filepath.WalkFunc {
func chown(root string, idMap userns.IDMap) filepath.WalkFunc {
return func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
var (
stat = info.Sys().(*syscall.Stat_t)
u, g = int(stat.Uid + uidInc), int(stat.Gid + gidInc)
)
stat := info.Sys().(*syscall.Stat_t)
h, cerr := idMap.ToHost(userns.User{Uid: stat.Uid, Gid: stat.Gid})
if cerr != nil {
return cerr
}
// be sure the lchown the path as to not de-reference the symlink to a host file
return os.Lchown(path, u, g)
if cerr = os.Lchown(path, int(h.Uid), int(h.Gid)); cerr != nil {
return cerr
}
// we must retain special permissions such as setuid, setgid and sticky bits
if mode := info.Mode(); mode&os.ModeSymlink == 0 && mode&(os.ModeSetuid|os.ModeSetgid|os.ModeSticky) != 0 {
return os.Chmod(path, mode)
}
return nil
}
}

View File

@ -20,9 +20,14 @@ package client
import (
"context"
"encoding/json"
"fmt"
"slices"
"github.com/containerd/containerd/v2/core/snapshots"
"github.com/containerd/containerd/v2/internal/userns"
"github.com/opencontainers/go-digest"
"github.com/opencontainers/runtime-spec/specs-go"
)
const (
@ -58,15 +63,15 @@ func resolveSnapshotOptions(ctx context.Context, client *Client, snapshotterName
}
needsRemap := false
var uidMap, gidMap string
var uidMapLabel, gidMapLabel string
if value, ok := local.Labels[snapshots.LabelSnapshotUIDMapping]; ok {
needsRemap = true
uidMap = value
uidMapLabel = value
}
if value, ok := local.Labels[snapshots.LabelSnapshotGIDMapping]; ok {
needsRemap = true
gidMap = value
gidMapLabel = value
}
if !needsRemap {
@ -84,24 +89,32 @@ func resolveSnapshotOptions(ctx context.Context, client *Client, snapshotterName
return "", fmt.Errorf("snapshotter %q doesn't support idmap mounts on this host, configure `slow_chown` to allow a slower and expensive fallback", snapshotterName)
}
var ctrUID, hostUID, length uint32
_, err = fmt.Sscanf(uidMap, "%d:%d:%d", &ctrUID, &hostUID, &length)
var uidMap, gidMap specs.LinuxIDMapping
_, err = fmt.Sscanf(uidMapLabel, "%d:%d:%d", &uidMap.ContainerID, &uidMap.HostID, &uidMap.Size)
if err != nil {
return "", fmt.Errorf("uidMap unparsable: %w", err)
return "", fmt.Errorf("uidMapLabel unparsable: %w", err)
}
var ctrGID, hostGID, lengthGID uint32
_, err = fmt.Sscanf(gidMap, "%d:%d:%d", &ctrGID, &hostGID, &lengthGID)
_, err = fmt.Sscanf(gidMapLabel, "%d:%d:%d", &gidMap.ContainerID, &gidMap.HostID, &gidMap.Size)
if err != nil {
return "", fmt.Errorf("gidMap unparsable: %w", err)
return "", fmt.Errorf("gidMapLabel unparsable: %w", err)
}
if ctrUID != 0 || ctrGID != 0 {
return "", fmt.Errorf("Container UID/GID of 0 only supported currently (%d/%d)", ctrUID, ctrGID)
if uidMap.ContainerID != 0 || gidMap.ContainerID != 0 {
return "", fmt.Errorf("Container UID/GID of 0 only supported currently (%d/%d)", uidMap.ContainerID, gidMap.ContainerID)
}
rsn := remappedSnapshot{
Parent: parent,
IDMap: userns.IDMap{
UidMap: []specs.LinuxIDMapping{uidMap},
GidMap: []specs.LinuxIDMapping{gidMap},
},
}
usernsID, err := rsn.ID()
if err != nil {
return "", fmt.Errorf("failed to remap snapshot: %w", err)
}
// TODO(dgl): length isn't taken into account for the intermediate snapshot id.
usernsID := fmt.Sprintf("%s-%d-%d", parent, hostUID, hostGID)
if _, err := snapshotter.Stat(ctx, usernsID); err == nil {
return usernsID, nil
}
@ -109,8 +122,8 @@ func resolveSnapshotOptions(ctx context.Context, client *Client, snapshotterName
if err != nil {
return "", err
}
// TODO(dgl): length isn't taken into account here yet either.
if err := remapRootFS(ctx, mounts, hostUID, hostGID); err != nil {
if err := remapRootFS(ctx, mounts, rsn.IDMap); err != nil {
snapshotter.Remove(ctx, usernsID+"-remap")
return "", err
}
@ -120,3 +133,27 @@ func resolveSnapshotOptions(ctx context.Context, client *Client, snapshotterName
return usernsID, nil
}
type remappedSnapshot struct {
Parent string `json:"Parent"`
IDMap userns.IDMap `json:"IDMap"`
}
func (s *remappedSnapshot) ID() (string, error) {
compare := func(a, b specs.LinuxIDMapping) int {
if a.ContainerID < b.ContainerID {
return -1
} else if a.ContainerID == b.ContainerID {
return 0
}
return 1
}
slices.SortStableFunc(s.IDMap.UidMap, compare)
slices.SortStableFunc(s.IDMap.GidMap, compare)
buf, err := json.Marshal(s)
if err != nil {
return "", err
}
return digest.FromBytes(buf).String(), nil
}

View File

@ -37,6 +37,7 @@ import (
"github.com/containerd/containerd/v2/pkg/oci"
"github.com/containerd/log"
"github.com/containerd/platforms"
"github.com/intel/goresctrl/pkg/blockio"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/urfave/cli/v2"
@ -45,13 +46,13 @@ import (
)
var platformRunFlags = []cli.Flag{
&cli.StringFlag{
&cli.StringSliceFlag{
Name: "uidmap",
Usage: "Run inside a user namespace with the specified UID mapping range; specified with the format `container-uid:host-uid:length`",
Usage: "Run inside a user namespace with the specified UID mapping ranges; specified with the format `container-uid:host-uid:length`",
},
&cli.StringFlag{
&cli.StringSliceFlag{
Name: "gidmap",
Usage: "Run inside a user namespace with the specified GID mapping range; specified with the format `container-gid:host-gid:length`",
Usage: "Run inside a user namespace with the specified GID mapping ranges; specified with the format `container-gid:host-gid:length`",
},
&cli.BoolFlag{
Name: "remap-labels",
@ -159,26 +160,28 @@ func NewContainer(ctx context.Context, client *containerd.Client, cliContext *cl
containerd.WithImageConfigLabels(image),
containerd.WithAdditionalContainerLabels(labels),
containerd.WithSnapshotter(snapshotter))
if uidmap, gidmap := cliContext.String("uidmap"), cliContext.String("gidmap"); uidmap != "" && gidmap != "" {
uidMap, err := parseIDMapping(uidmap)
if err != nil {
if uidmaps, gidmaps := cliContext.StringSlice("uidmap"), cliContext.StringSlice("gidmap"); len(uidmaps) > 0 && len(gidmaps) > 0 {
var uidSpec, gidSpec []specs.LinuxIDMapping
if uidSpec, err = parseIDMappingOption(uidmaps); err != nil {
return nil, err
}
gidMap, err := parseIDMapping(gidmap)
if err != nil {
if gidSpec, err = parseIDMappingOption(gidmaps); err != nil {
return nil, err
}
opts = append(opts,
oci.WithUserNamespace([]specs.LinuxIDMapping{uidMap}, []specs.LinuxIDMapping{gidMap}))
opts = append(opts, oci.WithUserNamespace(uidSpec, gidSpec))
// use snapshotter opts or the remapped snapshot support to shift the filesystem
// currently the snapshotters known to support the labels are:
// fuse-overlayfs - https://github.com/containerd/fuse-overlayfs-snapshotter
// overlay - in case of idmapped mount points are supported by host kernel (Linux kernel 5.19)
if cliContext.Bool("remap-labels") {
cOpts = append(cOpts, containerd.WithNewSnapshot(id, image,
containerd.WithRemapperLabels(0, uidMap.HostID, 0, gidMap.HostID, uidMap.Size)))
// TODO: the optimization code path on id mapped mounts only supports single mapping entry today.
if len(uidSpec) > 1 || len(gidSpec) > 1 {
return nil, errors.New("'remap-labels' option does not support multiple mappings")
}
cOpts = append(cOpts, containerd.WithNewSnapshot(id, image, containerd.WithRemapperLabels(0, uidSpec[0].HostID, 0, gidSpec[0].HostID, uidSpec[0].Size)))
} else {
cOpts = append(cOpts, containerd.WithRemappedSnapshot(id, image, uidMap.HostID, gidMap.HostID))
cOpts = append(cOpts, containerd.WithUserNSRemappedSnapshot(id, image, uidSpec, gidSpec))
}
} else {
// Even when "read-only" is set, we don't use KindView snapshot here. (#1495)
@ -415,6 +418,18 @@ func NewContainer(ctx context.Context, client *containerd.Client, cliContext *cl
return client.NewContainer(ctx, id, cOpts...)
}
func parseIDMappingOption(stringSlices []string) ([]specs.LinuxIDMapping, error) {
var res []specs.LinuxIDMapping
for _, str := range stringSlices {
m, err := parseIDMapping(str)
if err != nil {
return nil, err
}
res = append(res, m)
}
return res, nil
}
func parseIDMapping(mapping string) (specs.LinuxIDMapping, error) {
// We expect 3 parts, but limit to 4 to allow detection of invalid values.
parts := strings.SplitN(mapping, ":", 4)

View File

@ -40,6 +40,7 @@ import (
. "github.com/containerd/containerd/v2/client"
"github.com/containerd/containerd/v2/core/containers"
"github.com/containerd/containerd/v2/integration/failpoint"
"github.com/containerd/containerd/v2/integration/images"
"github.com/containerd/containerd/v2/pkg/cio"
"github.com/containerd/containerd/v2/pkg/fifosync"
"github.com/containerd/containerd/v2/pkg/oci"
@ -52,7 +53,8 @@ import (
"golang.org/x/sys/unix"
)
const testUserNSImage = "ghcr.io/containerd/alpine:3.14.0"
// We use this image for user ns tests because it has files with setuid bits
var testUserNSImage = images.Get(images.VolumeOwnership)
func TestTaskUpdate(t *testing.T) {
t.Parallel()
@ -1095,9 +1097,61 @@ func TestContainerKillInitPidHost(t *testing.T) {
}
func TestUserNamespaces(t *testing.T) {
t.Run("WritableRootFS", func(t *testing.T) { testUserNamespaces(t, false) })
// see #1373 and runc#1572
t.Run("ReadonlyRootFS", func(t *testing.T) { testUserNamespaces(t, true) })
for name, test := range map[string]struct {
testCmd oci.SpecOpts
roRootFS bool
exitCode uint32 // testUserNamespaces validates the exit code of the test container against this value
uidmaps []specs.LinuxIDMapping
gidmaps []specs.LinuxIDMapping
}{
"WritableRootFS": {
testCmd: withExitStatus(7),
roRootFS: false,
exitCode: 7,
uidmaps: []specs.LinuxIDMapping{{ContainerID: 0, HostID: 1000, Size: 65535}},
gidmaps: []specs.LinuxIDMapping{{ContainerID: 0, HostID: 2000, Size: 65535}},
},
// see #1373 and runc#1572
"ReadonlyRootFS": {
testCmd: withExitStatus(7),
roRootFS: true,
exitCode: 7,
uidmaps: []specs.LinuxIDMapping{{ContainerID: 0, HostID: 1000, Size: 65535}},
gidmaps: []specs.LinuxIDMapping{{ContainerID: 0, HostID: 2000, Size: 65535}},
},
"CheckSetUidBit": {
testCmd: withProcessArgs("bash", "-c", "[ -u /usr/bin/passwd ] && exit 7"),
roRootFS: false,
exitCode: 7,
uidmaps: []specs.LinuxIDMapping{{ContainerID: 0, HostID: 1000, Size: 65535}},
gidmaps: []specs.LinuxIDMapping{{ContainerID: 0, HostID: 2000, Size: 65535}},
},
"WritableRootFSMultipleMap": {
testCmd: withExitStatus(7),
roRootFS: false,
exitCode: 7,
uidmaps: []specs.LinuxIDMapping{{ContainerID: 0, HostID: 0, Size: 10}, {ContainerID: 10, HostID: 1000, Size: 65535}},
gidmaps: []specs.LinuxIDMapping{{ContainerID: 0, HostID: 0, Size: 20}, {ContainerID: 20, HostID: 2000, Size: 65535}},
},
"ReadonlyRootFSMultipleMap": {
testCmd: withExitStatus(7),
roRootFS: true,
exitCode: 7,
uidmaps: []specs.LinuxIDMapping{{ContainerID: 0, HostID: 0, Size: 20}, {ContainerID: 20, HostID: 2000, Size: 65535}},
gidmaps: []specs.LinuxIDMapping{{ContainerID: 0, HostID: 0, Size: 20}, {ContainerID: 20, HostID: 2000, Size: 65535}},
},
"CheckSetUidBitMultipleMap": {
testCmd: withProcessArgs("bash", "-c", "[ -u /usr/bin/passwd ] && exit 7"),
roRootFS: false,
exitCode: 7,
uidmaps: []specs.LinuxIDMapping{{ContainerID: 0, HostID: 0, Size: 20}, {ContainerID: 20, HostID: 2000, Size: 65535}},
gidmaps: []specs.LinuxIDMapping{{ContainerID: 0, HostID: 0, Size: 20}, {ContainerID: 20, HostID: 2000, Size: 65535}},
},
} {
t.Run(name, func(t *testing.T) {
testUserNamespaces(t, test.uidmaps, test.gidmaps, test.testCmd, test.roRootFS, test.exitCode)
})
}
}
func checkUserNS(t *testing.T) {
@ -1111,7 +1165,7 @@ func checkUserNS(t *testing.T) {
}
}
func testUserNamespaces(t *testing.T, readonlyRootFS bool) {
func testUserNamespaces(t *testing.T, uidmaps, gidmaps []specs.LinuxIDMapping, cmdOpt oci.SpecOpts, readonlyRootFS bool, expected uint32) {
checkUserNS(t)
client, err := newClient(t, address)
@ -1133,25 +1187,23 @@ func testUserNamespaces(t *testing.T, readonlyRootFS bool) {
}
opts := []NewContainerOpts{WithNewSpec(oci.WithImageConfig(image),
withExitStatus(7),
oci.WithUserNamespace([]specs.LinuxIDMapping{
{
ContainerID: 0,
HostID: 1000,
Size: 10000,
},
}, []specs.LinuxIDMapping{
{
ContainerID: 0,
HostID: 2000,
Size: 10000,
},
}),
cmdOpt,
oci.WithUserID(34), // run task as the "backup" user
oci.WithUserNamespace(uidmaps, gidmaps),
)}
if readonlyRootFS {
opts = append([]NewContainerOpts{WithRemappedSnapshotView(id, image, 1000, 2000)}, opts...)
if len(uidmaps) > 1 {
opts = append([]NewContainerOpts{WithUserNSRemappedSnapshotView(id, image, uidmaps, gidmaps)}, opts...)
} else {
opts = append([]NewContainerOpts{WithRemappedSnapshotView(id, image, 1000, 2000)}, opts...)
}
} else {
opts = append([]NewContainerOpts{WithRemappedSnapshot(id, image, 1000, 2000)}, opts...)
if len(uidmaps) > 1 {
opts = append([]NewContainerOpts{WithUserNSRemappedSnapshot(id, image, uidmaps, gidmaps)}, opts...)
} else {
opts = append([]NewContainerOpts{WithRemappedSnapshot(id, image, 1000, 2000)}, opts...)
}
}
container, err := client.NewContainer(ctx, id, opts...)
@ -1192,15 +1244,15 @@ func testUserNamespaces(t *testing.T, readonlyRootFS bool) {
if err != nil {
t.Fatal(err)
}
if code != 7 {
t.Errorf("expected status 7 from wait but received %d", code)
if code != expected {
t.Errorf("expected status %d from wait but received %d", expected, code)
}
deleteStatus, err := task.Delete(ctx)
if err != nil {
t.Fatal(err)
}
if ec := deleteStatus.ExitCode(); ec != 7 {
t.Errorf("expected status 7 from delete but received %d", ec)
if ec := deleteStatus.ExitCode(); ec != expected {
t.Errorf("expected status %d from delete but received %d", expected, ec)
}
}

98
internal/userns/idmap.go Normal file
View File

@ -0,0 +1,98 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
This file is copied and customized based on
https://github.com/moby/moby/blob/master/pkg/idtools/idtools.go
*/
package userns
import (
"errors"
"fmt"
"github.com/opencontainers/runtime-spec/specs-go"
)
const invalidID = 1<<32 - 1
var invalidUser = User{Uid: invalidID, Gid: invalidID}
// User is a Uid and Gid pair of a user
//
//nolint:revive
type User struct {
Uid uint32
Gid uint32
}
// IDMap contains the mappings of Uids and Gids.
//
//nolint:revive
type IDMap struct {
UidMap []specs.LinuxIDMapping `json:"UidMap"`
GidMap []specs.LinuxIDMapping `json:"GidMap"`
}
// ToHost returns the host user ID pair for the container ID pair.
func (i IDMap) ToHost(pair User) (User, error) {
var (
target User
err error
)
target.Uid, err = toHost(pair.Uid, i.UidMap)
if err != nil {
return invalidUser, err
}
target.Gid, err = toHost(pair.Gid, i.GidMap)
if err != nil {
return invalidUser, err
}
return target, nil
}
// toHost takes an id mapping and a remapped ID, and translates the
// ID to the mapped host ID. If no map is provided, then the translation
// assumes a 1-to-1 mapping and returns the passed in id #
func toHost(contID uint32, idMap []specs.LinuxIDMapping) (uint32, error) {
if idMap == nil {
return contID, nil
}
for _, m := range idMap {
high, err := safeSum(m.ContainerID, m.Size)
if err != nil {
break
}
if contID >= m.ContainerID && contID < high {
hostID, err := safeSum(m.HostID, contID-m.ContainerID)
if err != nil || hostID == invalidID {
break
}
return hostID, nil
}
}
return invalidID, fmt.Errorf("container ID %d cannot be mapped to a host ID", contID)
}
// safeSum returns the sum of x and y. or an error if the result overflows
func safeSum(x, y uint32) (uint32, error) {
z := x + y
if z < x || z < y {
return invalidID, errors.New("ID overflow")
}
return z, nil
}

View File

@ -0,0 +1,252 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package userns
import (
"testing"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/stretchr/testify/assert"
)
func TestToHost(t *testing.T) {
idmap := IDMap{
UidMap: []specs.LinuxIDMapping{
{
ContainerID: 0,
HostID: 1,
Size: 2,
},
{
ContainerID: 2,
HostID: 4,
Size: 1000,
},
},
GidMap: []specs.LinuxIDMapping{
{
ContainerID: 0,
HostID: 2,
Size: 4,
},
{
ContainerID: 4,
HostID: 8,
Size: 1000,
},
},
}
for _, test := range []struct {
container User
host User
}{
{
container: User{
Uid: 0,
Gid: 0,
},
host: User{
Uid: 1,
Gid: 2,
},
},
{
container: User{
Uid: 1,
Gid: 1,
},
host: User{
Uid: 2,
Gid: 3,
},
},
{
container: User{
Uid: 2,
Gid: 4,
},
host: User{
Uid: 4,
Gid: 8,
},
},
{
container: User{
Uid: 100,
Gid: 200,
},
host: User{
Uid: 102,
Gid: 204,
},
},
{
container: User{
Uid: 1001,
Gid: 1003,
},
host: User{
Uid: 1003,
Gid: 1007,
},
},
{
container: User{
Uid: 1004,
Gid: 1008,
},
host: invalidUser,
},
{
container: User{
Uid: 2000,
Gid: 2000,
},
host: invalidUser,
},
} {
r, err := idmap.ToHost(test.container)
assert.Equal(t, test.host, r)
if r == invalidUser {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
}
}
func TestToHostOverflow(t *testing.T) {
for _, test := range []struct {
idmap IDMap
user User
}{
{
idmap: IDMap{
UidMap: []specs.LinuxIDMapping{
{
ContainerID: 1<<32 - 1000,
HostID: 1000,
Size: 10000,
},
},
GidMap: []specs.LinuxIDMapping{
{
ContainerID: 0,
HostID: 1000,
Size: 10000,
},
},
},
user: User{
Uid: 1<<32 - 100,
Gid: 0,
},
},
{
idmap: IDMap{
UidMap: []specs.LinuxIDMapping{
{
ContainerID: 0,
HostID: 1000,
Size: 10000,
},
},
GidMap: []specs.LinuxIDMapping{
{
ContainerID: 1<<32 - 1000,
HostID: 1000,
Size: 10000,
},
},
},
user: User{
Uid: 0,
Gid: 1<<32 - 100,
},
},
{
idmap: IDMap{
UidMap: []specs.LinuxIDMapping{
{
ContainerID: 0,
HostID: 1000,
Size: 1<<32 - 1,
},
},
GidMap: []specs.LinuxIDMapping{
{
ContainerID: 0,
HostID: 1000,
Size: 1<<32 - 1,
},
},
},
user: User{
Uid: 1<<32 - 2,
Gid: 0,
},
},
{
idmap: IDMap{
UidMap: []specs.LinuxIDMapping{
{
ContainerID: 0,
HostID: 1000,
Size: 1<<32 - 1,
},
},
GidMap: []specs.LinuxIDMapping{
{
ContainerID: 0,
HostID: 1000,
Size: 1<<32 - 1,
},
},
},
user: User{
Uid: 0,
Gid: 1<<32 - 2,
},
},
{
idmap: IDMap{
UidMap: []specs.LinuxIDMapping{
{
ContainerID: 0,
HostID: 1,
Size: 1<<32 - 1,
},
},
GidMap: []specs.LinuxIDMapping{
{
ContainerID: 0,
HostID: 1,
Size: 1<<32 - 1,
},
},
},
user: User{
Uid: 1<<32 - 2,
Gid: 1<<32 - 2,
},
},
} {
r, err := test.idmap.ToHost(test.user)
assert.Error(t, err)
assert.Equal(t, r, invalidUser)
}
}