k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/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 "errors" 21 "fmt" 22 "os" 23 goruntime "runtime" 24 "testing" 25 26 "github.com/stretchr/testify/assert" 27 "github.com/stretchr/testify/require" 28 v1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/types" 31 utilfeature "k8s.io/apiserver/pkg/util/feature" 32 featuregatetesting "k8s.io/component-base/featuregate/testing" 33 runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" 34 pkgfeatures "k8s.io/kubernetes/pkg/features" 35 kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" 36 ) 37 38 const ( 39 // skip the first block 40 minimumMappingUID = userNsLength 41 // allocate enough space for 2000 user namespaces 42 mappingLen = userNsLength * 2000 43 testMaxPods = 110 44 ) 45 46 type testUserNsPodsManager struct { 47 podDir string 48 podList []types.UID 49 userns bool 50 maxPods int 51 mappingFirstID uint32 52 mappingLen uint32 53 } 54 55 func (m *testUserNsPodsManager) GetPodDir(podUID types.UID) string { 56 if m.podDir == "" { 57 return "/tmp/non-existant-dir.This-is-not-used-in-tests" 58 } 59 return m.podDir 60 } 61 62 func (m *testUserNsPodsManager) ListPodsFromDisk() ([]types.UID, error) { 63 if len(m.podList) == 0 { 64 return nil, nil 65 } 66 return m.podList, nil 67 } 68 69 func (m *testUserNsPodsManager) HandlerSupportsUserNamespaces(runtimeHandler string) (bool, error) { 70 if runtimeHandler == "error" { 71 return false, errors.New("unknown runtime") 72 } 73 return m.userns, nil 74 } 75 76 func (m *testUserNsPodsManager) GetKubeletMappings() (uint32, uint32, error) { 77 if m.mappingFirstID != 0 { 78 return m.mappingFirstID, m.mappingLen, nil 79 } 80 return minimumMappingUID, mappingLen, nil 81 } 82 83 func (m *testUserNsPodsManager) GetMaxPods() int { 84 if m.maxPods != 0 { 85 return m.maxPods 86 } 87 88 return testMaxPods 89 } 90 91 func TestUserNsManagerAllocate(t *testing.T) { 92 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true) 93 94 testUserNsPodsManager := &testUserNsPodsManager{} 95 m, err := MakeUserNsManager(testUserNsPodsManager) 96 require.NoError(t, err) 97 98 allocated, length, err := m.allocateOne("one") 99 assert.NoError(t, err) 100 assert.Equal(t, userNsLength, int(length), "m.isSet(%d).length=%v", allocated, length) 101 assert.Equal(t, true, m.isSet(allocated), "m.isSet(%d)", allocated) 102 103 allocated2, length2, err := m.allocateOne("two") 104 assert.NoError(t, err) 105 assert.NotEqual(t, allocated, allocated2, "allocated != allocated2") 106 assert.Equal(t, length, length2, "length == length2") 107 108 // verify that re-adding the same pod with the same settings won't fail 109 err = m.record("two", allocated2, length2) 110 assert.NoError(t, err) 111 // but it fails if anyting is different 112 err = m.record("two", allocated2+1, length2) 113 assert.Error(t, err) 114 115 m.Release("one") 116 m.Release("two") 117 assert.Equal(t, false, m.isSet(allocated), "m.isSet(%d)", allocated) 118 assert.Equal(t, false, m.isSet(allocated2), "m.nsSet(%d)", allocated2) 119 120 var allocs []uint32 121 for i := 0; i < 1000; i++ { 122 allocated, length, err = m.allocateOne(types.UID(fmt.Sprintf("%d", i))) 123 assert.Equal(t, userNsLength, int(length), "length is not the expected. iter: %v", i) 124 assert.NoError(t, err) 125 assert.True(t, allocated >= minimumMappingUID) 126 // The last ID of the userns range (allocated+userNsLength) should be within bounds. 127 assert.True(t, allocated <= minimumMappingUID+mappingLen-userNsLength) 128 allocs = append(allocs, allocated) 129 } 130 for i, v := range allocs { 131 assert.Equal(t, true, m.isSet(v), "m.isSet(%d) should be true", v) 132 m.Release(types.UID(fmt.Sprintf("%d", i))) 133 assert.Equal(t, false, m.isSet(v), "m.isSet(%d) should be false", v) 134 135 err = m.record(types.UID(fmt.Sprintf("%d", i)), v, userNsLength) 136 assert.NoError(t, err) 137 m.Release(types.UID(fmt.Sprintf("%d", i))) 138 assert.Equal(t, false, m.isSet(v), "m.isSet(%d) should be false", v) 139 } 140 } 141 142 func TestMakeUserNsManager(t *testing.T) { 143 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true) 144 145 cases := []struct { 146 name string 147 mappingFirstID uint32 148 mappingLen uint32 149 maxPods int 150 success bool 151 }{ 152 { 153 name: "default", 154 success: true, 155 }, 156 { 157 name: "firstID not multiple", 158 mappingFirstID: 65536 + 1, 159 }, 160 { 161 name: "firstID is less than 65535", 162 mappingFirstID: 1, 163 }, 164 { 165 name: "mappingLen not multiple", 166 mappingFirstID: 65536, 167 mappingLen: 65536 + 1, 168 }, 169 { 170 name: "range can't fit maxPods", 171 mappingFirstID: 65536, 172 mappingLen: 65536, 173 maxPods: 2, 174 }, 175 } 176 177 for _, tc := range cases { 178 t.Run(tc.name, func(t *testing.T) { 179 testUserNsPodsManager := &testUserNsPodsManager{ 180 podDir: t.TempDir(), 181 mappingFirstID: tc.mappingFirstID, 182 mappingLen: tc.mappingLen, 183 maxPods: tc.maxPods, 184 } 185 _, err := MakeUserNsManager(testUserNsPodsManager) 186 187 if tc.success { 188 assert.NoError(t, err) 189 } else { 190 assert.Error(t, err) 191 } 192 }) 193 } 194 } 195 196 func TestUserNsManagerParseUserNsFile(t *testing.T) { 197 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true) 198 199 cases := []struct { 200 name string 201 file string 202 success bool 203 }{ 204 { 205 name: "basic", 206 file: `{ 207 "uidMappings":[ { "hostId":131072, "containerId":0, "length":65536 } ], 208 "gidMappings":[ { "hostId":131072, "containerId":0, "length":65536 } ] 209 }`, 210 success: true, 211 }, 212 { 213 name: "invalid length", 214 file: `{ 215 "uidMappings":[ { "hostId":131072, "containerId":0, "length":0 } ], 216 "gidMappings":[ { "hostId":131072, "containerId":0, "length":0 } ] 217 }`, 218 success: false, 219 }, 220 { 221 name: "wrong offset", 222 file: `{ 223 "uidMappings":[ {"hostId":131072, "containerId":0, "length":65536 } ], 224 "gidMappings":[ {"hostId":1, "containerId":0, "length":65536 } ] 225 }`, 226 success: false, 227 }, 228 { 229 name: "two GID mappings", 230 file: `{ 231 "uidMappings":[ { "hostId":131072, "containerId":0, "length":userNsLength } ], 232 "gidMappings":[ { "hostId":131072, "containerId":0, "length":userNsLength }, { "hostId":196608, "containerId":0, "length":65536 } ] 233 }`, 234 success: false, 235 }, 236 { 237 name: "two UID mappings", 238 file: `{ 239 "uidMappings":[ { "hostId":131072, "containerId":0, "length":65536 }, { "hostId":196608, "containerId":0, "length":65536 } ], 240 "gidMappings":[ { "hostId":131072, "containerId":0, "length":65536 } ] 241 }`, 242 success: false, 243 }, 244 { 245 name: "no root UID", 246 file: `{ 247 "uidMappings":[ { "hostId":131072, "containerId":1, "length":65536 } ], 248 "gidMappings":[ { "hostId":131072, "containerId":0, "length":65536 } ] 249 }`, 250 success: false, 251 }, 252 { 253 name: "no root GID", 254 file: `{ 255 "uidMappings":[ { "hostId":131072, "containerId":0, "length":65536 } ], 256 "gidMappings":[ { "hostId":131072, "containerId":1, "length":65536 } ] 257 }`, 258 success: false, 259 }, 260 } 261 262 testUserNsPodsManager := &testUserNsPodsManager{} 263 m, err := MakeUserNsManager(testUserNsPodsManager) 264 assert.NoError(t, err) 265 266 for _, tc := range cases { 267 t.Run(tc.name, func(t *testing.T) { 268 // We don't validate the result. It was parsed with the json parser, we trust that. 269 _, err = m.parseUserNsFileAndRecord(types.UID(tc.name), []byte(tc.file)) 270 if (tc.success && err == nil) || (!tc.success && err != nil) { 271 return 272 } 273 274 t.Errorf("expected success: %v but got error: %v", tc.success, err) 275 }) 276 } 277 } 278 279 func TestGetOrCreateUserNamespaceMappings(t *testing.T) { 280 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true) 281 282 trueVal := true 283 falseVal := false 284 285 cases := []struct { 286 name string 287 pod *v1.Pod 288 expMode runtimeapi.NamespaceMode 289 runtimeUserns bool 290 runtimeHandler string 291 success bool 292 skipOnWindows bool 293 }{ 294 { 295 name: "no user namespace", 296 pod: &v1.Pod{}, 297 expMode: runtimeapi.NamespaceMode_NODE, 298 success: true, 299 }, 300 { 301 name: "nil pod", 302 pod: nil, 303 expMode: runtimeapi.NamespaceMode_NODE, 304 success: true, 305 }, 306 { 307 name: "opt-in to host user namespace", 308 pod: &v1.Pod{ 309 Spec: v1.PodSpec{ 310 HostUsers: &trueVal, 311 }, 312 }, 313 expMode: runtimeapi.NamespaceMode_NODE, 314 success: true, 315 }, 316 { 317 name: "user namespace", 318 pod: &v1.Pod{ 319 Spec: v1.PodSpec{ 320 HostUsers: &falseVal, 321 }, 322 }, 323 expMode: runtimeapi.NamespaceMode_POD, 324 runtimeUserns: true, 325 success: true, 326 skipOnWindows: true, 327 }, 328 { 329 name: "user namespace, but no runtime support", 330 pod: &v1.Pod{ 331 Spec: v1.PodSpec{ 332 HostUsers: &falseVal, 333 }, 334 }, 335 runtimeUserns: false, 336 }, 337 { 338 name: "user namespace, but runtime returns error", 339 pod: &v1.Pod{ 340 Spec: v1.PodSpec{ 341 HostUsers: &falseVal, 342 }, 343 }, 344 // This handler name makes the fake runtime return an error. 345 runtimeHandler: "error", 346 }, 347 } 348 349 for _, tc := range cases { 350 t.Run(tc.name, func(t *testing.T) { 351 if tc.skipOnWindows && goruntime.GOOS == "windows" { 352 // TODO: remove skip once the failing test has been fixed. 353 t.Skip("Skip failing test on Windows.") 354 } 355 // These tests will create the userns file, so use an existing podDir. 356 testUserNsPodsManager := &testUserNsPodsManager{ 357 podDir: t.TempDir(), 358 userns: tc.runtimeUserns, 359 } 360 m, err := MakeUserNsManager(testUserNsPodsManager) 361 assert.NoError(t, err) 362 363 userns, err := m.GetOrCreateUserNamespaceMappings(tc.pod, tc.runtimeHandler) 364 if (tc.success && err != nil) || (!tc.success && err == nil) { 365 t.Errorf("expected success: %v but got error: %v", tc.success, err) 366 } 367 368 if userns.GetMode() != tc.expMode { 369 t.Errorf("expected mode: %v but got: %v", tc.expMode, userns.GetMode()) 370 } 371 }) 372 } 373 } 374 375 func TestCleanupOrphanedPodUsernsAllocations(t *testing.T) { 376 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true) 377 378 cases := []struct { 379 name string 380 runningPods []*kubecontainer.Pod 381 pods []*v1.Pod 382 listPods []types.UID /* pods to list */ 383 podSetBeforeCleanup []types.UID /* pods to record before cleanup */ 384 podSetAfterCleanup []types.UID /* pods set expected after cleanup */ 385 podUnsetAfterCleanup []types.UID /* pods set expected after cleanup */ 386 }{ 387 { 388 name: "no stale pods", 389 listPods: []types.UID{"pod-1", "pod-2"}, 390 }, 391 { 392 name: "no stale pods set", 393 podSetBeforeCleanup: []types.UID{"pod-1", "pod-2"}, 394 listPods: []types.UID{"pod-1", "pod-2"}, 395 podUnsetAfterCleanup: []types.UID{"pod-1", "pod-2"}, 396 }, 397 { 398 name: "one running pod", 399 listPods: []types.UID{"pod-1", "pod-2"}, 400 podSetBeforeCleanup: []types.UID{"pod-1", "pod-2"}, 401 runningPods: []*kubecontainer.Pod{{ID: "pod-1"}}, 402 podSetAfterCleanup: []types.UID{"pod-1"}, 403 podUnsetAfterCleanup: []types.UID{"pod-2"}, 404 }, 405 { 406 name: "pod set before cleanup but not listed ==> unset", 407 podSetBeforeCleanup: []types.UID{"pod-1", "pod-2"}, 408 runningPods: []*kubecontainer.Pod{{ID: "pod-1"}}, 409 podUnsetAfterCleanup: []types.UID{"pod-1", "pod-2"}, 410 }, 411 { 412 name: "one pod", 413 listPods: []types.UID{"pod-1", "pod-2"}, 414 podSetBeforeCleanup: []types.UID{"pod-1", "pod-2"}, 415 pods: []*v1.Pod{{ObjectMeta: metav1.ObjectMeta{UID: "pod-1"}}}, 416 podSetAfterCleanup: []types.UID{"pod-1"}, 417 podUnsetAfterCleanup: []types.UID{"pod-2"}, 418 }, 419 { 420 name: "no listed pods ==> all unset", 421 podSetBeforeCleanup: []types.UID{"pod-1", "pod-2"}, 422 podUnsetAfterCleanup: []types.UID{"pod-1", "pod-2"}, 423 }, 424 } 425 426 for _, tc := range cases { 427 t.Run(tc.name, func(t *testing.T) { 428 testUserNsPodsManager := &testUserNsPodsManager{ 429 podDir: t.TempDir(), 430 podList: tc.listPods, 431 } 432 m, err := MakeUserNsManager(testUserNsPodsManager) 433 require.NoError(t, err) 434 435 // Record the userns range as used 436 for i, pod := range tc.podSetBeforeCleanup { 437 err := m.record(pod, uint32((i+1)*65536), 65536) 438 require.NoError(t, err) 439 } 440 441 err = m.CleanupOrphanedPodUsernsAllocations(tc.pods, tc.runningPods) 442 require.NoError(t, err) 443 444 for _, pod := range tc.podSetAfterCleanup { 445 ok := m.podAllocated(pod) 446 assert.True(t, ok, "pod %q should be allocated", pod) 447 } 448 449 for _, pod := range tc.podUnsetAfterCleanup { 450 ok := m.podAllocated(pod) 451 assert.False(t, ok, "pod %q should not be allocated", pod) 452 } 453 }) 454 } 455 } 456 457 type failingUserNsPodsManager struct { 458 testUserNsPodsManager 459 } 460 461 func (m *failingUserNsPodsManager) ListPodsFromDisk() ([]types.UID, error) { 462 return nil, os.ErrPermission 463 } 464 465 func TestMakeUserNsManagerFailsListPod(t *testing.T) { 466 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true) 467 468 testUserNsPodsManager := &failingUserNsPodsManager{} 469 _, err := MakeUserNsManager(testUserNsPodsManager) 470 assert.Error(t, err) 471 assert.ErrorContains(t, err, "read pods from disk") 472 } 473 474 func TestRecordBounds(t *testing.T) { 475 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, pkgfeatures.UserNamespacesSupport, true) 476 477 // Allow exactly for 1 pod 478 testUserNsPodsManager := &testUserNsPodsManager{ 479 mappingFirstID: 65536, 480 mappingLen: 65536, 481 maxPods: 1, 482 } 483 m, err := MakeUserNsManager(testUserNsPodsManager) 484 require.NoError(t, err) 485 486 // The first pod allocation should succeed. 487 err = m.record(types.UID(fmt.Sprintf("%d", 0)), 65536, 65536) 488 require.NoError(t, err) 489 490 // The next allocation should fail, as there is no space left. 491 err = m.record(types.UID(fmt.Sprintf("%d", 2)), uint32(2*65536), 65536) 492 assert.Error(t, err) 493 assert.ErrorContains(t, err, "out of range") 494 }