Support volume relabling for pods which specify an SELinux label

This commit is contained in:
Sami Wagiaalla
2015-10-07 15:19:06 -04:00
parent 1524d7490a
commit 1d352a16b8
30 changed files with 344 additions and 76 deletions

View File

@@ -250,6 +250,10 @@ func (b *awsElasticBlockStoreBuilder) IsReadOnly() bool {
return b.readOnly
}
func (b *awsElasticBlockStoreBuilder) SupportsSELinux() bool {
return true
}
func makeGlobalPDPath(host volume.VolumeHost, volumeID string) string {
// Clean up the URI to be more fs-friendly
name := volumeID

View File

@@ -187,6 +187,10 @@ func (cephfsVolume *cephfsBuilder) IsReadOnly() bool {
return cephfsVolume.readonly
}
func (cephfsVolume *cephfsBuilder) SupportsSELinux() bool {
return false
}
type cephfsCleaner struct {
*cephfs
}

View File

@@ -225,6 +225,10 @@ func (b *cinderVolumeBuilder) IsReadOnly() bool {
return b.readOnly
}
func (b *cinderVolumeBuilder) SupportsSELinux() bool {
return true
}
func makeGlobalPDName(host volume.VolumeHost, devName string) string {
return path.Join(host.GetPluginDir(cinderVolumePluginName), "mounts", devName)
}

View File

@@ -157,6 +157,10 @@ func (d *downwardAPIVolume) IsReadOnly() bool {
return true
}
func (d *downwardAPIVolume) SupportsSELinux() bool {
return true
}
// collectData collects requested downwardAPI in data map.
// Map's key is the requested name of file to dump
// Map's value is the (sorted) content of the field to be dumped in the file.

View File

@@ -1,27 +0,0 @@
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package empty_dir
// chconRunner knows how to chcon a directory.
type chconRunner interface {
SetContext(dir, context string) error
}
// newChconRunner returns a new chconRunner.
func newChconRunner() chconRunner {
return &realChconRunner{}
}

View File

@@ -1,34 +0,0 @@
// +build linux
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package empty_dir
import (
"github.com/docker/libcontainer/selinux"
)
type realChconRunner struct{}
func (_ *realChconRunner) SetContext(dir, context string) error {
// If SELinux is not enabled, return an empty string
if !selinux.SelinuxEnabled() {
return nil
}
return selinux.Setfilecon(dir, context)
}

View File

@@ -1,26 +0,0 @@
// +build !linux
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package empty_dir
type realChconRunner struct{}
func (_ *realChconRunner) SetContext(dir, context string) error {
// NOP
return nil
}

View File

@@ -70,10 +70,10 @@ func (plugin *emptyDirPlugin) CanSupport(spec *volume.Spec) bool {
}
func (plugin *emptyDirPlugin) NewBuilder(spec *volume.Spec, pod *api.Pod, opts volume.VolumeOptions) (volume.Builder, error) {
return plugin.newBuilderInternal(spec, pod, plugin.host.GetMounter(), &realMountDetector{plugin.host.GetMounter()}, opts, newChconRunner())
return plugin.newBuilderInternal(spec, pod, plugin.host.GetMounter(), &realMountDetector{plugin.host.GetMounter()}, opts)
}
func (plugin *emptyDirPlugin) newBuilderInternal(spec *volume.Spec, pod *api.Pod, mounter mount.Interface, mountDetector mountDetector, opts volume.VolumeOptions, chconRunner chconRunner) (volume.Builder, error) {
func (plugin *emptyDirPlugin) newBuilderInternal(spec *volume.Spec, pod *api.Pod, mounter mount.Interface, mountDetector mountDetector, opts volume.VolumeOptions) (volume.Builder, error) {
medium := api.StorageMediumDefault
if spec.Volume.EmptyDir != nil { // Support a non-specified source as EmptyDir.
medium = spec.Volume.EmptyDir.Medium
@@ -86,7 +86,6 @@ func (plugin *emptyDirPlugin) newBuilderInternal(spec *volume.Spec, pod *api.Pod
mountDetector: mountDetector,
plugin: plugin,
rootContext: opts.RootContext,
chconRunner: chconRunner,
}, nil
}
@@ -134,7 +133,6 @@ type emptyDir struct {
mountDetector mountDetector
plugin *emptyDirPlugin
rootContext string
chconRunner chconRunner
}
func (_ *emptyDir) SupportsOwnershipManagement() bool {
@@ -175,7 +173,7 @@ func (ed *emptyDir) SetUpAt(dir string) error {
switch ed.medium {
case api.StorageMediumDefault:
err = ed.setupDir(dir, securityContext)
err = ed.setupDir(dir)
case api.StorageMediumMemory:
err = ed.setupTmpfs(dir, securityContext)
default:
@@ -193,13 +191,17 @@ func (ed *emptyDir) IsReadOnly() bool {
return false
}
func (ed *emptyDir) SupportsSELinux() bool {
return true
}
// setupTmpfs creates a tmpfs mount at the specified directory with the
// specified SELinux context.
func (ed *emptyDir) setupTmpfs(dir string, selinuxContext string) error {
if ed.mounter == nil {
return fmt.Errorf("memory storage requested, but mounter is nil")
}
if err := ed.setupDir(dir, selinuxContext); err != nil {
if err := ed.setupDir(dir); err != nil {
return err
}
// Make SetUp idempotent.
@@ -228,7 +230,7 @@ func (ed *emptyDir) setupTmpfs(dir string, selinuxContext string) error {
// setupDir creates the directory with the specified SELinux context and
// the default permissions specified by the perm constant.
func (ed *emptyDir) setupDir(dir, selinuxContext string) error {
func (ed *emptyDir) setupDir(dir string) error {
// Create the directory if it doesn't already exist.
if err := os.MkdirAll(dir, perm); err != nil {
return err
@@ -262,12 +264,6 @@ func (ed *emptyDir) setupDir(dir, selinuxContext string) error {
}
}
// Set the context on the directory, if appropriate
if selinuxContext != "" {
glog.V(3).Infof("Setting SELinux context for %v to %v", dir, selinuxContext)
return ed.chconRunner.SetContext(dir, selinuxContext)
}
return nil
}

View File

@@ -69,30 +69,10 @@ func (fake *fakeMountDetector) GetMountMedium(path string) (storageMedium, bool,
return fake.medium, fake.isMount, nil
}
type fakeChconRequest struct {
dir string
context string
}
type fakeChconRunner struct {
requests []fakeChconRequest
}
func newFakeChconRunner() *fakeChconRunner {
return &fakeChconRunner{}
}
func (f *fakeChconRunner) SetContext(dir, context string) error {
f.requests = append(f.requests, fakeChconRequest{dir, context})
return nil
}
func TestPluginEmptyRootContext(t *testing.T) {
doTestPlugin(t, pluginTestConfig{
medium: api.StorageMediumDefault,
rootContext: "",
expectedChcons: 0,
expectedSetupMounts: 0,
expectedTeardownMounts: 0})
}
@@ -106,7 +86,6 @@ func TestPluginRootContextSet(t *testing.T) {
medium: api.StorageMediumDefault,
rootContext: "user:role:type:range",
expectedSELinuxContext: "user:role:type:range",
expectedChcons: 1,
expectedSetupMounts: 0,
expectedTeardownMounts: 0})
}
@@ -120,7 +99,6 @@ func TestPluginTmpfs(t *testing.T) {
medium: api.StorageMediumMemory,
rootContext: "user:role:type:range",
expectedSELinuxContext: "user:role:type:range",
expectedChcons: 1,
expectedSetupMounts: 1,
shouldBeMountedBeforeTeardown: true,
expectedTeardownMounts: 1})
@@ -132,7 +110,6 @@ type pluginTestConfig struct {
SELinuxOptions *api.SELinuxOptions
idempotent bool
expectedSELinuxContext string
expectedChcons int
expectedSetupMounts int
shouldBeMountedBeforeTeardown bool
expectedTeardownMounts int
@@ -160,7 +137,6 @@ func doTestPlugin(t *testing.T, config pluginTestConfig) {
mounter = mount.FakeMounter{}
mountDetector = fakeMountDetector{}
pod = &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}}
fakeChconRnr = &fakeChconRunner{}
)
// Set up the SELinux options on the pod
@@ -194,8 +170,7 @@ func doTestPlugin(t *testing.T, config pluginTestConfig) {
pod,
&mounter,
&mountDetector,
volume.VolumeOptions{RootContext: config.rootContext},
fakeChconRnr)
volume.VolumeOptions{RootContext: config.rootContext})
if err != nil {
t.Errorf("Failed to make a new Builder: %v", err)
}
@@ -231,19 +206,6 @@ func doTestPlugin(t *testing.T, config pluginTestConfig) {
t.Errorf("Volume directory was created unexpectedly")
}
// Check the number of chcons during setup
if e, a := config.expectedChcons, len(fakeChconRnr.requests); e != a {
t.Errorf("Expected %v chcon calls, got %v", e, a)
}
if config.expectedChcons == 1 {
if e, a := config.expectedSELinuxContext, fakeChconRnr.requests[0].context; e != a {
t.Errorf("Unexpected chcon context argument; expected: %v, got: %v", e, a)
}
if e, a := volPath, fakeChconRnr.requests[0].dir; e != a {
t.Errorf("Unexpected chcon path argument: expected: %v, got: %v", e, a)
}
}
// Check the number of mounts performed during setup
if e, a := config.expectedSetupMounts, len(mounter.Log); e != a {
t.Errorf("Expected %v mounter calls during setup, got %v", e, a)

View File

@@ -191,6 +191,10 @@ func (b *fcDiskBuilder) IsReadOnly() bool {
return b.readOnly
}
func (b *fcDiskBuilder) SupportsSELinux() bool {
return true
}
// Unmounts the bind mount, and detaches the disk only if the disk
// resource was the last reference to that disk on the kubelet.
func (c *fcDiskCleaner) TearDown() error {

View File

@@ -205,6 +205,10 @@ func (b flockerBuilder) IsReadOnly() bool {
return b.readOnly
}
func (b flockerBuilder) SupportsSELinux() bool {
return false
}
// updateDatasetPrimary will update the primary in Flocker and wait for it to
// be ready. If it never gets to ready state it will timeout and error.
func (b flockerBuilder) updateDatasetPrimary(datasetID, primaryUUID string) error {

View File

@@ -238,6 +238,10 @@ func (b *gcePersistentDiskBuilder) IsReadOnly() bool {
return b.readOnly
}
func (b *gcePersistentDiskBuilder) SupportsSELinux() bool {
return true
}
func makeGlobalPDName(host volume.VolumeHost, devName string) string {
return path.Join(host.GetPluginDir(gcePersistentDiskPluginName), "mounts", devName)
}

View File

@@ -122,6 +122,10 @@ func (b *gitRepoVolumeBuilder) IsReadOnly() bool {
return false
}
func (b *gitRepoVolumeBuilder) SupportsSELinux() bool {
return true
}
// This is the spec for the volume that this plugin wraps.
var wrappedVolumeSpec = &volume.Spec{
Volume: &api.Volume{VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},

View File

@@ -189,6 +189,10 @@ func (b *glusterfsBuilder) IsReadOnly() bool {
return b.readOnly
}
func (b *glusterfsBuilder) SupportsSELinux() bool {
return false
}
func (glusterfsVolume *glusterfs) GetPath() string {
name := glusterfsPluginName
return glusterfsVolume.plugin.host.GetPodVolumeDir(glusterfsVolume.pod.UID, util.EscapeQualifiedNameForDisk(name), glusterfsVolume.volName)

View File

@@ -185,6 +185,10 @@ func (b *hostPathBuilder) IsReadOnly() bool {
return b.readOnly
}
func (b *hostPathBuilder) SupportsSELinux() bool {
return false
}
func (b *hostPathBuilder) GetPath() string {
return b.path
}

View File

@@ -185,6 +185,10 @@ func (b *iscsiDiskBuilder) IsReadOnly() bool {
return b.readOnly
}
func (b *iscsiDiskBuilder) SupportsSELinux() bool {
return true
}
// Unmounts the bind mount, and detaches the disk only if the disk
// resource was the last reference to that disk on the kubelet.
func (c *iscsiDiskCleaner) TearDown() error {

View File

@@ -226,6 +226,10 @@ func (b *nfsBuilder) IsReadOnly() bool {
return b.readOnly
}
func (b *nfsBuilder) SupportsSELinux() bool {
return false
}
//
//func (c *nfsCleaner) GetPath() string {
// name := nfsPluginName

View File

@@ -219,6 +219,10 @@ func (b *rbd) IsReadOnly() bool {
return b.ReadOnly
}
func (b *rbd) SupportsSELinux() bool {
return true
}
// Unmounts the bind mount, and detaches the disk only if the disk
// resource was the last reference to that disk on the kubelet.
func (c *rbdCleaner) TearDown() error {

View File

@@ -176,6 +176,10 @@ func (sv *secretVolume) IsReadOnly() bool {
return false
}
func (sv *secretVolume) SupportsSELinux() bool {
return true
}
func totalSecretBytes(secret *api.Secret) int {
totalSize := 0
for _, bytes := range secret.Data {

View File

@@ -172,6 +172,10 @@ func (fv *FakeVolume) IsReadOnly() bool {
return false
}
func (fv *FakeVolume) SupportsSELinux() bool {
return false
}
func (fv *FakeVolume) GetPath() string {
return path.Join(fv.Plugin.Host.GetPodVolumeDir(fv.PodUID, util.EscapeQualifiedNameForDisk(fv.Plugin.PluginName), fv.VolName))
}

View File

@@ -53,6 +53,10 @@ type Builder interface {
// 2. Set the setgid bit is set (new files created in the volume will be owned by FSGroup)
// 3. Logical OR the permission bits with rw-rw----
SupportsOwnershipManagement() bool
// SupportsSELinux reports whether the given builder supports
// SELinux and would like the kubelet to relabel the volume to
// match the pod to which it will be attached.
SupportsSELinux() bool
}
// Cleaner interface provides methods to cleanup/unmount the volumes.