From bdc84a618b475cc21a39dfa2fe57eae68b6110b6 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Mon, 9 May 2016 21:26:11 +1000 Subject: [PATCH 09/94] rootless: add autogenerated rootless config from `runc spec` Since this is a runC-specific feature, this belongs here over in opencontainers/ocitools (which is for generic OCI runtimes). In addition, we don't create a new network namespace. This is because currently if you want to set up a veth bridge you need CAP_NET_ADMIN in both network namespaces' pinned user namespace to create the necessary interfaces in each network namespace. Change-Id: I682b9c82f75c04b58d523ddb084b6adbb543e3d1 Signed-off-by: Aleksa Sarai --- libcontainer/specconv/example.go | 73 ++++++++++++++++++++++++++++++-- libcontainer/specconv/spec_linux_test.go | 30 +++---------- spec.go | 11 ++++- 3 files changed, 85 insertions(+), 29 deletions(-) diff --git a/libcontainer/specconv/example.go b/libcontainer/specconv/example.go index 44fad97..9a4460c 100644 --- a/libcontainer/specconv/example.go +++ b/libcontainer/specconv/example.go @@ -1,16 +1,18 @@ package specconv import ( + "os" "runtime" + "strings" "github.com/opencontainers/runtime-spec/specs-go" ) func sPtr(s string) *string { return &s } -// ExampleSpec returns an example spec file, with many options set so a user -// can see what a standard spec file looks like. -func ExampleSpec() *specs.Spec { +// Example returns an example spec file, with many options set so a user can +// see what a standard spec file looks like. +func Example() *specs.Spec { return &specs.Spec{ Version: specs.Version, Platform: specs.Platform{ @@ -158,3 +160,68 @@ func ExampleSpec() *specs.Spec { }, } } + +// ExampleRootless returns an example spec file that works with rootless +// containers. It's essentially a modified version of the specfile from +// Example(). +func ToRootless(spec *specs.Spec) { + var namespaces []specs.LinuxNamespace + + // Remove networkns from the spec. + for _, ns := range spec.Linux.Namespaces { + switch ns.Type { + case specs.NetworkNamespace, specs.UserNamespace: + // Do nothing. + default: + namespaces = append(namespaces, ns) + } + } + // Add userns to the spec. + namespaces = append(namespaces, specs.LinuxNamespace{ + Type: specs.UserNamespace, + }) + 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, + }} + + // Fix up mounts. + var mounts []specs.Mount + for _, mount := range spec.Mounts { + // Ignore all mounts that are under /sys. + if strings.HasPrefix(mount.Destination, "/sys") { + continue + } + + // Remove all gid= and uid= mappings. + var options []string + for _, option := range mount.Options { + if !strings.HasPrefix(option, "gid=") && !strings.HasPrefix(option, "uid=") { + options = append(options, option) + } + } + + mount.Options = options + mounts = append(mounts, mount) + } + // Add the sysfs mount as an rbind. + mounts = append(mounts, specs.Mount{ + Source: "/sys", + Destination: "/sys", + Type: "none", + Options: []string{"rbind", "nosuid", "noexec", "nodev", "ro"}, + }) + spec.Mounts = mounts + + // Remove cgroup settings. + spec.Linux.Resources = nil +} diff --git a/libcontainer/specconv/spec_linux_test.go b/libcontainer/specconv/spec_linux_test.go index 741fae6..f7292f3 100644 --- a/libcontainer/specconv/spec_linux_test.go +++ b/libcontainer/specconv/spec_linux_test.go @@ -3,7 +3,6 @@ package specconv import ( - "os" "testing" "github.com/opencontainers/runc/libcontainer/configs/validate" @@ -53,8 +52,9 @@ func TestLinuxCgroupsPathNotSpecified(t *testing.T) { } func TestSpecconvExampleValidate(t *testing.T) { - spec := ExampleSpec() + spec := Example() spec.Root.Path = "/" + opts := &CreateOpts{ CgroupName: "ContainerID", UseSystemdCgroup: false, @@ -97,29 +97,9 @@ func TestDupNamespaces(t *testing.T) { } func TestRootlessSpecconvValidate(t *testing.T) { - spec := &specs.Spec{ - Linux: specs.Linux{ - Namespaces: []specs.Namespace{ - { - Type: specs.UserNamespace, - }, - }, - UIDMappings: []specs.IDMapping{ - { - HostID: uint32(os.Geteuid()), - ContainerID: 0, - Size: 1, - }, - }, - GIDMappings: []specs.IDMapping{ - { - HostID: uint32(os.Getegid()), - ContainerID: 0, - Size: 1, - }, - }, - }, - } + spec := Example() + spec.Root.Path = "/" + ToRootless(spec) opts := &CreateOpts{ CgroupName: "ContainerID", diff --git a/spec.go b/spec.go index d7df312..9024ad4 100644 --- a/spec.go +++ b/spec.go @@ -64,12 +64,21 @@ container on your host.`, Value: "", Usage: "path to the root of the bundle directory", }, + cli.BoolFlag{ + Name: "rootless", + Usage: "generate a configuration for a rootless container", + }, }, Action: func(context *cli.Context) error { if err := checkArgs(context, 0, exactArgs); err != nil { return err } - spec := specconv.ExampleSpec() + spec := specconv.Example() + + rootless := context.Bool("rootless") + if rootless { + specconv.ToRootless(spec) + } checkNoFile := func(name string) error { _, err := os.Stat(name) -- 2.7.4.3