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:
commit
ec471a7c9b
9 changed files with 126 additions and 46 deletions
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue