diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index 76816e0b9c..9ed0491940 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -10,6 +10,7 @@ import ( "github.com/dotcloud/docker/daemon/execdriver/native/template" "github.com/dotcloud/docker/pkg/apparmor" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/mount/nodes" ) // 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 { return nil, err } - } else { - container.Mounts = append(container.Mounts, libcontainer.Mount{Type: "devtmpfs"}) } if err := d.setupCgroups(container, c); err != nil { return nil, err @@ -97,11 +96,16 @@ func (d *driver) createNetwork(container *libcontainer.Container, c *execdriver. return nil } -func (d *driver) setPrivileged(container *libcontainer.Container) error { +func (d *driver) setPrivileged(container *libcontainer.Container) (err error) { container.Capabilities = libcontainer.GetAllCapabilities() container.Cgroups.DeviceAccess = true delete(container.Context, "restrictions") + delete(container.DeviceNodes, "additional") + + if container.DeviceNodes["required"], err = nodes.GetHostDeviceNodes(); err != nil { + return err + } if apparmor.IsEnabled() { container.Context["apparmor_profile"] = "unconfined" diff --git a/daemon/execdriver/native/template/default_template.go b/daemon/execdriver/native/template/default_template.go index ba52499c24..dbe3985f9b 100644 --- a/daemon/execdriver/native/template/default_template.go +++ b/daemon/execdriver/native/template/default_template.go @@ -4,6 +4,7 @@ import ( "github.com/dotcloud/docker/pkg/apparmor" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/cgroups" + "github.com/dotcloud/docker/pkg/libcontainer/mount/nodes" ) // New returns the docker default configuration for libcontainer @@ -33,6 +34,10 @@ func New() *libcontainer.Container { DeviceAccess: false, }, Context: libcontainer.Context{}, + DeviceNodes: map[string][]string{ + "required": nodes.DefaultNodes, + "additional": {"fuse"}, + }, } if apparmor.IsEnabled() { container.Context["apparmor_profile"] = "docker-default" diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index 0ea8d37c20..092cd5d93a 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -11,19 +11,20 @@ type Context map[string]string // Container defines configuration options for how a // container is setup inside a directory and how a process should be executed 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 diff --git a/pkg/libcontainer/container.json b/pkg/libcontainer/container.json index 07950fe58a..c3b0196b4a 100644 --- a/pkg/libcontainer/container.json +++ b/pkg/libcontainer/container.json @@ -43,5 +43,15 @@ { "type": "devtmpfs" } - ] + ], + "device_nodes": { + "required": [ + "null", + "zero", + "full", + "random", + "urandom", + "tty" + ] + } } diff --git a/pkg/libcontainer/container_test.go b/pkg/libcontainer/container_test.go index b3f240740c..d77ce313ae 100644 --- a/pkg/libcontainer/container_test.go +++ b/pkg/libcontainer/container_test.go @@ -4,12 +4,14 @@ import ( "encoding/json" "os" "testing" + + "github.com/dotcloud/docker/pkg/libcontainer/mount/nodes" ) // 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 } } @@ -47,18 +49,25 @@ func TestContainerJsonFormat(t *testing.T) { 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.Fail() } - if !hasCapability("MKNOD", container.Capabilities) { + if !contains("MKNOD", container.Capabilities) { t.Log("MKNOD should be enabled in capabilities mask") t.Fail() } - if hasCapability("SYS_CHROOT", container.Capabilities) { + if contains("SYS_CHROOT", container.Capabilities) { t.Log("capabilities mask should not contain SYS_CHROOT") t.Fail() } + + for _, n := range nodes.DefaultNodes { + if !contains(n, container.DeviceNodes["required"]) { + t.Logf("devices should contain %s", n) + t.Fail() + } + } } diff --git a/pkg/libcontainer/mount/init.go b/pkg/libcontainer/mount/init.go index c4148131ad..184df1e8ec 100644 --- a/pkg/libcontainer/mount/init.go +++ b/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 { 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) } 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{ {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", 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: "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}, } - 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 } diff --git a/pkg/libcontainer/mount/nodes/nodes.go b/pkg/libcontainer/mount/nodes/nodes.go index 1384682729..14b6f5ae57 100644 --- a/pkg/libcontainer/mount/nodes/nodes.go +++ b/pkg/libcontainer/mount/nodes/nodes.go @@ -4,6 +4,7 @@ package nodes import ( "fmt" + "io/ioutil" "os" "path/filepath" "syscall" @@ -21,11 +22,6 @@ var DefaultNodes = []string{ "tty", } -// AdditionalNodes includes nodes that are not required -var AdditionalNodes = []string{ - "fuse", -} - // CopyN copies the device node from the host into the rootfs func CopyN(rootfs string, nodesToCopy []string, shouldExist bool) error { oldMask := system.Umask(0000) @@ -61,3 +57,18 @@ func Copy(rootfs, node string, shouldExist bool) error { } 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 +} diff --git a/pkg/libcontainer/mount/nodes/nodes_unsupported.go b/pkg/libcontainer/mount/nodes/nodes_unsupported.go new file mode 100644 index 0000000000..24409f411f --- /dev/null +++ b/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 +}