support user remapping in ctr

* --uidmap support for one remapping
* --gidmap support for one remapping
* create IoUid and IoGid options for getNewTaskOpts

Signed-off-by: Jie Hao Liao <liaojh1998@gmail.com>
This commit is contained in:
Jie Hao Liao 2019-11-25 01:39:33 -06:00
parent f01665aa02
commit 9862cb8f85
2 changed files with 126 additions and 5 deletions

View File

@ -20,7 +20,9 @@ package run
import (
gocontext "context"
"fmt"
"path/filepath"
"strconv"
"strings"
"github.com/containerd/containerd"
@ -44,6 +46,14 @@ var platformRunFlags = []cli.Flag{
Name: "runc-systemd-cgroup",
Usage: "start runc with systemd cgroup manager",
},
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`",
},
}
// NewContainer creates a new container
@ -115,12 +125,30 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli
opts = append(opts, oci.WithImageConfig(image))
cOpts = append(cOpts,
containerd.WithImage(image),
containerd.WithSnapshotter(snapshotter),
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)
if err != nil {
return nil, err
}
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))
} else {
cOpts = append(cOpts, containerd.WithRemappedSnapshot(id, image, uidMap.HostID, gidMap.HostID))
}
} else {
// Even when "read-only" is set, we don't use KindView snapshot here. (#1495)
// We pass writable snapshot to the OCI runtime, and the runtime remounts it as read-only,
// after creating some mount points on demand.
containerd.WithNewSnapshot(id, image),
containerd.WithImageStopSignal(image, "SIGTERM"))
cOpts = append(cOpts, containerd.WithNewSnapshot(id, image))
}
cOpts = append(cOpts, containerd.WithImageStopSignal(image, "SIGTERM"))
}
if context.Bool("read-only") {
opts = append(opts, oci.WithRootFSReadonly())
@ -210,10 +238,51 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli
}
func getNewTaskOpts(context *cli.Context) []containerd.NewTaskOpts {
var (
tOpts []containerd.NewTaskOpts
)
if context.Bool("no-pivot") {
return []containerd.NewTaskOpts{containerd.WithNoPivotRoot}
tOpts = append(tOpts, containerd.WithNoPivotRoot)
}
return nil
if uidmap := context.String("uidmap"); uidmap != "" {
uidMap, err := parseIDMapping(uidmap)
if err != nil {
fmt.Printf("warning: expected to parse uidmap: %s\n", err.Error())
}
tOpts = append(tOpts, containerd.WithUIDOwner(uidMap.HostID))
}
if gidmap := context.String("gidmap"); gidmap != "" {
gidMap, err := parseIDMapping(gidmap)
if err != nil {
fmt.Printf("warning: expected to parse uidmap: %s\n", err.Error())
}
tOpts = append(tOpts, containerd.WithGIDOwner(gidMap.HostID))
}
return tOpts
}
func parseIDMapping(mapping string) (specs.LinuxIDMapping, error) {
parts := strings.Split(mapping, ":")
if len(parts) != 3 {
return specs.LinuxIDMapping{}, errors.New("user namespace mappings require the format `container-id:host-id:size`")
}
cID, err := strconv.ParseUint(parts[0], 0, 16)
if err != nil {
return specs.LinuxIDMapping{}, errors.Wrapf(err, "invalid container id for user namespace remapping")
}
hID, err := strconv.ParseUint(parts[1], 0, 16)
if err != nil {
return specs.LinuxIDMapping{}, errors.Wrapf(err, "invalid host id for user namespace remapping")
}
size, err := strconv.ParseUint(parts[2], 0, 16)
if err != nil {
return specs.LinuxIDMapping{}, errors.Wrapf(err, "invalid size for user namespace remapping")
}
return specs.LinuxIDMapping{
ContainerID: uint32(cID),
HostID: uint32(hID),
Size: uint32(size),
}, nil
}
func validNamespace(ns string) bool {

View File

@ -103,3 +103,55 @@ func WithShimCgroup(path string) NewTaskOpts {
return nil
}
}
// WithUIDOwner allows console I/O to work with the remapped UID in user namespace
func WithUIDOwner(uid uint32) NewTaskOpts {
return func(ctx context.Context, c *Client, ti *TaskInfo) error {
if CheckRuntime(ti.Runtime(), "io.containerd.runc") {
if ti.Options == nil {
ti.Options = &options.Options{}
}
opts, ok := ti.Options.(*options.Options)
if !ok {
return errors.New("invalid v2 shim create options format")
}
opts.IoUid = uid
} else {
if ti.Options == nil {
ti.Options = &runctypes.CreateOptions{}
}
opts, ok := ti.Options.(*runctypes.CreateOptions)
if !ok {
return errors.New("could not cast TaskInfo Options to CreateOptions")
}
opts.IoUid = uid
}
return nil
}
}
// WithGIDOwner allows console I/O to work with the remapped GID in user namespace
func WithGIDOwner(gid uint32) NewTaskOpts {
return func(ctx context.Context, c *Client, ti *TaskInfo) error {
if CheckRuntime(ti.Runtime(), "io.containerd.runc") {
if ti.Options == nil {
ti.Options = &options.Options{}
}
opts, ok := ti.Options.(*options.Options)
if !ok {
return errors.New("invalid v2 shim create options format")
}
opts.IoGid = gid
} else {
if ti.Options == nil {
ti.Options = &runctypes.CreateOptions{}
}
opts, ok := ti.Options.(*runctypes.CreateOptions)
if !ok {
return errors.New("could not cast TaskInfo Options to CreateOptions")
}
opts.IoGid = gid
}
return nil
}
}