mirror of https://github.com/k3s-io/k3s
Support volume relabling for pods which specify an SELinux label
parent
1524d7490a
commit
1d352a16b8
|
@ -238,6 +238,8 @@ type Mount struct {
|
||||||
HostPath string
|
HostPath string
|
||||||
// Whether the mount is read-only.
|
// Whether the mount is read-only.
|
||||||
ReadOnly bool
|
ReadOnly bool
|
||||||
|
// Whether the mount needs SELinux relabeling
|
||||||
|
SELinuxRelabel bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type PortMapping struct {
|
type PortMapping struct {
|
||||||
|
@ -273,7 +275,16 @@ type RunContainerOptions struct {
|
||||||
CgroupParent string
|
CgroupParent string
|
||||||
}
|
}
|
||||||
|
|
||||||
type VolumeMap map[string]volume.Volume
|
// VolumeInfo contains information about the volume.
|
||||||
|
type VolumeInfo struct {
|
||||||
|
// Builder is the volume's builder
|
||||||
|
Builder volume.Builder
|
||||||
|
// SELinuxLabeled indicates whether this volume has had the
|
||||||
|
// pod's SELinux label applied to it or not
|
||||||
|
SELinuxLabeled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type VolumeMap map[string]VolumeInfo
|
||||||
|
|
||||||
type Pods []*Pod
|
type Pods []*Pod
|
||||||
|
|
||||||
|
|
|
@ -606,13 +606,29 @@ func makeEnvList(envs []kubecontainer.EnvVar) (result []string) {
|
||||||
// can be understood by docker.
|
// can be understood by docker.
|
||||||
// Each element in the string is in the form of:
|
// Each element in the string is in the form of:
|
||||||
// '<HostPath>:<ContainerPath>', or
|
// '<HostPath>:<ContainerPath>', or
|
||||||
// '<HostPath>:<ContainerPath>:ro', if the path is read only.
|
// '<HostPath>:<ContainerPath>:ro', if the path is read only, or
|
||||||
func makeMountBindings(mounts []kubecontainer.Mount) (result []string) {
|
// '<HostPath>:<ContainerPath>:Z', if the volume requires SELinux
|
||||||
|
// relabeling and the pod provides an SELinux label
|
||||||
|
func makeMountBindings(mounts []kubecontainer.Mount, podHasSELinuxLabel bool) (result []string) {
|
||||||
for _, m := range mounts {
|
for _, m := range mounts {
|
||||||
bind := fmt.Sprintf("%s:%s", m.HostPath, m.ContainerPath)
|
bind := fmt.Sprintf("%s:%s", m.HostPath, m.ContainerPath)
|
||||||
if m.ReadOnly {
|
if m.ReadOnly {
|
||||||
bind += ":ro"
|
bind += ":ro"
|
||||||
}
|
}
|
||||||
|
// Only request relabeling if the pod provides an
|
||||||
|
// SELinux context. If the pod does not provide an
|
||||||
|
// SELinux context relabeling will label the volume
|
||||||
|
// with the container's randomly allocated MCS label.
|
||||||
|
// This would restrict access to the volume to the
|
||||||
|
// container which mounts it first.
|
||||||
|
if m.SELinuxRelabel && podHasSELinuxLabel {
|
||||||
|
if m.ReadOnly {
|
||||||
|
bind += ",Z"
|
||||||
|
} else {
|
||||||
|
bind += ":Z"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
result = append(result, bind)
|
result = append(result, bind)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -766,7 +782,8 @@ func (dm *DockerManager) runContainer(
|
||||||
dm.recorder.Eventf(ref, "Created", "Created with docker id %v", util.ShortenString(dockerContainer.ID, 12))
|
dm.recorder.Eventf(ref, "Created", "Created with docker id %v", util.ShortenString(dockerContainer.ID, 12))
|
||||||
}
|
}
|
||||||
|
|
||||||
binds := makeMountBindings(opts.Mounts)
|
podHasSELinuxLabel := pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SELinuxOptions != nil
|
||||||
|
binds := makeMountBindings(opts.Mounts, podHasSELinuxLabel)
|
||||||
|
|
||||||
// The reason we create and mount the log file in here (not in kubelet) is because
|
// The reason we create and mount the log file in here (not in kubelet) is because
|
||||||
// the file's location depends on the ID of the container, and we need to create and
|
// the file's location depends on the ID of the container, and we need to create and
|
||||||
|
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -62,6 +63,7 @@ import (
|
||||||
kubeletutil "k8s.io/kubernetes/pkg/kubelet/util"
|
kubeletutil "k8s.io/kubernetes/pkg/kubelet/util"
|
||||||
"k8s.io/kubernetes/pkg/labels"
|
"k8s.io/kubernetes/pkg/labels"
|
||||||
"k8s.io/kubernetes/pkg/runtime"
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
|
"k8s.io/kubernetes/pkg/securitycontext"
|
||||||
"k8s.io/kubernetes/pkg/types"
|
"k8s.io/kubernetes/pkg/types"
|
||||||
"k8s.io/kubernetes/pkg/util"
|
"k8s.io/kubernetes/pkg/util"
|
||||||
"k8s.io/kubernetes/pkg/util/bandwidth"
|
"k8s.io/kubernetes/pkg/util/bandwidth"
|
||||||
|
@ -73,6 +75,7 @@ import (
|
||||||
nodeutil "k8s.io/kubernetes/pkg/util/node"
|
nodeutil "k8s.io/kubernetes/pkg/util/node"
|
||||||
"k8s.io/kubernetes/pkg/util/oom"
|
"k8s.io/kubernetes/pkg/util/oom"
|
||||||
"k8s.io/kubernetes/pkg/util/procfs"
|
"k8s.io/kubernetes/pkg/util/procfs"
|
||||||
|
"k8s.io/kubernetes/pkg/util/selinux"
|
||||||
"k8s.io/kubernetes/pkg/util/sets"
|
"k8s.io/kubernetes/pkg/util/sets"
|
||||||
"k8s.io/kubernetes/pkg/version"
|
"k8s.io/kubernetes/pkg/version"
|
||||||
"k8s.io/kubernetes/pkg/volume"
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
@ -975,6 +978,47 @@ func (kl *Kubelet) syncNodeStatus() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// relabelVolumes relabels SELinux volumes to match the pod's
|
||||||
|
// SELinuxOptions specification. This is only needed if the pod uses
|
||||||
|
// hostPID or hostIPC. Otherwise relabeling is delegated to docker.
|
||||||
|
func (kl *Kubelet) relabelVolumes(pod *api.Pod, volumes kubecontainer.VolumeMap) error {
|
||||||
|
if pod.Spec.SecurityContext.SELinuxOptions == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rootDirContext, err := kl.getRootDirContext()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
chconRunner := selinux.NewChconRunner()
|
||||||
|
// Apply the pod's Level to the rootDirContext
|
||||||
|
rootDirSELinuxOptions, err := securitycontext.ParseSELinuxOptions(rootDirContext)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rootDirSELinuxOptions.Level = pod.Spec.SecurityContext.SELinuxOptions.Level
|
||||||
|
volumeContext := fmt.Sprintf("%s:%s:%s:%s", rootDirSELinuxOptions.User, rootDirSELinuxOptions.Role, rootDirSELinuxOptions.Type, rootDirSELinuxOptions.Level)
|
||||||
|
|
||||||
|
for _, volume := range volumes {
|
||||||
|
if volume.Builder.SupportsSELinux() && !volume.Builder.IsReadOnly() {
|
||||||
|
// Relabel the volume and its content to match the 'Level' of the pod
|
||||||
|
err := filepath.Walk(volume.Builder.GetPath(), func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return chconRunner.SetContext(path, volumeContext)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
volume.SELinuxLabeled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func makeMounts(pod *api.Pod, podDir string, container *api.Container, podVolumes kubecontainer.VolumeMap) ([]kubecontainer.Mount, error) {
|
func makeMounts(pod *api.Pod, podDir string, container *api.Container, podVolumes kubecontainer.VolumeMap) ([]kubecontainer.Mount, error) {
|
||||||
// Kubernetes only mounts on /etc/hosts if :
|
// Kubernetes only mounts on /etc/hosts if :
|
||||||
// - container does not use hostNetwork and
|
// - container does not use hostNetwork and
|
||||||
|
@ -991,11 +1035,21 @@ func makeMounts(pod *api.Pod, podDir string, container *api.Container, podVolume
|
||||||
glog.Warningf("Mount cannot be satisified for container %q, because the volume is missing: %q", container.Name, mount)
|
glog.Warningf("Mount cannot be satisified for container %q, because the volume is missing: %q", container.Name, mount)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
relabelVolume := false
|
||||||
|
// If the volume supports SELinux and it has not been
|
||||||
|
// relabeled already and it is not a read-only volume,
|
||||||
|
// relabel it and mark it as labeled
|
||||||
|
if vol.Builder.SupportsSELinux() && !vol.SELinuxLabeled && !vol.Builder.IsReadOnly() {
|
||||||
|
vol.SELinuxLabeled = true
|
||||||
|
relabelVolume = true
|
||||||
|
}
|
||||||
mounts = append(mounts, kubecontainer.Mount{
|
mounts = append(mounts, kubecontainer.Mount{
|
||||||
Name: mount.Name,
|
Name: mount.Name,
|
||||||
ContainerPath: mount.MountPath,
|
ContainerPath: mount.MountPath,
|
||||||
HostPath: vol.GetPath(),
|
HostPath: vol.Builder.GetPath(),
|
||||||
ReadOnly: mount.ReadOnly,
|
ReadOnly: mount.ReadOnly,
|
||||||
|
SELinuxRelabel: relabelVolume,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if mountEtcHostsFile {
|
if mountEtcHostsFile {
|
||||||
|
@ -1080,6 +1134,16 @@ func (kl *Kubelet) GenerateRunContainerOptions(pod *api.Pod, container *api.Cont
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.PortMappings = makePortMappings(container)
|
opts.PortMappings = makePortMappings(container)
|
||||||
|
// Docker does not relabel volumes if the container is running
|
||||||
|
// in the host pid or ipc namespaces so the kubelet must
|
||||||
|
// relabel the volumes
|
||||||
|
if pod.Spec.SecurityContext != nil && (pod.Spec.SecurityContext.HostIPC || pod.Spec.SecurityContext.HostPID) {
|
||||||
|
err = kl.relabelVolumes(pod, vol)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
opts.Mounts, err = makeMounts(pod, kl.getPodDir(pod.UID), container, vol)
|
opts.Mounts, err = makeMounts(pod, kl.getPodDir(pod.UID), container, vol)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -510,6 +510,26 @@ func (f *stubVolume) GetPath() string {
|
||||||
return f.path
|
return f.path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *stubVolume) IsReadOnly() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *stubVolume) SetUp() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *stubVolume) SetUpAt(dir string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *stubVolume) SupportsSELinux() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *stubVolume) SupportsOwnershipManagement() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func TestMakeVolumeMounts(t *testing.T) {
|
func TestMakeVolumeMounts(t *testing.T) {
|
||||||
container := api.Container{
|
container := api.Container{
|
||||||
VolumeMounts: []api.VolumeMount{
|
VolumeMounts: []api.VolumeMount{
|
||||||
|
@ -537,9 +557,9 @@ func TestMakeVolumeMounts(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
podVolumes := kubecontainer.VolumeMap{
|
podVolumes := kubecontainer.VolumeMap{
|
||||||
"disk": &stubVolume{"/mnt/disk"},
|
"disk": kubecontainer.VolumeInfo{Builder: &stubVolume{"/mnt/disk"}},
|
||||||
"disk4": &stubVolume{"/mnt/host"},
|
"disk4": kubecontainer.VolumeInfo{Builder: &stubVolume{"/mnt/host"}},
|
||||||
"disk5": &stubVolume{"/var/lib/kubelet/podID/volumes/empty/disk5"},
|
"disk5": kubecontainer.VolumeInfo{Builder: &stubVolume{"/var/lib/kubelet/podID/volumes/empty/disk5"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
pod := api.Pod{
|
pod := api.Pod{
|
||||||
|
@ -558,24 +578,28 @@ func TestMakeVolumeMounts(t *testing.T) {
|
||||||
"/etc/hosts",
|
"/etc/hosts",
|
||||||
"/mnt/disk",
|
"/mnt/disk",
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"disk",
|
"disk",
|
||||||
"/mnt/path3",
|
"/mnt/path3",
|
||||||
"/mnt/disk",
|
"/mnt/disk",
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"disk4",
|
"disk4",
|
||||||
"/mnt/path4",
|
"/mnt/path4",
|
||||||
"/mnt/host",
|
"/mnt/host",
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"disk5",
|
"disk5",
|
||||||
"/mnt/path5",
|
"/mnt/path5",
|
||||||
"/var/lib/kubelet/podID/volumes/empty/disk5",
|
"/var/lib/kubelet/podID/volumes/empty/disk5",
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(mounts, expectedMounts) {
|
if !reflect.DeepEqual(mounts, expectedMounts) {
|
||||||
|
|
|
@ -488,7 +488,7 @@ func (r *Runtime) makePodManifest(pod *api.Pod, pullSecrets []api.Secret) (*appc
|
||||||
manifest.Volumes = append(manifest.Volumes, appctypes.Volume{
|
manifest.Volumes = append(manifest.Volumes, appctypes.Volume{
|
||||||
Name: *volName,
|
Name: *volName,
|
||||||
Kind: "host",
|
Kind: "host",
|
||||||
Source: volume.GetPath(),
|
Source: volume.Builder.GetPath(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ import (
|
||||||
"github.com/docker/libcontainer/selinux"
|
"github.com/docker/libcontainer/selinux"
|
||||||
)
|
)
|
||||||
|
|
||||||
// getRootContext gets the SELinux context of the kubelet rootDir
|
// getRootDirContext gets the SELinux context of the kubelet rootDir
|
||||||
// or returns an error.
|
// or returns an error.
|
||||||
func (kl *Kubelet) getRootDirContext() (string, error) {
|
func (kl *Kubelet) getRootDirContext() (string, error) {
|
||||||
// If SELinux is not enabled, return an empty string
|
// If SELinux is not enabled, return an empty string
|
||||||
|
|
|
@ -146,7 +146,7 @@ func (kl *Kubelet) mountExternalVolumes(pod *api.Pod) (kubecontainer.VolumeMap,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
podVolumes[volSpec.Name] = builder
|
podVolumes[volSpec.Name] = kubecontainer.VolumeInfo{Builder: builder}
|
||||||
}
|
}
|
||||||
return podVolumes, nil
|
return podVolumes, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 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 selinux contains selinux utility functions.
|
||||||
|
package selinux
|
|
@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package empty_dir
|
package selinux
|
||||||
|
|
||||||
// chconRunner knows how to chcon a directory.
|
// chconRunner knows how to chcon a directory.
|
||||||
type chconRunner interface {
|
type ChconRunner interface {
|
||||||
SetContext(dir, context string) error
|
SetContext(dir, context string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// newChconRunner returns a new chconRunner.
|
// newChconRunner returns a new chconRunner.
|
||||||
func newChconRunner() chconRunner {
|
func NewChconRunner() ChconRunner {
|
||||||
return &realChconRunner{}
|
return &realChconRunner{}
|
||||||
}
|
}
|
|
@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package empty_dir
|
package selinux
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/docker/libcontainer/selinux"
|
"github.com/docker/libcontainer/selinux"
|
|
@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package empty_dir
|
package selinux
|
||||||
|
|
||||||
type realChconRunner struct{}
|
type realChconRunner struct{}
|
||||||
|
|
|
@ -250,6 +250,10 @@ func (b *awsElasticBlockStoreBuilder) IsReadOnly() bool {
|
||||||
return b.readOnly
|
return b.readOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *awsElasticBlockStoreBuilder) SupportsSELinux() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func makeGlobalPDPath(host volume.VolumeHost, volumeID string) string {
|
func makeGlobalPDPath(host volume.VolumeHost, volumeID string) string {
|
||||||
// Clean up the URI to be more fs-friendly
|
// Clean up the URI to be more fs-friendly
|
||||||
name := volumeID
|
name := volumeID
|
||||||
|
|
|
@ -187,6 +187,10 @@ func (cephfsVolume *cephfsBuilder) IsReadOnly() bool {
|
||||||
return cephfsVolume.readonly
|
return cephfsVolume.readonly
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cephfsVolume *cephfsBuilder) SupportsSELinux() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type cephfsCleaner struct {
|
type cephfsCleaner struct {
|
||||||
*cephfs
|
*cephfs
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,6 +225,10 @@ func (b *cinderVolumeBuilder) IsReadOnly() bool {
|
||||||
return b.readOnly
|
return b.readOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *cinderVolumeBuilder) SupportsSELinux() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func makeGlobalPDName(host volume.VolumeHost, devName string) string {
|
func makeGlobalPDName(host volume.VolumeHost, devName string) string {
|
||||||
return path.Join(host.GetPluginDir(cinderVolumePluginName), "mounts", devName)
|
return path.Join(host.GetPluginDir(cinderVolumePluginName), "mounts", devName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,6 +157,10 @@ func (d *downwardAPIVolume) IsReadOnly() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *downwardAPIVolume) SupportsSELinux() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// collectData collects requested downwardAPI in data map.
|
// collectData collects requested downwardAPI in data map.
|
||||||
// Map's key is the requested name of file to dump
|
// 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.
|
// Map's value is the (sorted) content of the field to be dumped in the 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) {
|
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
|
medium := api.StorageMediumDefault
|
||||||
if spec.Volume.EmptyDir != nil { // Support a non-specified source as EmptyDir.
|
if spec.Volume.EmptyDir != nil { // Support a non-specified source as EmptyDir.
|
||||||
medium = spec.Volume.EmptyDir.Medium
|
medium = spec.Volume.EmptyDir.Medium
|
||||||
|
@ -86,7 +86,6 @@ func (plugin *emptyDirPlugin) newBuilderInternal(spec *volume.Spec, pod *api.Pod
|
||||||
mountDetector: mountDetector,
|
mountDetector: mountDetector,
|
||||||
plugin: plugin,
|
plugin: plugin,
|
||||||
rootContext: opts.RootContext,
|
rootContext: opts.RootContext,
|
||||||
chconRunner: chconRunner,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +133,6 @@ type emptyDir struct {
|
||||||
mountDetector mountDetector
|
mountDetector mountDetector
|
||||||
plugin *emptyDirPlugin
|
plugin *emptyDirPlugin
|
||||||
rootContext string
|
rootContext string
|
||||||
chconRunner chconRunner
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_ *emptyDir) SupportsOwnershipManagement() bool {
|
func (_ *emptyDir) SupportsOwnershipManagement() bool {
|
||||||
|
@ -175,7 +173,7 @@ func (ed *emptyDir) SetUpAt(dir string) error {
|
||||||
|
|
||||||
switch ed.medium {
|
switch ed.medium {
|
||||||
case api.StorageMediumDefault:
|
case api.StorageMediumDefault:
|
||||||
err = ed.setupDir(dir, securityContext)
|
err = ed.setupDir(dir)
|
||||||
case api.StorageMediumMemory:
|
case api.StorageMediumMemory:
|
||||||
err = ed.setupTmpfs(dir, securityContext)
|
err = ed.setupTmpfs(dir, securityContext)
|
||||||
default:
|
default:
|
||||||
|
@ -193,13 +191,17 @@ func (ed *emptyDir) IsReadOnly() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ed *emptyDir) SupportsSELinux() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// setupTmpfs creates a tmpfs mount at the specified directory with the
|
// setupTmpfs creates a tmpfs mount at the specified directory with the
|
||||||
// specified SELinux context.
|
// specified SELinux context.
|
||||||
func (ed *emptyDir) setupTmpfs(dir string, selinuxContext string) error {
|
func (ed *emptyDir) setupTmpfs(dir string, selinuxContext string) error {
|
||||||
if ed.mounter == nil {
|
if ed.mounter == nil {
|
||||||
return fmt.Errorf("memory storage requested, but mounter is 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
|
return err
|
||||||
}
|
}
|
||||||
// Make SetUp idempotent.
|
// 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
|
// setupDir creates the directory with the specified SELinux context and
|
||||||
// the default permissions specified by the perm constant.
|
// 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.
|
// Create the directory if it doesn't already exist.
|
||||||
if err := os.MkdirAll(dir, perm); err != nil {
|
if err := os.MkdirAll(dir, perm); err != nil {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,30 +69,10 @@ func (fake *fakeMountDetector) GetMountMedium(path string) (storageMedium, bool,
|
||||||
return fake.medium, fake.isMount, nil
|
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) {
|
func TestPluginEmptyRootContext(t *testing.T) {
|
||||||
doTestPlugin(t, pluginTestConfig{
|
doTestPlugin(t, pluginTestConfig{
|
||||||
medium: api.StorageMediumDefault,
|
medium: api.StorageMediumDefault,
|
||||||
rootContext: "",
|
rootContext: "",
|
||||||
expectedChcons: 0,
|
|
||||||
expectedSetupMounts: 0,
|
expectedSetupMounts: 0,
|
||||||
expectedTeardownMounts: 0})
|
expectedTeardownMounts: 0})
|
||||||
}
|
}
|
||||||
|
@ -106,7 +86,6 @@ func TestPluginRootContextSet(t *testing.T) {
|
||||||
medium: api.StorageMediumDefault,
|
medium: api.StorageMediumDefault,
|
||||||
rootContext: "user:role:type:range",
|
rootContext: "user:role:type:range",
|
||||||
expectedSELinuxContext: "user:role:type:range",
|
expectedSELinuxContext: "user:role:type:range",
|
||||||
expectedChcons: 1,
|
|
||||||
expectedSetupMounts: 0,
|
expectedSetupMounts: 0,
|
||||||
expectedTeardownMounts: 0})
|
expectedTeardownMounts: 0})
|
||||||
}
|
}
|
||||||
|
@ -120,7 +99,6 @@ func TestPluginTmpfs(t *testing.T) {
|
||||||
medium: api.StorageMediumMemory,
|
medium: api.StorageMediumMemory,
|
||||||
rootContext: "user:role:type:range",
|
rootContext: "user:role:type:range",
|
||||||
expectedSELinuxContext: "user:role:type:range",
|
expectedSELinuxContext: "user:role:type:range",
|
||||||
expectedChcons: 1,
|
|
||||||
expectedSetupMounts: 1,
|
expectedSetupMounts: 1,
|
||||||
shouldBeMountedBeforeTeardown: true,
|
shouldBeMountedBeforeTeardown: true,
|
||||||
expectedTeardownMounts: 1})
|
expectedTeardownMounts: 1})
|
||||||
|
@ -132,7 +110,6 @@ type pluginTestConfig struct {
|
||||||
SELinuxOptions *api.SELinuxOptions
|
SELinuxOptions *api.SELinuxOptions
|
||||||
idempotent bool
|
idempotent bool
|
||||||
expectedSELinuxContext string
|
expectedSELinuxContext string
|
||||||
expectedChcons int
|
|
||||||
expectedSetupMounts int
|
expectedSetupMounts int
|
||||||
shouldBeMountedBeforeTeardown bool
|
shouldBeMountedBeforeTeardown bool
|
||||||
expectedTeardownMounts int
|
expectedTeardownMounts int
|
||||||
|
@ -160,7 +137,6 @@ func doTestPlugin(t *testing.T, config pluginTestConfig) {
|
||||||
mounter = mount.FakeMounter{}
|
mounter = mount.FakeMounter{}
|
||||||
mountDetector = fakeMountDetector{}
|
mountDetector = fakeMountDetector{}
|
||||||
pod = &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}}
|
pod = &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}}
|
||||||
fakeChconRnr = &fakeChconRunner{}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Set up the SELinux options on the pod
|
// Set up the SELinux options on the pod
|
||||||
|
@ -194,8 +170,7 @@ func doTestPlugin(t *testing.T, config pluginTestConfig) {
|
||||||
pod,
|
pod,
|
||||||
&mounter,
|
&mounter,
|
||||||
&mountDetector,
|
&mountDetector,
|
||||||
volume.VolumeOptions{RootContext: config.rootContext},
|
volume.VolumeOptions{RootContext: config.rootContext})
|
||||||
fakeChconRnr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Failed to make a new Builder: %v", err)
|
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")
|
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
|
// Check the number of mounts performed during setup
|
||||||
if e, a := config.expectedSetupMounts, len(mounter.Log); e != a {
|
if e, a := config.expectedSetupMounts, len(mounter.Log); e != a {
|
||||||
t.Errorf("Expected %v mounter calls during setup, got %v", e, a)
|
t.Errorf("Expected %v mounter calls during setup, got %v", e, a)
|
||||||
|
|
|
@ -191,6 +191,10 @@ func (b *fcDiskBuilder) IsReadOnly() bool {
|
||||||
return b.readOnly
|
return b.readOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *fcDiskBuilder) SupportsSELinux() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Unmounts the bind mount, and detaches the disk only if the disk
|
// Unmounts the bind mount, and detaches the disk only if the disk
|
||||||
// resource was the last reference to that disk on the kubelet.
|
// resource was the last reference to that disk on the kubelet.
|
||||||
func (c *fcDiskCleaner) TearDown() error {
|
func (c *fcDiskCleaner) TearDown() error {
|
||||||
|
|
|
@ -205,6 +205,10 @@ func (b flockerBuilder) IsReadOnly() bool {
|
||||||
return b.readOnly
|
return b.readOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b flockerBuilder) SupportsSELinux() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// updateDatasetPrimary will update the primary in Flocker and wait for it to
|
// 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.
|
// be ready. If it never gets to ready state it will timeout and error.
|
||||||
func (b flockerBuilder) updateDatasetPrimary(datasetID, primaryUUID string) error {
|
func (b flockerBuilder) updateDatasetPrimary(datasetID, primaryUUID string) error {
|
||||||
|
|
|
@ -238,6 +238,10 @@ func (b *gcePersistentDiskBuilder) IsReadOnly() bool {
|
||||||
return b.readOnly
|
return b.readOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *gcePersistentDiskBuilder) SupportsSELinux() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func makeGlobalPDName(host volume.VolumeHost, devName string) string {
|
func makeGlobalPDName(host volume.VolumeHost, devName string) string {
|
||||||
return path.Join(host.GetPluginDir(gcePersistentDiskPluginName), "mounts", devName)
|
return path.Join(host.GetPluginDir(gcePersistentDiskPluginName), "mounts", devName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,6 +122,10 @@ func (b *gitRepoVolumeBuilder) IsReadOnly() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *gitRepoVolumeBuilder) SupportsSELinux() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// This is the spec for the volume that this plugin wraps.
|
// This is the spec for the volume that this plugin wraps.
|
||||||
var wrappedVolumeSpec = &volume.Spec{
|
var wrappedVolumeSpec = &volume.Spec{
|
||||||
Volume: &api.Volume{VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
Volume: &api.Volume{VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||||
|
|
|
@ -189,6 +189,10 @@ func (b *glusterfsBuilder) IsReadOnly() bool {
|
||||||
return b.readOnly
|
return b.readOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *glusterfsBuilder) SupportsSELinux() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (glusterfsVolume *glusterfs) GetPath() string {
|
func (glusterfsVolume *glusterfs) GetPath() string {
|
||||||
name := glusterfsPluginName
|
name := glusterfsPluginName
|
||||||
return glusterfsVolume.plugin.host.GetPodVolumeDir(glusterfsVolume.pod.UID, util.EscapeQualifiedNameForDisk(name), glusterfsVolume.volName)
|
return glusterfsVolume.plugin.host.GetPodVolumeDir(glusterfsVolume.pod.UID, util.EscapeQualifiedNameForDisk(name), glusterfsVolume.volName)
|
||||||
|
|
|
@ -185,6 +185,10 @@ func (b *hostPathBuilder) IsReadOnly() bool {
|
||||||
return b.readOnly
|
return b.readOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *hostPathBuilder) SupportsSELinux() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (b *hostPathBuilder) GetPath() string {
|
func (b *hostPathBuilder) GetPath() string {
|
||||||
return b.path
|
return b.path
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,6 +185,10 @@ func (b *iscsiDiskBuilder) IsReadOnly() bool {
|
||||||
return b.readOnly
|
return b.readOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *iscsiDiskBuilder) SupportsSELinux() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Unmounts the bind mount, and detaches the disk only if the disk
|
// Unmounts the bind mount, and detaches the disk only if the disk
|
||||||
// resource was the last reference to that disk on the kubelet.
|
// resource was the last reference to that disk on the kubelet.
|
||||||
func (c *iscsiDiskCleaner) TearDown() error {
|
func (c *iscsiDiskCleaner) TearDown() error {
|
||||||
|
|
|
@ -226,6 +226,10 @@ func (b *nfsBuilder) IsReadOnly() bool {
|
||||||
return b.readOnly
|
return b.readOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *nfsBuilder) SupportsSELinux() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
//func (c *nfsCleaner) GetPath() string {
|
//func (c *nfsCleaner) GetPath() string {
|
||||||
// name := nfsPluginName
|
// name := nfsPluginName
|
||||||
|
|
|
@ -219,6 +219,10 @@ func (b *rbd) IsReadOnly() bool {
|
||||||
return b.ReadOnly
|
return b.ReadOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *rbd) SupportsSELinux() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Unmounts the bind mount, and detaches the disk only if the disk
|
// Unmounts the bind mount, and detaches the disk only if the disk
|
||||||
// resource was the last reference to that disk on the kubelet.
|
// resource was the last reference to that disk on the kubelet.
|
||||||
func (c *rbdCleaner) TearDown() error {
|
func (c *rbdCleaner) TearDown() error {
|
||||||
|
|
|
@ -176,6 +176,10 @@ func (sv *secretVolume) IsReadOnly() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sv *secretVolume) SupportsSELinux() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func totalSecretBytes(secret *api.Secret) int {
|
func totalSecretBytes(secret *api.Secret) int {
|
||||||
totalSize := 0
|
totalSize := 0
|
||||||
for _, bytes := range secret.Data {
|
for _, bytes := range secret.Data {
|
||||||
|
|
|
@ -172,6 +172,10 @@ func (fv *FakeVolume) IsReadOnly() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fv *FakeVolume) SupportsSELinux() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (fv *FakeVolume) GetPath() string {
|
func (fv *FakeVolume) GetPath() string {
|
||||||
return path.Join(fv.Plugin.Host.GetPodVolumeDir(fv.PodUID, util.EscapeQualifiedNameForDisk(fv.Plugin.PluginName), fv.VolName))
|
return path.Join(fv.Plugin.Host.GetPodVolumeDir(fv.PodUID, util.EscapeQualifiedNameForDisk(fv.Plugin.PluginName), fv.VolName))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
// 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----
|
// 3. Logical OR the permission bits with rw-rw----
|
||||||
SupportsOwnershipManagement() bool
|
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.
|
// Cleaner interface provides methods to cleanup/unmount the volumes.
|
||||||
|
|
|
@ -29,9 +29,10 @@ import (
|
||||||
"k8s.io/kubernetes/pkg/util"
|
"k8s.io/kubernetes/pkg/util"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
func scTestPod() *api.Pod {
|
func scTestPod(hostIPC bool, hostPID bool) *api.Pod {
|
||||||
podName := "security-context-" + string(util.NewUUID())
|
podName := "security-context-" + string(util.NewUUID())
|
||||||
pod := &api.Pod{
|
pod := &api.Pod{
|
||||||
ObjectMeta: api.ObjectMeta{
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
@ -39,7 +40,10 @@ func scTestPod() *api.Pod {
|
||||||
Labels: map[string]string{"name": podName},
|
Labels: map[string]string{"name": podName},
|
||||||
},
|
},
|
||||||
Spec: api.PodSpec{
|
Spec: api.PodSpec{
|
||||||
SecurityContext: &api.PodSecurityContext{},
|
SecurityContext: &api.PodSecurityContext{
|
||||||
|
HostIPC: hostIPC,
|
||||||
|
HostPID: hostPID,
|
||||||
|
},
|
||||||
Containers: []api.Container{
|
Containers: []api.Container{
|
||||||
{
|
{
|
||||||
Name: "test-container",
|
Name: "test-container",
|
||||||
|
@ -57,7 +61,7 @@ var _ = Describe("[Skipped] Security Context", func() {
|
||||||
framework := NewFramework("security-context")
|
framework := NewFramework("security-context")
|
||||||
|
|
||||||
It("should support pod.Spec.SecurityContext.SupplementalGroups", func() {
|
It("should support pod.Spec.SecurityContext.SupplementalGroups", func() {
|
||||||
pod := scTestPod()
|
pod := scTestPod(false, false)
|
||||||
pod.Spec.Containers[0].Command = []string{"id", "-G"}
|
pod.Spec.Containers[0].Command = []string{"id", "-G"}
|
||||||
pod.Spec.SecurityContext.SupplementalGroups = []int64{1234, 5678}
|
pod.Spec.SecurityContext.SupplementalGroups = []int64{1234, 5678}
|
||||||
groups := []string{"1234", "5678"}
|
groups := []string{"1234", "5678"}
|
||||||
|
@ -65,7 +69,7 @@ var _ = Describe("[Skipped] Security Context", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should support pod.Spec.SecurityContext.RunAsUser", func() {
|
It("should support pod.Spec.SecurityContext.RunAsUser", func() {
|
||||||
pod := scTestPod()
|
pod := scTestPod(false, false)
|
||||||
var uid int64 = 1001
|
var uid int64 = 1001
|
||||||
pod.Spec.SecurityContext.RunAsUser = &uid
|
pod.Spec.SecurityContext.RunAsUser = &uid
|
||||||
pod.Spec.Containers[0].Command = []string{"sh", "-c", "id -u"}
|
pod.Spec.Containers[0].Command = []string{"sh", "-c", "id -u"}
|
||||||
|
@ -76,7 +80,7 @@ var _ = Describe("[Skipped] Security Context", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
It("should support container.SecurityContext.RunAsUser", func() {
|
It("should support container.SecurityContext.RunAsUser", func() {
|
||||||
pod := scTestPod()
|
pod := scTestPod(false, false)
|
||||||
var uid int64 = 1001
|
var uid int64 = 1001
|
||||||
var overrideUid int64 = 1002
|
var overrideUid int64 = 1002
|
||||||
pod.Spec.SecurityContext.RunAsUser = &uid
|
pod.Spec.SecurityContext.RunAsUser = &uid
|
||||||
|
@ -88,4 +92,112 @@ var _ = Describe("[Skipped] Security Context", func() {
|
||||||
fmt.Sprintf("%v", overrideUid),
|
fmt.Sprintf("%v", overrideUid),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("should support volume SELinux relabeling", func() {
|
||||||
|
testPodSELinuxLabeling(framework, false, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should support volume SELinux relabeling when using hostIPC", func() {
|
||||||
|
testPodSELinuxLabeling(framework, true, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should support volume SELinux relabeling when using hostPID", func() {
|
||||||
|
testPodSELinuxLabeling(framework, false, true)
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
func testPodSELinuxLabeling(framework *Framework, hostIPC bool, hostPID bool) {
|
||||||
|
// Write and read a file with an empty_dir volume
|
||||||
|
// with a pod with the MCS label s0:c0,c1
|
||||||
|
pod := scTestPod(hostIPC, hostPID)
|
||||||
|
volumeName := "test-volume"
|
||||||
|
mountPath := "/mounted_volume"
|
||||||
|
pod.Spec.Containers[0].VolumeMounts = []api.VolumeMount{
|
||||||
|
{
|
||||||
|
Name: volumeName,
|
||||||
|
MountPath: mountPath,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pod.Spec.Volumes = []api.Volume{
|
||||||
|
{
|
||||||
|
Name: volumeName,
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
EmptyDir: &api.EmptyDirVolumeSource{
|
||||||
|
Medium: api.StorageMediumDefault,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pod.Spec.SecurityContext.SELinuxOptions = &api.SELinuxOptions{
|
||||||
|
Level: "s0:c0,c1",
|
||||||
|
}
|
||||||
|
pod.Spec.Containers[0].Command = []string{"sleep", "6000"}
|
||||||
|
|
||||||
|
client := framework.Client.Pods(framework.Namespace.Name)
|
||||||
|
_, err := client.Create(pod)
|
||||||
|
|
||||||
|
expectNoError(err, "Error creating pod %v", pod)
|
||||||
|
defer client.Delete(pod.Name, nil)
|
||||||
|
expectNoError(waitForPodRunningInNamespace(framework.Client, pod.Name, framework.Namespace.Name))
|
||||||
|
|
||||||
|
testContent := "hello"
|
||||||
|
testFilePath := mountPath + "/TEST"
|
||||||
|
err = framework.WriteFileViaContainer(pod.Name, pod.Spec.Containers[0].Name, testFilePath, testContent)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
content, err := framework.ReadFileViaContainer(pod.Name, pod.Spec.Containers[0].Name, testFilePath)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(content).To(ContainSubstring(testContent))
|
||||||
|
|
||||||
|
foundPod, err := framework.Client.Pods(framework.Namespace.Name).Get(pod.Name)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
// Confirm that the file can be accessed from a second
|
||||||
|
// pod using host_path with the same MCS label
|
||||||
|
volumeHostPath := fmt.Sprintf("/var/lib/kubelet/pods/%s/volumes/kubernetes.io~empty-dir/%s", foundPod.UID, volumeName)
|
||||||
|
By("confirming a container with the same label can read the file")
|
||||||
|
pod = scTestPod(hostIPC, hostPID)
|
||||||
|
pod.Spec.NodeName = foundPod.Spec.NodeName
|
||||||
|
volumeMounts := []api.VolumeMount{
|
||||||
|
{
|
||||||
|
Name: volumeName,
|
||||||
|
MountPath: mountPath,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
volumes := []api.Volume{
|
||||||
|
{
|
||||||
|
Name: volumeName,
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
HostPath: &api.HostPathVolumeSource{
|
||||||
|
Path: volumeHostPath,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pod.Spec.Containers[0].VolumeMounts = volumeMounts
|
||||||
|
pod.Spec.Volumes = volumes
|
||||||
|
pod.Spec.Containers[0].Command = []string{"cat", testFilePath}
|
||||||
|
pod.Spec.SecurityContext.SELinuxOptions = &api.SELinuxOptions{
|
||||||
|
Level: "s0:c0,c1",
|
||||||
|
}
|
||||||
|
|
||||||
|
framework.TestContainerOutput("Pod with same MCS label reading test file", pod, 0, []string{testContent})
|
||||||
|
// Confirm that the same pod with a different MCS
|
||||||
|
// label cannot access the volume
|
||||||
|
pod = scTestPod(hostIPC, hostPID)
|
||||||
|
pod.Spec.Volumes = volumes
|
||||||
|
pod.Spec.Containers[0].VolumeMounts = volumeMounts
|
||||||
|
pod.Spec.Containers[0].Command = []string{"sleep", "6000"}
|
||||||
|
pod.Spec.SecurityContext.SELinuxOptions = &api.SELinuxOptions{
|
||||||
|
Level: "s0:c2,c3",
|
||||||
|
}
|
||||||
|
_, err = client.Create(pod)
|
||||||
|
expectNoError(err, "Error creating pod %v", pod)
|
||||||
|
defer client.Delete(pod.Name, nil)
|
||||||
|
|
||||||
|
err = framework.WaitForPodRunning(pod.Name)
|
||||||
|
expectNoError(err, "Error waiting for pod to run %v", pod)
|
||||||
|
|
||||||
|
content, err = framework.ReadFileViaContainer(pod.Name, "test-container", testFilePath)
|
||||||
|
Expect(content).NotTo(ContainSubstring(testContent))
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue