From 6f81aa6aa9edb9b1f6ba3a5d52ee3197454552f2 Mon Sep 17 00:00:00 2001 From: Rodrigo Campos Date: Fri, 15 Mar 2024 16:37:26 +0100 Subject: [PATCH] pkg/kubelet/kuberuntime: Add userns tests for NamespacesForPod Signed-off-by: Rodrigo Campos --- .../container/testing/fake_runtime_helper.go | 37 ++++++- pkg/kubelet/kuberuntime/util/util_test.go | 101 +++++++++++++++++- 2 files changed, 132 insertions(+), 6 deletions(-) diff --git a/pkg/kubelet/container/testing/fake_runtime_helper.go b/pkg/kubelet/container/testing/fake_runtime_helper.go index f52ec20a965..e6eb3936217 100644 --- a/pkg/kubelet/container/testing/fake_runtime_helper.go +++ b/pkg/kubelet/container/testing/fake_runtime_helper.go @@ -18,10 +18,13 @@ package testing import ( "context" + "fmt" v1 "k8s.io/api/core/v1" kubetypes "k8s.io/apimachinery/pkg/types" + utilfeature "k8s.io/apiserver/pkg/util/feature" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" + "k8s.io/kubernetes/pkg/features" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" ) @@ -33,6 +36,7 @@ type FakeRuntimeHelper struct { HostName string HostDomain string PodContainerDir string + RuntimeHandlers map[string]kubecontainer.RuntimeHandler Err error } @@ -69,7 +73,38 @@ func (f *FakeRuntimeHelper) GetExtraSupplementalGroupsForPod(pod *v1.Pod) []int6 } func (f *FakeRuntimeHelper) GetOrCreateUserNamespaceMappings(pod *v1.Pod, runtimeHandler string) (*runtimeapi.UserNamespace, error) { - return nil, nil + featureEnabled := utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesSupport) + if pod == nil || pod.Spec.HostUsers == nil { + return nil, nil + } + // pod.Spec.HostUsers is set to true/false + if !featureEnabled { + return nil, fmt.Errorf("the feature gate %q is disabled: can't set spec.HostUsers", features.UserNamespacesSupport) + } + if *pod.Spec.HostUsers { + return nil, nil + } + + // From here onwards, hostUsers=false and the feature gate is enabled. + + // if the pod requested a user namespace and the runtime doesn't support user namespaces then return an error. + if h, ok := f.RuntimeHandlers[runtimeHandler]; !ok { + return nil, fmt.Errorf("RuntimeClass handler %q not found", runtimeHandler) + } else if !h.SupportsUserNamespaces { + return nil, fmt.Errorf("RuntimeClass handler %q does not support user namespaces", runtimeHandler) + } + + ids := &runtimeapi.IDMapping{ + HostId: 65536, + ContainerId: 0, + Length: 65536, + } + + return &runtimeapi.UserNamespace{ + Mode: runtimeapi.NamespaceMode_POD, + Uids: []*runtimeapi.IDMapping{ids}, + Gids: []*runtimeapi.IDMapping{ids}, + }, nil } func (f *FakeRuntimeHelper) PrepareDynamicResources(pod *v1.Pod) error { diff --git a/pkg/kubelet/kuberuntime/util/util_test.go b/pkg/kubelet/kuberuntime/util/util_test.go index eeaca1fcc56..b067781cf58 100644 --- a/pkg/kubelet/kuberuntime/util/util_test.go +++ b/pkg/kubelet/kuberuntime/util/util_test.go @@ -22,7 +22,10 @@ import ( "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" + 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" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" kubecontainertest "k8s.io/kubernetes/pkg/kubelet/container/testing" ) @@ -162,10 +165,25 @@ func TestPodSandboxChanged(t *testing.T) { } } +type fakeRuntimeHandlerResolver struct{} + +func (*fakeRuntimeHandlerResolver) LookupRuntimeHandler(s *string) (string, error) { + return "", nil +} + func TestNamespacesForPod(t *testing.T) { + usernsIDs := &runtimeapi.IDMapping{ + HostId: 65536, + ContainerId: 0, + Length: 65536, + } + for desc, test := range map[string]struct { - input *v1.Pod - expected *runtimeapi.NamespaceOption + input *v1.Pod + runtimeHandlers map[string]kubecontainer.RuntimeHandler + usernsEnabled bool + expected *runtimeapi.NamespaceOption + expErr bool }{ "nil pod -> default v1 namespaces": { input: nil, @@ -221,11 +239,84 @@ func TestNamespacesForPod(t *testing.T) { Pid: runtimeapi.NamespaceMode_CONTAINER, }, }, + "hostUsers: false and feature enabled": { + input: &v1.Pod{ + Spec: v1.PodSpec{ + HostUsers: &[]bool{false}[0], + }, + }, + usernsEnabled: true, + runtimeHandlers: map[string]kubecontainer.RuntimeHandler{ + "": { + SupportsUserNamespaces: true, + }, + }, + expected: &runtimeapi.NamespaceOption{ + Ipc: runtimeapi.NamespaceMode_POD, + Network: runtimeapi.NamespaceMode_POD, + Pid: runtimeapi.NamespaceMode_CONTAINER, + UsernsOptions: &runtimeapi.UserNamespace{ + Mode: runtimeapi.NamespaceMode_POD, + Uids: []*runtimeapi.IDMapping{usernsIDs}, + Gids: []*runtimeapi.IDMapping{usernsIDs}, + }, + }, + }, + // The hostUsers field can't be set to any value if the feature is disabled. + "hostUsers: false and feature disabled --> error": { + input: &v1.Pod{ + Spec: v1.PodSpec{ + HostUsers: &[]bool{false}[0], + }, + }, + usernsEnabled: false, + expErr: true, + }, + // The hostUsers field can't be set to any value if the feature is disabled. + "hostUsers: true and feature disabled --> error": { + input: &v1.Pod{ + Spec: v1.PodSpec{ + HostUsers: &[]bool{true}[0], + }, + }, + usernsEnabled: false, + expErr: true, + }, + "error if runtime handler not found": { + input: &v1.Pod{ + Spec: v1.PodSpec{ + HostUsers: &[]bool{false}[0], + }, + }, + usernsEnabled: true, + runtimeHandlers: map[string]kubecontainer.RuntimeHandler{ + "other": {}, + }, + expErr: true, + }, + "error if runtime handler does not support userns": { + input: &v1.Pod{ + Spec: v1.PodSpec{ + HostUsers: &[]bool{false}[0], + }, + }, + usernsEnabled: true, + expErr: true, + }, } { t.Run(desc, func(t *testing.T) { - actual, err := NamespacesForPod(test.input, &kubecontainertest.FakeRuntimeHelper{}, nil) - require.NoError(t, err) - require.Equal(t, test.expected, actual) + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, test.usernsEnabled) + + fakeRuntimeHelper := kubecontainertest.FakeRuntimeHelper{ + RuntimeHandlers: test.runtimeHandlers, + } + actual, err := NamespacesForPod(test.input, &fakeRuntimeHelper, &fakeRuntimeHandlerResolver{}) + if test.expErr { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, test.expected, actual) + } }) } }