Skip to content

Commit

Permalink
Merge branch 'uidmap'
Browse files Browse the repository at this point in the history
  • Loading branch information
henry118 committed Jun 6, 2024
2 parents b7462ec + d7b2512 commit 49a603e
Show file tree
Hide file tree
Showing 10 changed files with 957 additions and 39 deletions.
33 changes: 18 additions & 15 deletions client/container_opts_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,23 @@ import (

"github.com/containerd/containerd/v2/core/containers"
"github.com/containerd/containerd/v2/core/mount"
"github.com/containerd/containerd/v2/pkg/idtools"
"github.com/containerd/errdefs"
"github.com/opencontainers/image-spec/identity"
)

// 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)
func WithRemappedSnapshot(id string, i Image, idmap idtools.IdentityMapping) NewContainerOpts {
return withRemappedSnapshotBase(id, i, idmap, 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)
func WithRemappedSnapshotView(id string, i Image, idmap idtools.IdentityMapping) NewContainerOpts {
return withRemappedSnapshotBase(id, i, idmap, true)
}

func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool) NewContainerOpts {
func withRemappedSnapshotBase(id string, i Image, idmap idtools.IdentityMapping, 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 {
Expand All @@ -51,7 +52,8 @@ func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool

var (
parent = identity.ChainID(diffIDs).String()
usernsID = fmt.Sprintf("%s-%d-%d", parent, uid, gid)
rootMap = idmap.RootPair()
usernsID = fmt.Sprintf("%s-%d-%d", parent, rootMap.UID, rootMap.GID)
)
c.Snapshotter, err = client.resolveSnapshotterName(ctx, c.Snapshotter)
if err != nil {
Expand All @@ -74,7 +76,7 @@ func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool
if err != nil {
return err
}
if err := remapRootFS(ctx, mounts, uid, gid); err != nil {
if err := remapRootFS(ctx, mounts, idmap); err != nil {
snapshotter.Remove(ctx, usernsID)
return err
}
Expand All @@ -95,22 +97,23 @@ 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 idtools.IdentityMapping) 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 idtools.IdentityMapping) 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(idtools.Identity{UID: int(stat.Uid), GID: int(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)
return os.Lchown(path, h.UID, h.GID)
}
}
7 changes: 6 additions & 1 deletion client/snapshotter_opts_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"fmt"

"github.com/containerd/containerd/v2/core/snapshots"
"github.com/containerd/containerd/v2/pkg/idtools"
)

const (
Expand Down Expand Up @@ -110,7 +111,11 @@ func resolveSnapshotOptions(ctx context.Context, client *Client, snapshotterName
return "", err
}
// TODO(dgl): length isn't taken into account here yet either.
if err := remapRootFS(ctx, mounts, hostUID, hostGID); err != nil {
idmap := idtools.IdentityMapping{
UIDMaps: []idtools.IDMap{{ContainerID: int(ctrUID), HostID: int(hostUID), Size: int(length)}},
GIDMaps: []idtools.IDMap{{ContainerID: int(ctrGID), HostID: int(hostGID), Size: int(lengthGID)}},
}
if err := remapRootFS(ctx, mounts, idmap); err != nil {
snapshotter.Remove(ctx, usernsID+"-remap")
return "", err
}
Expand Down
40 changes: 24 additions & 16 deletions cmd/ctr/commands/run/run_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ import (
"github.com/containerd/containerd/v2/contrib/seccomp"
"github.com/containerd/containerd/v2/core/containers"
"github.com/containerd/containerd/v2/core/snapshots"
"github.com/containerd/containerd/v2/pkg/idtools"
"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"
Expand All @@ -46,12 +48,8 @@ import (

var platformRunFlags = []cli.Flag{
&cli.StringFlag{
Name: "uidmap",
Usage: "Run inside a user namespace with the specified UID mapping range; specified with the format `container-uid:host-uid:length`",
},
&cli.StringFlag{
Name: "gidmap",
Usage: "Run inside a user namespace with the specified GID mapping range; specified with the format `container-gid:host-gid:length`",
Name: "userns-remap",
Usage: "Run inside a user namespace with the specified user",
},
&cli.BoolFlag{
Name: "remap-labels",
Expand Down Expand Up @@ -159,26 +157,35 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli
containerd.WithImageConfigLabels(image),
containerd.WithAdditionalContainerLabels(labels),
containerd.WithSnapshotter(snapshotter))
if uidmap, gidmap := context.String("uidmap"), context.String("gidmap"); uidmap != "" && gidmap != "" {
uidMap, err := parseIDMapping(uidmap)
if err != nil {
return nil, err
}
gidMap, err := parseIDMapping(gidmap)

userns := context.String("userns-remap")
if userns != "" {
idmap, err := idtools.LoadIdentityMapping(userns)
if err != nil {
return nil, err
}
opts = append(opts,
oci.WithUserNamespace([]specs.LinuxIDMapping{uidMap}, []specs.LinuxIDMapping{gidMap}))
uidSpecs, gidSpecs := idmap.ToSpec()
opts = append(opts, oci.WithUserNamespace(uidSpecs, gidSpecs))
// 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 context.Bool("remap-labels") {
// TODO: the optimization code path on id mapped mounts only supports single
// mapping entry today. use the root pair in this scenario for now.
rp := idmap.RootPair()
size := func() int {
for _, m := range idmap.UIDMaps {
if m.ContainerID == 0 {
return m.Size
}
}
return 0
}()
cOpts = append(cOpts, containerd.WithNewSnapshot(id, image,
containerd.WithRemapperLabels(0, uidMap.HostID, 0, gidMap.HostID, uidMap.Size)))
containerd.WithRemapperLabels(0, uint32(rp.UID), 0, uint32(rp.GID), uint32(size))))
} else {
cOpts = append(cOpts, containerd.WithRemappedSnapshot(id, image, uidMap.HostID, gidMap.HostID))
cOpts = append(cOpts, containerd.WithRemappedSnapshot(id, image, idmap))
}
} else {
// Even when "read-only" is set, we don't use KindView snapshot here. (#1495)
Expand Down Expand Up @@ -415,6 +422,7 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli
return client.NewContainer(ctx, id, cOpts...)
}

//lint:ignore U1000 Ignore unused function
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)
Expand Down
22 changes: 15 additions & 7 deletions integration/client/container_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
. "github.com/containerd/containerd/v2/client"
"github.com/containerd/containerd/v2/core/containers"
"github.com/containerd/containerd/v2/pkg/cio"
"github.com/containerd/containerd/v2/pkg/idtools"
"github.com/containerd/containerd/v2/pkg/oci"
"github.com/containerd/containerd/v2/pkg/shim"
"github.com/containerd/containerd/v2/pkg/sys"
Expand Down Expand Up @@ -1150,26 +1151,33 @@ func testUserNamespaces(t *testing.T, readonlyRootFS bool) {
t.Fatal(err)
}

opts := []NewContainerOpts{WithNewSpec(oci.WithImageConfig(image),
withExitStatus(7),
oci.WithUserNamespace([]specs.LinuxIDMapping{
idmap := idtools.IdentityMapping{
UIDMaps: []idtools.IDMap{
{
ContainerID: 0,
HostID: 1000,
Size: 10000,
},
}, []specs.LinuxIDMapping{
},
GIDMaps: []idtools.IDMap{
{
ContainerID: 0,
HostID: 2000,
Size: 10000,
},
}),
},
}

uidMap, gidMap := idmap.ToSpec()

opts := []NewContainerOpts{WithNewSpec(oci.WithImageConfig(image),
withExitStatus(7),
oci.WithUserNamespace(uidMap, gidMap),
)}
if readonlyRootFS {
opts = append([]NewContainerOpts{WithRemappedSnapshotView(id, image, 1000, 2000)}, opts...)
opts = append([]NewContainerOpts{WithRemappedSnapshotView(id, image, idmap)}, opts...)
} else {
opts = append([]NewContainerOpts{WithRemappedSnapshot(id, image, 1000, 2000)}, opts...)
opts = append([]NewContainerOpts{WithRemappedSnapshot(id, image, idmap)}, opts...)
}

container, err := client.NewContainer(ctx, id, opts...)
Expand Down
Loading

0 comments on commit 49a603e

Please sign in to comment.