k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/plugin/pkg/admission/serviceaccount/admission_test.go (about) 1 /* 2 Copyright 2014 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 serviceaccount 18 19 import ( 20 "context" 21 "reflect" 22 "strings" 23 "testing" 24 25 "github.com/google/go-cmp/cmp" 26 "github.com/stretchr/testify/assert" 27 corev1 "k8s.io/api/core/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/types" 30 "k8s.io/apiserver/pkg/admission" 31 admissiontesting "k8s.io/apiserver/pkg/admission/testing" 32 "k8s.io/client-go/informers" 33 "k8s.io/client-go/kubernetes/fake" 34 api "k8s.io/kubernetes/pkg/apis/core" 35 v1defaults "k8s.io/kubernetes/pkg/apis/core/v1" 36 "k8s.io/kubernetes/pkg/controller" 37 kubelet "k8s.io/kubernetes/pkg/kubelet/types" 38 utilpointer "k8s.io/utils/pointer" 39 ) 40 41 func TestIgnoresNonCreate(t *testing.T) { 42 for _, op := range []admission.Operation{admission.Delete, admission.Connect} { 43 handler := NewServiceAccount() 44 if handler.Handles(op) { 45 t.Errorf("Expected not to handle operation %s", op) 46 } 47 } 48 } 49 50 func TestIgnoresNonPodResource(t *testing.T) { 51 pod := &api.Pod{} 52 attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "myns", "myname", api.Resource("CustomResource").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 53 handler := admissiontesting.WithReinvocationTesting(t, NewServiceAccount()) 54 err := handler.Admit(context.TODO(), attrs, nil) 55 if err != nil { 56 t.Errorf("Expected non-pod resource allowed, got err: %v", err) 57 } 58 } 59 60 func TestIgnoresNilObject(t *testing.T) { 61 attrs := admission.NewAttributesRecord(nil, nil, api.Kind("Pod").WithVersion("version"), "myns", "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 62 handler := admissiontesting.WithReinvocationTesting(t, NewServiceAccount()) 63 err := handler.Admit(context.TODO(), attrs, nil) 64 if err != nil { 65 t.Errorf("Expected nil object allowed allowed, got err: %v", err) 66 } 67 } 68 69 func TestIgnoresNonPodObject(t *testing.T) { 70 obj := &api.Namespace{} 71 attrs := admission.NewAttributesRecord(obj, nil, api.Kind("Pod").WithVersion("version"), "myns", "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 72 handler := admissiontesting.WithReinvocationTesting(t, NewServiceAccount()) 73 err := handler.Admit(context.TODO(), attrs, nil) 74 if err != nil { 75 t.Errorf("Expected non pod object allowed, got err: %v", err) 76 } 77 } 78 79 func TestIgnoresMirrorPod(t *testing.T) { 80 pod := &api.Pod{ 81 ObjectMeta: metav1.ObjectMeta{ 82 Annotations: map[string]string{ 83 kubelet.ConfigMirrorAnnotationKey: "true", 84 }, 85 }, 86 Spec: api.PodSpec{ 87 Volumes: []api.Volume{ 88 {VolumeSource: api.VolumeSource{}}, 89 }, 90 }, 91 } 92 attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "myns", "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 93 err := admissiontesting.WithReinvocationTesting(t, NewServiceAccount()).Admit(context.TODO(), attrs, nil) 94 if err != nil { 95 t.Errorf("Expected mirror pod without service account or secrets allowed, got err: %v", err) 96 } 97 } 98 99 func TestRejectsMirrorPodWithServiceAccount(t *testing.T) { 100 pod := &api.Pod{ 101 ObjectMeta: metav1.ObjectMeta{ 102 Annotations: map[string]string{ 103 kubelet.ConfigMirrorAnnotationKey: "true", 104 }, 105 }, 106 Spec: api.PodSpec{ 107 ServiceAccountName: "default", 108 }, 109 } 110 attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "myns", "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 111 err := admissiontesting.WithReinvocationTesting(t, NewServiceAccount()).Admit(context.TODO(), attrs, nil) 112 if err == nil { 113 t.Errorf("Expected a mirror pod to be prevented from referencing a service account") 114 } 115 } 116 117 func TestRejectsMirrorPodWithSecretVolumes(t *testing.T) { 118 pod := &api.Pod{ 119 ObjectMeta: metav1.ObjectMeta{ 120 Annotations: map[string]string{ 121 kubelet.ConfigMirrorAnnotationKey: "true", 122 }, 123 }, 124 Spec: api.PodSpec{ 125 Volumes: []api.Volume{ 126 {VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: "mysecret"}}}, 127 }, 128 }, 129 } 130 attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "myns", "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 131 err := admissiontesting.WithReinvocationTesting(t, NewServiceAccount()).Admit(context.TODO(), attrs, nil) 132 if err == nil { 133 t.Errorf("Expected a mirror pod to be prevented from referencing a secret volume") 134 } 135 } 136 137 func TestRejectsMirrorPodWithServiceAccountTokenVolumeProjections(t *testing.T) { 138 pod := &api.Pod{ 139 ObjectMeta: metav1.ObjectMeta{ 140 Annotations: map[string]string{ 141 kubelet.ConfigMirrorAnnotationKey: "true", 142 }, 143 }, 144 Spec: api.PodSpec{ 145 Volumes: []api.Volume{ 146 {VolumeSource: api.VolumeSource{ 147 Projected: &api.ProjectedVolumeSource{ 148 Sources: []api.VolumeProjection{{ServiceAccountToken: &api.ServiceAccountTokenProjection{}}}, 149 }, 150 }, 151 }, 152 }, 153 }, 154 } 155 attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "myns", "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 156 err := admissiontesting.WithReinvocationTesting(t, NewServiceAccount()).Admit(context.TODO(), attrs, nil) 157 if err == nil { 158 t.Errorf("Expected a mirror pod to be prevented from referencing a ServiceAccountToken volume projection") 159 } 160 } 161 162 func TestAssignsDefaultServiceAccountAndBoundTokenWithNoSecretTokens(t *testing.T) { 163 ns := "myns" 164 165 admit := NewServiceAccount() 166 informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) 167 admit.SetExternalKubeInformerFactory(informerFactory) 168 admit.MountServiceAccountToken = true 169 170 // Add the default service account for the ns into the cache 171 informerFactory.Core().V1().ServiceAccounts().Informer().GetStore().Add(&corev1.ServiceAccount{ 172 ObjectMeta: metav1.ObjectMeta{ 173 Name: DefaultServiceAccountName, 174 Namespace: ns, 175 }, 176 }) 177 178 v1PodIn := &corev1.Pod{ 179 Spec: corev1.PodSpec{ 180 Containers: []corev1.Container{{}}, 181 }, 182 } 183 v1defaults.SetObjectDefaults_Pod(v1PodIn) 184 pod := &api.Pod{} 185 if err := v1defaults.Convert_v1_Pod_To_core_Pod(v1PodIn, pod, nil); err != nil { 186 t.Fatal(err) 187 } 188 attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 189 err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil) 190 if err != nil { 191 t.Fatalf("Expected success, got: %v", err) 192 } 193 194 expectedVolumes := []api.Volume{{ 195 Name: "cleared", 196 VolumeSource: api.VolumeSource{ 197 Projected: &api.ProjectedVolumeSource{ 198 Sources: []api.VolumeProjection{ 199 {ServiceAccountToken: &api.ServiceAccountTokenProjection{ExpirationSeconds: 3607, Path: "token"}}, 200 {ConfigMap: &api.ConfigMapProjection{LocalObjectReference: api.LocalObjectReference{Name: "kube-root-ca.crt"}, Items: []api.KeyToPath{{Key: "ca.crt", Path: "ca.crt"}}}}, 201 {DownwardAPI: &api.DownwardAPIProjection{Items: []api.DownwardAPIVolumeFile{{Path: "namespace", FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.namespace"}}}}}, 202 }, 203 DefaultMode: utilpointer.Int32(0644), 204 }, 205 }, 206 }} 207 expectedVolumeMounts := []api.VolumeMount{{ 208 Name: "cleared", 209 ReadOnly: true, 210 MountPath: "/var/run/secrets/kubernetes.io/serviceaccount", 211 }} 212 213 // clear generated volume names 214 for i := range pod.Spec.Volumes { 215 if len(pod.Spec.Volumes[i].Name) > 0 { 216 pod.Spec.Volumes[i].Name = "cleared" 217 } 218 } 219 for i := range pod.Spec.Containers[0].VolumeMounts { 220 if len(pod.Spec.Containers[0].VolumeMounts[i].Name) > 0 { 221 pod.Spec.Containers[0].VolumeMounts[i].Name = "cleared" 222 } 223 } 224 225 if !reflect.DeepEqual(expectedVolumes, pod.Spec.Volumes) { 226 t.Errorf("unexpected volumes: %s", cmp.Diff(expectedVolumes, pod.Spec.Volumes)) 227 } 228 if !reflect.DeepEqual(expectedVolumeMounts, pod.Spec.Containers[0].VolumeMounts) { 229 t.Errorf("unexpected volumes: %s", cmp.Diff(expectedVolumeMounts, pod.Spec.Containers[0].VolumeMounts)) 230 } 231 232 // ensure result converted to v1 matches defaulted object 233 v1PodOut := &corev1.Pod{} 234 if err := v1defaults.Convert_core_Pod_To_v1_Pod(pod, v1PodOut, nil); err != nil { 235 t.Fatal(err) 236 } 237 v1PodOutDefaulted := v1PodOut.DeepCopy() 238 v1defaults.SetObjectDefaults_Pod(v1PodOutDefaulted) 239 if !reflect.DeepEqual(v1PodOut, v1PodOutDefaulted) { 240 t.Error(cmp.Diff(v1PodOut, v1PodOutDefaulted)) 241 } 242 } 243 244 func TestFetchesUncachedServiceAccount(t *testing.T) { 245 ns := "myns" 246 247 // Build a test client that the admission plugin can use to look up the service account missing from its cache 248 client := fake.NewSimpleClientset(&corev1.ServiceAccount{ 249 ObjectMeta: metav1.ObjectMeta{ 250 Name: DefaultServiceAccountName, 251 Namespace: ns, 252 }, 253 }) 254 255 admit := NewServiceAccount() 256 informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) 257 admit.SetExternalKubeInformerFactory(informerFactory) 258 admit.client = client 259 260 pod := &api.Pod{} 261 attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 262 err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil) 263 if err != nil { 264 t.Errorf("Unexpected error: %v", err) 265 } 266 if pod.Spec.ServiceAccountName != DefaultServiceAccountName { 267 t.Errorf("Expected service account %s assigned, got %s", DefaultServiceAccountName, pod.Spec.ServiceAccountName) 268 } 269 } 270 271 func TestDeniesInvalidServiceAccount(t *testing.T) { 272 ns := "myns" 273 274 // Build a test client that the admission plugin can use to look up the service account missing from its cache 275 client := fake.NewSimpleClientset() 276 277 admit := NewServiceAccount() 278 admit.SetExternalKubeClientSet(client) 279 informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) 280 admit.SetExternalKubeInformerFactory(informerFactory) 281 282 pod := &api.Pod{} 283 attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 284 err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil) 285 if err == nil { 286 t.Errorf("Expected error for missing service account, got none") 287 } 288 } 289 290 func TestAutomountsAPIToken(t *testing.T) { 291 292 admit := NewServiceAccount() 293 informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) 294 admit.SetExternalKubeInformerFactory(informerFactory) 295 admit.generateName = testGenerateName 296 admit.MountServiceAccountToken = true 297 298 ns := "myns" 299 serviceAccountName := DefaultServiceAccountName 300 serviceAccountUID := "12345" 301 302 tokenName := generatedVolumeName 303 304 expectedVolume := api.Volume{ 305 Name: tokenName, 306 VolumeSource: api.VolumeSource{ 307 Projected: TokenVolumeSource(), 308 }, 309 } 310 expectedVolumeMount := api.VolumeMount{ 311 Name: tokenName, 312 ReadOnly: true, 313 MountPath: DefaultAPITokenMountPath, 314 } 315 // Add the default service account for the ns with a token into the cache 316 informerFactory.Core().V1().ServiceAccounts().Informer().GetStore().Add(&corev1.ServiceAccount{ 317 ObjectMeta: metav1.ObjectMeta{ 318 Name: serviceAccountName, 319 Namespace: ns, 320 UID: types.UID(serviceAccountUID), 321 }, 322 }) 323 324 pod := &api.Pod{ 325 Spec: api.PodSpec{ 326 Containers: []api.Container{ 327 {}, 328 }, 329 }, 330 } 331 attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 332 err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil) 333 if err != nil { 334 t.Errorf("Unexpected error: %v", err) 335 } 336 if pod.Spec.ServiceAccountName != DefaultServiceAccountName { 337 t.Errorf("Expected service account %s assigned, got %s", DefaultServiceAccountName, pod.Spec.ServiceAccountName) 338 } 339 if len(pod.Spec.Volumes) != 1 { 340 t.Fatalf("Expected 1 volume, got %d", len(pod.Spec.Volumes)) 341 } 342 if !reflect.DeepEqual(expectedVolume, pod.Spec.Volumes[0]) { 343 t.Fatalf("Expected\n\t%#v\ngot\n\t%#v", expectedVolume, pod.Spec.Volumes[0]) 344 } 345 if len(pod.Spec.Containers[0].VolumeMounts) != 1 { 346 t.Fatalf("Expected 1 volume mount, got %d", len(pod.Spec.Containers[0].VolumeMounts)) 347 } 348 if !reflect.DeepEqual(expectedVolumeMount, pod.Spec.Containers[0].VolumeMounts[0]) { 349 t.Fatalf("Expected\n\t%#v\ngot\n\t%#v", expectedVolumeMount, pod.Spec.Containers[0].VolumeMounts[0]) 350 } 351 352 // testing InitContainers 353 pod = &api.Pod{ 354 Spec: api.PodSpec{ 355 InitContainers: []api.Container{ 356 {}, 357 }, 358 }, 359 } 360 attrs = admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 361 if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err != nil { 362 t.Errorf("Unexpected error: %v", err) 363 } 364 if pod.Spec.ServiceAccountName != DefaultServiceAccountName { 365 t.Errorf("Expected service account %s assigned, got %s", DefaultServiceAccountName, pod.Spec.ServiceAccountName) 366 } 367 if len(pod.Spec.Volumes) != 1 { 368 t.Fatalf("Expected 1 volume, got %d", len(pod.Spec.Volumes)) 369 } 370 if !reflect.DeepEqual(expectedVolume, pod.Spec.Volumes[0]) { 371 t.Fatalf("Expected\n\t%#v\ngot\n\t%#v", expectedVolume, pod.Spec.Volumes[0]) 372 } 373 if len(pod.Spec.InitContainers[0].VolumeMounts) != 1 { 374 t.Fatalf("Expected 1 volume mount, got %d", len(pod.Spec.InitContainers[0].VolumeMounts)) 375 } 376 if !reflect.DeepEqual(expectedVolumeMount, pod.Spec.InitContainers[0].VolumeMounts[0]) { 377 t.Fatalf("Expected\n\t%#v\ngot\n\t%#v", expectedVolumeMount, pod.Spec.InitContainers[0].VolumeMounts[0]) 378 } 379 } 380 381 func TestRespectsExistingMount(t *testing.T) { 382 ns := "myns" 383 serviceAccountName := DefaultServiceAccountName 384 serviceAccountUID := "12345" 385 386 expectedVolumeMount := api.VolumeMount{ 387 Name: "my-custom-mount", 388 ReadOnly: false, 389 MountPath: DefaultAPITokenMountPath, 390 } 391 392 admit := NewServiceAccount() 393 informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) 394 admit.SetExternalKubeInformerFactory(informerFactory) 395 admit.MountServiceAccountToken = true 396 397 // Add the default service account for the ns with a token into the cache 398 informerFactory.Core().V1().ServiceAccounts().Informer().GetStore().Add(&corev1.ServiceAccount{ 399 ObjectMeta: metav1.ObjectMeta{ 400 Name: serviceAccountName, 401 Namespace: ns, 402 UID: types.UID(serviceAccountUID), 403 }, 404 }) 405 406 // Define a pod with a container that already mounts a volume at the API token path 407 // Admission should respect that 408 // Additionally, no volume should be created if no container is going to use it 409 pod := &api.Pod{ 410 Spec: api.PodSpec{ 411 Containers: []api.Container{ 412 { 413 VolumeMounts: []api.VolumeMount{ 414 expectedVolumeMount, 415 }, 416 }, 417 }, 418 }, 419 } 420 attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 421 err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil) 422 if err != nil { 423 t.Errorf("Unexpected error: %v", err) 424 } 425 if pod.Spec.ServiceAccountName != DefaultServiceAccountName { 426 t.Errorf("Expected service account %s assigned, got %s", DefaultServiceAccountName, pod.Spec.ServiceAccountName) 427 } 428 if len(pod.Spec.Volumes) != 0 { 429 t.Fatalf("Expected 0 volumes (shouldn't create a volume for a secret we don't need), got %d", len(pod.Spec.Volumes)) 430 } 431 if len(pod.Spec.Containers[0].VolumeMounts) != 1 { 432 t.Fatalf("Expected 1 volume mount, got %d", len(pod.Spec.Containers[0].VolumeMounts)) 433 } 434 if !reflect.DeepEqual(expectedVolumeMount, pod.Spec.Containers[0].VolumeMounts[0]) { 435 t.Fatalf("Expected\n\t%#v\ngot\n\t%#v", expectedVolumeMount, pod.Spec.Containers[0].VolumeMounts[0]) 436 } 437 438 // check init containers 439 pod = &api.Pod{ 440 Spec: api.PodSpec{ 441 InitContainers: []api.Container{ 442 { 443 VolumeMounts: []api.VolumeMount{ 444 expectedVolumeMount, 445 }, 446 }, 447 }, 448 }, 449 } 450 attrs = admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 451 if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err != nil { 452 t.Errorf("Unexpected error: %v", err) 453 } 454 if pod.Spec.ServiceAccountName != DefaultServiceAccountName { 455 t.Errorf("Expected service account %s assigned, got %s", DefaultServiceAccountName, pod.Spec.ServiceAccountName) 456 } 457 if len(pod.Spec.Volumes) != 0 { 458 t.Fatalf("Expected 0 volumes (shouldn't create a volume for a secret we don't need), got %d", len(pod.Spec.Volumes)) 459 } 460 if len(pod.Spec.InitContainers[0].VolumeMounts) != 1 { 461 t.Fatalf("Expected 1 volume mount, got %d", len(pod.Spec.InitContainers[0].VolumeMounts)) 462 } 463 if !reflect.DeepEqual(expectedVolumeMount, pod.Spec.InitContainers[0].VolumeMounts[0]) { 464 t.Fatalf("Expected\n\t%#v\ngot\n\t%#v", expectedVolumeMount, pod.Spec.InitContainers[0].VolumeMounts[0]) 465 } 466 } 467 468 func TestAllowsReferencedSecret(t *testing.T) { 469 ns := "myns" 470 471 admit := NewServiceAccount() 472 informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) 473 admit.SetExternalKubeInformerFactory(informerFactory) 474 admit.LimitSecretReferences = true 475 476 // Add the default service account for the ns with a secret reference into the cache 477 informerFactory.Core().V1().ServiceAccounts().Informer().GetStore().Add(&corev1.ServiceAccount{ 478 ObjectMeta: metav1.ObjectMeta{ 479 Name: DefaultServiceAccountName, 480 Namespace: ns, 481 }, 482 Secrets: []corev1.ObjectReference{ 483 {Name: "foo"}, 484 }, 485 }) 486 487 pod1 := &api.Pod{ 488 Spec: api.PodSpec{ 489 Volumes: []api.Volume{ 490 {VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: "foo"}}}, 491 }, 492 }, 493 } 494 attrs := admission.NewAttributesRecord(pod1, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 495 if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err != nil { 496 t.Errorf("Unexpected error: %v", err) 497 } 498 499 pod2 := &api.Pod{ 500 Spec: api.PodSpec{ 501 Containers: []api.Container{ 502 { 503 Name: "container-1", 504 Env: []api.EnvVar{ 505 { 506 Name: "env-1", 507 ValueFrom: &api.EnvVarSource{ 508 SecretKeyRef: &api.SecretKeySelector{ 509 LocalObjectReference: api.LocalObjectReference{Name: "foo"}, 510 }, 511 }, 512 }, 513 }, 514 }, 515 }, 516 }, 517 } 518 attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 519 if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err != nil { 520 t.Errorf("Unexpected error: %v", err) 521 } 522 523 pod2 = &api.Pod{ 524 Spec: api.PodSpec{ 525 Containers: []api.Container{ 526 { 527 Name: "container-1", 528 EnvFrom: []api.EnvFromSource{ 529 { 530 SecretRef: &api.SecretEnvSource{ 531 LocalObjectReference: api.LocalObjectReference{ 532 Name: "foo"}}}}, 533 }, 534 }, 535 }, 536 } 537 attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 538 if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err != nil { 539 t.Errorf("Unexpected error: %v", err) 540 } 541 542 pod2 = &api.Pod{ 543 Spec: api.PodSpec{ 544 InitContainers: []api.Container{ 545 { 546 Name: "container-1", 547 Env: []api.EnvVar{ 548 { 549 Name: "env-1", 550 ValueFrom: &api.EnvVarSource{ 551 SecretKeyRef: &api.SecretKeySelector{ 552 LocalObjectReference: api.LocalObjectReference{Name: "foo"}, 553 }, 554 }, 555 }, 556 }, 557 }, 558 }, 559 }, 560 } 561 attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 562 if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err != nil { 563 t.Errorf("Unexpected error: %v", err) 564 } 565 566 pod2 = &api.Pod{ 567 Spec: api.PodSpec{ 568 InitContainers: []api.Container{ 569 { 570 Name: "container-1", 571 EnvFrom: []api.EnvFromSource{ 572 { 573 SecretRef: &api.SecretEnvSource{ 574 LocalObjectReference: api.LocalObjectReference{ 575 Name: "foo"}}}}, 576 }, 577 }, 578 }, 579 } 580 attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 581 if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err != nil { 582 t.Errorf("Unexpected error: %v", err) 583 } 584 585 pod2 = &api.Pod{ 586 Spec: api.PodSpec{ 587 ServiceAccountName: DefaultServiceAccountName, 588 EphemeralContainers: []api.EphemeralContainer{ 589 { 590 EphemeralContainerCommon: api.EphemeralContainerCommon{ 591 Name: "container-2", 592 Env: []api.EnvVar{ 593 { 594 Name: "env-1", 595 ValueFrom: &api.EnvVarSource{ 596 SecretKeyRef: &api.SecretKeySelector{ 597 LocalObjectReference: api.LocalObjectReference{Name: "foo"}, 598 }, 599 }, 600 }, 601 }, 602 }, 603 }, 604 }, 605 }, 606 } 607 // validate enforces restrictions on secret mounts when operation==create and subresource=='' or operation==update and subresource==ephemeralcontainers" 608 attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "ephemeralcontainers", admission.Update, &metav1.UpdateOptions{}, false, nil) 609 if err := admit.Validate(context.TODO(), attrs, nil); err != nil { 610 t.Errorf("Unexpected error: %v", err) 611 } 612 613 pod2 = &api.Pod{ 614 Spec: api.PodSpec{ 615 ServiceAccountName: DefaultServiceAccountName, 616 EphemeralContainers: []api.EphemeralContainer{ 617 { 618 EphemeralContainerCommon: api.EphemeralContainerCommon{ 619 Name: "container-2", 620 EnvFrom: []api.EnvFromSource{{ 621 SecretRef: &api.SecretEnvSource{ 622 LocalObjectReference: api.LocalObjectReference{ 623 Name: "foo"}}}}, 624 }, 625 }, 626 }, 627 }, 628 } 629 // validate enforces restrictions on secret mounts when operation==update and subresource==ephemeralcontainers" 630 attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "ephemeralcontainers", admission.Update, &metav1.UpdateOptions{}, false, nil) 631 if err := admit.Validate(context.TODO(), attrs, nil); err != nil { 632 t.Errorf("Unexpected error: %v", err) 633 } 634 } 635 636 func TestRejectsUnreferencedSecretVolumes(t *testing.T) { 637 ns := "myns" 638 639 admit := NewServiceAccount() 640 informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) 641 admit.SetExternalKubeInformerFactory(informerFactory) 642 admit.LimitSecretReferences = true 643 644 // Add the default service account for the ns into the cache 645 informerFactory.Core().V1().ServiceAccounts().Informer().GetStore().Add(&corev1.ServiceAccount{ 646 ObjectMeta: metav1.ObjectMeta{ 647 Name: DefaultServiceAccountName, 648 Namespace: ns, 649 }, 650 }) 651 652 pod1 := &api.Pod{ 653 Spec: api.PodSpec{ 654 Volumes: []api.Volume{ 655 {VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: "foo"}}}, 656 }, 657 }, 658 } 659 attrs := admission.NewAttributesRecord(pod1, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 660 if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err == nil { 661 t.Errorf("Expected rejection for using a secret the service account does not reference") 662 } 663 664 pod2 := &api.Pod{ 665 Spec: api.PodSpec{ 666 Containers: []api.Container{ 667 { 668 Name: "container-1", 669 Env: []api.EnvVar{ 670 { 671 Name: "env-1", 672 ValueFrom: &api.EnvVarSource{ 673 SecretKeyRef: &api.SecretKeySelector{ 674 LocalObjectReference: api.LocalObjectReference{Name: "foo"}, 675 }, 676 }, 677 }, 678 }, 679 }, 680 }, 681 }, 682 } 683 attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 684 if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err == nil || !strings.Contains(err.Error(), "with envVar") { 685 t.Errorf("Unexpected error: %v", err) 686 } 687 688 pod2 = &api.Pod{ 689 Spec: api.PodSpec{ 690 Containers: []api.Container{ 691 { 692 Name: "container-1", 693 EnvFrom: []api.EnvFromSource{ 694 { 695 SecretRef: &api.SecretEnvSource{ 696 LocalObjectReference: api.LocalObjectReference{ 697 Name: "foo"}}}}, 698 }, 699 }, 700 }, 701 } 702 attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 703 if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err == nil || !strings.Contains(err.Error(), "with envFrom") { 704 t.Errorf("Unexpected error: %v", err) 705 } 706 707 pod2 = &api.Pod{ 708 Spec: api.PodSpec{ 709 ServiceAccountName: DefaultServiceAccountName, 710 InitContainers: []api.Container{ 711 { 712 Name: "container-1", 713 Env: []api.EnvVar{ 714 { 715 Name: "env-1", 716 ValueFrom: &api.EnvVarSource{ 717 SecretKeyRef: &api.SecretKeySelector{ 718 LocalObjectReference: api.LocalObjectReference{Name: "foo"}, 719 }, 720 }, 721 }, 722 }, 723 }, 724 }, 725 }, 726 } 727 attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil) 728 if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err != nil { 729 t.Errorf("admit only enforces restrictions on secret mounts when operation==create. Unexpected error: %v", err) 730 } 731 attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 732 if err := admit.Validate(context.TODO(), attrs, nil); err == nil || !strings.Contains(err.Error(), "with envVar") { 733 t.Errorf("validate only enforces restrictions on secret mounts when operation==create and subresource==''. Unexpected error: %v", err) 734 } 735 736 pod2 = &api.Pod{ 737 Spec: api.PodSpec{ 738 ServiceAccountName: DefaultServiceAccountName, 739 InitContainers: []api.Container{ 740 { 741 Name: "container-1", 742 EnvFrom: []api.EnvFromSource{ 743 { 744 SecretRef: &api.SecretEnvSource{ 745 LocalObjectReference: api.LocalObjectReference{ 746 Name: "foo"}}}}, 747 }, 748 }, 749 }, 750 } 751 attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil) 752 if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err != nil { 753 t.Errorf("admit only enforces restrictions on secret mounts when operation==create. Unexpected error: %v", err) 754 } 755 attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 756 if err := admit.Validate(context.TODO(), attrs, nil); err == nil || !strings.Contains(err.Error(), "with envFrom") { 757 t.Errorf("validate only enforces restrictions on secret mounts when operation==create and subresource==''. Unexpected error: %v", err) 758 } 759 760 pod2 = &api.Pod{ 761 Spec: api.PodSpec{ 762 ServiceAccountName: DefaultServiceAccountName, 763 EphemeralContainers: []api.EphemeralContainer{ 764 { 765 EphemeralContainerCommon: api.EphemeralContainerCommon{ 766 Name: "container-2", 767 Env: []api.EnvVar{ 768 { 769 Name: "env-1", 770 ValueFrom: &api.EnvVarSource{ 771 SecretKeyRef: &api.SecretKeySelector{ 772 LocalObjectReference: api.LocalObjectReference{Name: "foo"}, 773 }, 774 }, 775 }, 776 }, 777 }, 778 }, 779 }, 780 }, 781 } 782 attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil) 783 if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err != nil { 784 t.Errorf("admit only enforces restrictions on secret mounts when operation==create and subresource==''. Unexpected error: %v", err) 785 } 786 attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "ephemeralcontainers", admission.Update, &metav1.UpdateOptions{}, false, nil) 787 if err := admit.Validate(context.TODO(), attrs, nil); err == nil || !strings.Contains(err.Error(), "with envVar") { 788 t.Errorf("validate enforces restrictions on secret mounts when operation==update and subresource==ephemeralcontainers. Unexpected error: %v", err) 789 } 790 791 pod2 = &api.Pod{ 792 Spec: api.PodSpec{ 793 ServiceAccountName: DefaultServiceAccountName, 794 EphemeralContainers: []api.EphemeralContainer{ 795 { 796 EphemeralContainerCommon: api.EphemeralContainerCommon{ 797 Name: "container-2", 798 EnvFrom: []api.EnvFromSource{{ 799 SecretRef: &api.SecretEnvSource{ 800 LocalObjectReference: api.LocalObjectReference{ 801 Name: "foo"}}}}, 802 }, 803 }, 804 }, 805 }, 806 } 807 attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "ephemeralcontainers", admission.Update, &metav1.UpdateOptions{}, false, nil) 808 if err := admit.Validate(context.TODO(), attrs, nil); err == nil || !strings.Contains(err.Error(), "with envFrom") { 809 t.Errorf("validate enforces restrictions on secret mounts when operation==update and subresource==ephemeralcontainers. Unexpected error: %v", err) 810 } 811 } 812 813 func TestAllowUnreferencedSecretVolumesForPermissiveSAs(t *testing.T) { 814 ns := "myns" 815 816 admit := NewServiceAccount() 817 informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) 818 admit.SetExternalKubeInformerFactory(informerFactory) 819 admit.LimitSecretReferences = false 820 821 // Add the default service account for the ns into the cache 822 informerFactory.Core().V1().ServiceAccounts().Informer().GetStore().Add(&corev1.ServiceAccount{ 823 ObjectMeta: metav1.ObjectMeta{ 824 Name: DefaultServiceAccountName, 825 Namespace: ns, 826 Annotations: map[string]string{EnforceMountableSecretsAnnotation: "true"}, 827 }, 828 }) 829 830 pod := &api.Pod{ 831 Spec: api.PodSpec{ 832 Volumes: []api.Volume{ 833 {VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: "foo"}}}, 834 }, 835 }, 836 } 837 attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 838 err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil) 839 if err == nil { 840 t.Errorf("Expected rejection for using a secret the service account does not reference") 841 } 842 } 843 844 func TestAllowsReferencedImagePullSecrets(t *testing.T) { 845 ns := "myns" 846 847 admit := NewServiceAccount() 848 informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) 849 admit.SetExternalKubeInformerFactory(informerFactory) 850 admit.LimitSecretReferences = true 851 852 // Add the default service account for the ns with a secret reference into the cache 853 informerFactory.Core().V1().ServiceAccounts().Informer().GetStore().Add(&corev1.ServiceAccount{ 854 ObjectMeta: metav1.ObjectMeta{ 855 Name: DefaultServiceAccountName, 856 Namespace: ns, 857 }, 858 ImagePullSecrets: []corev1.LocalObjectReference{ 859 {Name: "foo"}, 860 }, 861 }) 862 863 pod := &api.Pod{ 864 Spec: api.PodSpec{ 865 ImagePullSecrets: []api.LocalObjectReference{{Name: "foo"}}, 866 }, 867 } 868 attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 869 err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil) 870 if err != nil { 871 t.Errorf("Unexpected error: %v", err) 872 } 873 } 874 875 func TestRejectsUnreferencedImagePullSecrets(t *testing.T) { 876 ns := "myns" 877 878 admit := NewServiceAccount() 879 informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) 880 admit.SetExternalKubeInformerFactory(informerFactory) 881 admit.LimitSecretReferences = true 882 883 // Add the default service account for the ns into the cache 884 informerFactory.Core().V1().ServiceAccounts().Informer().GetStore().Add(&corev1.ServiceAccount{ 885 ObjectMeta: metav1.ObjectMeta{ 886 Name: DefaultServiceAccountName, 887 Namespace: ns, 888 }, 889 }) 890 891 pod := &api.Pod{ 892 Spec: api.PodSpec{ 893 ImagePullSecrets: []api.LocalObjectReference{{Name: "foo"}}, 894 }, 895 } 896 attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 897 err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil) 898 if err == nil { 899 t.Errorf("Expected rejection for using a secret the service account does not reference") 900 } 901 } 902 903 func TestDoNotAddImagePullSecrets(t *testing.T) { 904 ns := "myns" 905 906 admit := NewServiceAccount() 907 informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) 908 admit.SetExternalKubeInformerFactory(informerFactory) 909 admit.LimitSecretReferences = true 910 911 // Add the default service account for the ns with a secret reference into the cache 912 informerFactory.Core().V1().ServiceAccounts().Informer().GetStore().Add(&corev1.ServiceAccount{ 913 ObjectMeta: metav1.ObjectMeta{ 914 Name: DefaultServiceAccountName, 915 Namespace: ns, 916 }, 917 ImagePullSecrets: []corev1.LocalObjectReference{ 918 {Name: "foo"}, 919 {Name: "bar"}, 920 }, 921 }) 922 923 pod := &api.Pod{ 924 Spec: api.PodSpec{ 925 ImagePullSecrets: []api.LocalObjectReference{{Name: "foo"}}, 926 }, 927 } 928 attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 929 err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil) 930 if err != nil { 931 t.Errorf("Unexpected error: %v", err) 932 } 933 934 if len(pod.Spec.ImagePullSecrets) != 1 || pod.Spec.ImagePullSecrets[0].Name != "foo" { 935 t.Errorf("unexpected image pull secrets: %v", pod.Spec.ImagePullSecrets) 936 } 937 } 938 939 func TestAddImagePullSecrets(t *testing.T) { 940 ns := "myns" 941 942 admit := NewServiceAccount() 943 informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) 944 admit.SetExternalKubeInformerFactory(informerFactory) 945 admit.LimitSecretReferences = true 946 947 sa := &corev1.ServiceAccount{ 948 ObjectMeta: metav1.ObjectMeta{ 949 Name: DefaultServiceAccountName, 950 Namespace: ns, 951 }, 952 ImagePullSecrets: []corev1.LocalObjectReference{ 953 {Name: "foo"}, 954 {Name: "bar"}, 955 }, 956 } 957 originalSA := sa.DeepCopy() 958 expected := []api.LocalObjectReference{ 959 {Name: "foo"}, 960 {Name: "bar"}, 961 } 962 // Add the default service account for the ns with a secret reference into the cache 963 informerFactory.Core().V1().ServiceAccounts().Informer().GetStore().Add(sa) 964 965 pod := &api.Pod{} 966 attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil) 967 err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil) 968 if err != nil { 969 t.Errorf("Unexpected error: %v", err) 970 } 971 972 assert.EqualValues(t, expected, pod.Spec.ImagePullSecrets, "expected %v, got %v", expected, pod.Spec.ImagePullSecrets) 973 974 pod.Spec.ImagePullSecrets[1] = api.LocalObjectReference{Name: "baz"} 975 if !reflect.DeepEqual(originalSA, sa) { 976 t.Errorf("accidentally mutated the ServiceAccount.ImagePullSecrets: %v", sa.ImagePullSecrets) 977 } 978 } 979 980 func testGenerateName(n string) string { 981 return n + "abc123" 982 } 983 984 var generatedVolumeName = testGenerateName(ServiceAccountVolumeName + "-")