Browse code

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

Michael Crosby authored on 2014/05/20 09:13:00
Showing 8 changed files
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"github.com/dotcloud/docker/daemon/execdriver/native/template"
11 11
 	"github.com/dotcloud/docker/pkg/apparmor"
12 12
 	"github.com/dotcloud/docker/pkg/libcontainer"
13
+	"github.com/dotcloud/docker/pkg/libcontainer/mount/nodes"
13 14
 )
14 15
 
15 16
 // createContainer populates and configures the container type with the
... ...
@@ -34,8 +35,6 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Container
34 34
 		if err := d.setPrivileged(container); err != nil {
35 35
 			return nil, err
36 36
 		}
37
-	} else {
38
-		container.Mounts = append(container.Mounts, libcontainer.Mount{Type: "devtmpfs"})
39 37
 	}
40 38
 	if err := d.setupCgroups(container, c); err != nil {
41 39
 		return nil, err
... ...
@@ -97,11 +96,16 @@ func (d *driver) createNetwork(container *libcontainer.Container, c *execdriver.
97 97
 	return nil
98 98
 }
99 99
 
100
-func (d *driver) setPrivileged(container *libcontainer.Container) error {
100
+func (d *driver) setPrivileged(container *libcontainer.Container) (err error) {
101 101
 	container.Capabilities = libcontainer.GetAllCapabilities()
102 102
 	container.Cgroups.DeviceAccess = true
103 103
 
104 104
 	delete(container.Context, "restrictions")
105
+	delete(container.DeviceNodes, "additional")
106
+
107
+	if container.DeviceNodes["required"], err = nodes.GetHostDeviceNodes(); err != nil {
108
+		return err
109
+	}
105 110
 
106 111
 	if apparmor.IsEnabled() {
107 112
 		container.Context["apparmor_profile"] = "unconfined"
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"github.com/dotcloud/docker/pkg/apparmor"
5 5
 	"github.com/dotcloud/docker/pkg/libcontainer"
6 6
 	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
7
+	"github.com/dotcloud/docker/pkg/libcontainer/mount/nodes"
7 8
 )
8 9
 
9 10
 // New returns the docker default configuration for libcontainer
... ...
@@ -33,6 +34,10 @@ func New() *libcontainer.Container {
33 33
 			DeviceAccess: false,
34 34
 		},
35 35
 		Context: libcontainer.Context{},
36
+		DeviceNodes: map[string][]string{
37
+			"required":   nodes.DefaultNodes,
38
+			"additional": {"fuse"},
39
+		},
36 40
 	}
37 41
 	if apparmor.IsEnabled() {
38 42
 		container.Context["apparmor_profile"] = "docker-default"
... ...
@@ -11,19 +11,20 @@ type Context map[string]string
11 11
 // Container defines configuration options for how a
12 12
 // container is setup inside a directory and how a process should be executed
13 13
 type Container struct {
14
-	Hostname     string          `json:"hostname,omitempty"`      // hostname
15
-	ReadonlyFs   bool            `json:"readonly_fs,omitempty"`   // set the containers rootfs as readonly
16
-	NoPivotRoot  bool            `json:"no_pivot_root,omitempty"` // this can be enabled if you are running in ramdisk
17
-	User         string          `json:"user,omitempty"`          // user to execute the process as
18
-	WorkingDir   string          `json:"working_dir,omitempty"`   // current working directory
19
-	Env          []string        `json:"environment,omitempty"`   // environment to set
20
-	Tty          bool            `json:"tty,omitempty"`           // setup a proper tty or not
21
-	Namespaces   map[string]bool `json:"namespaces,omitempty"`    // namespaces to apply
22
-	Capabilities []string        `json:"capabilities,omitempty"`  // capabilities given to the container
23
-	Networks     []*Network      `json:"networks,omitempty"`      // nil for host's network stack
24
-	Cgroups      *cgroups.Cgroup `json:"cgroups,omitempty"`       // cgroups
25
-	Context      Context         `json:"context,omitempty"`       // generic context for specific options (apparmor, selinux)
26
-	Mounts       Mounts          `json:"mounts,omitempty"`
14
+	Hostname     string              `json:"hostname,omitempty"`      // hostname
15
+	ReadonlyFs   bool                `json:"readonly_fs,omitempty"`   // set the containers rootfs as readonly
16
+	NoPivotRoot  bool                `json:"no_pivot_root,omitempty"` // this can be enabled if you are running in ramdisk
17
+	User         string              `json:"user,omitempty"`          // user to execute the process as
18
+	WorkingDir   string              `json:"working_dir,omitempty"`   // current working directory
19
+	Env          []string            `json:"environment,omitempty"`   // environment to set
20
+	Tty          bool                `json:"tty,omitempty"`           // setup a proper tty or not
21
+	Namespaces   map[string]bool     `json:"namespaces,omitempty"`    // namespaces to apply
22
+	Capabilities []string            `json:"capabilities,omitempty"`  // capabilities given to the container
23
+	Networks     []*Network          `json:"networks,omitempty"`      // nil for host's network stack
24
+	Cgroups      *cgroups.Cgroup     `json:"cgroups,omitempty"`       // cgroups
25
+	Context      Context             `json:"context,omitempty"`       // generic context for specific options (apparmor, selinux)
26
+	Mounts       Mounts              `json:"mounts,omitempty"`
27
+	DeviceNodes  map[string][]string `json:"device_nodes,omitempty"` // device nodes to add to the container's /dev
27 28
 }
28 29
 
29 30
 // Network defines configuration for a container's networking stack
... ...
@@ -43,5 +43,15 @@
43 43
     {
44 44
       "type": "devtmpfs"
45 45
     }
46
-  ]
46
+  ],
47
+  "device_nodes": {
48
+      "required": [
49
+          "null",
50
+          "zero",
51
+          "full",
52
+          "random",
53
+          "urandom",
54
+          "tty"
55
+      ]
56
+  }
47 57
 }
... ...
@@ -4,12 +4,14 @@ import (
4 4
 	"encoding/json"
5 5
 	"os"
6 6
 	"testing"
7
+
8
+	"github.com/dotcloud/docker/pkg/libcontainer/mount/nodes"
7 9
 )
8 10
 
9 11
 // Checks whether the expected capability is specified in the capabilities.
10
-func hasCapability(expected string, capabilities []string) bool {
11
-	for _, capability := range capabilities {
12
-		if capability == expected {
12
+func contains(expected string, values []string) bool {
13
+	for _, v := range values {
14
+		if v == expected {
13 15
 			return true
14 16
 		}
15 17
 	}
... ...
@@ -47,18 +49,25 @@ func TestContainerJsonFormat(t *testing.T) {
47 47
 		t.Fail()
48 48
 	}
49 49
 
50
-	if hasCapability("SYS_ADMIN", container.Capabilities) {
50
+	if contains("SYS_ADMIN", container.Capabilities) {
51 51
 		t.Log("SYS_ADMIN should not be enabled in capabilities mask")
52 52
 		t.Fail()
53 53
 	}
54 54
 
55
-	if !hasCapability("MKNOD", container.Capabilities) {
55
+	if !contains("MKNOD", container.Capabilities) {
56 56
 		t.Log("MKNOD should be enabled in capabilities mask")
57 57
 		t.Fail()
58 58
 	}
59 59
 
60
-	if hasCapability("SYS_CHROOT", container.Capabilities) {
60
+	if contains("SYS_CHROOT", container.Capabilities) {
61 61
 		t.Log("capabilities mask should not contain SYS_CHROOT")
62 62
 		t.Fail()
63 63
 	}
64
+
65
+	for _, n := range nodes.DefaultNodes {
66
+		if !contains(n, container.DeviceNodes["required"]) {
67
+			t.Logf("devices should contain %s", n)
68
+			t.Fail()
69
+		}
70
+	}
64 71
 }
... ...
@@ -48,10 +48,10 @@ func InitializeMountNamespace(rootfs, console string, container *libcontainer.Co
48 48
 	if err := setupBindmounts(rootfs, container.Mounts); err != nil {
49 49
 		return fmt.Errorf("bind mounts %s", err)
50 50
 	}
51
-	if err := nodes.CopyN(rootfs, nodes.DefaultNodes, true); err != nil {
52
-		return fmt.Errorf("copy dev nodes %s", err)
51
+	if err := nodes.CopyN(rootfs, container.DeviceNodes["required"], true); err != nil {
52
+		return fmt.Errorf("copy required dev nodes %s", err)
53 53
 	}
54
-	if err := nodes.CopyN(rootfs, nodes.AdditionalNodes, false); err != nil {
54
+	if err := nodes.CopyN(rootfs, container.DeviceNodes["additional"], false); err != nil {
55 55
 		return fmt.Errorf("copy additional dev nodes %s", err)
56 56
 	}
57 57
 	if err := SetupPtmx(rootfs, console, container.Context["mount_label"]); err != nil {
... ...
@@ -195,13 +195,11 @@ func newSystemMounts(rootfs, mountLabel string, mounts libcontainer.Mounts) []mo
195 195
 	systemMounts := []mount{
196 196
 		{source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags},
197 197
 		{source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags},
198
+		{source: "tmpfs", path: filepath.Join(rootfs, "dev"), device: "tmpfs", flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME, data: label.FormatMountLabel("mode=755", mountLabel)},
198 199
 		{source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)},
199 200
 		{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)},
200 201
 		{source: "tmpfs", path: filepath.Join(rootfs, "run"), device: "tmpfs", flags: defaultMountFlags},
201 202
 	}
202 203
 
203
-	if len(mounts.OfType("devtmpfs")) == 1 {
204
-		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...)
205
-	}
206 204
 	return systemMounts
207 205
 }
... ...
@@ -4,6 +4,7 @@ package nodes
4 4
 
5 5
 import (
6 6
 	"fmt"
7
+	"io/ioutil"
7 8
 	"os"
8 9
 	"path/filepath"
9 10
 	"syscall"
... ...
@@ -21,11 +22,6 @@ var DefaultNodes = []string{
21 21
 	"tty",
22 22
 }
23 23
 
24
-// AdditionalNodes includes nodes that are not required
25
-var AdditionalNodes = []string{
26
-	"fuse",
27
-}
28
-
29 24
 // CopyN copies the device node from the host into the rootfs
30 25
 func CopyN(rootfs string, nodesToCopy []string, shouldExist bool) error {
31 26
 	oldMask := system.Umask(0000)
... ...
@@ -61,3 +57,18 @@ func Copy(rootfs, node string, shouldExist bool) error {
61 61
 	}
62 62
 	return nil
63 63
 }
64
+
65
+func GetHostDeviceNodes() ([]string, error) {
66
+	files, err := ioutil.ReadDir("/dev")
67
+	if err != nil {
68
+		return nil, err
69
+	}
70
+
71
+	out := []string{}
72
+	for _, f := range files {
73
+		if f.Mode()&os.ModeDevice == os.ModeDevice {
74
+			out = append(out, f.Name())
75
+		}
76
+	}
77
+	return out, nil
78
+}
64 79
new file mode 100644
... ...
@@ -0,0 +1,11 @@
0
+// +build !linux
1
+
2
+package nodes
3
+
4
+import "github.com/dotcloud/docker/pkg/libcontainer"
5
+
6
+var DefaultNodes = []string{}
7
+
8
+func GetHostDeviceNodes() ([]string, error) {
9
+	return nil, libcontainer.ErrUnsupported
10
+}