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

Modify volume mounts SELinux labels on the fly based on :Z or :z
This commit is contained in:
Arnaud Porterie 2015-05-27 14:46:16 -07:00
commit ec471a7c9b
9 changed files with 126 additions and 46 deletions

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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 {