diff --git a/daemon/container.go b/daemon/container.go index 9837b7b189..d91a68baf8 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -1025,6 +1025,7 @@ func copyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) func (container *Container) networkMounts() []execdriver.Mount { var mounts []execdriver.Mount if container.ResolvConfPath != "" { + label.SetFileLabel(container.ResolvConfPath, container.MountLabel) mounts = append(mounts, execdriver.Mount{ Source: container.ResolvConfPath, Destination: "/etc/resolv.conf", @@ -1033,6 +1034,7 @@ func (container *Container) networkMounts() []execdriver.Mount { }) } if container.HostnamePath != "" { + label.SetFileLabel(container.HostnamePath, container.MountLabel) mounts = append(mounts, execdriver.Mount{ Source: container.HostnamePath, Destination: "/etc/hostname", @@ -1041,6 +1043,7 @@ func (container *Container) networkMounts() []execdriver.Mount { }) } if container.HostsPath != "" { + label.SetFileLabel(container.HostsPath, container.MountLabel) mounts = append(mounts, execdriver.Mount{ Source: container.HostsPath, Destination: "/etc/hosts", diff --git a/daemon/create.go b/daemon/create.go index b3e50e56dc..4a02fc0227 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -129,6 +129,9 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos if err != nil { return nil, nil, err } + if err := label.Relabel(v.Path(), container.MountLabel, "z"); err != nil { + return nil, nil, err + } if err := container.copyImagePathContent(v, destination); err != nil { return nil, nil, err diff --git a/daemon/daemon.go b/daemon/daemon.go index 0246dd0a86..8ff13f8940 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1221,16 +1221,21 @@ func (daemon *Daemon) verifyHostConfig(hostConfig *runconfig.HostConfig) ([]stri } func (daemon *Daemon) setHostConfig(container *Container, hostConfig *runconfig.HostConfig) error { + container.Lock() + if err := parseSecurityOpt(container, hostConfig); err != nil { + container.Unlock() + return err + } + container.Unlock() + + // Do not lock while creating volumes since this could be calling out to external plugins + // Don't want to block other actions, like `docker ps` because we're waiting on an external plugin if err := daemon.registerMountPoints(container, hostConfig); err != nil { return err } container.Lock() defer container.Unlock() - if err := parseSecurityOpt(container, hostConfig); err != nil { - return err - } - // Register any links from the host config before starting the container if err := daemon.RegisterLinks(container, hostConfig); err != nil { return err diff --git a/daemon/volumes.go b/daemon/volumes.go index 963bd58da3..19cc153c46 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -11,6 +11,7 @@ import ( "github.com/docker/docker/pkg/chrootarchive" "github.com/docker/docker/runconfig" "github.com/docker/docker/volume" + "github.com/docker/libcontainer/label" ) type mountPoint struct { @@ -20,6 +21,7 @@ type mountPoint struct { RW bool Volume volume.Volume `json:"-"` Source string + Relabel string } func (m *mountPoint) Setup() (string, error) { @@ -50,7 +52,7 @@ func (m *mountPoint) Path() string { return m.Source } -func parseBindMount(spec string, config *runconfig.Config) (*mountPoint, error) { +func parseBindMount(spec string, mountLabel string, config *runconfig.Config) (*mountPoint, error) { bind := &mountPoint{ RW: true, } @@ -61,10 +63,13 @@ func parseBindMount(spec string, config *runconfig.Config) (*mountPoint, error) bind.Destination = arr[1] case 3: bind.Destination = arr[1] - if !validMountMode(arr[2]) { - return nil, fmt.Errorf("invalid mode for volumes-from: %s", arr[2]) + mode := arr[2] + if !validMountMode(mode) { + return nil, fmt.Errorf("invalid mode for volumes-from: %s", mode) } - bind.RW = arr[2] == "rw" + bind.RW = rwModes[mode] + // Relabel will apply a SELinux label, if necessary + bind.Relabel = mode default: return nil, fmt.Errorf("Invalid volume specification: %s", spec) } @@ -106,12 +111,28 @@ func parseVolumesFrom(spec string) (string, string, error) { return id, mode, nil } +// read-write modes +var rwModes = map[string]bool{ + "rw": true, + "rw,Z": true, + "rw,z": true, + "z,rw": true, + "Z,rw": true, + "Z": true, + "z": true, +} + +// read-only modes +var roModes = map[string]bool{ + "ro": true, + "ro,Z": true, + "ro,z": true, + "z,ro": true, + "Z,ro": true, +} + func validMountMode(mode string) bool { - validModes := map[string]bool{ - "rw": true, - "ro": true, - } - return validModes[mode] + return roModes[mode] || rwModes[mode] } func copyExistingContents(source, destination string) error { @@ -180,7 +201,7 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc // 3. Read bind mounts for _, b := range hostConfig.Binds { // #10618 - bind, err := parseBindMount(b, container.Config) + bind, err := parseBindMount(b, container.MountLabel, container.Config) if err != nil { return err } @@ -190,18 +211,29 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc } if len(bind.Name) > 0 && len(bind.Driver) > 0 { + // create the volume v, err := createVolume(bind.Name, bind.Driver) if err != nil { return err } bind.Volume = v + bind.Source = v.Path() + // Since this is just a named volume and not a typical bind, set to shared mode `z` + if bind.Relabel == "" { + bind.Relabel = "z" + } } + if err := label.Relabel(bind.Source, container.MountLabel, bind.Relabel); err != nil { + return err + } binds[bind.Destination] = true mountPoints[bind.Destination] = bind } + container.Lock() container.MountPoints = mountPoints + container.Unlock() return nil } @@ -250,7 +282,6 @@ func (daemon *Daemon) verifyOldVolumesInfo(container *Container) error { func createVolume(name, driverName string) (volume.Volume, error) { vd, err := getVolumeDriver(driverName) - if err != nil { return nil, err } diff --git a/daemon/volumes_experimental_unit_test.go b/daemon/volumes_experimental_unit_test.go index 1201f5154e..842e101e1e 100644 --- a/daemon/volumes_experimental_unit_test.go +++ b/daemon/volumes_experimental_unit_test.go @@ -34,28 +34,29 @@ func TestGetVolumeDriver(t *testing.T) { func TestParseBindMount(t *testing.T) { cases := []struct { - bind string - driver string - expDest string - expSource string - expName string - expDriver string - expRW bool - fail bool + bind string + driver string + expDest string + expSource string + expName string + expDriver string + mountLabel string + expRW bool + fail bool }{ - {"/tmp:/tmp", "", "/tmp", "/tmp", "", "", true, false}, - {"/tmp:/tmp:ro", "", "/tmp", "/tmp", "", "", false, false}, - {"/tmp:/tmp:rw", "", "/tmp", "/tmp", "", "", true, false}, - {"/tmp:/tmp:foo", "", "/tmp", "/tmp", "", "", false, true}, - {"name:/tmp", "", "/tmp", "", "name", "local", true, false}, - {"name:/tmp", "external", "/tmp", "", "name", "external", true, false}, - {"name:/tmp:ro", "local", "/tmp", "", "name", "local", false, false}, - {"local/name:/tmp:rw", "", "/tmp", "", "local/name", "local", true, false}, + {"/tmp:/tmp", "", "/tmp", "/tmp", "", "", "", true, false}, + {"/tmp:/tmp:ro", "", "/tmp", "/tmp", "", "", "", false, false}, + {"/tmp:/tmp:rw", "", "/tmp", "/tmp", "", "", "", true, false}, + {"/tmp:/tmp:foo", "", "/tmp", "/tmp", "", "", "", false, true}, + {"name:/tmp", "", "/tmp", "", "name", "local", "", true, false}, + {"name:/tmp", "external", "/tmp", "", "name", "external", "", true, false}, + {"name:/tmp:ro", "local", "/tmp", "", "name", "local", "", false, false}, + {"local/name:/tmp:rw", "", "/tmp", "", "local/name", "local", "", true, false}, } for _, c := range cases { conf := &runconfig.Config{VolumeDriver: c.driver} - m, err := parseBindMount(c.bind, conf) + m, err := parseBindMount(c.bind, c.mountLabel, conf) if c.fail { if err == nil { t.Fatalf("Expected error, was nil, for spec %s\n", c.bind) diff --git a/daemon/volumes_stubs_unit_test.go b/daemon/volumes_stubs_unit_test.go index a3cafe6550..5f100e5263 100644 --- a/daemon/volumes_stubs_unit_test.go +++ b/daemon/volumes_stubs_unit_test.go @@ -37,24 +37,25 @@ func TestGetVolumeDefaultDriver(t *testing.T) { func TestParseBindMount(t *testing.T) { cases := []struct { - bind string - expDest string - expSource string - expName string - expRW bool - fail bool + bind string + expDest string + expSource string + expName string + mountLabel string + expRW bool + fail bool }{ - {"/tmp:/tmp", "/tmp", "/tmp", "", true, false}, - {"/tmp:/tmp:ro", "/tmp", "/tmp", "", false, false}, - {"/tmp:/tmp:rw", "/tmp", "/tmp", "", true, false}, - {"/tmp:/tmp:foo", "/tmp", "/tmp", "", false, true}, - {"name:/tmp", "", "", "", false, true}, - {"local/name:/tmp:rw", "", "", "", true, true}, + {"/tmp:/tmp", "/tmp", "/tmp", "", "", true, false}, + {"/tmp:/tmp:ro", "/tmp", "/tmp", "", "", false, false}, + {"/tmp:/tmp:rw", "/tmp", "/tmp", "", "", true, false}, + {"/tmp:/tmp:foo", "/tmp", "/tmp", "", "", false, true}, + {"name:/tmp", "", "", "", "", false, true}, + {"local/name:/tmp:rw", "", "", "", "", true, true}, } for _, c := range cases { conf := &runconfig.Config{} - m, err := parseBindMount(c.bind, conf) + m, err := parseBindMount(c.bind, c.mountLabel, conf) if c.fail { if err == nil { t.Fatalf("Expected error, was nil, for spec %s\n", c.bind) diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index eec0f1cefc..1544c35458 100644 --- a/docs/man/docker-run.1.md +++ b/docs/man/docker-run.1.md @@ -396,6 +396,21 @@ used in other containers using the **--volumes-from** option. read-only or read-write mode, respectively. By default, the volumes are mounted read-write. See examples. +Labeling systems like SELinux require proper labels be placed on volume content +mounted into a container, otherwise the secuirty system might prevent the +processes running inside the container from using the content. By default, +volumes are not relabeled. + +Two suffixes :z or :Z can be added to the volume mount. These suffixes tell +Docker to relabel file objects on the shared volumes. The 'z' option tells +Docker that the volume content will be shared between containers. Docker will +label the content with a shared content label. Shared volumes labels allow all +containers to read/write content. The 'Z' option tells Docker to label the +content with a private unshared label. Private volumes can only be used by the +current container. + +Note: Multiple Volume options can be added separated by a "," + **--volumes-from**=[] Mount volumes from the specified container(s) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 3d92b3ed67..c5e276e3a3 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -2181,6 +2181,19 @@ mount the volumes in read-only or read-write mode, respectively. By default, the volumes are mounted in the same mode (read write or read only) as the reference container. +Labeling systems like SELinux require proper labels be placed on volume content +mounted into a container, otherwise the security system might prevent the +processes running inside the container from using the content. By default, +volumes are not relabeled. + +Two suffixes :z or :Z can be added to the volume mount. These suffixes tell +Docker to relabel file objects on the shared volumes. The 'z' option tells +Docker that the volume content will be shared between containers. Docker will +label the content with a shared content label. Shared volumes labels allow all +containers to read/write content. The 'Z' option tells Docker to label the +content with a private unshared label. Private volumes can only be used by the +current container. + The `-a` flag tells `docker run` to bind to the container's `STDIN`, `STDOUT` or `STDERR`. This makes it possible to manipulate the output and input as needed. @@ -2222,7 +2235,7 @@ flag: $ docker run --device=/dev/sda:/dev/xvdc --rm -it ubuntu fdisk /dev/xvdc Command (m for help): q - $ docker run --device=/dev/sda:/dev/xvdc:r --rm -it ubuntu fdisk /dev/xvdc + $ docker run --device=/dev/sda:/dev/xvdc:ro --rm -it ubuntu fdisk /dev/xvdc You will not be able to write the partition table. Command (m for help): q diff --git a/runconfig/config_test.go b/runconfig/config_test.go index 8b1a49f11b..27727a495e 100644 --- a/runconfig/config_test.go +++ b/runconfig/config_test.go @@ -114,6 +114,14 @@ func TestParseRunVolumes(t *testing.T) { t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds) } + if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:roZ -v /hostVar:/containerVar:rwZ"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp:roZ", "/hostVar:/containerVar:rwZ") != nil { + t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:roZ -v /hostVar:/containerVar:rwZ` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds) + } + + if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:Z -v /hostVar:/containerVar:z"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp:Z", "/hostVar:/containerVar:z") != nil { + t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:Z -v /hostVar:/containerVar:z` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds) + } + if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /containerVar"); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != "/hostTmp:/containerTmp" { t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /containerVar` should mount-bind only /hostTmp into /containeTmp. Received %v", hostConfig.Binds) } else if _, exists := config.Volumes["/containerVar"]; !exists {