From 331793118fd9d23a6fa4a59c0a2aba4c5e24b197 Mon Sep 17 00:00:00 2001 From: Jie Hao Liao Date: Thu, 12 Dec 2019 00:10:54 -0600 Subject: [PATCH 1/3] allow user namespace remapping using snapshotters Signed-off-by: Jie Hao Liao --- container_linux_test.go | 4 ++-- container_opts_unix.go | 49 ++++++++++++++++++++++++++++++----------- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/container_linux_test.go b/container_linux_test.go index ece24bf66..9953e7133 100644 --- a/container_linux_test.go +++ b/container_linux_test.go @@ -1432,9 +1432,9 @@ func testUserNamespaces(t *testing.T, readonlyRootFS bool) { }), )} if readonlyRootFS { - opts = append([]NewContainerOpts{WithRemappedSnapshotView(id, image, 1000, 2000)}, opts...) + opts = append([]NewContainerOpts{WithRemappedSnapshotView(id, image, 1000, 2000, remapperChown)}, opts...) } else { - opts = append([]NewContainerOpts{WithRemappedSnapshot(id, image, 1000, 2000)}, opts...) + opts = append([]NewContainerOpts{WithRemappedSnapshot(id, image, 1000, 2000, remapperChown)}, opts...) } container, err := client.NewContainer(ctx, id, opts...) diff --git a/container_opts_unix.go b/container_opts_unix.go index b109a10ec..04be00cd1 100644 --- a/container_opts_unix.go +++ b/container_opts_unix.go @@ -28,22 +28,35 @@ import ( "github.com/containerd/containerd/containers" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/mount" + "github.com/containerd/containerd/snapshots" "github.com/opencontainers/image-spec/identity" ) +// Remapper specifies the type of remapping to be done +type Remapper string + +const ( + remapperChown Remapper = "chown" + remapperSnapshotter Remapper = "snapshotter" +) + // 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, uid, gid uint32, remapper Remapper) NewContainerOpts { + return withRemappedSnapshotBase(id, i, uid, gid, false, remapper) } // 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, uid, gid uint32, remapper Remapper) NewContainerOpts { + return withRemappedSnapshotBase(id, i, uid, gid, true, remapper) } -func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool) NewContainerOpts { +func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool, remapper Remapper) NewContainerOpts { return func(ctx context.Context, client *Client, c *containers.Container) error { + if remapper != remapperChown && remapper != remapperSnapshotter { + return fmt.Errorf("remapper must be either '%s' or '%s'", remapperChown, remapperSnapshotter) + } + diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), client.platform) if err != nil { return err @@ -52,6 +65,7 @@ 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) + opts = []snapshots.Opt{} ) c.Snapshotter, err = client.resolveSnapshotterName(ctx, c.Snapshotter) if err != nil { @@ -61,8 +75,15 @@ func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool if err != nil { return err } + if remapper == remapperSnapshotter { + opts = append(opts, + snapshots.WithLabels(map[string]string{ + "containerd.io/snapshot/uidmapping": fmt.Sprintf("%d:%d:%d", 0, uid, 65535), + "containerd.io/snapshot/gidmapping": fmt.Sprintf("%d:%d:%d", 0, gid, 65535), + })) + } if _, err := snapshotter.Stat(ctx, usernsID); err == nil { - if _, err := snapshotter.Prepare(ctx, id, usernsID); err == nil { + if _, err := snapshotter.Prepare(ctx, id, usernsID, opts...); err == nil { c.SnapshotKey = id c.Image = i.Name() return nil @@ -70,21 +91,23 @@ 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", parent, opts...) if err != nil { return err } - if err := remapRootFS(ctx, mounts, uid, gid); err != nil { - snapshotter.Remove(ctx, usernsID) - return err + if remapper == remapperChown { + if err := remapRootFS(ctx, mounts, uid, gid); err != nil { + snapshotter.Remove(ctx, usernsID) + return err + } } - if err := snapshotter.Commit(ctx, usernsID, usernsID+"-remap"); err != nil { + if err := snapshotter.Commit(ctx, usernsID, usernsID+"-remap", opts...); err != nil { return err } if readonly { - _, err = snapshotter.View(ctx, id, usernsID) + _, err = snapshotter.View(ctx, id, usernsID, opts...) } else { - _, err = snapshotter.Prepare(ctx, id, usernsID) + _, err = snapshotter.Prepare(ctx, id, usernsID, opts...) } if err != nil { return err From c76bf5504786ee4cfc7ba43c4ade1bae07c51ca2 Mon Sep 17 00:00:00 2001 From: Phil Estes Date: Fri, 15 May 2020 16:33:15 -0400 Subject: [PATCH 2/3] Make unique snapshotter opt for label-assisted remapping Provide a snapshotter opt to add labels used by any supporting snapshotter to handle user namespace filesystem remapping. Currently supported by the fuse-overlayfs snapshotter, and others can use this information as well. Signed-off-by: Phil Estes --- container_linux_test.go | 4 ++-- container_opts_unix.go | 49 +++++++++++----------------------------- snapshotter_opts_unix.go | 35 ++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 38 deletions(-) create mode 100644 snapshotter_opts_unix.go diff --git a/container_linux_test.go b/container_linux_test.go index 9953e7133..ece24bf66 100644 --- a/container_linux_test.go +++ b/container_linux_test.go @@ -1432,9 +1432,9 @@ func testUserNamespaces(t *testing.T, readonlyRootFS bool) { }), )} if readonlyRootFS { - opts = append([]NewContainerOpts{WithRemappedSnapshotView(id, image, 1000, 2000, remapperChown)}, opts...) + opts = append([]NewContainerOpts{WithRemappedSnapshotView(id, image, 1000, 2000)}, opts...) } else { - opts = append([]NewContainerOpts{WithRemappedSnapshot(id, image, 1000, 2000, remapperChown)}, opts...) + opts = append([]NewContainerOpts{WithRemappedSnapshot(id, image, 1000, 2000)}, opts...) } container, err := client.NewContainer(ctx, id, opts...) diff --git a/container_opts_unix.go b/container_opts_unix.go index 04be00cd1..b109a10ec 100644 --- a/container_opts_unix.go +++ b/container_opts_unix.go @@ -28,35 +28,22 @@ import ( "github.com/containerd/containerd/containers" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/mount" - "github.com/containerd/containerd/snapshots" "github.com/opencontainers/image-spec/identity" ) -// Remapper specifies the type of remapping to be done -type Remapper string - -const ( - remapperChown Remapper = "chown" - remapperSnapshotter Remapper = "snapshotter" -) - // 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, remapper Remapper) NewContainerOpts { - return withRemappedSnapshotBase(id, i, uid, gid, false, remapper) +func WithRemappedSnapshot(id string, i Image, uid, gid uint32) NewContainerOpts { + return withRemappedSnapshotBase(id, i, uid, gid, false) } // WithRemappedSnapshotView is similar to WithRemappedSnapshot but rootfs is mounted as read-only. -func WithRemappedSnapshotView(id string, i Image, uid, gid uint32, remapper Remapper) NewContainerOpts { - return withRemappedSnapshotBase(id, i, uid, gid, true, remapper) +func WithRemappedSnapshotView(id string, i Image, uid, gid uint32) NewContainerOpts { + return withRemappedSnapshotBase(id, i, uid, gid, true) } -func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool, remapper Remapper) NewContainerOpts { +func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool) NewContainerOpts { return func(ctx context.Context, client *Client, c *containers.Container) error { - if remapper != remapperChown && remapper != remapperSnapshotter { - return fmt.Errorf("remapper must be either '%s' or '%s'", remapperChown, remapperSnapshotter) - } - diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), client.platform) if err != nil { return err @@ -65,7 +52,6 @@ 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) - opts = []snapshots.Opt{} ) c.Snapshotter, err = client.resolveSnapshotterName(ctx, c.Snapshotter) if err != nil { @@ -75,15 +61,8 @@ func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool if err != nil { return err } - if remapper == remapperSnapshotter { - opts = append(opts, - snapshots.WithLabels(map[string]string{ - "containerd.io/snapshot/uidmapping": fmt.Sprintf("%d:%d:%d", 0, uid, 65535), - "containerd.io/snapshot/gidmapping": fmt.Sprintf("%d:%d:%d", 0, gid, 65535), - })) - } if _, err := snapshotter.Stat(ctx, usernsID); err == nil { - if _, err := snapshotter.Prepare(ctx, id, usernsID, opts...); err == nil { + if _, err := snapshotter.Prepare(ctx, id, usernsID); err == nil { c.SnapshotKey = id c.Image = i.Name() return nil @@ -91,23 +70,21 @@ func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool return err } } - mounts, err := snapshotter.Prepare(ctx, usernsID+"-remap", parent, opts...) + mounts, err := snapshotter.Prepare(ctx, usernsID+"-remap", parent) if err != nil { return err } - if remapper == remapperChown { - if err := remapRootFS(ctx, mounts, uid, gid); err != nil { - snapshotter.Remove(ctx, usernsID) - return err - } + if err := remapRootFS(ctx, mounts, uid, gid); err != nil { + snapshotter.Remove(ctx, usernsID) + return err } - if err := snapshotter.Commit(ctx, usernsID, usernsID+"-remap", opts...); err != nil { + if err := snapshotter.Commit(ctx, usernsID, usernsID+"-remap"); err != nil { return err } if readonly { - _, err = snapshotter.View(ctx, id, usernsID, opts...) + _, err = snapshotter.View(ctx, id, usernsID) } else { - _, err = snapshotter.Prepare(ctx, id, usernsID, opts...) + _, err = snapshotter.Prepare(ctx, id, usernsID) } if err != nil { return err diff --git a/snapshotter_opts_unix.go b/snapshotter_opts_unix.go new file mode 100644 index 000000000..1964379d4 --- /dev/null +++ b/snapshotter_opts_unix.go @@ -0,0 +1,35 @@ +// +build !windows + +/* + 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 containerd + +import ( + "fmt" + + "github.com/containerd/containerd/snapshots" +) + +// WithRemapperLabels creates the labels used by any supporting snapshotter +// to shift the filesystem ownership (user namespace mapping) automatically; currently +// supported by the fuse-overlayfs snapshotter +func WithRemapperLabels(ctrUID, hostUID, ctrGID, hostGID, length uint32) snapshots.Opt { + return snapshots.WithLabels(map[string]string{ + "containerd.io/snapshot/uidmapping": fmt.Sprintf("%d:%d:%d", ctrUID, hostUID, length), + "containerd.io/snapshot/gidmapping": fmt.Sprintf("%d:%d:%d", ctrGID, hostGID, length), + }) +} From 45c28f56b2fe37c92752714fbed76d3d1b22dbb6 Mon Sep 17 00:00:00 2001 From: Phil Estes Date: Fri, 26 Jun 2020 12:05:07 -0400 Subject: [PATCH 3/3] Add ability to use remapper labels versus remapping snapshot helper A simple starting point for testing the remapper labels with fuse-overlayfs snapshotter Signed-off-by: Phil Estes --- cmd/ctr/commands/run/run_unix.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cmd/ctr/commands/run/run_unix.go b/cmd/ctr/commands/run/run_unix.go index 920edbfd6..0f004119b 100644 --- a/cmd/ctr/commands/run/run_unix.go +++ b/cmd/ctr/commands/run/run_unix.go @@ -54,6 +54,10 @@ var platformRunFlags = []cli.Flag{ Name: "gidmap", Usage: "run inside a user namespace with the specified GID mapping range; specified with the format `container-gid:host-gid:length`", }, + cli.BoolFlag{ + Name: "remap-labels", + Usage: "provide the user namespace ID remapping to the snapshotter via label options; requires snapshotter support", + }, } // NewContainer creates a new container @@ -137,8 +141,12 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli } opts = append(opts, oci.WithUserNamespace([]specs.LinuxIDMapping{uidMap}, []specs.LinuxIDMapping{gidMap})) - if context.Bool("read-only") { - cOpts = append(cOpts, containerd.WithRemappedSnapshotView(id, image, uidMap.HostID, gidMap.HostID)) + // use snapshotter opts or the remapped snapshot support to shift the filesystem + // currently the only snapshotter known to support the labels is fuse-overlayfs: + // https://github.com/AkihiroSuda/containerd-fuse-overlayfs + if context.Bool("remap-labels") { + cOpts = append(cOpts, containerd.WithNewSnapshot(id, image, + containerd.WithRemapperLabels(0, uidMap.HostID, 0, gidMap.HostID, uidMap.Size))) } else { cOpts = append(cOpts, containerd.WithRemappedSnapshot(id, image, uidMap.HostID, gidMap.HostID)) }