From bdc84a618b475cc21a39dfa2fe57eae68b6110b6 Mon Sep 17 00:00:00 2001
From: Aleksa Sarai <asarai@suse.de>
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 <asarai@suse.de>
---
 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

