From 357ca32831c94462f68cba3dafa78a6b739f07b7 Mon Sep 17 00:00:00 2001
From: "Guillaume J. Charmes" <guillaume.charmes@docker.com>
Date: Mon, 24 Feb 2014 21:52:29 -0800
Subject: [PATCH] Better capability/namespace management

Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes <guillaume.charmes@docker.com> (github: creack)
---
 libcontainer/capabilities/capabilities.go |  20 +--
 libcontainer/nsinit/execin.go             |   7 +-
 libcontainer/nsinit/ns_linux.go           |  24 +---
 libcontainer/types.go                     | 155 ++++++++++++++++------
 4 files changed, 119 insertions(+), 87 deletions(-)

diff --git a/libcontainer/capabilities/capabilities.go b/libcontainer/capabilities/capabilities.go
index 65fd455..3c6d752 100644
--- a/libcontainer/capabilities/capabilities.go
+++ b/libcontainer/capabilities/capabilities.go
@@ -6,24 +6,6 @@ import (
 	"os"
 )
 
-var capMap = map[libcontainer.Capability]capability.Cap{
-	libcontainer.CAP_SETPCAP:        capability.CAP_SETPCAP,
-	libcontainer.CAP_SYS_MODULE:     capability.CAP_SYS_MODULE,
-	libcontainer.CAP_SYS_RAWIO:      capability.CAP_SYS_RAWIO,
-	libcontainer.CAP_SYS_PACCT:      capability.CAP_SYS_PACCT,
-	libcontainer.CAP_SYS_ADMIN:      capability.CAP_SYS_ADMIN,
-	libcontainer.CAP_SYS_NICE:       capability.CAP_SYS_NICE,
-	libcontainer.CAP_SYS_RESOURCE:   capability.CAP_SYS_RESOURCE,
-	libcontainer.CAP_SYS_TIME:       capability.CAP_SYS_TIME,
-	libcontainer.CAP_SYS_TTY_CONFIG: capability.CAP_SYS_TTY_CONFIG,
-	libcontainer.CAP_MKNOD:          capability.CAP_MKNOD,
-	libcontainer.CAP_AUDIT_WRITE:    capability.CAP_AUDIT_WRITE,
-	libcontainer.CAP_AUDIT_CONTROL:  capability.CAP_AUDIT_CONTROL,
-	libcontainer.CAP_MAC_OVERRIDE:   capability.CAP_MAC_OVERRIDE,
-	libcontainer.CAP_MAC_ADMIN:      capability.CAP_MAC_ADMIN,
-	libcontainer.CAP_NET_ADMIN:      capability.CAP_NET_ADMIN,
-}
-
 // DropCapabilities drops capabilities for the current process based
 // on the container's configuration.
 func DropCapabilities(container *libcontainer.Container) error {
@@ -45,7 +27,7 @@ func DropCapabilities(container *libcontainer.Container) error {
 func getCapabilities(container *libcontainer.Container) []capability.Cap {
 	drop := []capability.Cap{}
 	for _, c := range container.Capabilities {
-		drop = append(drop, capMap[c])
+		drop = append(drop, c.Value)
 	}
 	return drop
 }
diff --git a/libcontainer/nsinit/execin.go b/libcontainer/nsinit/execin.go
index 463196c..306250c 100644
--- a/libcontainer/nsinit/execin.go
+++ b/libcontainer/nsinit/execin.go
@@ -14,7 +14,7 @@ import (
 // ExecIn uses an existing pid and joins the pid's namespaces with the new command.
 func (ns *linuxNs) ExecIn(container *libcontainer.Container, nspid int, args []string) (int, error) {
 	for _, ns := range container.Namespaces {
-		if err := system.Unshare(namespaceMap[ns]); err != nil {
+		if err := system.Unshare(ns.Value); err != nil {
 			return -1, err
 		}
 	}
@@ -42,8 +42,7 @@ func (ns *linuxNs) ExecIn(container *libcontainer.Container, nspid int, args []s
 
 	// if the container has a new pid and mount namespace we need to
 	// remount proc and sys to pick up the changes
-	if container.Namespaces.Contains(libcontainer.CLONE_NEWNS) &&
-		container.Namespaces.Contains(libcontainer.CLONE_NEWPID) {
+	if container.Namespaces.Contains("CLONE_NEWNS") && container.Namespaces.Contains("CLONE_NEWPID") {
 		pid, err := system.Fork()
 		if err != nil {
 			return -1, err
@@ -84,7 +83,7 @@ dropAndExec:
 func (ns *linuxNs) getNsFds(pid int, container *libcontainer.Container) ([]uintptr, error) {
 	fds := make([]uintptr, len(container.Namespaces))
 	for i, ns := range container.Namespaces {
-		f, err := os.OpenFile(filepath.Join("/proc/", strconv.Itoa(pid), "ns", namespaceFileMap[ns]), os.O_RDONLY, 0)
+		f, err := os.OpenFile(filepath.Join("/proc/", strconv.Itoa(pid), "ns", ns.File), os.O_RDONLY, 0)
 		if err != nil {
 			return fds, err
 		}
diff --git a/libcontainer/nsinit/ns_linux.go b/libcontainer/nsinit/ns_linux.go
index 58af247..ab6322e 100644
--- a/libcontainer/nsinit/ns_linux.go
+++ b/libcontainer/nsinit/ns_linux.go
@@ -2,35 +2,13 @@ package nsinit
 
 import (
 	"github.com/dotcloud/docker/pkg/libcontainer"
-	"syscall"
 )
 
-var namespaceMap = map[libcontainer.Namespace]int{
-	libcontainer.CLONE_NEWNS:   syscall.CLONE_NEWNS,
-	libcontainer.CLONE_NEWUTS:  syscall.CLONE_NEWUTS,
-	libcontainer.CLONE_NEWIPC:  syscall.CLONE_NEWIPC,
-	libcontainer.CLONE_NEWUSER: syscall.CLONE_NEWUSER,
-	libcontainer.CLONE_NEWPID:  syscall.CLONE_NEWPID,
-	libcontainer.CLONE_NEWNET:  syscall.CLONE_NEWNET,
-}
-
-// namespaceFileMap is used to convert the libcontainer types
-// into the names of the files located in /proc/<pid>/ns/* for
-// each namespace
-var namespaceFileMap = map[libcontainer.Namespace]string{
-	libcontainer.CLONE_NEWNS:   "mnt",
-	libcontainer.CLONE_NEWUTS:  "uts",
-	libcontainer.CLONE_NEWIPC:  "ipc",
-	libcontainer.CLONE_NEWUSER: "user",
-	libcontainer.CLONE_NEWPID:  "pid",
-	libcontainer.CLONE_NEWNET:  "net",
-}
-
 // getNamespaceFlags parses the container's Namespaces options to set the correct
 // flags on clone, unshare, and setns
 func GetNamespaceFlags(namespaces libcontainer.Namespaces) (flag int) {
 	for _, ns := range namespaces {
-		flag |= namespaceMap[ns]
+		flag |= ns.Value
 	}
 	return flag
 }
diff --git a/libcontainer/types.go b/libcontainer/types.go
index bb54ff5..cb64db1 100644
--- a/libcontainer/types.go
+++ b/libcontainer/types.go
@@ -1,58 +1,131 @@
 package libcontainer
 
-// These constants are defined as string types so that
-// it is clear when adding the configuration in config files
-// instead of using ints or other types
-const (
-	CAP_SETPCAP        Capability = "SETPCAP"
-	CAP_SYS_MODULE     Capability = "SYS_MODULE"
-	CAP_SYS_RAWIO      Capability = "SYS_RAWIO"
-	CAP_SYS_PACCT      Capability = "SYS_PACCT"
-	CAP_SYS_ADMIN      Capability = "SYS_ADMIN"
-	CAP_SYS_NICE       Capability = "SYS_NICE"
-	CAP_SYS_RESOURCE   Capability = "SYS_RESOURCE"
-	CAP_SYS_TIME       Capability = "SYS_TIME"
-	CAP_SYS_TTY_CONFIG Capability = "SYS_TTY_CONFIG"
-	CAP_MKNOD          Capability = "MKNOD"
-	CAP_AUDIT_WRITE    Capability = "AUDIT_WRITE"
-	CAP_AUDIT_CONTROL  Capability = "AUDIT_CONTROL"
-	CAP_MAC_OVERRIDE   Capability = "MAC_OVERRIDE"
-	CAP_MAC_ADMIN      Capability = "MAC_ADMIN"
-	CAP_NET_ADMIN      Capability = "NET_ADMIN"
+import (
+	"encoding/json"
+	"errors"
+	"github.com/syndtr/gocapability/capability"
+	"os"
+	"syscall"
+)
 
-	CLONE_NEWNS   Namespace = "NEWNS"   // mount
-	CLONE_NEWUTS  Namespace = "NEWUTS"  // utsname
-	CLONE_NEWIPC  Namespace = "NEWIPC"  // ipc
-	CLONE_NEWUSER Namespace = "NEWUSER" // user
-	CLONE_NEWPID  Namespace = "NEWPID"  // pid
-	CLONE_NEWNET  Namespace = "NEWNET"  // network
+var (
+	ErrUnkownNamespace error = errors.New("Unkown namespace")
+)
+
+// namespaceList is used to convert the libcontainer types
+// into the names of the files located in /proc/<pid>/ns/* for
+// each namespace
+var (
+	namespaceList = Namespaces{
+		{Key: "NEWNS", Value: syscall.CLONE_NEWNS, File: "mnt"},
+		{Key: "NEWUTS", Value: syscall.CLONE_NEWUTS, File: "uts"},
+		{Key: "NEWIPC", Value: syscall.CLONE_NEWIPC, File: "ipc"},
+		{Key: "NEWUSER", Value: syscall.CLONE_NEWUSER, File: "user"},
+		{Key: "NEWPID", Value: syscall.CLONE_NEWPID, File: "pid"},
+		{Key: "NEWNET", Value: syscall.CLONE_NEWNET, File: "net"},
+	}
+	capabilityList = Capabilities{
+		{Key: "SETPCAP", Value: capability.CAP_SETPCAP},
+		{Key: "SYS_MODULE", Value: capability.CAP_SYS_MODULE},
+		{Key: "SYS_RAWIO", Value: capability.CAP_SYS_RAWIO},
+		{Key: "SYS_PACCT", Value: capability.CAP_SYS_PACCT},
+		{Key: "SYS_ADMIN", Value: capability.CAP_SYS_ADMIN},
+		{Key: "SYS_NICE", Value: capability.CAP_SYS_NICE},
+		{Key: "SYS_RESOURCE", Value: capability.CAP_SYS_RESOURCE},
+		{Key: "SYS_TIME", Value: capability.CAP_SYS_TIME},
+		{Key: "SYS_TTY_CONFIG", Value: capability.CAP_SYS_TTY_CONFIG},
+		{Key: "MKNOD", Value: capability.CAP_MKNOD},
+		{Key: "AUDIT_WRITE", Value: capability.CAP_AUDIT_WRITE},
+		{Key: "AUDIT_CONTROL", Value: capability.CAP_AUDIT_CONTROL},
+		{Key: "MAC_OVERRIDE", Value: capability.CAP_MAC_OVERRIDE},
+		{Key: "MAC_ADMIN", Value: capability.CAP_MAC_ADMIN},
+		{Key: "NET_ADMIN", Value: capability.CAP_NET_ADMIN},
+	}
 )
 
 type (
-	Namespace    string
-	Namespaces   []Namespace
-	Capability   string
-	Capabilities []Capability
+	Namespace struct {
+		Key   string
+		Value int
+		File  string
+	}
+	Namespaces []*Namespace
 )
 
+func (ns *Namespace) MarshalJSON() ([]byte, error) {
+	return json.Marshal(ns.Key)
+}
+
+func (ns *Namespace) UnmarshalJSON(src []byte) error {
+	var nsName string
+	if err := json.Unmarshal(src, &nsName); err != nil {
+		return err
+	}
+	ret := GetNamespace(nsName)
+	if ret == nil {
+		return ErrUnkownNamespace
+	}
+	*ns = *ret
+	return nil
+}
+
+func GetNamespace(key string) *Namespace {
+	for _, ns := range namespaceList {
+		if ns.Key == key {
+			return ns
+		}
+	}
+	if os.Getenv("DEBUG") != "" {
+		panic("Unreachable: Namespace not found")
+	}
+	return nil
+}
+
 // Contains returns true if the specified Namespace is
 // in the slice
-func (n Namespaces) Contains(ns Namespace) bool {
-	for _, nns := range n {
-		if nns == ns {
-			return true
+func (n Namespaces) Contains(ns string) bool {
+	return GetNamespace(ns) != nil
+}
+
+type (
+	Capability struct {
+		Key   string
+		Value capability.Cap
+	}
+	Capabilities []*Capability
+)
+
+func (ns *Capability) MarshalJSON() ([]byte, error) {
+	return json.Marshal(ns.Key)
+}
+
+func (ns *Capability) UnmarshalJSON(src []byte) error {
+	var capName string
+	if err := json.Unmarshal(src, &capName); err != nil {
+		return err
+	}
+	ret := GetCapability(capName)
+	if ret == nil {
+		return ErrUnkownNamespace
+	}
+	*ns = *ret
+	return nil
+}
+
+func GetCapability(key string) *Capability {
+	for _, capp := range capabilityList {
+		if capp.Key == key {
+			return capp
 		}
 	}
-	return false
+	if os.Getenv("DEBUG") != "" {
+		panic("Unreachable: Namespace not found")
+	}
+	return nil
 }
 
 // Contains returns true if the specified Capability is
 // in the slice
-func (c Capabilities) Contains(capp Capability) bool {
-	for _, cc := range c {
-		if cc == capp {
-			return true
-		}
-	}
-	return false
+func (c Capabilities) Contains(capp string) bool {
+	return GetCapability(capp) != nil
 }