k8s.io/kubernetes@v1.29.3/pkg/kubelet/userns/userns_manager_test.go (about)

     1  /*
     2  Copyright 2022 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package userns
    18  
    19  import (
    20  	"fmt"
    21  	"testing"
    22  
    23  	"github.com/stretchr/testify/assert"
    24  	"github.com/stretchr/testify/require"
    25  	v1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/types"
    28  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    29  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    30  	runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
    31  	pkgfeatures "k8s.io/kubernetes/pkg/features"
    32  	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
    33  )
    34  
    35  type testUserNsPodsManager struct {
    36  	podDir  string
    37  	podList []types.UID
    38  }
    39  
    40  func (m *testUserNsPodsManager) GetPodDir(podUID types.UID) string {
    41  	if m.podDir == "" {
    42  		return "/tmp/non-existant-dir.This-is-not-used-in-tests"
    43  	}
    44  	return m.podDir
    45  }
    46  
    47  func (m *testUserNsPodsManager) ListPodsFromDisk() ([]types.UID, error) {
    48  	if len(m.podList) == 0 {
    49  		return nil, nil
    50  	}
    51  	return m.podList, nil
    52  }
    53  
    54  func TestUserNsManagerAllocate(t *testing.T) {
    55  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true)()
    56  
    57  	testUserNsPodsManager := &testUserNsPodsManager{}
    58  	m, err := MakeUserNsManager(testUserNsPodsManager)
    59  	require.NoError(t, err)
    60  
    61  	assert.Equal(t, true, m.isSet(0*65536), "m.isSet(0) should be true")
    62  
    63  	allocated, length, err := m.allocateOne("one")
    64  	assert.NoError(t, err)
    65  	assert.Equal(t, userNsLength, int(length), "m.isSet(%d).length=%v", allocated, length)
    66  	assert.Equal(t, true, m.isSet(allocated), "m.isSet(%d)", allocated)
    67  
    68  	allocated2, length2, err := m.allocateOne("two")
    69  	assert.NoError(t, err)
    70  	assert.NotEqual(t, allocated, allocated2, "allocated != allocated2")
    71  	assert.Equal(t, length, length2, "length == length2")
    72  
    73  	// verify that re-adding the same pod with the same settings won't fail
    74  	err = m.record("two", allocated2, length2)
    75  	assert.NoError(t, err)
    76  	// but it fails if anyting is different
    77  	err = m.record("two", allocated2+1, length2)
    78  	assert.Error(t, err)
    79  
    80  	m.Release("one")
    81  	m.Release("two")
    82  	assert.Equal(t, false, m.isSet(allocated), "m.isSet(%d)", allocated)
    83  	assert.Equal(t, false, m.isSet(allocated2), "m.nsSet(%d)", allocated2)
    84  
    85  	var allocs []uint32
    86  	for i := 0; i < 1000; i++ {
    87  		allocated, length, err = m.allocateOne(types.UID(fmt.Sprintf("%d", i)))
    88  		assert.Equal(t, userNsLength, int(length), "length is not the expected. iter: %v", i)
    89  		assert.NoError(t, err)
    90  		allocs = append(allocs, allocated)
    91  	}
    92  	for i, v := range allocs {
    93  		assert.Equal(t, true, m.isSet(v), "m.isSet(%d) should be true", v)
    94  		m.Release(types.UID(fmt.Sprintf("%d", i)))
    95  		assert.Equal(t, false, m.isSet(v), "m.isSet(%d) should be false", v)
    96  
    97  		err = m.record(types.UID(fmt.Sprintf("%d", i)), v, userNsLength)
    98  		assert.NoError(t, err)
    99  		m.Release(types.UID(fmt.Sprintf("%d", i)))
   100  		assert.Equal(t, false, m.isSet(v), "m.isSet(%d) should be false", v)
   101  	}
   102  }
   103  
   104  func TestUserNsManagerParseUserNsFile(t *testing.T) {
   105  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true)()
   106  
   107  	cases := []struct {
   108  		name    string
   109  		file    string
   110  		success bool
   111  	}{
   112  		{
   113  			name: "basic",
   114  			file: `{
   115  	                        "uidMappings":[ { "hostId":131072, "containerId":0, "length":65536 } ],
   116  	                        "gidMappings":[ { "hostId":131072, "containerId":0, "length":65536 } ]
   117                                 }`,
   118  			success: true,
   119  		},
   120  		{
   121  			name: "invalid length",
   122  			file: `{
   123  	                        "uidMappings":[ { "hostId":131072, "containerId":0, "length":0 } ],
   124  	                        "gidMappings":[ { "hostId":131072, "containerId":0, "length":0 } ]
   125                                 }`,
   126  			success: false,
   127  		},
   128  		{
   129  			name: "wrong offset",
   130  			file: `{
   131  	                        "uidMappings":[ {"hostId":131072, "containerId":0, "length":65536 } ],
   132  	                        "gidMappings":[ {"hostId":1, "containerId":0, "length":65536 } ]
   133                                 }`,
   134  			success: false,
   135  		},
   136  		{
   137  			name: "two GID mappings",
   138  			file: `{
   139  	                        "uidMappings":[ { "hostId":131072, "containerId":0, "length":userNsLength } ],
   140  	                        "gidMappings":[ { "hostId":131072, "containerId":0, "length":userNsLength }, { "hostId":196608, "containerId":0, "length":65536 } ]
   141                                 }`,
   142  			success: false,
   143  		},
   144  		{
   145  			name: "two UID mappings",
   146  			file: `{
   147  	                        "uidMappings":[ { "hostId":131072, "containerId":0, "length":65536 }, { "hostId":196608, "containerId":0, "length":65536 } ],
   148  	                        "gidMappings":[ { "hostId":131072, "containerId":0, "length":65536 } ]
   149                                 }`,
   150  			success: false,
   151  		},
   152  		{
   153  			name: "no root UID",
   154  			file: `{
   155  	                        "uidMappings":[ { "hostId":131072, "containerId":1, "length":65536 } ],
   156  	                        "gidMappings":[ { "hostId":131072, "containerId":0, "length":65536 } ]
   157                                 }`,
   158  			success: false,
   159  		},
   160  		{
   161  			name: "no root GID",
   162  			file: `{
   163  	                        "uidMappings":[ { "hostId":131072, "containerId":0, "length":65536 } ],
   164  	                        "gidMappings":[ { "hostId":131072, "containerId":1, "length":65536 } ]
   165                                 }`,
   166  			success: false,
   167  		},
   168  	}
   169  
   170  	testUserNsPodsManager := &testUserNsPodsManager{}
   171  	m, err := MakeUserNsManager(testUserNsPodsManager)
   172  	assert.NoError(t, err)
   173  
   174  	for _, tc := range cases {
   175  		t.Run(tc.name, func(t *testing.T) {
   176  			// We don't validate the result. It was parsed with the json parser, we trust that.
   177  			_, err = m.parseUserNsFileAndRecord(types.UID(tc.name), []byte(tc.file))
   178  			if (tc.success && err == nil) || (!tc.success && err != nil) {
   179  				return
   180  			}
   181  
   182  			t.Errorf("expected success: %v but got error: %v", tc.success, err)
   183  		})
   184  	}
   185  }
   186  
   187  func TestGetOrCreateUserNamespaceMappings(t *testing.T) {
   188  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true)()
   189  
   190  	trueVal := true
   191  	falseVal := false
   192  
   193  	cases := []struct {
   194  		name    string
   195  		pod     *v1.Pod
   196  		expMode runtimeapi.NamespaceMode
   197  		success bool
   198  	}{
   199  		{
   200  			name:    "no user namespace",
   201  			pod:     &v1.Pod{},
   202  			expMode: runtimeapi.NamespaceMode_NODE,
   203  			success: true,
   204  		},
   205  		{
   206  			name: "opt-in to host user namespace",
   207  			pod: &v1.Pod{
   208  				Spec: v1.PodSpec{
   209  					HostUsers: &trueVal,
   210  				},
   211  			},
   212  			expMode: runtimeapi.NamespaceMode_NODE,
   213  			success: true,
   214  		},
   215  		{
   216  			name: "user namespace",
   217  			pod: &v1.Pod{
   218  				Spec: v1.PodSpec{
   219  					HostUsers: &falseVal,
   220  				},
   221  			},
   222  			expMode: runtimeapi.NamespaceMode_POD,
   223  			success: true,
   224  		},
   225  	}
   226  
   227  	for _, tc := range cases {
   228  		t.Run(tc.name, func(t *testing.T) {
   229  			// These tests will create the userns file, so use an existing podDir.
   230  			testUserNsPodsManager := &testUserNsPodsManager{podDir: t.TempDir()}
   231  			m, err := MakeUserNsManager(testUserNsPodsManager)
   232  			assert.NoError(t, err)
   233  
   234  			userns, err := m.GetOrCreateUserNamespaceMappings(tc.pod)
   235  			if (tc.success && err != nil) || (!tc.success && err == nil) {
   236  				t.Errorf("expected success: %v but got error: %v", tc.success, err)
   237  			}
   238  
   239  			if userns.GetMode() != tc.expMode {
   240  				t.Errorf("expected mode: %v but got: %v", tc.expMode, userns.GetMode())
   241  			}
   242  		})
   243  	}
   244  }
   245  
   246  func TestCleanupOrphanedPodUsernsAllocations(t *testing.T) {
   247  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true)()
   248  
   249  	cases := []struct {
   250  		name                 string
   251  		runningPods          []*kubecontainer.Pod
   252  		pods                 []*v1.Pod
   253  		listPods             []types.UID /* pods to list */
   254  		podSetBeforeCleanup  []types.UID /* pods to record before cleanup */
   255  		podSetAfterCleanup   []types.UID /* pods set expected after cleanup */
   256  		podUnsetAfterCleanup []types.UID /* pods set expected after cleanup */
   257  	}{
   258  		{
   259  			name:     "no stale pods",
   260  			listPods: []types.UID{"pod-1", "pod-2"},
   261  		},
   262  		{
   263  			name:                 "no stale pods set",
   264  			podSetBeforeCleanup:  []types.UID{"pod-1", "pod-2"},
   265  			listPods:             []types.UID{"pod-1", "pod-2"},
   266  			podUnsetAfterCleanup: []types.UID{"pod-1", "pod-2"},
   267  		},
   268  		{
   269  			name:                 "one running pod",
   270  			listPods:             []types.UID{"pod-1", "pod-2"},
   271  			podSetBeforeCleanup:  []types.UID{"pod-1", "pod-2"},
   272  			runningPods:          []*kubecontainer.Pod{{ID: "pod-1"}},
   273  			podSetAfterCleanup:   []types.UID{"pod-1"},
   274  			podUnsetAfterCleanup: []types.UID{"pod-2"},
   275  		},
   276  		{
   277  			name:                 "pod set before cleanup but not listed ==> unset",
   278  			podSetBeforeCleanup:  []types.UID{"pod-1", "pod-2"},
   279  			runningPods:          []*kubecontainer.Pod{{ID: "pod-1"}},
   280  			podUnsetAfterCleanup: []types.UID{"pod-1", "pod-2"},
   281  		},
   282  		{
   283  			name:                 "one pod",
   284  			listPods:             []types.UID{"pod-1", "pod-2"},
   285  			podSetBeforeCleanup:  []types.UID{"pod-1", "pod-2"},
   286  			pods:                 []*v1.Pod{{ObjectMeta: metav1.ObjectMeta{UID: "pod-1"}}},
   287  			podSetAfterCleanup:   []types.UID{"pod-1"},
   288  			podUnsetAfterCleanup: []types.UID{"pod-2"},
   289  		},
   290  		{
   291  			name:                 "no listed pods ==> all unset",
   292  			podSetBeforeCleanup:  []types.UID{"pod-1", "pod-2"},
   293  			podUnsetAfterCleanup: []types.UID{"pod-1", "pod-2"},
   294  		},
   295  	}
   296  
   297  	for _, tc := range cases {
   298  		t.Run(tc.name, func(t *testing.T) {
   299  			testUserNsPodsManager := &testUserNsPodsManager{
   300  				podList: tc.listPods,
   301  			}
   302  			m, err := MakeUserNsManager(testUserNsPodsManager)
   303  			require.NoError(t, err)
   304  
   305  			// Record the userns range as used
   306  			for i, pod := range tc.podSetBeforeCleanup {
   307  				err := m.record(pod, uint32((i+1)*65536), 65536)
   308  				require.NoError(t, err)
   309  			}
   310  
   311  			err = m.CleanupOrphanedPodUsernsAllocations(tc.pods, tc.runningPods)
   312  			require.NoError(t, err)
   313  
   314  			for _, pod := range tc.podSetAfterCleanup {
   315  				ok := m.podAllocated(pod)
   316  				assert.True(t, ok, "pod %q should be allocated", pod)
   317  			}
   318  
   319  			for _, pod := range tc.podUnsetAfterCleanup {
   320  				ok := m.podAllocated(pod)
   321  				assert.False(t, ok, "pod %q should not be allocated", pod)
   322  			}
   323  		})
   324  	}
   325  }
   326  
   327  func TestAllocateMaxPods(t *testing.T) {
   328  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true)()
   329  
   330  	testUserNsPodsManager := &testUserNsPodsManager{}
   331  	m, err := MakeUserNsManager(testUserNsPodsManager)
   332  	require.NoError(t, err)
   333  
   334  	// The first maxPods allocations should succeed.
   335  	for i := 0; i < maxPods; i++ {
   336  		_, _, err = m.allocateOne(types.UID(fmt.Sprintf("%d", i)))
   337  		require.NoError(t, err)
   338  	}
   339  
   340  	// The next allocation should fail, hitting maxPods.
   341  	_, _, err = m.allocateOne(types.UID(fmt.Sprintf("%d", maxPods+1)))
   342  	assert.Error(t, err)
   343  }
   344  
   345  func TestRecordMaxPods(t *testing.T) {
   346  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true)()
   347  
   348  	testUserNsPodsManager := &testUserNsPodsManager{}
   349  	m, err := MakeUserNsManager(testUserNsPodsManager)
   350  	require.NoError(t, err)
   351  
   352  	// The first maxPods allocations should succeed.
   353  	for i := 0; i < maxPods; i++ {
   354  		err = m.record(types.UID(fmt.Sprintf("%d", i)), uint32((i+1)*65536), 65536)
   355  		require.NoError(t, err)
   356  	}
   357  
   358  	// The next allocation should fail, hitting maxPods.
   359  	err = m.record(types.UID(fmt.Sprintf("%d", maxPods+1)), uint32((maxPods+1)*65536), 65536)
   360  	assert.Error(t, err)
   361  }