kubelet: add GetHostIDsForPod()

In future commits we will need this to set the user/group of supported
volumes of KEP 127 - Phase 1.

Signed-off-by: Rodrigo Campos <rodrigoca@microsoft.com>
This commit is contained in:
Rodrigo Campos 2022-07-07 16:51:55 +02:00
parent 9b2fc639a0
commit d07c2688fe
9 changed files with 233 additions and 0 deletions

View File

@ -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 ""
}

View File

@ -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 ""
}

View File

@ -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 ""
}

View File

@ -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) {

View File

@ -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)
}

View File

@ -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++ {

View File

@ -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" {

View File

@ -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

View File

@ -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)
}