Selaa lähdekoodia

Merge pull request #12572 from jfrazelle/selinux-labels-carry

Modify volume mounts SELinux labels on the fly based on :Z or :z
Arnaud Porterie 10 vuotta sitten
vanhempi
commit
ec471a7c9b

+ 3 - 0
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",

+ 3 - 0
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

+ 9 - 4
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 {
-	if err := daemon.registerMountPoints(container, hostConfig); err != nil {
+	container.Lock()
+	if err := parseSecurityOpt(container, hostConfig); err != nil {
+		container.Unlock()
 		return err
 	}
+	container.Unlock()
 
-	container.Lock()
-	defer container.Unlock()
-	if err := parseSecurityOpt(container, hostConfig); err != nil {
+	// 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()
 	// Register any links from the host config before starting the container
 	if err := daemon.RegisterLinks(container, hostConfig); err != nil {
 		return err

+ 42 - 11
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
 	}

+ 18 - 17
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)

+ 14 - 13
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)

+ 15 - 0
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)
 

+ 14 - 1
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

+ 8 - 0
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 {