c8d: Handle userns properly

If the daemon is run with --userns-remap we need to chown the prepared
snapshot

Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
This commit is contained in:
Djordje Lukic 2023-08-30 15:47:00 +02:00
parent 833b514d98
commit 0313544f4a
No known key found for this signature in database
6 changed files with 116 additions and 4 deletions

View file

@ -16,6 +16,8 @@ import (
"github.com/pkg/errors"
)
const remapSuffix = "-remap"
// PrepareSnapshot prepares a snapshot from a parent image for a container
func (i *ImageService) PrepareSnapshot(ctx context.Context, id string, parentImage string, platform *ocispec.Platform) error {
var parentSnapshot string
@ -63,7 +65,6 @@ func (i *ImageService) PrepareSnapshot(ctx context.Context, id string, parentIma
if err != nil {
return err
}
if err := ls.AddResource(ctx, lease, leases.Resource{
ID: id,
Type: "snapshots/" + i.StorageDriver(),
@ -71,8 +72,13 @@ func (i *ImageService) PrepareSnapshot(ctx context.Context, id string, parentIma
return err
}
s := i.client.SnapshotService(i.StorageDriver())
_, err = s.Prepare(ctx, id, parentSnapshot)
snapshotter := i.client.SnapshotService(i.StorageDriver())
if !i.idMapping.Empty() {
return i.remapSnapshot(ctx, snapshotter, id, parentSnapshot, lease)
}
_, err = snapshotter.Prepare(ctx, id, parentSnapshot)
return err
}

View file

@ -0,0 +1,84 @@
//go:build !windows
package containerd
import (
"context"
"fmt"
"os"
"path/filepath"
"syscall"
"github.com/containerd/containerd/leases"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/mount"
"github.com/containerd/containerd/snapshots"
"github.com/docker/docker/pkg/idtools"
)
func (i *ImageService) remapSnapshot(ctx context.Context, snapshotter snapshots.Snapshotter, id string, parentSnapshot string, lease leases.Lease) error {
ls := i.client.LeasesService()
rootPair := i.idMapping.RootPair()
usernsID := fmt.Sprintf("%s-%d-%d", parentSnapshot, rootPair.UID, rootPair.GID)
remappedID := usernsID + remapSuffix
// If the remapped snapshot already exist we only need to prepare the new snapshot
if _, err := snapshotter.Stat(ctx, usernsID); err == nil {
_, err = snapshotter.Prepare(ctx, id, usernsID)
return err
}
if err := ls.AddResource(ctx, lease, leases.Resource{
ID: remappedID,
Type: "snapshots/" + i.StorageDriver(),
}); err != nil {
return err
}
if err := ls.AddResource(ctx, lease, leases.Resource{
ID: usernsID,
Type: "snapshots/" + i.StorageDriver(),
}); err != nil {
return err
}
mounts, err := snapshotter.Prepare(ctx, remappedID, parentSnapshot)
if err != nil {
return err
}
if err := i.remapRootFS(ctx, mounts); err != nil {
if rmErr := snapshotter.Remove(ctx, usernsID); rmErr != nil {
log.G(ctx).WithError(rmErr).Warn("failed to remove snapshot after remap error")
}
return err
}
if err := snapshotter.Commit(ctx, usernsID, remappedID); err != nil {
return err
}
_, err = snapshotter.Prepare(ctx, id, usernsID)
return err
}
func (i *ImageService) remapRootFS(ctx context.Context, mounts []mount.Mount) error {
return mount.WithTempMount(ctx, mounts, func(root string) error {
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
stat := info.Sys().(*syscall.Stat_t)
if stat == nil {
return fmt.Errorf("cannot get underlying data for %s", path)
}
ids, err := i.idMapping.ToHost(idtools.Identity{UID: int(stat.Uid), GID: int(stat.Gid)})
if err != nil {
return err
}
return os.Lchown(path, ids.UID, ids.GID)
})
})
}

View file

@ -0,0 +1,12 @@
package containerd
import (
"context"
"github.com/containerd/containerd/leases"
"github.com/containerd/containerd/snapshots"
)
func (i *ImageService) remapSnapshot(ctx context.Context, snapshotter snapshots.Snapshotter, id string, parentSnapshot string, lease leases.Lease) error {
return nil
}

View file

@ -19,6 +19,7 @@ import (
"github.com/docker/docker/errdefs"
"github.com/docker/docker/image"
"github.com/docker/docker/layer"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/registry"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
@ -34,6 +35,7 @@ type ImageService struct {
eventsService *daemonevents.Events
pruneRunning atomic.Bool
refCountMounter snapshotter.Mounter
idMapping idtools.IdentityMapping
}
type RegistryConfigProvider interface {
@ -49,6 +51,7 @@ type ImageServiceConfig struct {
Registry RegistryConfigProvider
EventsService *daemonevents.Events
RefCountMounter snapshotter.Mounter
IDMapping idtools.IdentityMapping
}
// NewService creates a new ImageService.
@ -61,6 +64,7 @@ func NewService(config ImageServiceConfig) *ImageService {
registryService: config.Registry,
eventsService: config.EventsService,
refCountMounter: config.RefCountMounter,
idMapping: config.IDMapping,
}
}

View file

@ -1072,6 +1072,7 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S
RegistryHosts: d.RegistryHosts,
Registry: d.registryService,
EventsService: d.EventsService,
IDMapping: idMapping,
RefCountMounter: snapshotter.NewMounter(config.Root, driverName, idMapping),
})
} else {

View file

@ -16,6 +16,7 @@ import (
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/testutil"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
// user namespaces test: run daemon with remapped root setting
@ -27,6 +28,10 @@ func (s *DockerDaemonSuite) TestDaemonUserNamespaceRootSetting(c *testing.T) {
ctx := testutil.GetContext(c)
s.d.StartWithBusybox(ctx, c, "--userns-remap", "default")
out, err := s.d.Cmd("run", "busybox", "stat", "-c", "%u:%g", "/bin/cat")
assert.Check(c, err)
assert.Assert(c, is.Equal(strings.TrimSpace(out), "0:0"))
tmpDir, err := os.MkdirTemp("", "userns")
assert.NilError(c, err)
@ -47,7 +52,7 @@ func (s *DockerDaemonSuite) TestDaemonUserNamespaceRootSetting(c *testing.T) {
// writable by the remapped root UID/GID pair
assert.NilError(c, os.Chown(tmpDir, uid, gid))
out, err := s.d.Cmd("run", "-d", "--name", "userns", "-v", tmpDir+":/goofy", "-v", tmpDirNotExists+":/donald", "busybox", "sh", "-c", "touch /goofy/testfile; exec top")
out, err = s.d.Cmd("run", "-d", "--name", "userns", "-v", tmpDir+":/goofy", "-v", tmpDirNotExists+":/donald", "busybox", "sh", "-c", "touch /goofy/testfile; exec top")
assert.NilError(c, err, "Output: %s", out)
user := s.findUser(c, "userns")