Browse Source

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 years ago
parent
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 {
 func (container *Container) networkMounts() []execdriver.Mount {
 	var mounts []execdriver.Mount
 	var mounts []execdriver.Mount
 	if container.ResolvConfPath != "" {
 	if container.ResolvConfPath != "" {
+		label.SetFileLabel(container.ResolvConfPath, container.MountLabel)
 		mounts = append(mounts, execdriver.Mount{
 		mounts = append(mounts, execdriver.Mount{
 			Source:      container.ResolvConfPath,
 			Source:      container.ResolvConfPath,
 			Destination: "/etc/resolv.conf",
 			Destination: "/etc/resolv.conf",
@@ -1033,6 +1034,7 @@ func (container *Container) networkMounts() []execdriver.Mount {
 		})
 		})
 	}
 	}
 	if container.HostnamePath != "" {
 	if container.HostnamePath != "" {
+		label.SetFileLabel(container.HostnamePath, container.MountLabel)
 		mounts = append(mounts, execdriver.Mount{
 		mounts = append(mounts, execdriver.Mount{
 			Source:      container.HostnamePath,
 			Source:      container.HostnamePath,
 			Destination: "/etc/hostname",
 			Destination: "/etc/hostname",
@@ -1041,6 +1043,7 @@ func (container *Container) networkMounts() []execdriver.Mount {
 		})
 		})
 	}
 	}
 	if container.HostsPath != "" {
 	if container.HostsPath != "" {
+		label.SetFileLabel(container.HostsPath, container.MountLabel)
 		mounts = append(mounts, execdriver.Mount{
 		mounts = append(mounts, execdriver.Mount{
 			Source:      container.HostsPath,
 			Source:      container.HostsPath,
 			Destination: "/etc/hosts",
 			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 {
 		if err != nil {
 			return nil, nil, err
 			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 {
 		if err := container.copyImagePathContent(v, destination); err != nil {
 			return nil, nil, err
 			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 {
 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
 		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
 		return err
 	}
 	}
 
 
+	container.Lock()
+	defer container.Unlock()
 	// Register any links from the host config before starting the container
 	// Register any links from the host config before starting the container
 	if err := daemon.RegisterLinks(container, hostConfig); err != nil {
 	if err := daemon.RegisterLinks(container, hostConfig); err != nil {
 		return err
 		return err

+ 42 - 11
daemon/volumes.go

@@ -11,6 +11,7 @@ import (
 	"github.com/docker/docker/pkg/chrootarchive"
 	"github.com/docker/docker/pkg/chrootarchive"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/volume"
 	"github.com/docker/docker/volume"
+	"github.com/docker/libcontainer/label"
 )
 )
 
 
 type mountPoint struct {
 type mountPoint struct {
@@ -20,6 +21,7 @@ type mountPoint struct {
 	RW          bool
 	RW          bool
 	Volume      volume.Volume `json:"-"`
 	Volume      volume.Volume `json:"-"`
 	Source      string
 	Source      string
+	Relabel     string
 }
 }
 
 
 func (m *mountPoint) Setup() (string, error) {
 func (m *mountPoint) Setup() (string, error) {
@@ -50,7 +52,7 @@ func (m *mountPoint) Path() string {
 	return m.Source
 	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{
 	bind := &mountPoint{
 		RW: true,
 		RW: true,
 	}
 	}
@@ -61,10 +63,13 @@ func parseBindMount(spec string, config *runconfig.Config) (*mountPoint, error)
 		bind.Destination = arr[1]
 		bind.Destination = arr[1]
 	case 3:
 	case 3:
 		bind.Destination = arr[1]
 		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:
 	default:
 		return nil, fmt.Errorf("Invalid volume specification: %s", spec)
 		return nil, fmt.Errorf("Invalid volume specification: %s", spec)
 	}
 	}
@@ -106,12 +111,28 @@ func parseVolumesFrom(spec string) (string, string, error) {
 	return id, mode, nil
 	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 {
 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 {
 func copyExistingContents(source, destination string) error {
@@ -180,7 +201,7 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc
 	// 3. Read bind mounts
 	// 3. Read bind mounts
 	for _, b := range hostConfig.Binds {
 	for _, b := range hostConfig.Binds {
 		// #10618
 		// #10618
-		bind, err := parseBindMount(b, container.Config)
+		bind, err := parseBindMount(b, container.MountLabel, container.Config)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -190,18 +211,29 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc
 		}
 		}
 
 
 		if len(bind.Name) > 0 && len(bind.Driver) > 0 {
 		if len(bind.Name) > 0 && len(bind.Driver) > 0 {
+			// create the volume
 			v, err := createVolume(bind.Name, bind.Driver)
 			v, err := createVolume(bind.Name, bind.Driver)
 			if err != nil {
 			if err != nil {
 				return err
 				return err
 			}
 			}
 			bind.Volume = v
 			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
 		binds[bind.Destination] = true
 		mountPoints[bind.Destination] = bind
 		mountPoints[bind.Destination] = bind
 	}
 	}
 
 
+	container.Lock()
 	container.MountPoints = mountPoints
 	container.MountPoints = mountPoints
+	container.Unlock()
 
 
 	return nil
 	return nil
 }
 }
@@ -250,7 +282,6 @@ func (daemon *Daemon) verifyOldVolumesInfo(container *Container) error {
 
 
 func createVolume(name, driverName string) (volume.Volume, error) {
 func createVolume(name, driverName string) (volume.Volume, error) {
 	vd, err := getVolumeDriver(driverName)
 	vd, err := getVolumeDriver(driverName)
-
 	if err != nil {
 	if err != nil {
 		return nil, err
 		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) {
 func TestParseBindMount(t *testing.T) {
 	cases := []struct {
 	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 {
 	for _, c := range cases {
 		conf := &runconfig.Config{VolumeDriver: c.driver}
 		conf := &runconfig.Config{VolumeDriver: c.driver}
-		m, err := parseBindMount(c.bind, conf)
+		m, err := parseBindMount(c.bind, c.mountLabel, conf)
 		if c.fail {
 		if c.fail {
 			if err == nil {
 			if err == nil {
 				t.Fatalf("Expected error, was nil, for spec %s\n", c.bind)
 				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) {
 func TestParseBindMount(t *testing.T) {
 	cases := []struct {
 	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 {
 	for _, c := range cases {
 		conf := &runconfig.Config{}
 		conf := &runconfig.Config{}
-		m, err := parseBindMount(c.bind, conf)
+		m, err := parseBindMount(c.bind, c.mountLabel, conf)
 		if c.fail {
 		if c.fail {
 			if err == nil {
 			if err == nil {
 				t.Fatalf("Expected error, was nil, for spec %s\n", c.bind)
 				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-only or read-write mode, respectively. By default, the volumes are mounted
 read-write. See examples.
 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**=[]
 **--volumes-from**=[]
    Mount volumes from the specified container(s)
    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 volumes are mounted in the same mode (read write or read only) as
 the reference container.
 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
 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.
 `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
 	$ docker run --device=/dev/sda:/dev/xvdc --rm -it ubuntu fdisk  /dev/xvdc
 
 
 	Command (m for help): q
 	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.
 	You will not be able to write the partition table.
 
 
 	Command (m for help): q
 	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)
 		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" {
 	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)
 		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 {
 	} else if _, exists := config.Volumes["/containerVar"]; !exists {