mirror of https://github.com/k3s-io/k3s
Remove pod mutation for PVs with supplemental GIDs
parent
73a3d48dc8
commit
cbdd121d2d
|
@ -44,6 +44,10 @@ type RuntimeHelper interface {
|
||||||
GetClusterDNS(pod *api.Pod) (dnsServers []string, dnsSearches []string, err error)
|
GetClusterDNS(pod *api.Pod) (dnsServers []string, dnsSearches []string, err error)
|
||||||
GetPodDir(podUID types.UID) string
|
GetPodDir(podUID types.UID) string
|
||||||
GeneratePodHostNameAndDomain(pod *api.Pod) (hostname string, hostDomain string, err error)
|
GeneratePodHostNameAndDomain(pod *api.Pod) (hostname string, hostDomain string, err error)
|
||||||
|
// GetExtraSupplementalGroupsForPod returns a list of the extra
|
||||||
|
// supplemental groups for the Pod. These extra supplemental groups come
|
||||||
|
// from annotations on persistent volumes that the pod depends on.
|
||||||
|
GetExtraSupplementalGroupsForPod(pod *api.Pod) []int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShouldContainerBeRestarted checks whether a container needs to be restarted.
|
// ShouldContainerBeRestarted checks whether a container needs to be restarted.
|
||||||
|
|
|
@ -691,9 +691,10 @@ func (dm *DockerManager) runContainer(
|
||||||
|
|
||||||
glog.V(3).Infof("Container %v/%v/%v: setting entrypoint \"%v\" and command \"%v\"", pod.Namespace, pod.Name, container.Name, dockerOpts.Config.Entrypoint, dockerOpts.Config.Cmd)
|
glog.V(3).Infof("Container %v/%v/%v: setting entrypoint \"%v\" and command \"%v\"", pod.Namespace, pod.Name, container.Name, dockerOpts.Config.Entrypoint, dockerOpts.Config.Cmd)
|
||||||
|
|
||||||
|
supplementalGids := dm.runtimeHelper.GetExtraSupplementalGroupsForPod(pod)
|
||||||
securityContextProvider := securitycontext.NewSimpleSecurityContextProvider()
|
securityContextProvider := securitycontext.NewSimpleSecurityContextProvider()
|
||||||
securityContextProvider.ModifyContainerConfig(pod, container, dockerOpts.Config)
|
securityContextProvider.ModifyContainerConfig(pod, container, dockerOpts.Config)
|
||||||
securityContextProvider.ModifyHostConfig(pod, container, dockerOpts.HostConfig)
|
securityContextProvider.ModifyHostConfig(pod, container, dockerOpts.HostConfig, supplementalGids)
|
||||||
createResp, err := dm.client.CreateContainer(dockerOpts)
|
createResp, err := dm.client.CreateContainer(dockerOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dm.recorder.Eventf(ref, api.EventTypeWarning, events.FailedToCreateContainer, "Failed to create docker container with error: %v", err)
|
dm.recorder.Eventf(ref, api.EventTypeWarning, events.FailedToCreateContainer, "Failed to create docker container with error: %v", err)
|
||||||
|
|
|
@ -99,6 +99,10 @@ func (f *fakeRuntimeHelper) GetPodDir(kubetypes.UID) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *fakeRuntimeHelper) GetExtraSupplementalGroupsForPod(pod *api.Pod) []int64 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func createTestDockerManager(fakeHTTPClient *fakeHTTP, fakeDocker *FakeDockerClient) (*DockerManager, *FakeDockerClient) {
|
func createTestDockerManager(fakeHTTPClient *fakeHTTP, fakeDocker *FakeDockerClient) (*DockerManager, *FakeDockerClient) {
|
||||||
if fakeHTTPClient == nil {
|
if fakeHTTPClient == nil {
|
||||||
fakeHTTPClient = &fakeHTTP{}
|
fakeHTTPClient = &fakeHTTP{}
|
||||||
|
@ -108,7 +112,13 @@ func createTestDockerManager(fakeHTTPClient *fakeHTTP, fakeDocker *FakeDockerCli
|
||||||
}
|
}
|
||||||
fakeRecorder := &record.FakeRecorder{}
|
fakeRecorder := &record.FakeRecorder{}
|
||||||
containerRefManager := kubecontainer.NewRefManager()
|
containerRefManager := kubecontainer.NewRefManager()
|
||||||
networkPlugin, _ := network.InitNetworkPlugin([]network.NetworkPlugin{}, "", nettest.NewFakeHost(nil), componentconfig.HairpinNone, "10.0.0.0/8")
|
networkPlugin, _ := network.InitNetworkPlugin(
|
||||||
|
[]network.NetworkPlugin{},
|
||||||
|
"",
|
||||||
|
nettest.NewFakeHost(nil),
|
||||||
|
componentconfig.HairpinNone,
|
||||||
|
"10.0.0.0/8")
|
||||||
|
|
||||||
dockerManager := NewFakeDockerManager(
|
dockerManager := NewFakeDockerManager(
|
||||||
fakeDocker,
|
fakeDocker,
|
||||||
fakeRecorder,
|
fakeRecorder,
|
||||||
|
|
|
@ -227,3 +227,10 @@ func (kl *Kubelet) getHostIPAnyWay() (net.IP, error) {
|
||||||
}
|
}
|
||||||
return nodeutil.GetNodeHostIP(node)
|
return nodeutil.GetNodeHostIP(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetExtraSupplementalGroupsForPod returns a list of the extra
|
||||||
|
// supplemental groups for the Pod. These extra supplemental groups come
|
||||||
|
// from annotations on persistent volumes that the pod depends on.
|
||||||
|
func (kl *Kubelet) GetExtraSupplementalGroupsForPod(pod *api.Pod) []int64 {
|
||||||
|
return kl.volumeManager.GetExtraSupplementalGroupsForPod(pod)
|
||||||
|
}
|
||||||
|
|
|
@ -99,16 +99,10 @@ type VolumeManager interface {
|
||||||
// volumes.
|
// volumes.
|
||||||
GetMountedVolumesForPod(podName types.UniquePodName) container.VolumeMap
|
GetMountedVolumesForPod(podName types.UniquePodName) container.VolumeMap
|
||||||
|
|
||||||
// GetVolumesForPodAndApplySupplementalGroups, like GetVolumesForPod returns
|
// GetExtraSupplementalGroupsForPod returns a list of the extra
|
||||||
// a VolumeMap containing the volumes referenced by the specified pod that
|
// supplemental groups for the Pod. These extra supplemental groups come
|
||||||
// are successfully attached and mounted. The key in the map is the
|
// from annotations on persistent volumes that the pod depends on.
|
||||||
// OuterVolumeSpecName (i.e. pod.Spec.Volumes[x].Name).
|
GetExtraSupplementalGroupsForPod(pod *api.Pod) []int64
|
||||||
// It returns an empty VolumeMap if pod has no volumes.
|
|
||||||
// In addition for every volume that specifies a VolumeGidValue, it appends
|
|
||||||
// the SecurityContext.SupplementalGroups for the specified pod.
|
|
||||||
// XXX: https://github.com/kubernetes/kubernetes/issues/27197 mutating the
|
|
||||||
// pod object is bad, and should be avoided.
|
|
||||||
GetVolumesForPodAndAppendSupplementalGroups(pod *api.Pod) container.VolumeMap
|
|
||||||
|
|
||||||
// Returns a list of all volumes that implement the volume.Attacher
|
// Returns a list of all volumes that implement the volume.Attacher
|
||||||
// interface and are currently in use according to the actual and desired
|
// interface and are currently in use according to the actual and desired
|
||||||
|
@ -227,12 +221,34 @@ func (vm *volumeManager) Run(stopCh <-chan struct{}) {
|
||||||
|
|
||||||
func (vm *volumeManager) GetMountedVolumesForPod(
|
func (vm *volumeManager) GetMountedVolumesForPod(
|
||||||
podName types.UniquePodName) container.VolumeMap {
|
podName types.UniquePodName) container.VolumeMap {
|
||||||
return vm.getVolumesForPodHelper(podName, nil /* pod */)
|
podVolumes := make(container.VolumeMap)
|
||||||
|
for _, mountedVolume := range vm.actualStateOfWorld.GetMountedVolumesForPod(podName) {
|
||||||
|
podVolumes[mountedVolume.OuterVolumeSpecName] = container.VolumeInfo{Mounter: mountedVolume.Mounter}
|
||||||
|
}
|
||||||
|
return podVolumes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *volumeManager) GetVolumesForPodAndAppendSupplementalGroups(
|
func (vm *volumeManager) GetExtraSupplementalGroupsForPod(pod *api.Pod) []int64 {
|
||||||
pod *api.Pod) container.VolumeMap {
|
podName := volumehelper.GetUniquePodName(pod)
|
||||||
return vm.getVolumesForPodHelper("" /* podName */, pod)
|
supplementalGroups := sets.NewString()
|
||||||
|
|
||||||
|
for _, mountedVolume := range vm.actualStateOfWorld.GetMountedVolumesForPod(podName) {
|
||||||
|
if mountedVolume.VolumeGidValue != "" {
|
||||||
|
supplementalGroups.Insert(mountedVolume.VolumeGidValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]int64, 0, supplementalGroups.Len())
|
||||||
|
for _, group := range supplementalGroups.List() {
|
||||||
|
iGroup, extra := getExtraSupplementalGid(group, pod)
|
||||||
|
if !extra {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, int64(iGroup))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *volumeManager) GetVolumesInUse() []api.UniqueVolumeName {
|
func (vm *volumeManager) GetVolumesInUse() []api.UniqueVolumeName {
|
||||||
|
@ -279,33 +295,6 @@ func (vm *volumeManager) MarkVolumesAsReportedInUse(
|
||||||
vm.desiredStateOfWorld.MarkVolumesReportedInUse(volumesReportedAsInUse)
|
vm.desiredStateOfWorld.MarkVolumesReportedInUse(volumesReportedAsInUse)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getVolumesForPodHelper is a helper method implements the common logic for
|
|
||||||
// the GetVolumesForPod methods.
|
|
||||||
// XXX: https://github.com/kubernetes/kubernetes/issues/27197 mutating the pod
|
|
||||||
// object is bad, and should be avoided.
|
|
||||||
func (vm *volumeManager) getVolumesForPodHelper(
|
|
||||||
podName types.UniquePodName, pod *api.Pod) container.VolumeMap {
|
|
||||||
if pod != nil {
|
|
||||||
podName = volumehelper.GetUniquePodName(pod)
|
|
||||||
}
|
|
||||||
podVolumes := make(container.VolumeMap)
|
|
||||||
for _, mountedVolume := range vm.actualStateOfWorld.GetMountedVolumesForPod(podName) {
|
|
||||||
podVolumes[mountedVolume.OuterVolumeSpecName] =
|
|
||||||
container.VolumeInfo{Mounter: mountedVolume.Mounter}
|
|
||||||
if pod != nil {
|
|
||||||
err := applyPersistentVolumeAnnotations(
|
|
||||||
mountedVolume.VolumeGidValue, pod)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("applyPersistentVolumeAnnotations failed for pod %q volume %q with: %v",
|
|
||||||
podName,
|
|
||||||
mountedVolume.VolumeName,
|
|
||||||
err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return podVolumes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vm *volumeManager) WaitForAttachAndMount(pod *api.Pod) error {
|
func (vm *volumeManager) WaitForAttachAndMount(pod *api.Pod) error {
|
||||||
expectedVolumes := getExpectedVolumes(pod)
|
expectedVolumes := getExpectedVolumes(pod)
|
||||||
if len(expectedVolumes) == 0 {
|
if len(expectedVolumes) == 0 {
|
||||||
|
@ -395,32 +384,26 @@ func getExpectedVolumes(pod *api.Pod) []string {
|
||||||
return expectedVolumes
|
return expectedVolumes
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyPersistentVolumeAnnotations appends a pod
|
// getExtraSupplementalGid returns the value of an extra supplemental GID as
|
||||||
// SecurityContext.SupplementalGroups if a GID annotation is provided.
|
// defined by an annotation on a volume and a boolean indicating whether the
|
||||||
// XXX: https://github.com/kubernetes/kubernetes/issues/27197 mutating the pod
|
// volume defined a GID that the pod doesn't already request.
|
||||||
// object is bad, and should be avoided.
|
func getExtraSupplementalGid(volumeGidValue string, pod *api.Pod) (int64, bool) {
|
||||||
func applyPersistentVolumeAnnotations(
|
if volumeGidValue == "" {
|
||||||
volumeGidValue string, pod *api.Pod) error {
|
return 0, false
|
||||||
if volumeGidValue != "" {
|
|
||||||
gid, err := strconv.ParseInt(volumeGidValue, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"Invalid value for %s %v",
|
|
||||||
volumehelper.VolumeGidAnnotationKey,
|
|
||||||
err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if pod.Spec.SecurityContext == nil {
|
|
||||||
pod.Spec.SecurityContext = &api.PodSecurityContext{}
|
|
||||||
}
|
|
||||||
for _, existingGid := range pod.Spec.SecurityContext.SupplementalGroups {
|
|
||||||
if gid == existingGid {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pod.Spec.SecurityContext.SupplementalGroups =
|
|
||||||
append(pod.Spec.SecurityContext.SupplementalGroups, gid)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
gid, err := strconv.ParseInt(volumeGidValue, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if pod.Spec.SecurityContext != nil {
|
||||||
|
for _, existingGid := range pod.Spec.SecurityContext.SupplementalGroups {
|
||||||
|
if gid == existingGid {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gid, true
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,278 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors.
|
||||||
|
|
||||||
|
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 volumemanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||||
|
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||||
|
containertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
|
||||||
|
"k8s.io/kubernetes/pkg/kubelet/pod"
|
||||||
|
kubepod "k8s.io/kubernetes/pkg/kubelet/pod"
|
||||||
|
podtest "k8s.io/kubernetes/pkg/kubelet/pod/testing"
|
||||||
|
"k8s.io/kubernetes/pkg/util/mount"
|
||||||
|
utiltesting "k8s.io/kubernetes/pkg/util/testing"
|
||||||
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
volumetest "k8s.io/kubernetes/pkg/volume/testing"
|
||||||
|
"k8s.io/kubernetes/pkg/volume/util/types"
|
||||||
|
"k8s.io/kubernetes/pkg/volume/util/volumehelper"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testHostname = "test-hostname"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetMountedVolumesForPodAndGetVolumesInUse(t *testing.T) {
|
||||||
|
tmpDir, err := utiltesting.MkTmpdir("volumeManagerTest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("can't make a temp dir: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
podManager := kubepod.NewBasicPodManager(podtest.NewFakeMirrorClient())
|
||||||
|
|
||||||
|
node, pod, pv, claim := createObjects()
|
||||||
|
kubeClient := fake.NewSimpleClientset(node, pod, pv, claim)
|
||||||
|
|
||||||
|
manager, err := newTestVolumeManager(tmpDir, podManager, kubeClient)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to initialize volume manager: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stopCh := make(chan struct{})
|
||||||
|
go manager.Run(stopCh)
|
||||||
|
defer close(stopCh)
|
||||||
|
|
||||||
|
podManager.SetPods([]*api.Pod{pod})
|
||||||
|
|
||||||
|
// Fake node status update
|
||||||
|
go simulateVolumeInUseUpdate(
|
||||||
|
api.UniqueVolumeName(node.Status.VolumesAttached[0].Name),
|
||||||
|
stopCh,
|
||||||
|
manager)
|
||||||
|
|
||||||
|
err = manager.WaitForAttachAndMount(pod)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected success: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedMounted := pod.Spec.Volumes[0].Name
|
||||||
|
actualMounted := manager.GetMountedVolumesForPod(types.UniquePodName(pod.ObjectMeta.UID))
|
||||||
|
if _, ok := actualMounted[expectedMounted]; !ok || (len(actualMounted) != 1) {
|
||||||
|
t.Errorf("Expected %v to be mounted to pod but got %v", expectedMounted, actualMounted)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedInUse := []api.UniqueVolumeName{api.UniqueVolumeName(node.Status.VolumesAttached[0].Name)}
|
||||||
|
actualInUse := manager.GetVolumesInUse()
|
||||||
|
if !reflect.DeepEqual(expectedInUse, actualInUse) {
|
||||||
|
t.Errorf("Expected %v to be in use but got %v", expectedInUse, actualInUse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetExtraSupplementalGroupsForPod(t *testing.T) {
|
||||||
|
tmpDir, err := utiltesting.MkTmpdir("volumeManagerTest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("can't make a temp dir: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
podManager := kubepod.NewBasicPodManager(podtest.NewFakeMirrorClient())
|
||||||
|
|
||||||
|
node, pod, _, claim := createObjects()
|
||||||
|
|
||||||
|
existingGid := pod.Spec.SecurityContext.SupplementalGroups[0]
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
gidAnnotation string
|
||||||
|
expected []int64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
gidAnnotation: "777",
|
||||||
|
expected: []int64{777},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gidAnnotation: strconv.FormatInt(existingGid, 10),
|
||||||
|
expected: []int64{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gidAnnotation: "a",
|
||||||
|
expected: []int64{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gidAnnotation: "",
|
||||||
|
expected: []int64{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
pv := &api.PersistentVolume{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "pvA",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
volumehelper.VolumeGidAnnotationKey: tc.gidAnnotation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: api.PersistentVolumeSpec{
|
||||||
|
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||||
|
GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{
|
||||||
|
PDName: "fake-device",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ClaimRef: &api.ObjectReference{
|
||||||
|
Name: claim.ObjectMeta.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
kubeClient := fake.NewSimpleClientset(node, pod, pv, claim)
|
||||||
|
|
||||||
|
manager, err := newTestVolumeManager(tmpDir, podManager, kubeClient)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to initialize volume manager: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
stopCh := make(chan struct{})
|
||||||
|
go manager.Run(stopCh)
|
||||||
|
|
||||||
|
podManager.SetPods([]*api.Pod{pod})
|
||||||
|
|
||||||
|
// Fake node status update
|
||||||
|
go simulateVolumeInUseUpdate(
|
||||||
|
api.UniqueVolumeName(node.Status.VolumesAttached[0].Name),
|
||||||
|
stopCh,
|
||||||
|
manager)
|
||||||
|
|
||||||
|
err = manager.WaitForAttachAndMount(pod)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected success: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := manager.GetExtraSupplementalGroupsForPod(pod)
|
||||||
|
if !reflect.DeepEqual(tc.expected, actual) {
|
||||||
|
t.Errorf("Expected supplemental groups %v, got %v", tc.expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
close(stopCh)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestVolumeManager(
|
||||||
|
tmpDir string,
|
||||||
|
podManager pod.Manager,
|
||||||
|
kubeClient internalclientset.Interface) (VolumeManager, error) {
|
||||||
|
plug := &volumetest.FakeVolumePlugin{PluginName: "fake", Host: nil}
|
||||||
|
plugMgr := &volume.VolumePluginMgr{}
|
||||||
|
plugMgr.InitPlugins([]volume.VolumePlugin{plug}, volumetest.NewFakeVolumeHost(tmpDir, kubeClient, nil, "" /* rootContext */))
|
||||||
|
|
||||||
|
vm, err := NewVolumeManager(
|
||||||
|
true,
|
||||||
|
testHostname,
|
||||||
|
podManager,
|
||||||
|
kubeClient,
|
||||||
|
plugMgr,
|
||||||
|
&containertest.FakeRuntime{},
|
||||||
|
&mount.FakeMounter{})
|
||||||
|
return vm, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// createObjects returns objects for making a fake clientset. The pv is
|
||||||
|
// already attached to the node and bound to the claim used by the pod.
|
||||||
|
func createObjects() (*api.Node, *api.Pod, *api.PersistentVolume, *api.PersistentVolumeClaim) {
|
||||||
|
node := &api.Node{
|
||||||
|
ObjectMeta: api.ObjectMeta{Name: testHostname},
|
||||||
|
Status: api.NodeStatus{
|
||||||
|
VolumesAttached: []api.AttachedVolume{
|
||||||
|
{
|
||||||
|
Name: "fake/pvA",
|
||||||
|
DevicePath: "fake/path",
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
Spec: api.NodeSpec{ExternalID: testHostname},
|
||||||
|
}
|
||||||
|
pod := &api.Pod{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "abc",
|
||||||
|
Namespace: "nsA",
|
||||||
|
UID: "1234",
|
||||||
|
},
|
||||||
|
Spec: api.PodSpec{
|
||||||
|
Volumes: []api.Volume{
|
||||||
|
{
|
||||||
|
Name: "vol1",
|
||||||
|
VolumeSource: api.VolumeSource{
|
||||||
|
PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{
|
||||||
|
ClaimName: "claimA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SecurityContext: &api.PodSecurityContext{
|
||||||
|
SupplementalGroups: []int64{555},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pv := &api.PersistentVolume{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "pvA",
|
||||||
|
},
|
||||||
|
Spec: api.PersistentVolumeSpec{
|
||||||
|
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||||
|
GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{
|
||||||
|
PDName: "fake-device",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ClaimRef: &api.ObjectReference{
|
||||||
|
Name: "claimA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
claim := &api.PersistentVolumeClaim{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "claimA",
|
||||||
|
Namespace: "nsA",
|
||||||
|
},
|
||||||
|
Spec: api.PersistentVolumeClaimSpec{
|
||||||
|
VolumeName: "pvA",
|
||||||
|
},
|
||||||
|
Status: api.PersistentVolumeClaimStatus{
|
||||||
|
Phase: api.ClaimBound,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return node, pod, pv, claim
|
||||||
|
}
|
||||||
|
|
||||||
|
func simulateVolumeInUseUpdate(
|
||||||
|
volumeName api.UniqueVolumeName,
|
||||||
|
stopCh <-chan struct{},
|
||||||
|
volumeManager VolumeManager) {
|
||||||
|
ticker := time.NewTicker(100 * time.Millisecond)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
volumeManager.MarkVolumesAsReportedInUse(
|
||||||
|
[]api.UniqueVolumeName{volumeName})
|
||||||
|
case <-stopCh:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,5 +41,5 @@ type FakeSecurityContextProvider struct{}
|
||||||
|
|
||||||
func (p FakeSecurityContextProvider) ModifyContainerConfig(pod *api.Pod, container *api.Container, config *dockercontainer.Config) {
|
func (p FakeSecurityContextProvider) ModifyContainerConfig(pod *api.Pod, container *api.Container, config *dockercontainer.Config) {
|
||||||
}
|
}
|
||||||
func (p FakeSecurityContextProvider) ModifyHostConfig(pod *api.Pod, container *api.Container, hostConfig *dockercontainer.HostConfig) {
|
func (p FakeSecurityContextProvider) ModifyHostConfig(pod *api.Pod, container *api.Container, hostConfig *dockercontainer.HostConfig, supplementalGids []int64) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,12 +47,12 @@ func (p SimpleSecurityContextProvider) ModifyContainerConfig(pod *api.Pod, conta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ModifyHostConfig is called before the Docker runContainer call.
|
// ModifyHostConfig is called before the Docker runContainer call. The
|
||||||
// The security context provider can make changes to the HostConfig, affecting
|
// security context provider can make changes to the HostConfig, affecting
|
||||||
// security options, whether the container is privileged, volume binds, etc.
|
// security options, whether the container is privileged, volume binds, etc.
|
||||||
func (p SimpleSecurityContextProvider) ModifyHostConfig(pod *api.Pod, container *api.Container, hostConfig *dockercontainer.HostConfig) {
|
func (p SimpleSecurityContextProvider) ModifyHostConfig(pod *api.Pod, container *api.Container, hostConfig *dockercontainer.HostConfig, supplementalGids []int64) {
|
||||||
// Apply pod security context
|
// Apply supplemental groups
|
||||||
if container.Name != leaky.PodInfraContainerName && pod.Spec.SecurityContext != nil {
|
if container.Name != leaky.PodInfraContainerName {
|
||||||
// TODO: We skip application of supplemental groups to the
|
// TODO: We skip application of supplemental groups to the
|
||||||
// infra container to work around a runc issue which
|
// infra container to work around a runc issue which
|
||||||
// requires containers to have the '/etc/group'. For
|
// requires containers to have the '/etc/group'. For
|
||||||
|
@ -60,15 +60,17 @@ func (p SimpleSecurityContextProvider) ModifyHostConfig(pod *api.Pod, container
|
||||||
// https://github.com/opencontainers/runc/pull/313
|
// https://github.com/opencontainers/runc/pull/313
|
||||||
// This can be removed once the fix makes it into the
|
// This can be removed once the fix makes it into the
|
||||||
// required version of docker.
|
// required version of docker.
|
||||||
if pod.Spec.SecurityContext.SupplementalGroups != nil {
|
if pod.Spec.SecurityContext != nil {
|
||||||
hostConfig.GroupAdd = make([]string, len(pod.Spec.SecurityContext.SupplementalGroups))
|
for _, group := range pod.Spec.SecurityContext.SupplementalGroups {
|
||||||
for i, group := range pod.Spec.SecurityContext.SupplementalGroups {
|
hostConfig.GroupAdd = append(hostConfig.GroupAdd, strconv.Itoa(int(group)))
|
||||||
hostConfig.GroupAdd[i] = strconv.Itoa(int(group))
|
}
|
||||||
|
if pod.Spec.SecurityContext.FSGroup != nil {
|
||||||
|
hostConfig.GroupAdd = append(hostConfig.GroupAdd, strconv.Itoa(int(*pod.Spec.SecurityContext.FSGroup)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if pod.Spec.SecurityContext.FSGroup != nil {
|
for _, group := range supplementalGids {
|
||||||
hostConfig.GroupAdd = append(hostConfig.GroupAdd, strconv.Itoa(int(*pod.Spec.SecurityContext.FSGroup)))
|
hostConfig.GroupAdd = append(hostConfig.GroupAdd, strconv.Itoa(int(group)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -166,7 +166,7 @@ func TestModifyHostConfig(t *testing.T) {
|
||||||
dummyContainer.SecurityContext = tc.sc
|
dummyContainer.SecurityContext = tc.sc
|
||||||
dockerCfg := &dockercontainer.HostConfig{}
|
dockerCfg := &dockercontainer.HostConfig{}
|
||||||
|
|
||||||
provider.ModifyHostConfig(pod, dummyContainer, dockerCfg)
|
provider.ModifyHostConfig(pod, dummyContainer, dockerCfg, nil)
|
||||||
|
|
||||||
if e, a := tc.expected, dockerCfg; !reflect.DeepEqual(e, a) {
|
if e, a := tc.expected, dockerCfg; !reflect.DeepEqual(e, a) {
|
||||||
t.Errorf("%v: unexpected modification of host config\nExpected:\n\n%#v\n\nGot:\n\n%#v", tc.name, e, a)
|
t.Errorf("%v: unexpected modification of host config\nExpected:\n\n%#v\n\nGot:\n\n%#v", tc.name, e, a)
|
||||||
|
@ -181,32 +181,50 @@ func TestModifyHostConfigPodSecurityContext(t *testing.T) {
|
||||||
supplementalGroupHC.GroupAdd = []string{"2222"}
|
supplementalGroupHC.GroupAdd = []string{"2222"}
|
||||||
fsGroupHC := fullValidHostConfig()
|
fsGroupHC := fullValidHostConfig()
|
||||||
fsGroupHC.GroupAdd = []string{"1234"}
|
fsGroupHC.GroupAdd = []string{"1234"}
|
||||||
|
extraSupplementalGroupHC := fullValidHostConfig()
|
||||||
|
extraSupplementalGroupHC.GroupAdd = []string{"1234"}
|
||||||
bothHC := fullValidHostConfig()
|
bothHC := fullValidHostConfig()
|
||||||
bothHC.GroupAdd = []string{"2222", "1234"}
|
bothHC.GroupAdd = []string{"2222", "1234"}
|
||||||
fsGroup := int64(1234)
|
fsGroup := int64(1234)
|
||||||
|
extraSupplementalGroup := []int64{1234}
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
securityContext *api.PodSecurityContext
|
securityContext *api.PodSecurityContext
|
||||||
expected *dockercontainer.HostConfig
|
expected *dockercontainer.HostConfig
|
||||||
|
extraSupplementalGroups []int64
|
||||||
}{
|
}{
|
||||||
"nil": {
|
"nil": {
|
||||||
securityContext: nil,
|
securityContext: nil,
|
||||||
expected: fullValidHostConfig(),
|
expected: fullValidHostConfig(),
|
||||||
|
extraSupplementalGroups: nil,
|
||||||
},
|
},
|
||||||
"SupplementalGroup": {
|
"SupplementalGroup": {
|
||||||
securityContext: supplementalGroupsSC,
|
securityContext: supplementalGroupsSC,
|
||||||
expected: supplementalGroupHC,
|
expected: supplementalGroupHC,
|
||||||
|
extraSupplementalGroups: nil,
|
||||||
},
|
},
|
||||||
"FSGroup": {
|
"FSGroup": {
|
||||||
securityContext: &api.PodSecurityContext{FSGroup: &fsGroup},
|
securityContext: &api.PodSecurityContext{FSGroup: &fsGroup},
|
||||||
expected: fsGroupHC,
|
expected: fsGroupHC,
|
||||||
|
extraSupplementalGroups: nil,
|
||||||
},
|
},
|
||||||
"FSGroup + SupplementalGroups": {
|
"FSGroup + SupplementalGroups": {
|
||||||
securityContext: &api.PodSecurityContext{
|
securityContext: &api.PodSecurityContext{
|
||||||
SupplementalGroups: []int64{2222},
|
SupplementalGroups: []int64{2222},
|
||||||
FSGroup: &fsGroup,
|
FSGroup: &fsGroup,
|
||||||
},
|
},
|
||||||
expected: bothHC,
|
expected: bothHC,
|
||||||
|
extraSupplementalGroups: nil,
|
||||||
|
},
|
||||||
|
"ExtraSupplementalGroup": {
|
||||||
|
securityContext: nil,
|
||||||
|
expected: extraSupplementalGroupHC,
|
||||||
|
extraSupplementalGroups: extraSupplementalGroup,
|
||||||
|
},
|
||||||
|
"ExtraSupplementalGroup + SupplementalGroups": {
|
||||||
|
securityContext: supplementalGroupsSC,
|
||||||
|
expected: bothHC,
|
||||||
|
extraSupplementalGroups: extraSupplementalGroup,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +238,7 @@ func TestModifyHostConfigPodSecurityContext(t *testing.T) {
|
||||||
for k, v := range testCases {
|
for k, v := range testCases {
|
||||||
dummyPod.Spec.SecurityContext = v.securityContext
|
dummyPod.Spec.SecurityContext = v.securityContext
|
||||||
dockerCfg := &dockercontainer.HostConfig{}
|
dockerCfg := &dockercontainer.HostConfig{}
|
||||||
provider.ModifyHostConfig(dummyPod, dummyContainer, dockerCfg)
|
provider.ModifyHostConfig(dummyPod, dummyContainer, dockerCfg, v.extraSupplementalGroups)
|
||||||
if !reflect.DeepEqual(v.expected, dockerCfg) {
|
if !reflect.DeepEqual(v.expected, dockerCfg) {
|
||||||
t.Errorf("unexpected modification of host config for %s. Expected: %#v Got: %#v", k, v.expected, dockerCfg)
|
t.Errorf("unexpected modification of host config for %s. Expected: %#v Got: %#v", k, v.expected, dockerCfg)
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,11 @@ type SecurityContextProvider interface {
|
||||||
// security options, whether the container is privileged, volume binds, etc.
|
// security options, whether the container is privileged, volume binds, etc.
|
||||||
// An error is returned if it's not possible to secure the container as requested
|
// An error is returned if it's not possible to secure the container as requested
|
||||||
// with a security context.
|
// with a security context.
|
||||||
ModifyHostConfig(pod *api.Pod, container *api.Container, hostConfig *dockercontainer.HostConfig)
|
//
|
||||||
|
// - pod: the pod to modify the docker hostconfig for
|
||||||
|
// - container: the container to modify the hostconfig for
|
||||||
|
// - supplementalGids: additional supplemental GIDs associated with the pod's volumes
|
||||||
|
ModifyHostConfig(pod *api.Pod, container *api.Container, hostConfig *dockercontainer.HostConfig, supplementalGids []int64)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
Loading…
Reference in New Issue