From 160dc79db095767de74deb75b8e1ae86f213593c Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Mon, 11 May 2015 18:28:36 -0400 Subject: [PATCH 1/3] Modify volume mounts SELinux labels on the fly based on :Z or :z This patch is extending the qualifiers on the -v command to allow an admin to tell the system to relabel, content. There might be a need for something similar for changing the DAC Permissions. Signed-off-by: Jessica Frazelle --- docs/man/docker-run.1.md | 15 +++++++++++++++ docs/sources/reference/commandline/cli.md | 15 ++++++++++++++- runconfig/config_test.go | 8 ++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index eec0f1cef..1544c3545 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 3d92b3ed6..c5e276e3a 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 8b1a49f11..27727a495 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 { From af7d17a6c906d348eb0929766a81fd04fc173c44 Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Sun, 24 May 2015 18:39:31 -0700 Subject: [PATCH 2/3] apply selinux labels volume patch on volumes refactor Signed-off-by: Jessica Frazelle --- daemon/volumes.go | 65 ++++++++++++++++++++---- daemon/volumes_experimental_unit_test.go | 35 ++++++------- daemon/volumes_stubs_unit_test.go | 27 +++++----- 3 files changed, 86 insertions(+), 41 deletions(-) diff --git a/daemon/volumes.go b/daemon/volumes.go index 963bd58da..87b9143df 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -6,11 +6,14 @@ import ( "io/ioutil" "os" "path/filepath" + "runtime" "strings" + "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/chrootarchive" "github.com/docker/docker/runconfig" "github.com/docker/docker/volume" + "github.com/docker/libcontainer/label" ) type mountPoint struct { @@ -50,7 +53,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 +64,17 @@ 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 = rwModes[mode] + // check if we need to apply a SELinux label + if strings.ContainsAny(mode, "zZ") { + if err := label.Relabel(bind.Source, mountLabel, mode); err != nil { + return nil, err + } } - bind.RW = arr[2] == "rw" default: return nil, fmt.Errorf("Invalid volume specification: %s", spec) } @@ -106,12 +116,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 { @@ -177,10 +203,13 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc } } + // lock for labels + runtime.LockOSThread() + defer runtime.UnlockOSThread() // 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,11 +219,26 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc } if len(bind.Name) > 0 && len(bind.Driver) > 0 { + // set the label + if err := label.SetFileCreateLabel(container.MountLabel); err != nil { + return fmt.Errorf("Unable to setup default labeling for volume creation %s: %v", bind.Source, err) + } + + // create the volume v, err := createVolume(bind.Name, bind.Driver) if err != nil { + // reset the label + if e := label.SetFileCreateLabel(""); e != nil { + logrus.Errorf("Unable to reset labeling for volume creation %s: %v", bind.Source, e) + } return err } bind.Volume = v + + // reset the label + if err := label.SetFileCreateLabel(""); err != nil { + return fmt.Errorf("Unable to reset labeling for volume creation %s: %v", bind.Source, err) + } } binds[bind.Destination] = true @@ -250,7 +294,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 1201f5154..842e101e1 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 a3cafe655..5f100e526 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) From b2a43baf2e2cc68c83383a7524441f81bc4c4725 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Wed, 27 May 2015 15:29:49 -0400 Subject: [PATCH 3/3] Use SELinux labels for volumes Fixes a regression from the volumes refactor where the vfs graphdriver was setting labels for volumes to `s0` so that they can both be written to by the container and shared with other containers. When moving away from vfs this was never re-introduced. Since this needs to happen regardless of volume driver, this is implemented outside of the driver. Fixes issue where `z` and `Z` labels are not set for bind-mounts. Don't lock while creating volumes Signed-off-by: Brian Goff --- daemon/container.go | 3 +++ daemon/create.go | 3 +++ daemon/daemon.go | 13 +++++++++---- daemon/volumes.go | 36 ++++++++++++------------------------ 4 files changed, 27 insertions(+), 28 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index 4e5551370..2db9568e7 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -1009,6 +1009,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", @@ -1017,6 +1018,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", @@ -1025,6 +1027,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 b3e50e56d..4a02fc022 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 0246dd0a8..8ff13f894 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 87b9143df..19cc153c4 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -6,10 +6,8 @@ import ( "io/ioutil" "os" "path/filepath" - "runtime" "strings" - "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/chrootarchive" "github.com/docker/docker/runconfig" "github.com/docker/docker/volume" @@ -23,6 +21,7 @@ type mountPoint struct { RW bool Volume volume.Volume `json:"-"` Source string + Relabel string } func (m *mountPoint) Setup() (string, error) { @@ -69,12 +68,8 @@ func parseBindMount(spec string, mountLabel string, config *runconfig.Config) (* return nil, fmt.Errorf("invalid mode for volumes-from: %s", mode) } bind.RW = rwModes[mode] - // check if we need to apply a SELinux label - if strings.ContainsAny(mode, "zZ") { - if err := label.Relabel(bind.Source, mountLabel, mode); err != nil { - return nil, err - } - } + // Relabel will apply a SELinux label, if necessary + bind.Relabel = mode default: return nil, fmt.Errorf("Invalid volume specification: %s", spec) } @@ -203,9 +198,6 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc } } - // lock for labels - runtime.LockOSThread() - defer runtime.UnlockOSThread() // 3. Read bind mounts for _, b := range hostConfig.Binds { // #10618 @@ -219,33 +211,29 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc } if len(bind.Name) > 0 && len(bind.Driver) > 0 { - // set the label - if err := label.SetFileCreateLabel(container.MountLabel); err != nil { - return fmt.Errorf("Unable to setup default labeling for volume creation %s: %v", bind.Source, err) - } - // create the volume v, err := createVolume(bind.Name, bind.Driver) if err != nil { - // reset the label - if e := label.SetFileCreateLabel(""); e != nil { - logrus.Errorf("Unable to reset labeling for volume creation %s: %v", bind.Source, e) - } return err } bind.Volume = v - - // reset the label - if err := label.SetFileCreateLabel(""); err != nil { - return fmt.Errorf("Unable to reset labeling for volume creation %s: %v", bind.Source, err) + 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 }