浏览代码

Mount /dev in tmpfs for privileged containers
Docker-DCO-1.1-Signed-off-by: Michael Crosby <michael@crosbymichael.com> (github: crosbymichael)

Michael Crosby 11 年之前
父节点
当前提交
34c05c58c8

+ 7 - 3
daemon/execdriver/native/create.go

@@ -10,6 +10,7 @@ import (
 	"github.com/dotcloud/docker/daemon/execdriver/native/template"
 	"github.com/dotcloud/docker/daemon/execdriver/native/template"
 	"github.com/dotcloud/docker/pkg/apparmor"
 	"github.com/dotcloud/docker/pkg/apparmor"
 	"github.com/dotcloud/docker/pkg/libcontainer"
 	"github.com/dotcloud/docker/pkg/libcontainer"
+	"github.com/dotcloud/docker/pkg/libcontainer/mount/nodes"
 )
 )
 
 
 // createContainer populates and configures the container type with the
 // createContainer populates and configures the container type with the
@@ -34,8 +35,6 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Container
 		if err := d.setPrivileged(container); err != nil {
 		if err := d.setPrivileged(container); err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
-	} else {
-		container.Mounts = append(container.Mounts, libcontainer.Mount{Type: "devtmpfs"})
 	}
 	}
 	if err := d.setupCgroups(container, c); err != nil {
 	if err := d.setupCgroups(container, c); err != nil {
 		return nil, err
 		return nil, err
@@ -97,11 +96,16 @@ func (d *driver) createNetwork(container *libcontainer.Container, c *execdriver.
 	return nil
 	return nil
 }
 }
 
 
-func (d *driver) setPrivileged(container *libcontainer.Container) error {
+func (d *driver) setPrivileged(container *libcontainer.Container) (err error) {
 	container.Capabilities = libcontainer.GetAllCapabilities()
 	container.Capabilities = libcontainer.GetAllCapabilities()
 	container.Cgroups.DeviceAccess = true
 	container.Cgroups.DeviceAccess = true
 
 
 	delete(container.Context, "restrictions")
 	delete(container.Context, "restrictions")
+	delete(container.DeviceNodes, "additional")
+
+	if container.DeviceNodes["required"], err = nodes.GetHostDeviceNodes(); err != nil {
+		return err
+	}
 
 
 	if apparmor.IsEnabled() {
 	if apparmor.IsEnabled() {
 		container.Context["apparmor_profile"] = "unconfined"
 		container.Context["apparmor_profile"] = "unconfined"

+ 5 - 0
daemon/execdriver/native/template/default_template.go

@@ -4,6 +4,7 @@ import (
 	"github.com/dotcloud/docker/pkg/apparmor"
 	"github.com/dotcloud/docker/pkg/apparmor"
 	"github.com/dotcloud/docker/pkg/libcontainer"
 	"github.com/dotcloud/docker/pkg/libcontainer"
 	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
 	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
+	"github.com/dotcloud/docker/pkg/libcontainer/mount/nodes"
 )
 )
 
 
 // New returns the docker default configuration for libcontainer
 // New returns the docker default configuration for libcontainer
@@ -33,6 +34,10 @@ func New() *libcontainer.Container {
 			DeviceAccess: false,
 			DeviceAccess: false,
 		},
 		},
 		Context: libcontainer.Context{},
 		Context: libcontainer.Context{},
+		DeviceNodes: map[string][]string{
+			"required":   nodes.DefaultNodes,
+			"additional": {"fuse"},
+		},
 	}
 	}
 	if apparmor.IsEnabled() {
 	if apparmor.IsEnabled() {
 		container.Context["apparmor_profile"] = "docker-default"
 		container.Context["apparmor_profile"] = "docker-default"

+ 14 - 13
pkg/libcontainer/container.go

@@ -11,19 +11,20 @@ type Context map[string]string
 // Container defines configuration options for how a
 // Container defines configuration options for how a
 // container is setup inside a directory and how a process should be executed
 // container is setup inside a directory and how a process should be executed
 type Container struct {
 type Container struct {
-	Hostname     string          `json:"hostname,omitempty"`      // hostname
-	ReadonlyFs   bool            `json:"readonly_fs,omitempty"`   // set the containers rootfs as readonly
-	NoPivotRoot  bool            `json:"no_pivot_root,omitempty"` // this can be enabled if you are running in ramdisk
-	User         string          `json:"user,omitempty"`          // user to execute the process as
-	WorkingDir   string          `json:"working_dir,omitempty"`   // current working directory
-	Env          []string        `json:"environment,omitempty"`   // environment to set
-	Tty          bool            `json:"tty,omitempty"`           // setup a proper tty or not
-	Namespaces   map[string]bool `json:"namespaces,omitempty"`    // namespaces to apply
-	Capabilities []string        `json:"capabilities,omitempty"`  // capabilities given to the container
-	Networks     []*Network      `json:"networks,omitempty"`      // nil for host's network stack
-	Cgroups      *cgroups.Cgroup `json:"cgroups,omitempty"`       // cgroups
-	Context      Context         `json:"context,omitempty"`       // generic context for specific options (apparmor, selinux)
-	Mounts       Mounts          `json:"mounts,omitempty"`
+	Hostname     string              `json:"hostname,omitempty"`      // hostname
+	ReadonlyFs   bool                `json:"readonly_fs,omitempty"`   // set the containers rootfs as readonly
+	NoPivotRoot  bool                `json:"no_pivot_root,omitempty"` // this can be enabled if you are running in ramdisk
+	User         string              `json:"user,omitempty"`          // user to execute the process as
+	WorkingDir   string              `json:"working_dir,omitempty"`   // current working directory
+	Env          []string            `json:"environment,omitempty"`   // environment to set
+	Tty          bool                `json:"tty,omitempty"`           // setup a proper tty or not
+	Namespaces   map[string]bool     `json:"namespaces,omitempty"`    // namespaces to apply
+	Capabilities []string            `json:"capabilities,omitempty"`  // capabilities given to the container
+	Networks     []*Network          `json:"networks,omitempty"`      // nil for host's network stack
+	Cgroups      *cgroups.Cgroup     `json:"cgroups,omitempty"`       // cgroups
+	Context      Context             `json:"context,omitempty"`       // generic context for specific options (apparmor, selinux)
+	Mounts       Mounts              `json:"mounts,omitempty"`
+	DeviceNodes  map[string][]string `json:"device_nodes,omitempty"` // device nodes to add to the container's /dev
 }
 }
 
 
 // Network defines configuration for a container's networking stack
 // Network defines configuration for a container's networking stack

+ 11 - 1
pkg/libcontainer/container.json

@@ -43,5 +43,15 @@
     {
     {
       "type": "devtmpfs"
       "type": "devtmpfs"
     }
     }
-  ]
+  ],
+  "device_nodes": {
+      "required": [
+          "null",
+          "zero",
+          "full",
+          "random",
+          "urandom",
+          "tty"
+      ]
+  }
 }
 }

+ 15 - 6
pkg/libcontainer/container_test.go

@@ -4,12 +4,14 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"os"
 	"os"
 	"testing"
 	"testing"
+
+	"github.com/dotcloud/docker/pkg/libcontainer/mount/nodes"
 )
 )
 
 
 // Checks whether the expected capability is specified in the capabilities.
 // Checks whether the expected capability is specified in the capabilities.
-func hasCapability(expected string, capabilities []string) bool {
-	for _, capability := range capabilities {
-		if capability == expected {
+func contains(expected string, values []string) bool {
+	for _, v := range values {
+		if v == expected {
 			return true
 			return true
 		}
 		}
 	}
 	}
@@ -47,18 +49,25 @@ func TestContainerJsonFormat(t *testing.T) {
 		t.Fail()
 		t.Fail()
 	}
 	}
 
 
-	if hasCapability("SYS_ADMIN", container.Capabilities) {
+	if contains("SYS_ADMIN", container.Capabilities) {
 		t.Log("SYS_ADMIN should not be enabled in capabilities mask")
 		t.Log("SYS_ADMIN should not be enabled in capabilities mask")
 		t.Fail()
 		t.Fail()
 	}
 	}
 
 
-	if !hasCapability("MKNOD", container.Capabilities) {
+	if !contains("MKNOD", container.Capabilities) {
 		t.Log("MKNOD should be enabled in capabilities mask")
 		t.Log("MKNOD should be enabled in capabilities mask")
 		t.Fail()
 		t.Fail()
 	}
 	}
 
 
-	if hasCapability("SYS_CHROOT", container.Capabilities) {
+	if contains("SYS_CHROOT", container.Capabilities) {
 		t.Log("capabilities mask should not contain SYS_CHROOT")
 		t.Log("capabilities mask should not contain SYS_CHROOT")
 		t.Fail()
 		t.Fail()
 	}
 	}
+
+	for _, n := range nodes.DefaultNodes {
+		if !contains(n, container.DeviceNodes["required"]) {
+			t.Logf("devices should contain %s", n)
+			t.Fail()
+		}
+	}
 }
 }

+ 4 - 6
pkg/libcontainer/mount/init.go

@@ -48,10 +48,10 @@ func InitializeMountNamespace(rootfs, console string, container *libcontainer.Co
 	if err := setupBindmounts(rootfs, container.Mounts); err != nil {
 	if err := setupBindmounts(rootfs, container.Mounts); err != nil {
 		return fmt.Errorf("bind mounts %s", err)
 		return fmt.Errorf("bind mounts %s", err)
 	}
 	}
-	if err := nodes.CopyN(rootfs, nodes.DefaultNodes, true); err != nil {
-		return fmt.Errorf("copy dev nodes %s", err)
+	if err := nodes.CopyN(rootfs, container.DeviceNodes["required"], true); err != nil {
+		return fmt.Errorf("copy required dev nodes %s", err)
 	}
 	}
-	if err := nodes.CopyN(rootfs, nodes.AdditionalNodes, false); err != nil {
+	if err := nodes.CopyN(rootfs, container.DeviceNodes["additional"], false); err != nil {
 		return fmt.Errorf("copy additional dev nodes %s", err)
 		return fmt.Errorf("copy additional dev nodes %s", err)
 	}
 	}
 	if err := SetupPtmx(rootfs, console, container.Context["mount_label"]); err != nil {
 	if err := SetupPtmx(rootfs, console, container.Context["mount_label"]); err != nil {
@@ -195,13 +195,11 @@ func newSystemMounts(rootfs, mountLabel string, mounts libcontainer.Mounts) []mo
 	systemMounts := []mount{
 	systemMounts := []mount{
 		{source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags},
 		{source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags},
 		{source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags},
 		{source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags},
+		{source: "tmpfs", path: filepath.Join(rootfs, "dev"), device: "tmpfs", flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME, data: label.FormatMountLabel("mode=755", mountLabel)},
 		{source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)},
 		{source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)},
 		{source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)},
 		{source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)},
 		{source: "tmpfs", path: filepath.Join(rootfs, "run"), device: "tmpfs", flags: defaultMountFlags},
 		{source: "tmpfs", path: filepath.Join(rootfs, "run"), device: "tmpfs", flags: defaultMountFlags},
 	}
 	}
 
 
-	if len(mounts.OfType("devtmpfs")) == 1 {
-		systemMounts = append([]mount{{source: "tmpfs", path: filepath.Join(rootfs, "dev"), device: "tmpfs", flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME, data: label.FormatMountLabel("mode=755", mountLabel)}}, systemMounts...)
-	}
 	return systemMounts
 	return systemMounts
 }
 }

+ 16 - 5
pkg/libcontainer/mount/nodes/nodes.go

@@ -4,6 +4,7 @@ package nodes
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+	"io/ioutil"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
 	"syscall"
 	"syscall"
@@ -21,11 +22,6 @@ var DefaultNodes = []string{
 	"tty",
 	"tty",
 }
 }
 
 
-// AdditionalNodes includes nodes that are not required
-var AdditionalNodes = []string{
-	"fuse",
-}
-
 // CopyN copies the device node from the host into the rootfs
 // CopyN copies the device node from the host into the rootfs
 func CopyN(rootfs string, nodesToCopy []string, shouldExist bool) error {
 func CopyN(rootfs string, nodesToCopy []string, shouldExist bool) error {
 	oldMask := system.Umask(0000)
 	oldMask := system.Umask(0000)
@@ -61,3 +57,18 @@ func Copy(rootfs, node string, shouldExist bool) error {
 	}
 	}
 	return nil
 	return nil
 }
 }
+
+func GetHostDeviceNodes() ([]string, error) {
+	files, err := ioutil.ReadDir("/dev")
+	if err != nil {
+		return nil, err
+	}
+
+	out := []string{}
+	for _, f := range files {
+		if f.Mode()&os.ModeDevice == os.ModeDevice {
+			out = append(out, f.Name())
+		}
+	}
+	return out, nil
+}

+ 11 - 0
pkg/libcontainer/mount/nodes/nodes_unsupported.go

@@ -0,0 +1,11 @@
+// +build !linux
+
+package nodes
+
+import "github.com/dotcloud/docker/pkg/libcontainer"
+
+var DefaultNodes = []string{}
+
+func GetHostDeviceNodes() ([]string, error) {
+	return nil, libcontainer.ErrUnsupported
+}