Skip to content

Commit

Permalink
rootless: optional support for generating config with subuid map
Browse files Browse the repository at this point in the history
Signed-off-by: Akihiro Suda <[email protected]>
  • Loading branch information
AkihiroSuda committed Jun 9, 2018
1 parent dd56ece commit c4ab5c9
Show file tree
Hide file tree
Showing 3 changed files with 302 additions and 31 deletions.
139 changes: 128 additions & 11 deletions libcontainer/specconv/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package specconv

import (
"os"
"sort"
"strings"

"github.com/opencontainers/runc/libcontainer/system"
"github.com/opencontainers/runc/libcontainer/user"
"github.com/opencontainers/runtime-spec/specs-go"
)

Expand Down Expand Up @@ -155,10 +158,60 @@ func Example() *specs.Spec {
}
}

// RootlessOpts is an optional spec for ToRootless
type RootlessOpts struct {
// Add all subuids and subgids to spec.Linux.{U,G}IDMappings.
// Requires newuidmap(1) and newgidmap(1) with suid bit.
// Ignored when running in userns.
MapAllSubIDs bool
}

// Run-time context for ToRootless.
type RootlessContext struct {
EUID uint32
EGID uint32
SubUIDs []user.SubID
SubGIDs []user.SubID
UIDMap []user.IDMap
GIDMap []user.IDMap
InUserNS bool
}

// ToRootless converts the given spec file into one that should work with
// rootless containers, by removing incompatible options and adding others that
// are needed.
func ToRootless(spec *specs.Spec) {
func ToRootless(spec *specs.Spec, opts *RootlessOpts) error {
var err error
ctx := RootlessContext{}
ctx.EUID = uint32(os.Geteuid())
ctx.EGID = uint32(os.Getegid())
ctx.SubUIDs, err = user.CurrentUserSubUIDs()
if err != nil && !os.IsNotExist(err) {
return err
}
ctx.SubGIDs, err = user.CurrentGroupSubGIDs()
if err != nil && !os.IsNotExist(err) {
return err
}
ctx.UIDMap, err = user.CurrentProcessUIDMap()
if err != nil && !os.IsNotExist(err) {
return err
}
uidMapExists := !os.IsNotExist(err)
ctx.GIDMap, err = user.CurrentProcessUIDMap()
if err != nil && !os.IsNotExist(err) {
return err
}
ctx.InUserNS = uidMapExists && system.UIDMapInUserNS(ctx.UIDMap)
return ToRootlessWithContext(ctx, spec, opts)
}

// ToRootlessWithContext converts the spec with the run-time context.
// ctx can be internally modified for sorting.
func ToRootlessWithContext(ctx RootlessContext, spec *specs.Spec, opts *RootlessOpts) error {
if opts == nil {
opts = &RootlessOpts{}
}
var namespaces []specs.LinuxNamespace

// Remove networkns from the spec.
Expand All @@ -177,16 +230,66 @@ func ToRootless(spec *specs.Spec) {
spec.Linux.Namespaces = namespaces

// Add mappings for the current user.
spec.Linux.UIDMappings = []specs.LinuxIDMapping{{
HostID: uint32(os.Geteuid()),
ContainerID: 0,
Size: 1,
}}
spec.Linux.GIDMappings = []specs.LinuxIDMapping{{
HostID: uint32(os.Getegid()),
ContainerID: 0,
Size: 1,
}}
if ctx.InUserNS {
uNextContainerID := 0
sort.Sort(idmapSorter(ctx.UIDMap))
for _, uidmap := range ctx.UIDMap {
spec.Linux.UIDMappings = append(spec.Linux.UIDMappings,
specs.LinuxIDMapping{
HostID: uint32(uidmap.ID),
ContainerID: uint32(uNextContainerID),
Size: uint32(uidmap.Count),
})
uNextContainerID += uidmap.Count
}
gNextContainerID := 0
sort.Sort(idmapSorter(ctx.GIDMap))
for _, gidmap := range ctx.GIDMap {
spec.Linux.GIDMappings = append(spec.Linux.GIDMappings,
specs.LinuxIDMapping{
HostID: uint32(gidmap.ID),
ContainerID: uint32(gNextContainerID),
Size: uint32(gidmap.Count),
})
gNextContainerID += gidmap.Count
}
// opts.MapAllSubIDs is ignored in userns
} else {
spec.Linux.UIDMappings = []specs.LinuxIDMapping{{
HostID: ctx.EUID,
ContainerID: 0,
Size: 1,
}}
spec.Linux.GIDMappings = []specs.LinuxIDMapping{{
HostID: ctx.EGID,
ContainerID: 0,
Size: 1,
}}
if opts.MapAllSubIDs {
uNextContainerID := 1
sort.Sort(subIDSorter(ctx.SubUIDs))
for _, subuid := range ctx.SubUIDs {
spec.Linux.UIDMappings = append(spec.Linux.UIDMappings,
specs.LinuxIDMapping{
HostID: uint32(subuid.SubID),
ContainerID: uint32(uNextContainerID),
Size: uint32(subuid.Count),
})
uNextContainerID += subuid.Count
}
gNextContainerID := 1
sort.Sort(subIDSorter(ctx.SubGIDs))
for _, subgid := range ctx.SubGIDs {
spec.Linux.GIDMappings = append(spec.Linux.GIDMappings,
specs.LinuxIDMapping{
HostID: uint32(subgid.SubID),
ContainerID: uint32(gNextContainerID),
Size: uint32(subgid.Count),
})
gNextContainerID += subgid.Count
}
}
}

// Fix up mounts.
var mounts []specs.Mount
Expand Down Expand Up @@ -218,4 +321,18 @@ func ToRootless(spec *specs.Spec) {

// Remove cgroup settings.
spec.Linux.Resources = nil
return nil
}

// subIDSorter is required for Go <= 1.7
type subIDSorter []user.SubID

func (x subIDSorter) Len() int { return len(x) }
func (x subIDSorter) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x subIDSorter) Less(i, j int) bool { return x[i].SubID < x[j].SubID }

type idmapSorter []user.IDMap

func (x idmapSorter) Len() int { return len(x) }
func (x idmapSorter) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x idmapSorter) Less(i, j int) bool { return x[i].ID < x[j].ID }
189 changes: 170 additions & 19 deletions libcontainer/specconv/spec_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
package specconv

import (
"os"
"reflect"
"testing"

"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/configs/validate"
"github.com/opencontainers/runc/libcontainer/user"
"github.com/opencontainers/runtime-spec/specs-go"
)

Expand Down Expand Up @@ -418,28 +419,178 @@ func TestDupNamespaces(t *testing.T) {
}

func TestRootlessSpecconvValidate(t *testing.T) {
if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) {
t.Skip("userns is unsupported")
specGen := func() *specs.Spec {
spec := Example()
spec.Root.Path = "/"
return spec
}
cases := []struct {
ctx RootlessContext
opts *RootlessOpts
additionalValidator func(t *testing.T, s *specs.Spec)
}{
{
ctx: RootlessContext{
EUID: 0,
EGID: 0,
},
},
{
ctx: RootlessContext{
EUID: 4242,
EGID: 4242,
},
},
{
ctx: RootlessContext{
EUID: 4242,
EGID: 4242,
// empty subuid / subgid
},
opts: &RootlessOpts{
MapAllSubIDs: true,
},
},
{
ctx: RootlessContext{
EUID: 4242,
EGID: 4242,
SubUIDs: []user.SubID{
{
Name: "dummy",
SubID: 14242,
Count: 65536,
},
{
Name: "dummy",
SubID: 114242,
Count: 65536,
},
},
SubGIDs: []user.SubID{
{
Name: "dummy",
SubID: 14242,
Count: 65536,
},
{
Name: "dummy",
SubID: 114242,
Count: 65536,
},
},
},
opts: &RootlessOpts{
MapAllSubIDs: true,
},
additionalValidator: func(t *testing.T, s *specs.Spec) {
expectedUIDMappings := []specs.LinuxIDMapping{
{
HostID: 4242,
ContainerID: 0,
Size: 1,
},
{
HostID: 14242,
ContainerID: 1,
Size: 65536,
},
{
HostID: 114242,
ContainerID: 65537,
Size: 65536,
},
}
if !reflect.DeepEqual(expectedUIDMappings, s.Linux.UIDMappings) {
t.Errorf("expected %#v, got %#v", expectedUIDMappings, s.Linux.UIDMappings)
}
expectedGIDMappings := expectedUIDMappings
if !reflect.DeepEqual(expectedGIDMappings, s.Linux.GIDMappings) {
t.Errorf("expected %#v, got %#v", expectedGIDMappings, s.Linux.GIDMappings)
}
},
},
{
ctx: RootlessContext{
EUID: 0,
EGID: 0,
UIDMap: []user.IDMap{
{
ID: 0,
ParentID: 4242,
Count: 1,
},
{
ID: 1,
ParentID: 231072,
Count: 65536,
},
},
GIDMap: []user.IDMap{
{
ID: 0,
ParentID: 4242,
Count: 1,
},
{
ID: 1,
ParentID: 231072,
Count: 65536,
},
},
InUserNS: true,
},
additionalValidator: func(t *testing.T, s *specs.Spec) {
expectedUIDMappings := []specs.LinuxIDMapping{
{
HostID: 0,
ContainerID: 0,
Size: 1,
},
{
HostID: 1,
ContainerID: 1,
Size: 65536,
},
}
if !reflect.DeepEqual(expectedUIDMappings, s.Linux.UIDMappings) {
t.Errorf("expected %#v, got %#v", expectedUIDMappings, s.Linux.UIDMappings)
}
expectedGIDMappings := expectedUIDMappings
if !reflect.DeepEqual(expectedGIDMappings, s.Linux.GIDMappings) {
t.Errorf("expected %#v, got %#v", expectedGIDMappings, s.Linux.GIDMappings)
}
},
},
}

spec := Example()
spec.Root.Path = "/"
ToRootless(spec)
for _, c := range cases {
spec := specGen()
err := ToRootlessWithContext(c.ctx, spec, c.opts)
if err != nil {
t.Errorf("Couldn't convert a rootful spec to rootless: %v", err)
}

opts := &CreateOpts{
CgroupName: "ContainerID",
UseSystemdCgroup: false,
Spec: spec,
Rootless: true,
}
// t.Logf("%#v", spec)
if c.additionalValidator != nil {
c.additionalValidator(t, spec)
}

config, err := CreateLibcontainerConfig(opts)
if err != nil {
t.Errorf("Couldn't create libcontainer config: %v", err)
}
opts := &CreateOpts{
CgroupName: "ContainerID",
UseSystemdCgroup: false,
Spec: spec,
Rootless: true,
}

validator := validate.New()
if err := validator.Validate(config); err != nil {
t.Errorf("Expected specconv to produce valid rootless container config: %v", err)
config, err := CreateLibcontainerConfig(opts)
if err != nil {
t.Errorf("Couldn't create libcontainer config: %v", err)
}

validator := validate.New()
if err := validator.Validate(config); err != nil {
t.Errorf("Expected specconv to produce valid rootless container config: %v", err)
}
}
}
5 changes: 4 additions & 1 deletion spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ generate a proper rootless spec file.`,

rootless := context.Bool("rootless")
if rootless {
specconv.ToRootless(spec)
opts := &specconv.RootlessOpts{}
if err := specconv.ToRootless(spec, opts); err != nil {
return err
}
}

checkNoFile := func(name string) error {
Expand Down

0 comments on commit c4ab5c9

Please sign in to comment.