ソースを参照

Merge pull request #6961 from crosbymichael/devices-flag

Add --device flag to allow additional host devices in container
Victor Vieux 11 年 前
コミット
9216ced003

+ 16 - 2
daemon/container.go

@@ -209,6 +209,20 @@ func populateCommand(c *Container, env []string) error {
 		return fmt.Errorf("invalid network mode: %s", c.hostConfig.NetworkMode)
 	}
 
+	// Build lists of devices allowed and created within the container.
+	userSpecifiedDevices := make([]*devices.Device, len(c.hostConfig.Devices))
+	for i, deviceMapping := range c.hostConfig.Devices {
+		device, err := devices.GetDevice(deviceMapping.PathOnHost, deviceMapping.CgroupPermissions)
+		device.Path = deviceMapping.PathInContainer
+		if err != nil {
+			return fmt.Errorf("error gathering device information while adding custom device %s", err)
+		}
+		userSpecifiedDevices[i] = device
+	}
+	allowedDevices := append(devices.DefaultAllowedDevices, userSpecifiedDevices...)
+
+	autoCreatedDevices := append(devices.DefaultAutoCreatedDevices, userSpecifiedDevices...)
+
 	// TODO: this can be removed after lxc-conf is fully deprecated
 	mergeLxcConfIntoOptions(c.hostConfig, context)
 
@@ -231,8 +245,8 @@ func populateCommand(c *Container, env []string) error {
 		User:               c.Config.User,
 		Config:             context,
 		Resources:          resources,
-		AllowedDevices:     devices.DefaultAllowedDevices,
-		AutoCreatedDevices: devices.DefaultAutoCreatedDevices,
+		AllowedDevices:     allowedDevices,
+		AutoCreatedDevices: autoCreatedDevices,
 	}
 	c.command.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
 	c.command.Env = env

+ 15 - 0
docs/sources/reference/commandline/cli.md

@@ -946,6 +946,7 @@ removed before the image is removed.
       -u, --user=""              Username or UID
       -v, --volume=[]            Bind mount a volume (e.g., from the host: -v /host:/container, from docker: -v /container)
       --volumes-from=[]          Mount volumes from the specified container(s)
+      --device=[]                Add a host device to the container (e.g. --device=/dev/sdc[:/dev/xvdc[:rwm]])
       -w, --workdir=""           Working directory inside the container
 
 The `docker run` command first `creates` a writeable container layer over the
@@ -1122,6 +1123,20 @@ logs could be retrieved using `docker logs`. This is
 useful if you need to pipe a file or something else into a container and
 retrieve the container's ID once the container has finished running.
 
+   $ sudo docker run --device=/dev/sdc:/dev/xvdc --device=/dev/sdd --device=/dev/zero:/dev/nulo -i -t ubuntu ls -l /dev/{xvdc,sdd,nulo}
+   brw-rw---- 1 root disk 8, 2 Feb  9 16:05 /dev/xvdc
+   brw-rw---- 1 root disk 8, 3 Feb  9 16:05 /dev/sdd
+   crw-rw-rw- 1 root root 1, 5 Feb  9 16:05 /dev/nulo
+
+It is often necessary to directly expose devices to a container.  ``--device``
+option enables that.  For example, a specific block storage device or loop
+device or audio device can be added to an otherwise unprivileged container
+(without the ``--privileged`` flag) and have the application directly access it.
+
+** Security note: **
+
+``--device`` cannot be safely used with ephemeral devices.  Block devices that may be removed should not be added to untrusted containers with ``--device``!
+
 **A complete example:**
 
     $ sudo docker run -d --name static static-web-files sh

+ 16 - 0
integration-cli/docker_cli_run_test.go

@@ -919,6 +919,22 @@ func TestRunUnprivilegedWithChroot(t *testing.T) {
 	logDone("run - unprivileged with chroot")
 }
 
+func TestAddingOptionalDevices(t *testing.T) {
+	cmd := exec.Command(dockerBinary, "run", "--device", "/dev/zero:/dev/nulo", "busybox", "sh", "-c", "ls /dev/nulo")
+
+	out, _, err := runCommandWithOutput(cmd)
+	if err != nil {
+		t.Fatal(err, out)
+	}
+
+	if actual := strings.Trim(out, "\r\n"); actual != "/dev/nulo" {
+		t.Fatalf("expected output /dev/nulo, received %s", actual)
+	}
+	deleteAllContainers()
+
+	logDone("run - test --device argument")
+}
+
 func TestModeHostname(t *testing.T) {
 	cmd := exec.Command(dockerBinary, "run", "-h=testhostname", "busybox", "cat", "/etc/hostname")
 

+ 8 - 0
runconfig/hostconfig.go

@@ -19,6 +19,12 @@ func (n NetworkMode) IsContainer() bool {
 	return len(parts) > 1 && parts[0] == "container"
 }
 
+type DeviceMapping struct {
+	PathOnHost        string
+	PathInContainer   string
+	CgroupPermissions string
+}
+
 type HostConfig struct {
 	Binds           []string
 	ContainerIDFile string
@@ -30,6 +36,7 @@ type HostConfig struct {
 	Dns             []string
 	DnsSearch       []string
 	VolumesFrom     []string
+	Devices         []DeviceMapping
 	NetworkMode     NetworkMode
 }
 
@@ -42,6 +49,7 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
 	}
 	job.GetenvJson("LxcConf", &hostConfig.LxcConf)
 	job.GetenvJson("PortBindings", &hostConfig.PortBindings)
+	job.GetenvJson("Devices", &hostConfig.Devices)
 	if Binds := job.GetenvList("Binds"); Binds != nil {
 		hostConfig.Binds = Binds
 	}

+ 43 - 0
runconfig/parse.go

@@ -41,6 +41,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 		flVolumes = opts.NewListOpts(opts.ValidatePath)
 		flLinks   = opts.NewListOpts(opts.ValidateLink)
 		flEnv     = opts.NewListOpts(opts.ValidateEnv)
+		flDevices = opts.NewListOpts(opts.ValidatePath)
 
 		flPublish     opts.ListOpts
 		flExpose      opts.ListOpts
@@ -74,6 +75,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 	cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR.")
 	cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume (e.g., from the host: -v /host:/container, from Docker: -v /container)")
 	cmd.Var(&flLinks, []string{"#link", "-link"}, "Add link to another container in the form of name:alias")
+	cmd.Var(&flDevices, []string{"-device"}, "Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc)")
 	cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
 	cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a line delimited file of environment variables")
 
@@ -191,6 +193,16 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 		}
 	}
 
+	// parse device mappings
+	deviceMappings := []DeviceMapping{}
+	for _, device := range flDevices.GetAll() {
+		deviceMapping, err := ParseDevice(device)
+		if err != nil {
+			return nil, nil, cmd, err
+		}
+		deviceMappings = append(deviceMappings, deviceMapping)
+	}
+
 	// collect all the environment variables for the container
 	envVariables := []string{}
 	for _, ef := range flEnvFile.GetAll() {
@@ -245,6 +257,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 		DnsSearch:       flDnsSearch.GetAll(),
 		VolumesFrom:     flVolumesFrom.GetAll(),
 		NetworkMode:     netMode,
+		Devices:         deviceMappings,
 	}
 
 	if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit {
@@ -303,3 +316,33 @@ func parseNetMode(netMode string) (NetworkMode, error) {
 	}
 	return NetworkMode(netMode), nil
 }
+
+func ParseDevice(device string) (DeviceMapping, error) {
+	src := ""
+	dst := ""
+	permissions := "rwm"
+	arr := strings.Split(device, ":")
+	switch len(arr) {
+	case 3:
+		permissions = arr[2]
+		fallthrough
+	case 2:
+		dst = arr[1]
+		fallthrough
+	case 1:
+		src = arr[0]
+	default:
+		return DeviceMapping{}, fmt.Errorf("Invalid device specification: %s", device)
+	}
+
+	if dst == "" {
+		dst = src
+	}
+
+	deviceMapping := DeviceMapping{
+		PathOnHost:        src,
+		PathInContainer:   dst,
+		CgroupPermissions: permissions,
+	}
+	return deviceMapping, nil
+}