diff --git a/pkg/controller/volume/attachdetach/attach_detach_controller.go b/pkg/controller/volume/attachdetach/attach_detach_controller.go index 31d898f1f2d..1d628afbfc5 100644 --- a/pkg/controller/volume/attachdetach/attach_detach_controller.go +++ b/pkg/controller/volume/attachdetach/attach_detach_controller.go @@ -819,6 +819,10 @@ func (adc *attachDetachController) GetPodVolumeDir(podUID types.UID, pluginName, return "" } +func (adc *attachDetachController) GetHostIDsForPod(pod *v1.Pod, containerUID, containerGID *int64) (hostUID, hostGID *int64, err error) { + return nil, nil, nil +} + func (adc *attachDetachController) GetPodPluginDir(podUID types.UID, pluginName string) string { return "" } diff --git a/pkg/controller/volume/expand/expand_controller.go b/pkg/controller/volume/expand/expand_controller.go index 9196d285661..345d67e3ea0 100644 --- a/pkg/controller/volume/expand/expand_controller.go +++ b/pkg/controller/volume/expand/expand_controller.go @@ -394,6 +394,10 @@ func (expc *expandController) GetPodsDir() string { return "" } +func (expc *expandController) GetHostIDsForPod(pod *v1.Pod, containerUID, containerGID *int64) (hostUID, hostGID *int64, err error) { + return nil, nil, nil +} + func (expc *expandController) GetPodVolumeDir(podUID types.UID, pluginName string, volumeName string) string { return "" } diff --git a/pkg/controller/volume/persistentvolume/volume_host.go b/pkg/controller/volume/persistentvolume/volume_host.go index c064823c9c2..b47f183a61d 100644 --- a/pkg/controller/volume/persistentvolume/volume_host.go +++ b/pkg/controller/volume/persistentvolume/volume_host.go @@ -55,6 +55,10 @@ func (ctrl *PersistentVolumeController) GetPodVolumeDir(podUID types.UID, plugin return "" } +func (ctrl *PersistentVolumeController) GetHostIDsForPod(pod *v1.Pod, containerUID, containerGID *int64) (hostUID, hostGID *int64, err error) { + return nil, nil, nil +} + func (ctrl *PersistentVolumeController) GetPodPluginDir(podUID types.UID, pluginName string) string { return "" } diff --git a/pkg/kubelet/kubelet_pods.go b/pkg/kubelet/kubelet_pods.go index 93d1cd9a8b5..ecbf81099bd 100644 --- a/pkg/kubelet/kubelet_pods.go +++ b/pkg/kubelet/kubelet_pods.go @@ -425,6 +425,10 @@ func (kl *Kubelet) GetOrCreateUserNamespaceMappings(pod *v1.Pod) (*runtimeapi.Us return kl.usernsManager.GetOrCreateUserNamespaceMappings(pod) } +func (kl *Kubelet) getHostIDsForPod(pod *v1.Pod, containerUID, containerGID *int64) (hostUID, hostGID *int64, err error) { + return kl.usernsManager.getHostIDsForPod(pod, containerUID, containerGID) +} + // GeneratePodHostNameAndDomain creates a hostname and domain name for a pod, // given that pod's spec and annotations or returns an error. func (kl *Kubelet) GeneratePodHostNameAndDomain(pod *v1.Pod) (string, string, error) { diff --git a/pkg/kubelet/userns_manager.go b/pkg/kubelet/userns_manager.go index 4ad96cba8f5..07fb6551224 100644 --- a/pkg/kubelet/userns_manager.go +++ b/pkg/kubelet/userns_manager.go @@ -528,3 +528,68 @@ func (m *usernsManager) CleanupOrphanedPodUsernsAllocations(pods []*v1.Pod, runn return nil } + +// getHostIDsForPod if the pod uses user namespaces, takes the uid and gid +// inside the container and returns the host UID and GID those are mapped to on +// the host. If containerUID/containerGID is nil, then it returns the host +// UID/GID for ID 0 inside the container. +// If the pod is not using user namespaces, as there is no mapping needed, the +// same containerUID and containerGID params are returned. +func (m *usernsManager) getHostIDsForPod(pod *v1.Pod, containerUID, containerGID *int64) (hostUID, hostGID *int64, err error) { + if !utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesStatelessPodsSupport) { + return containerUID, containerGID, nil + } + + if pod == nil || pod.Spec.HostUsers == nil || *pod.Spec.HostUsers == true { + return containerUID, containerGID, nil + } + + mapping, err := m.GetOrCreateUserNamespaceMappings(pod) + if err != nil { + err = fmt.Errorf("Error getting pod user namespace mapping: %w", err) + return + } + + uid, err := hostIDFromMapping(mapping.Uids, containerUID) + if err != nil { + err = fmt.Errorf("Error getting host UID: %w", err) + return + } + + gid, err := hostIDFromMapping(mapping.Gids, containerGID) + if err != nil { + err = fmt.Errorf("Error getting host GID: %w", err) + return + } + + return &uid, &gid, nil +} + +func hostIDFromMapping(mapping []*runtimeapi.IDMapping, containerId *int64) (int64, error) { + if len(mapping) == 0 { + return 0, fmt.Errorf("can't use empty user namespace mapping") + } + + // If none is requested, root inside the container is used + id := int64(0) + if containerId != nil { + id = *containerId + } + + for _, m := range mapping { + if m == nil { + continue + } + + firstId := int64(m.ContainerId) + lastId := firstId + int64(m.Length) - 1 + + // The id we are looking for is in the range + if id >= firstId && id <= lastId { + // Return the host id for this container id + return int64(m.HostId) + id - firstId, nil + } + } + + return 0, fmt.Errorf("ID: %v not present in pod user namespace", id) +} diff --git a/pkg/kubelet/userns_manager_test.go b/pkg/kubelet/userns_manager_test.go index a445a40cde3..c3752426551 100644 --- a/pkg/kubelet/userns_manager_test.go +++ b/pkg/kubelet/userns_manager_test.go @@ -25,6 +25,7 @@ import ( "k8s.io/apimachinery/pkg/types" utilfeature "k8s.io/apiserver/pkg/util/feature" featuregatetesting "k8s.io/component-base/featuregate/testing" + runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" pkgfeatures "k8s.io/kubernetes/pkg/features" ) @@ -176,6 +177,136 @@ func TestUserNsManagerParseUserNsFile(t *testing.T) { } } +func TestUserNsManagerHostIDFromMapping(t *testing.T) { + // mapping []*runtimeapi.IDMapping, containerId *int64 + + cases := []struct { + name string + success bool + containerId int64 // -1 means a nil ptr will be used. + expHostId int64 + m []*runtimeapi.IDMapping + }{ + { + name: "one basic mapping", + success: true, + containerId: -1, + expHostId: 0, + m: []*runtimeapi.IDMapping{ + { + HostId: 0, + ContainerId: 0, + Length: userNsLength, + }, + }, + }, + { + name: "one unprivileged mapping", + success: true, + containerId: -1, + expHostId: userNsLength * 2, + m: []*runtimeapi.IDMapping{ + { + HostId: userNsLength * 2, + ContainerId: 0, + Length: userNsLength, + }, + }, + }, + { + name: "one unprivileged mapping random id", + success: true, + containerId: 3, + expHostId: userNsLength*2 + 3, + m: []*runtimeapi.IDMapping{ + { + HostId: userNsLength * 2, + ContainerId: 0, + Length: userNsLength, + }, + }, + }, + { + name: "two unprivileged mapping", + success: true, + containerId: 0, + expHostId: userNsLength*2 + 0, + m: []*runtimeapi.IDMapping{ + { + HostId: userNsLength * 2, + ContainerId: 0, + Length: 1, + }, + { + HostId: userNsLength*2 + 10, + ContainerId: 1, + Length: 1, + }, + }, + }, + { + name: "two unprivileged mapping - random id", + success: true, + containerId: 1, + expHostId: userNsLength*2 + 10, + m: []*runtimeapi.IDMapping{ + { + HostId: userNsLength * 2, + ContainerId: 0, + Length: 1, + }, + { + HostId: userNsLength*2 + 10, + ContainerId: 1, + Length: 1, + }, + }, + }, + { + name: "two unprivileged mapping - not mapped user", + success: false, + containerId: 3, + m: []*runtimeapi.IDMapping{ + { + HostId: userNsLength * 2, + ContainerId: 0, + Length: 1, + }, + { + HostId: userNsLength*2 + 1, + ContainerId: 1, + Length: 1, + }, + }, + }, + { + name: "no mappings", + success: false, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + var containerId *int64 + if tc.containerId != -1 { + containerId = &tc.containerId + } + + id, err := hostIDFromMapping(tc.m, containerId) + if (tc.success && err != nil) || (!tc.success && err == nil) { + t.Fatalf("%v: expected success: %v - got error: %v", tc.name, tc.success, err) + } + if !tc.success && err != nil { + return + } + + if id != tc.expHostId { + t.Errorf("expected: %v - got: %v", tc.expHostId, id) + } + }) + } +} + func BenchmarkBitmaskFindAndSetFirstZero(t *testing.B) { b := makeBitArray(userNsLength) for i := 0; i < userNsLength; i++ { diff --git a/pkg/kubelet/volume_host.go b/pkg/kubelet/volume_host.go index fe880506b2b..9c771c08635 100644 --- a/pkg/kubelet/volume_host.go +++ b/pkg/kubelet/volume_host.go @@ -128,6 +128,16 @@ func (kvh *kubeletVolumeHost) GetPodsDir() string { return kvh.kubelet.getPodsDir() } +// GetHostIDsForPod if the pod uses user namespaces, takes the uid and gid +// inside the container and returns the host UID and GID those are mapped to on +// the host. If containerUID/containerGID is nil, then it returns the host +// UID/GID for ID 0 inside the container. +// If the pod is not using user namespaces, as there is no mapping needed, the +// same containerUID and containerGID params are returned. +func (kvh *kubeletVolumeHost) GetHostIDsForPod(pod *v1.Pod, containerUID, containerGID *int64) (hostUID, hostGID *int64, err error) { + return kvh.kubelet.getHostIDsForPod(pod, containerUID, containerGID) +} + func (kvh *kubeletVolumeHost) GetPodVolumeDir(podUID types.UID, pluginName string, volumeName string) string { dir := kvh.kubelet.getPodVolumeDir(podUID, pluginName, volumeName) if runtime.GOOS == "windows" { diff --git a/pkg/volume/plugins.go b/pkg/volume/plugins.go index 6b40a59dd35..5fd44568a01 100644 --- a/pkg/volume/plugins.go +++ b/pkg/volume/plugins.go @@ -340,6 +340,13 @@ type KubeletVolumeHost interface { WaitForCacheSync() error // Returns hostutil.HostUtils GetHostUtil() hostutil.HostUtils + // GetHostIDsForPod if the pod uses user namespaces, takes the uid and + // gid inside the container and returns the host UID and GID those are + // mapped to on the host. If containerUID/containerGID is nil, then it + // returns the host UID/GID for ID 0 inside the container. + // If the pod is not using user namespaces, as there is no mapping needed, the + // same containerUID and containerGID params are returned. + GetHostIDsForPod(pod *v1.Pod, containerUID, containerGID *int64) (hostUID, hostGID *int64, err error) } // AttachDetachVolumeHost is a AttachDetach Controller specific interface that plugins can use diff --git a/pkg/volume/testing/volume_host.go b/pkg/volume/testing/volume_host.go index e422acfdcf9..dd0e6c227c9 100644 --- a/pkg/volume/testing/volume_host.go +++ b/pkg/volume/testing/volume_host.go @@ -118,6 +118,10 @@ func (f *fakeVolumeHost) GetPodsDir() string { return filepath.Join(f.rootDir, "pods") } +func (f *fakeVolumeHost) GetHostIDsForPod(pod *v1.Pod, containerUID, containerGID *int64) (hostUID, hostGID *int64, err error) { + return containerUID, containerGID, nil +} + func (f *fakeVolumeHost) GetPodVolumeDir(podUID types.UID, pluginName, volumeName string) string { return filepath.Join(f.rootDir, "pods", string(podUID), "volumes", pluginName, volumeName) }