k8s.io/kubernetes@v1.29.3/pkg/volume/projected/projected_test.go (about) 1 /* 2 Copyright 2015 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 projected 18 19 import ( 20 "crypto/ed25519" 21 "crypto/rand" 22 "crypto/x509" 23 "crypto/x509/pkix" 24 "encoding/pem" 25 "fmt" 26 "math/big" 27 "os" 28 "path/filepath" 29 "reflect" 30 "strings" 31 "testing" 32 33 "github.com/google/go-cmp/cmp" 34 authenticationv1 "k8s.io/api/authentication/v1" 35 certificatesv1alpha1 "k8s.io/api/certificates/v1alpha1" 36 v1 "k8s.io/api/core/v1" 37 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 38 "k8s.io/apimachinery/pkg/runtime" 39 "k8s.io/apimachinery/pkg/types" 40 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 41 clientset "k8s.io/client-go/kubernetes" 42 "k8s.io/client-go/kubernetes/fake" 43 clitesting "k8s.io/client-go/testing" 44 pkgauthenticationv1 "k8s.io/kubernetes/pkg/apis/authentication/v1" 45 pkgcorev1 "k8s.io/kubernetes/pkg/apis/core/v1" 46 "k8s.io/kubernetes/pkg/volume" 47 "k8s.io/kubernetes/pkg/volume/emptydir" 48 volumetest "k8s.io/kubernetes/pkg/volume/testing" 49 "k8s.io/kubernetes/pkg/volume/util" 50 utilptr "k8s.io/utils/pointer" 51 ) 52 53 func TestCollectDataWithSecret(t *testing.T) { 54 caseMappingMode := int32(0400) 55 cases := []struct { 56 name string 57 mappings []v1.KeyToPath 58 secret *v1.Secret 59 mode int32 60 optional bool 61 payload map[string]util.FileProjection 62 success bool 63 }{ 64 { 65 name: "no overrides", 66 secret: &v1.Secret{ 67 Data: map[string][]byte{ 68 "foo": []byte("foo"), 69 "bar": []byte("bar"), 70 }, 71 }, 72 mode: 0644, 73 payload: map[string]util.FileProjection{ 74 "foo": {Data: []byte("foo"), Mode: 0644}, 75 "bar": {Data: []byte("bar"), Mode: 0644}, 76 }, 77 success: true, 78 }, 79 { 80 name: "basic 1", 81 mappings: []v1.KeyToPath{ 82 { 83 Key: "foo", 84 Path: "path/to/foo.txt", 85 }, 86 }, 87 secret: &v1.Secret{ 88 Data: map[string][]byte{ 89 "foo": []byte("foo"), 90 "bar": []byte("bar"), 91 }, 92 }, 93 mode: 0644, 94 payload: map[string]util.FileProjection{ 95 "path/to/foo.txt": {Data: []byte("foo"), Mode: 0644}, 96 }, 97 success: true, 98 }, 99 { 100 name: "subdirs", 101 mappings: []v1.KeyToPath{ 102 { 103 Key: "foo", 104 Path: "path/to/1/2/3/foo.txt", 105 }, 106 }, 107 secret: &v1.Secret{ 108 Data: map[string][]byte{ 109 "foo": []byte("foo"), 110 "bar": []byte("bar"), 111 }, 112 }, 113 mode: 0644, 114 payload: map[string]util.FileProjection{ 115 "path/to/1/2/3/foo.txt": {Data: []byte("foo"), Mode: 0644}, 116 }, 117 success: true, 118 }, 119 { 120 name: "subdirs 2", 121 mappings: []v1.KeyToPath{ 122 { 123 Key: "foo", 124 Path: "path/to/1/2/3/foo.txt", 125 }, 126 }, 127 secret: &v1.Secret{ 128 Data: map[string][]byte{ 129 "foo": []byte("foo"), 130 "bar": []byte("bar"), 131 }, 132 }, 133 mode: 0644, 134 payload: map[string]util.FileProjection{ 135 "path/to/1/2/3/foo.txt": {Data: []byte("foo"), Mode: 0644}, 136 }, 137 success: true, 138 }, 139 { 140 name: "subdirs 3", 141 mappings: []v1.KeyToPath{ 142 { 143 Key: "foo", 144 Path: "path/to/1/2/3/foo.txt", 145 }, 146 { 147 Key: "bar", 148 Path: "another/path/to/the/esteemed/bar.bin", 149 }, 150 }, 151 secret: &v1.Secret{ 152 Data: map[string][]byte{ 153 "foo": []byte("foo"), 154 "bar": []byte("bar"), 155 }, 156 }, 157 mode: 0644, 158 payload: map[string]util.FileProjection{ 159 "path/to/1/2/3/foo.txt": {Data: []byte("foo"), Mode: 0644}, 160 "another/path/to/the/esteemed/bar.bin": {Data: []byte("bar"), Mode: 0644}, 161 }, 162 success: true, 163 }, 164 { 165 name: "non existent key", 166 mappings: []v1.KeyToPath{ 167 { 168 Key: "zab", 169 Path: "path/to/foo.txt", 170 }, 171 }, 172 secret: &v1.Secret{ 173 Data: map[string][]byte{ 174 "foo": []byte("foo"), 175 "bar": []byte("bar"), 176 }, 177 }, 178 mode: 0644, 179 success: false, 180 }, 181 { 182 name: "mapping with Mode", 183 mappings: []v1.KeyToPath{ 184 { 185 Key: "foo", 186 Path: "foo.txt", 187 Mode: &caseMappingMode, 188 }, 189 { 190 Key: "bar", 191 Path: "bar.bin", 192 Mode: &caseMappingMode, 193 }, 194 }, 195 secret: &v1.Secret{ 196 Data: map[string][]byte{ 197 "foo": []byte("foo"), 198 "bar": []byte("bar"), 199 }, 200 }, 201 mode: 0644, 202 payload: map[string]util.FileProjection{ 203 "foo.txt": {Data: []byte("foo"), Mode: caseMappingMode}, 204 "bar.bin": {Data: []byte("bar"), Mode: caseMappingMode}, 205 }, 206 success: true, 207 }, 208 { 209 name: "mapping with defaultMode", 210 mappings: []v1.KeyToPath{ 211 { 212 Key: "foo", 213 Path: "foo.txt", 214 }, 215 { 216 Key: "bar", 217 Path: "bar.bin", 218 }, 219 }, 220 secret: &v1.Secret{ 221 Data: map[string][]byte{ 222 "foo": []byte("foo"), 223 "bar": []byte("bar"), 224 }, 225 }, 226 mode: 0644, 227 payload: map[string]util.FileProjection{ 228 "foo.txt": {Data: []byte("foo"), Mode: 0644}, 229 "bar.bin": {Data: []byte("bar"), Mode: 0644}, 230 }, 231 success: true, 232 }, 233 { 234 name: "optional non existent key", 235 mappings: []v1.KeyToPath{ 236 { 237 Key: "zab", 238 Path: "path/to/foo.txt", 239 }, 240 }, 241 secret: &v1.Secret{ 242 Data: map[string][]byte{ 243 "foo": []byte("foo"), 244 "bar": []byte("bar"), 245 }, 246 }, 247 mode: 0644, 248 optional: true, 249 payload: map[string]util.FileProjection{}, 250 success: true, 251 }, 252 } 253 254 for _, tc := range cases { 255 t.Run(tc.name, func(t *testing.T) { 256 257 testNamespace := "test_projected_namespace" 258 tc.secret.ObjectMeta = metav1.ObjectMeta{ 259 Namespace: testNamespace, 260 Name: tc.name, 261 } 262 263 source := makeProjection(tc.name, utilptr.Int32Ptr(tc.mode), "secret") 264 source.Sources[0].Secret.Items = tc.mappings 265 source.Sources[0].Secret.Optional = &tc.optional 266 267 testPodUID := types.UID("test_pod_uid") 268 pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, UID: testPodUID}} 269 client := fake.NewSimpleClientset(tc.secret) 270 tempDir, host := newTestHost(t, client) 271 defer os.RemoveAll(tempDir) 272 var myVolumeMounter = projectedVolumeMounter{ 273 projectedVolume: &projectedVolume{ 274 sources: source.Sources, 275 podUID: pod.UID, 276 plugin: &projectedPlugin{ 277 host: host, 278 getSecret: host.GetSecretFunc(), 279 }, 280 }, 281 source: *source, 282 pod: pod, 283 } 284 285 actualPayload, err := myVolumeMounter.collectData(volume.MounterArgs{}) 286 if err != nil && tc.success { 287 t.Errorf("%v: unexpected failure making payload: %v", tc.name, err) 288 return 289 } 290 if err == nil && !tc.success { 291 t.Errorf("%v: unexpected success making payload", tc.name) 292 return 293 } 294 if !tc.success { 295 return 296 } 297 if e, a := tc.payload, actualPayload; !reflect.DeepEqual(e, a) { 298 t.Errorf("%v: expected and actual payload do not match", tc.name) 299 } 300 }) 301 } 302 } 303 304 func TestCollectDataWithConfigMap(t *testing.T) { 305 caseMappingMode := int32(0400) 306 cases := []struct { 307 name string 308 mappings []v1.KeyToPath 309 configMap *v1.ConfigMap 310 mode int32 311 optional bool 312 payload map[string]util.FileProjection 313 success bool 314 }{ 315 { 316 name: "no overrides", 317 configMap: &v1.ConfigMap{ 318 Data: map[string]string{ 319 "foo": "foo", 320 "bar": "bar", 321 }, 322 }, 323 mode: 0644, 324 payload: map[string]util.FileProjection{ 325 "foo": {Data: []byte("foo"), Mode: 0644}, 326 "bar": {Data: []byte("bar"), Mode: 0644}, 327 }, 328 success: true, 329 }, 330 { 331 name: "basic 1", 332 mappings: []v1.KeyToPath{ 333 { 334 Key: "foo", 335 Path: "path/to/foo.txt", 336 }, 337 }, 338 configMap: &v1.ConfigMap{ 339 Data: map[string]string{ 340 "foo": "foo", 341 "bar": "bar", 342 }, 343 }, 344 mode: 0644, 345 payload: map[string]util.FileProjection{ 346 "path/to/foo.txt": {Data: []byte("foo"), Mode: 0644}, 347 }, 348 success: true, 349 }, 350 { 351 name: "subdirs", 352 mappings: []v1.KeyToPath{ 353 { 354 Key: "foo", 355 Path: "path/to/1/2/3/foo.txt", 356 }, 357 }, 358 configMap: &v1.ConfigMap{ 359 Data: map[string]string{ 360 "foo": "foo", 361 "bar": "bar", 362 }, 363 }, 364 mode: 0644, 365 payload: map[string]util.FileProjection{ 366 "path/to/1/2/3/foo.txt": {Data: []byte("foo"), Mode: 0644}, 367 }, 368 success: true, 369 }, 370 { 371 name: "subdirs 2", 372 mappings: []v1.KeyToPath{ 373 { 374 Key: "foo", 375 Path: "path/to/1/2/3/foo.txt", 376 }, 377 }, 378 configMap: &v1.ConfigMap{ 379 Data: map[string]string{ 380 "foo": "foo", 381 "bar": "bar", 382 }, 383 }, 384 mode: 0644, 385 payload: map[string]util.FileProjection{ 386 "path/to/1/2/3/foo.txt": {Data: []byte("foo"), Mode: 0644}, 387 }, 388 success: true, 389 }, 390 { 391 name: "subdirs 3", 392 mappings: []v1.KeyToPath{ 393 { 394 Key: "foo", 395 Path: "path/to/1/2/3/foo.txt", 396 }, 397 { 398 Key: "bar", 399 Path: "another/path/to/the/esteemed/bar.bin", 400 }, 401 }, 402 configMap: &v1.ConfigMap{ 403 Data: map[string]string{ 404 "foo": "foo", 405 "bar": "bar", 406 }, 407 }, 408 mode: 0644, 409 payload: map[string]util.FileProjection{ 410 "path/to/1/2/3/foo.txt": {Data: []byte("foo"), Mode: 0644}, 411 "another/path/to/the/esteemed/bar.bin": {Data: []byte("bar"), Mode: 0644}, 412 }, 413 success: true, 414 }, 415 { 416 name: "non existent key", 417 mappings: []v1.KeyToPath{ 418 { 419 Key: "zab", 420 Path: "path/to/foo.txt", 421 }, 422 }, 423 configMap: &v1.ConfigMap{ 424 Data: map[string]string{ 425 "foo": "foo", 426 "bar": "bar", 427 }, 428 }, 429 mode: 0644, 430 success: false, 431 }, 432 { 433 name: "mapping with Mode", 434 mappings: []v1.KeyToPath{ 435 { 436 Key: "foo", 437 Path: "foo.txt", 438 Mode: &caseMappingMode, 439 }, 440 { 441 Key: "bar", 442 Path: "bar.bin", 443 Mode: &caseMappingMode, 444 }, 445 }, 446 configMap: &v1.ConfigMap{ 447 Data: map[string]string{ 448 "foo": "foo", 449 "bar": "bar", 450 }, 451 }, 452 mode: 0644, 453 payload: map[string]util.FileProjection{ 454 "foo.txt": {Data: []byte("foo"), Mode: caseMappingMode}, 455 "bar.bin": {Data: []byte("bar"), Mode: caseMappingMode}, 456 }, 457 success: true, 458 }, 459 { 460 name: "mapping with defaultMode", 461 mappings: []v1.KeyToPath{ 462 { 463 Key: "foo", 464 Path: "foo.txt", 465 }, 466 { 467 Key: "bar", 468 Path: "bar.bin", 469 }, 470 }, 471 configMap: &v1.ConfigMap{ 472 Data: map[string]string{ 473 "foo": "foo", 474 "bar": "bar", 475 }, 476 }, 477 mode: 0644, 478 payload: map[string]util.FileProjection{ 479 "foo.txt": {Data: []byte("foo"), Mode: 0644}, 480 "bar.bin": {Data: []byte("bar"), Mode: 0644}, 481 }, 482 success: true, 483 }, 484 { 485 name: "optional non existent key", 486 mappings: []v1.KeyToPath{ 487 { 488 Key: "zab", 489 Path: "path/to/foo.txt", 490 }, 491 }, 492 configMap: &v1.ConfigMap{ 493 Data: map[string]string{ 494 "foo": "foo", 495 "bar": "bar", 496 }, 497 }, 498 mode: 0644, 499 optional: true, 500 payload: map[string]util.FileProjection{}, 501 success: true, 502 }, 503 } 504 for _, tc := range cases { 505 t.Run(tc.name, func(t *testing.T) { 506 testNamespace := "test_projected_namespace" 507 tc.configMap.ObjectMeta = metav1.ObjectMeta{ 508 Namespace: testNamespace, 509 Name: tc.name, 510 } 511 512 source := makeProjection(tc.name, utilptr.Int32Ptr(tc.mode), "configMap") 513 source.Sources[0].ConfigMap.Items = tc.mappings 514 source.Sources[0].ConfigMap.Optional = &tc.optional 515 516 testPodUID := types.UID("test_pod_uid") 517 pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, UID: testPodUID}} 518 client := fake.NewSimpleClientset(tc.configMap) 519 tempDir, host := newTestHost(t, client) 520 defer os.RemoveAll(tempDir) 521 var myVolumeMounter = projectedVolumeMounter{ 522 projectedVolume: &projectedVolume{ 523 sources: source.Sources, 524 podUID: pod.UID, 525 plugin: &projectedPlugin{ 526 host: host, 527 getConfigMap: host.GetConfigMapFunc(), 528 }, 529 }, 530 source: *source, 531 pod: pod, 532 } 533 534 actualPayload, err := myVolumeMounter.collectData(volume.MounterArgs{}) 535 if err != nil && tc.success { 536 t.Errorf("%v: unexpected failure making payload: %v", tc.name, err) 537 return 538 } 539 if err == nil && !tc.success { 540 t.Errorf("%v: unexpected success making payload", tc.name) 541 return 542 } 543 if !tc.success { 544 return 545 } 546 if e, a := tc.payload, actualPayload; !reflect.DeepEqual(e, a) { 547 t.Errorf("%v: expected and actual payload do not match", tc.name) 548 } 549 }) 550 } 551 } 552 553 func TestCollectDataWithDownwardAPI(t *testing.T) { 554 testNamespace := "test_projected_namespace" 555 testPodUID := types.UID("test_pod_uid") 556 testPodName := "podName" 557 558 cases := []struct { 559 name string 560 volumeFile []v1.DownwardAPIVolumeFile 561 pod *v1.Pod 562 mode int32 563 payload map[string]util.FileProjection 564 success bool 565 }{ 566 { 567 name: "annotation", 568 volumeFile: []v1.DownwardAPIVolumeFile{ 569 {Path: "annotation", FieldRef: &v1.ObjectFieldSelector{ 570 FieldPath: "metadata.annotations['a1']"}}}, 571 pod: &v1.Pod{ 572 ObjectMeta: metav1.ObjectMeta{ 573 Name: testPodName, 574 Namespace: testNamespace, 575 Annotations: map[string]string{ 576 "a1": "value1", 577 "a2": "value2", 578 }, 579 UID: testPodUID}, 580 }, 581 mode: 0644, 582 payload: map[string]util.FileProjection{ 583 "annotation": {Data: []byte("value1"), Mode: 0644}, 584 }, 585 success: true, 586 }, 587 { 588 name: "annotation-error", 589 volumeFile: []v1.DownwardAPIVolumeFile{ 590 {Path: "annotation", FieldRef: &v1.ObjectFieldSelector{ 591 FieldPath: "metadata.annotations['']"}}}, 592 pod: &v1.Pod{ 593 ObjectMeta: metav1.ObjectMeta{ 594 Name: testPodName, 595 Namespace: testNamespace, 596 Annotations: map[string]string{ 597 "a1": "value1", 598 "a2": "value2", 599 }, 600 UID: testPodUID}, 601 }, 602 mode: 0644, 603 payload: map[string]util.FileProjection{ 604 "annotation": {Data: []byte("does-not-matter-because-this-test-case-will-fail-anyway"), Mode: 0644}, 605 }, 606 success: false, 607 }, 608 { 609 name: "labels", 610 volumeFile: []v1.DownwardAPIVolumeFile{ 611 {Path: "labels", FieldRef: &v1.ObjectFieldSelector{ 612 FieldPath: "metadata.labels"}}}, 613 pod: &v1.Pod{ 614 ObjectMeta: metav1.ObjectMeta{ 615 Name: testPodName, 616 Namespace: testNamespace, 617 Labels: map[string]string{ 618 "key1": "value1", 619 "key2": "value2"}, 620 UID: testPodUID}, 621 }, 622 mode: 0644, 623 payload: map[string]util.FileProjection{ 624 "labels": {Data: []byte("key1=\"value1\"\nkey2=\"value2\""), Mode: 0644}, 625 }, 626 success: true, 627 }, 628 { 629 name: "annotations", 630 volumeFile: []v1.DownwardAPIVolumeFile{ 631 {Path: "annotations", FieldRef: &v1.ObjectFieldSelector{ 632 FieldPath: "metadata.annotations"}}}, 633 pod: &v1.Pod{ 634 ObjectMeta: metav1.ObjectMeta{ 635 Name: testPodName, 636 Namespace: testNamespace, 637 Annotations: map[string]string{ 638 "a1": "value1", 639 "a2": "value2"}, 640 UID: testPodUID}, 641 }, 642 mode: 0644, 643 payload: map[string]util.FileProjection{ 644 "annotations": {Data: []byte("a1=\"value1\"\na2=\"value2\""), Mode: 0644}, 645 }, 646 success: true, 647 }, 648 { 649 name: "name", 650 volumeFile: []v1.DownwardAPIVolumeFile{ 651 {Path: "name_file_name", FieldRef: &v1.ObjectFieldSelector{ 652 FieldPath: "metadata.name"}}}, 653 pod: &v1.Pod{ 654 ObjectMeta: metav1.ObjectMeta{ 655 Name: testPodName, 656 Namespace: testNamespace, 657 UID: testPodUID}, 658 }, 659 mode: 0644, 660 payload: map[string]util.FileProjection{ 661 "name_file_name": {Data: []byte(testPodName), Mode: 0644}, 662 }, 663 success: true, 664 }, 665 { 666 name: "namespace", 667 volumeFile: []v1.DownwardAPIVolumeFile{ 668 {Path: "namespace_file_name", FieldRef: &v1.ObjectFieldSelector{ 669 FieldPath: "metadata.namespace"}}}, 670 pod: &v1.Pod{ 671 ObjectMeta: metav1.ObjectMeta{ 672 Name: testPodName, 673 Namespace: testNamespace, 674 UID: testPodUID}, 675 }, 676 mode: 0644, 677 payload: map[string]util.FileProjection{ 678 "namespace_file_name": {Data: []byte(testNamespace), Mode: 0644}, 679 }, 680 success: true, 681 }, 682 } 683 684 for _, tc := range cases { 685 t.Run(tc.name, func(t *testing.T) { 686 source := makeProjection("", utilptr.Int32Ptr(tc.mode), "downwardAPI") 687 source.Sources[0].DownwardAPI.Items = tc.volumeFile 688 689 client := fake.NewSimpleClientset(tc.pod) 690 tempDir, host := newTestHost(t, client) 691 defer os.RemoveAll(tempDir) 692 var myVolumeMounter = projectedVolumeMounter{ 693 projectedVolume: &projectedVolume{ 694 sources: source.Sources, 695 podUID: tc.pod.UID, 696 plugin: &projectedPlugin{ 697 host: host, 698 }, 699 }, 700 source: *source, 701 pod: tc.pod, 702 } 703 704 actualPayload, err := myVolumeMounter.collectData(volume.MounterArgs{}) 705 if err != nil && tc.success { 706 t.Errorf("%v: unexpected failure making payload: %v", tc.name, err) 707 return 708 } 709 if err == nil && !tc.success { 710 t.Errorf("%v: unexpected success making payload", tc.name) 711 return 712 } 713 if !tc.success { 714 return 715 } 716 if e, a := tc.payload, actualPayload; !reflect.DeepEqual(e, a) { 717 t.Errorf("%v: expected and actual payload do not match", tc.name) 718 } 719 }) 720 721 } 722 } 723 724 func TestCollectDataWithServiceAccountToken(t *testing.T) { 725 scheme := runtime.NewScheme() 726 utilruntime.Must(pkgauthenticationv1.RegisterDefaults(scheme)) 727 utilruntime.Must(pkgcorev1.RegisterDefaults(scheme)) 728 729 minute := int64(60) 730 cases := []struct { 731 name string 732 svcacct string 733 audience string 734 defaultMode *int32 735 fsUser *int64 736 fsGroup *int64 737 expiration *int64 738 path string 739 740 wantPayload map[string]util.FileProjection 741 wantErr error 742 }{ 743 { 744 name: "good service account", 745 audience: "https://example.com", 746 defaultMode: utilptr.Int32Ptr(0644), 747 path: "token", 748 expiration: &minute, 749 750 wantPayload: map[string]util.FileProjection{ 751 "token": {Data: []byte("test_projected_namespace:foo:60:[https://example.com]"), Mode: 0644}, 752 }, 753 }, 754 { 755 name: "good service account other path", 756 audience: "https://example.com", 757 defaultMode: utilptr.Int32Ptr(0644), 758 path: "other-token", 759 expiration: &minute, 760 wantPayload: map[string]util.FileProjection{ 761 "other-token": {Data: []byte("test_projected_namespace:foo:60:[https://example.com]"), Mode: 0644}, 762 }, 763 }, 764 { 765 name: "good service account defaults audience", 766 defaultMode: utilptr.Int32Ptr(0644), 767 path: "token", 768 expiration: &minute, 769 770 wantPayload: map[string]util.FileProjection{ 771 "token": {Data: []byte("test_projected_namespace:foo:60:[https://api]"), Mode: 0644}, 772 }, 773 }, 774 { 775 name: "good service account defaults expiration", 776 defaultMode: utilptr.Int32Ptr(0644), 777 path: "token", 778 779 wantPayload: map[string]util.FileProjection{ 780 "token": {Data: []byte("test_projected_namespace:foo:3600:[https://api]"), Mode: 0644}, 781 }, 782 }, 783 { 784 name: "no default mode", 785 path: "token", 786 wantErr: fmt.Errorf("no defaultMode used, not even the default value for it"), 787 }, 788 { 789 name: "fsUser != nil", 790 defaultMode: utilptr.Int32Ptr(0644), 791 fsUser: utilptr.Int64Ptr(1000), 792 path: "token", 793 wantPayload: map[string]util.FileProjection{ 794 "token": { 795 Data: []byte("test_projected_namespace:foo:3600:[https://api]"), 796 Mode: 0600, 797 FsUser: utilptr.Int64Ptr(1000), 798 }, 799 }, 800 }, 801 { 802 name: "fsGroup != nil", 803 defaultMode: utilptr.Int32Ptr(0644), 804 fsGroup: utilptr.Int64Ptr(1000), 805 path: "token", 806 wantPayload: map[string]util.FileProjection{ 807 "token": { 808 Data: []byte("test_projected_namespace:foo:3600:[https://api]"), 809 Mode: 0600, 810 }, 811 }, 812 }, 813 { 814 name: "fsUser != nil && fsGroup != nil", 815 defaultMode: utilptr.Int32Ptr(0644), 816 fsGroup: utilptr.Int64Ptr(1000), 817 fsUser: utilptr.Int64Ptr(1000), 818 path: "token", 819 wantPayload: map[string]util.FileProjection{ 820 "token": { 821 Data: []byte("test_projected_namespace:foo:3600:[https://api]"), 822 Mode: 0600, 823 FsUser: utilptr.Int64Ptr(1000), 824 }, 825 }, 826 }, 827 } 828 829 for _, tc := range cases { 830 t.Run(tc.name, func(t *testing.T) { 831 testNamespace := "test_projected_namespace" 832 source := makeProjection(tc.name, tc.defaultMode, "serviceAccountToken") 833 source.Sources[0].ServiceAccountToken.Audience = tc.audience 834 source.Sources[0].ServiceAccountToken.ExpirationSeconds = tc.expiration 835 source.Sources[0].ServiceAccountToken.Path = tc.path 836 837 testPodUID := types.UID("test_pod_uid") 838 pod := &v1.Pod{ 839 ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, UID: testPodUID}, 840 Spec: v1.PodSpec{ServiceAccountName: "foo"}, 841 } 842 scheme.Default(pod) 843 844 client := &fake.Clientset{} 845 client.AddReactor("create", "serviceaccounts", clitesting.ReactionFunc(func(action clitesting.Action) (bool, runtime.Object, error) { 846 tr := action.(clitesting.CreateAction).GetObject().(*authenticationv1.TokenRequest) 847 scheme.Default(tr) 848 if len(tr.Spec.Audiences) == 0 { 849 tr.Spec.Audiences = []string{"https://api"} 850 } 851 tr.Status.Token = fmt.Sprintf("%v:%v:%d:%v", action.GetNamespace(), "foo", *tr.Spec.ExpirationSeconds, tr.Spec.Audiences) 852 return true, tr, nil 853 })) 854 855 tempDir, host := newTestHost(t, client) 856 defer os.RemoveAll(tempDir) 857 858 var myVolumeMounter = projectedVolumeMounter{ 859 projectedVolume: &projectedVolume{ 860 sources: source.Sources, 861 podUID: pod.UID, 862 plugin: &projectedPlugin{ 863 host: host, 864 getServiceAccountToken: host.GetServiceAccountTokenFunc(), 865 }, 866 }, 867 source: *source, 868 pod: pod, 869 } 870 871 gotPayload, err := myVolumeMounter.collectData(volume.MounterArgs{FsUser: tc.fsUser, FsGroup: tc.fsGroup}) 872 if err != nil && (tc.wantErr == nil || tc.wantErr.Error() != err.Error()) { 873 t.Fatalf("collectData() = unexpected err: %v", err) 874 } 875 if diff := cmp.Diff(tc.wantPayload, gotPayload); diff != "" { 876 t.Errorf("collectData() = unexpected diff (-want +got):\n%s", diff) 877 } 878 }) 879 } 880 } 881 882 func TestCollectDataWithClusterTrustBundle(t *testing.T) { 883 // This test is limited by the use of a fake clientset and volume host. We 884 // can't meaningfully test that label selectors end up doing the correct 885 // thing for example. 886 887 goodCert1 := mustMakeRoot(t, "root1") 888 889 testCases := []struct { 890 name string 891 892 source v1.ProjectedVolumeSource 893 bundles []runtime.Object 894 895 fsUser *int64 896 fsGroup *int64 897 898 wantPayload map[string]util.FileProjection 899 wantErr error 900 }{ 901 { 902 name: "single ClusterTrustBundle by name", 903 source: v1.ProjectedVolumeSource{ 904 Sources: []v1.VolumeProjection{ 905 { 906 ClusterTrustBundle: &v1.ClusterTrustBundleProjection{ 907 Name: utilptr.String("foo"), 908 Path: "bundle.pem", 909 }, 910 }, 911 }, 912 DefaultMode: utilptr.Int32(0644), 913 }, 914 bundles: []runtime.Object{ 915 &certificatesv1alpha1.ClusterTrustBundle{ 916 ObjectMeta: metav1.ObjectMeta{ 917 Name: "foo", 918 }, 919 Spec: certificatesv1alpha1.ClusterTrustBundleSpec{ 920 TrustBundle: string(goodCert1), 921 }, 922 }, 923 }, 924 wantPayload: map[string]util.FileProjection{ 925 "bundle.pem": { 926 Data: []byte(goodCert1), 927 Mode: 0644, 928 }, 929 }, 930 }, 931 { 932 name: "single ClusterTrustBundle by signer name", 933 source: v1.ProjectedVolumeSource{ 934 Sources: []v1.VolumeProjection{ 935 { 936 ClusterTrustBundle: &v1.ClusterTrustBundleProjection{ 937 SignerName: utilptr.String("foo.example/bar"), // Note: fake client doesn't understand selection by signer name. 938 LabelSelector: &metav1.LabelSelector{ 939 MatchLabels: map[string]string{ 940 "key": "non-value", // Note: fake client doesn't actually act on label selectors. 941 }, 942 }, 943 Path: "bundle.pem", 944 }, 945 }, 946 }, 947 DefaultMode: utilptr.Int32(0644), 948 }, 949 bundles: []runtime.Object{ 950 &certificatesv1alpha1.ClusterTrustBundle{ 951 ObjectMeta: metav1.ObjectMeta{ 952 Name: "foo:example:bar", 953 Labels: map[string]string{ 954 "key": "value", 955 }, 956 }, 957 Spec: certificatesv1alpha1.ClusterTrustBundleSpec{ 958 SignerName: "foo.example/bar", 959 TrustBundle: string(goodCert1), 960 }, 961 }, 962 }, 963 wantPayload: map[string]util.FileProjection{ 964 "bundle.pem": { 965 Data: []byte(goodCert1), 966 Mode: 0644, 967 }, 968 }, 969 }, 970 { 971 name: "single ClusterTrustBundle by name, non-default mode", 972 source: v1.ProjectedVolumeSource{ 973 Sources: []v1.VolumeProjection{ 974 { 975 ClusterTrustBundle: &v1.ClusterTrustBundleProjection{ 976 Name: utilptr.String("foo"), 977 Path: "bundle.pem", 978 }, 979 }, 980 }, 981 DefaultMode: utilptr.Int32(0600), 982 }, 983 bundles: []runtime.Object{ 984 &certificatesv1alpha1.ClusterTrustBundle{ 985 ObjectMeta: metav1.ObjectMeta{ 986 Name: "foo", 987 }, 988 Spec: certificatesv1alpha1.ClusterTrustBundleSpec{ 989 TrustBundle: string(goodCert1), 990 }, 991 }, 992 }, 993 wantPayload: map[string]util.FileProjection{ 994 "bundle.pem": { 995 Data: []byte(goodCert1), 996 Mode: 0600, 997 }, 998 }, 999 }, 1000 } 1001 1002 for _, tc := range testCases { 1003 t.Run(tc.name, func(t *testing.T) { 1004 pod := &v1.Pod{ 1005 ObjectMeta: metav1.ObjectMeta{ 1006 Namespace: "default", 1007 UID: types.UID("test_pod_uid"), 1008 }, 1009 Spec: v1.PodSpec{ServiceAccountName: "foo"}, 1010 } 1011 1012 client := fake.NewSimpleClientset(tc.bundles...) 1013 1014 tempDir, host := newTestHost(t, client) 1015 defer os.RemoveAll(tempDir) 1016 1017 var myVolumeMounter = projectedVolumeMounter{ 1018 projectedVolume: &projectedVolume{ 1019 sources: tc.source.Sources, 1020 podUID: pod.UID, 1021 plugin: &projectedPlugin{ 1022 host: host, 1023 kvHost: host.(volume.KubeletVolumeHost), 1024 }, 1025 }, 1026 source: tc.source, 1027 pod: pod, 1028 } 1029 1030 gotPayload, err := myVolumeMounter.collectData(volume.MounterArgs{FsUser: tc.fsUser, FsGroup: tc.fsGroup}) 1031 if err != nil { 1032 t.Fatalf("Unexpected failure making payload: %v", err) 1033 } 1034 if diff := cmp.Diff(tc.wantPayload, gotPayload); diff != "" { 1035 t.Fatalf("Bad payload; diff (-want +got)\n%s", diff) 1036 } 1037 }) 1038 } 1039 } 1040 1041 func newTestHost(t *testing.T, clientset clientset.Interface) (string, volume.VolumeHost) { 1042 tempDir, err := os.MkdirTemp("", "projected_volume_test.") 1043 if err != nil { 1044 t.Fatalf("can't make a temp rootdir: %v", err) 1045 } 1046 1047 return tempDir, volumetest.NewFakeKubeletVolumeHost(t, tempDir, clientset, emptydir.ProbeVolumePlugins()) 1048 } 1049 1050 func TestCanSupport(t *testing.T) { 1051 pluginMgr := volume.VolumePluginMgr{} 1052 tempDir, host := newTestHost(t, nil) 1053 defer os.RemoveAll(tempDir) 1054 pluginMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, host) 1055 1056 plugin, err := pluginMgr.FindPluginByName(projectedPluginName) 1057 if err != nil { 1058 t.Fatal("Can't find the plugin by name") 1059 } 1060 if plugin.GetPluginName() != projectedPluginName { 1061 t.Errorf("Wrong name: %s", plugin.GetPluginName()) 1062 } 1063 if !plugin.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{Projected: &v1.ProjectedVolumeSource{}}}}) { 1064 t.Errorf("Expected true") 1065 } 1066 if plugin.CanSupport(&volume.Spec{}) { 1067 t.Errorf("Expected false") 1068 } 1069 } 1070 1071 func TestPlugin(t *testing.T) { 1072 var ( 1073 testPodUID = types.UID("test_pod_uid") 1074 testVolumeName = "test_volume_name" 1075 testNamespace = "test_projected_namespace" 1076 testName = "test_projected_name" 1077 1078 volumeSpec = makeVolumeSpec(testVolumeName, testName, 0644) 1079 secret = makeSecret(testNamespace, testName) 1080 client = fake.NewSimpleClientset(&secret) 1081 pluginMgr = volume.VolumePluginMgr{} 1082 rootDir, host = newTestHost(t, client) 1083 ) 1084 defer os.RemoveAll(rootDir) 1085 pluginMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, host) 1086 1087 plugin, err := pluginMgr.FindPluginByName(projectedPluginName) 1088 if err != nil { 1089 t.Fatal("Can't find the plugin by name") 1090 } 1091 1092 pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, UID: testPodUID}} 1093 mounter, err := plugin.NewMounter(volume.NewSpecFromVolume(volumeSpec), pod, volume.VolumeOptions{}) 1094 if err != nil { 1095 t.Errorf("Failed to make a new Mounter: %v", err) 1096 } 1097 if mounter == nil { 1098 t.Errorf("Got a nil Mounter") 1099 } 1100 1101 volumePath := mounter.GetPath() 1102 if !strings.HasSuffix(volumePath, filepath.Join("pods/test_pod_uid/volumes/kubernetes.io~projected", testVolumeName)) { 1103 t.Errorf("Got unexpected path: %s", volumePath) 1104 } 1105 1106 err = mounter.SetUp(volume.MounterArgs{}) 1107 if err != nil { 1108 t.Errorf("Failed to setup volume: %v", err) 1109 } 1110 if _, err := os.Stat(volumePath); err != nil { 1111 if os.IsNotExist(err) { 1112 t.Errorf("SetUp() failed, volume path not created: %s", volumePath) 1113 } else { 1114 t.Errorf("SetUp() failed: %v", err) 1115 } 1116 } 1117 1118 // secret volume should create its own empty wrapper path 1119 podWrapperMetadataDir := fmt.Sprintf("%v/pods/test_pod_uid/plugins/kubernetes.io~empty-dir/wrapped_test_volume_name", rootDir) 1120 1121 if _, err := os.Stat(podWrapperMetadataDir); err != nil { 1122 if os.IsNotExist(err) { 1123 t.Errorf("SetUp() failed, empty-dir wrapper path is not created: %s", podWrapperMetadataDir) 1124 } else { 1125 t.Errorf("SetUp() failed: %v", err) 1126 } 1127 } 1128 doTestSecretDataInVolume(volumePath, secret, t) 1129 defer doTestCleanAndTeardown(plugin, testPodUID, testVolumeName, volumePath, t) 1130 } 1131 1132 func TestInvalidPathProjected(t *testing.T) { 1133 var ( 1134 testPodUID = types.UID("test_pod_uid") 1135 testVolumeName = "test_volume_name" 1136 testNamespace = "test_projected_namespace" 1137 testName = "test_projected_name" 1138 1139 volumeSpec = makeVolumeSpec(testVolumeName, testName, 0644) 1140 secret = makeSecret(testNamespace, testName) 1141 client = fake.NewSimpleClientset(&secret) 1142 pluginMgr = volume.VolumePluginMgr{} 1143 rootDir, host = newTestHost(t, client) 1144 ) 1145 volumeSpec.Projected.Sources[0].Secret.Items = []v1.KeyToPath{ 1146 {Key: "missing", Path: "missing"}, 1147 } 1148 1149 defer os.RemoveAll(rootDir) 1150 pluginMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, host) 1151 1152 plugin, err := pluginMgr.FindPluginByName(projectedPluginName) 1153 if err != nil { 1154 t.Fatal("Can't find the plugin by name") 1155 } 1156 1157 pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, UID: testPodUID}} 1158 mounter, err := plugin.NewMounter(volume.NewSpecFromVolume(volumeSpec), pod, volume.VolumeOptions{}) 1159 if err != nil { 1160 t.Errorf("Failed to make a new Mounter: %v", err) 1161 } 1162 if mounter == nil { 1163 t.Errorf("Got a nil Mounter") 1164 } 1165 1166 volumePath := mounter.GetPath() 1167 if !strings.HasSuffix(volumePath, filepath.Join("pods/test_pod_uid/volumes/kubernetes.io~projected", testVolumeName)) { 1168 t.Errorf("Got unexpected path: %s", volumePath) 1169 } 1170 1171 var mounterArgs volume.MounterArgs 1172 err = mounter.SetUp(mounterArgs) 1173 if err == nil { 1174 t.Errorf("Expected error while setting up secret") 1175 } 1176 1177 _, err = os.Stat(volumePath) 1178 if err == nil { 1179 t.Errorf("Expected path %s to not exist", volumePath) 1180 } 1181 } 1182 1183 // Test the case where the plugin's ready file exists, but the volume dir is not a 1184 // mountpoint, which is the state the system will be in after reboot. The dir 1185 // should be mounter and the secret data written to it. 1186 func TestPluginReboot(t *testing.T) { 1187 var ( 1188 testPodUID = types.UID("test_pod_uid3") 1189 testVolumeName = "test_volume_name" 1190 testNamespace = "test_secret_namespace" 1191 testName = "test_secret_name" 1192 1193 volumeSpec = makeVolumeSpec(testVolumeName, testName, 0644) 1194 secret = makeSecret(testNamespace, testName) 1195 client = fake.NewSimpleClientset(&secret) 1196 pluginMgr = volume.VolumePluginMgr{} 1197 rootDir, host = newTestHost(t, client) 1198 ) 1199 defer os.RemoveAll(rootDir) 1200 pluginMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, host) 1201 1202 plugin, err := pluginMgr.FindPluginByName(projectedPluginName) 1203 if err != nil { 1204 t.Fatal("Can't find the plugin by name") 1205 } 1206 1207 pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, UID: testPodUID}} 1208 mounter, err := plugin.NewMounter(volume.NewSpecFromVolume(volumeSpec), pod, volume.VolumeOptions{}) 1209 if err != nil { 1210 t.Errorf("Failed to make a new Mounter: %v", err) 1211 } 1212 if mounter == nil { 1213 t.Errorf("Got a nil Mounter") 1214 } 1215 1216 podMetadataDir := fmt.Sprintf("%v/pods/test_pod_uid3/plugins/kubernetes.io~projected/test_volume_name", rootDir) 1217 util.SetReady(podMetadataDir) 1218 volumePath := mounter.GetPath() 1219 if !strings.HasSuffix(volumePath, filepath.FromSlash("pods/test_pod_uid3/volumes/kubernetes.io~projected/test_volume_name")) { 1220 t.Errorf("Got unexpected path: %s", volumePath) 1221 } 1222 1223 err = mounter.SetUp(volume.MounterArgs{}) 1224 if err != nil { 1225 t.Errorf("Failed to setup volume: %v", err) 1226 } 1227 if _, err := os.Stat(volumePath); err != nil { 1228 if os.IsNotExist(err) { 1229 t.Errorf("SetUp() failed, volume path not created: %s", volumePath) 1230 } else { 1231 t.Errorf("SetUp() failed: %v", err) 1232 } 1233 } 1234 1235 doTestSecretDataInVolume(volumePath, secret, t) 1236 doTestCleanAndTeardown(plugin, testPodUID, testVolumeName, volumePath, t) 1237 } 1238 1239 func TestPluginOptional(t *testing.T) { 1240 var ( 1241 testPodUID = types.UID("test_pod_uid") 1242 testVolumeName = "test_volume_name" 1243 testNamespace = "test_secret_namespace" 1244 testName = "test_secret_name" 1245 trueVal = true 1246 1247 volumeSpec = makeVolumeSpec(testVolumeName, testName, 0644) 1248 client = fake.NewSimpleClientset() 1249 pluginMgr = volume.VolumePluginMgr{} 1250 rootDir, host = newTestHost(t, client) 1251 ) 1252 volumeSpec.VolumeSource.Projected.Sources[0].Secret.Optional = &trueVal 1253 defer os.RemoveAll(rootDir) 1254 pluginMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, host) 1255 1256 plugin, err := pluginMgr.FindPluginByName(projectedPluginName) 1257 if err != nil { 1258 t.Fatal("Can't find the plugin by name") 1259 } 1260 1261 pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, UID: testPodUID}} 1262 mounter, err := plugin.NewMounter(volume.NewSpecFromVolume(volumeSpec), pod, volume.VolumeOptions{}) 1263 if err != nil { 1264 t.Errorf("Failed to make a new Mounter: %v", err) 1265 } 1266 if mounter == nil { 1267 t.Errorf("Got a nil Mounter") 1268 } 1269 1270 volumePath := mounter.GetPath() 1271 if !strings.HasSuffix(volumePath, filepath.FromSlash("pods/test_pod_uid/volumes/kubernetes.io~projected/test_volume_name")) { 1272 t.Errorf("Got unexpected path: %s", volumePath) 1273 } 1274 1275 err = mounter.SetUp(volume.MounterArgs{}) 1276 if err != nil { 1277 t.Errorf("Failed to setup volume: %v", err) 1278 } 1279 if _, err := os.Stat(volumePath); err != nil { 1280 if os.IsNotExist(err) { 1281 t.Errorf("SetUp() failed, volume path not created: %s", volumePath) 1282 } else { 1283 t.Errorf("SetUp() failed: %v", err) 1284 } 1285 } 1286 1287 // secret volume should create its own empty wrapper path 1288 podWrapperMetadataDir := fmt.Sprintf("%v/pods/test_pod_uid/plugins/kubernetes.io~empty-dir/wrapped_test_volume_name", rootDir) 1289 1290 if _, err := os.Stat(podWrapperMetadataDir); err != nil { 1291 if os.IsNotExist(err) { 1292 t.Errorf("SetUp() failed, empty-dir wrapper path is not created: %s", podWrapperMetadataDir) 1293 } else { 1294 t.Errorf("SetUp() failed: %v", err) 1295 } 1296 } 1297 1298 datadirSymlink := filepath.Join(volumePath, "..data") 1299 datadir, err := os.Readlink(datadirSymlink) 1300 if err != nil && os.IsNotExist(err) { 1301 t.Fatalf("couldn't find volume path's data dir, %s", datadirSymlink) 1302 } else if err != nil { 1303 t.Fatalf("couldn't read symlink, %s", datadirSymlink) 1304 } 1305 datadirPath := filepath.Join(volumePath, datadir) 1306 1307 infos, err := os.ReadDir(volumePath) 1308 if err != nil { 1309 t.Fatalf("couldn't find volume path, %s", volumePath) 1310 } 1311 if len(infos) != 0 { 1312 for _, fi := range infos { 1313 if fi.Name() != "..data" && fi.Name() != datadir { 1314 t.Errorf("empty data volume directory, %s, is not empty. Contains: %s", datadirSymlink, fi.Name()) 1315 } 1316 } 1317 } 1318 1319 infos, err = os.ReadDir(datadirPath) 1320 if err != nil { 1321 t.Fatalf("couldn't find volume data path, %s", datadirPath) 1322 } 1323 if len(infos) != 0 { 1324 t.Errorf("empty data directory, %s, is not empty. Contains: %s", datadirSymlink, infos[0].Name()) 1325 } 1326 1327 defer doTestCleanAndTeardown(plugin, testPodUID, testVolumeName, volumePath, t) 1328 } 1329 1330 func TestPluginOptionalKeys(t *testing.T) { 1331 var ( 1332 testPodUID = types.UID("test_pod_uid") 1333 testVolumeName = "test_volume_name" 1334 testNamespace = "test_secret_namespace" 1335 testName = "test_secret_name" 1336 trueVal = true 1337 1338 volumeSpec = makeVolumeSpec(testVolumeName, testName, 0644) 1339 secret = makeSecret(testNamespace, testName) 1340 client = fake.NewSimpleClientset(&secret) 1341 pluginMgr = volume.VolumePluginMgr{} 1342 rootDir, host = newTestHost(t, client) 1343 ) 1344 volumeSpec.VolumeSource.Projected.Sources[0].Secret.Items = []v1.KeyToPath{ 1345 {Key: "data-1", Path: "data-1"}, 1346 {Key: "data-2", Path: "data-2"}, 1347 {Key: "data-3", Path: "data-3"}, 1348 {Key: "missing", Path: "missing"}, 1349 } 1350 volumeSpec.VolumeSource.Projected.Sources[0].Secret.Optional = &trueVal 1351 defer os.RemoveAll(rootDir) 1352 pluginMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, host) 1353 1354 plugin, err := pluginMgr.FindPluginByName(projectedPluginName) 1355 if err != nil { 1356 t.Fatal("Can't find the plugin by name") 1357 } 1358 1359 pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, UID: testPodUID}} 1360 mounter, err := plugin.NewMounter(volume.NewSpecFromVolume(volumeSpec), pod, volume.VolumeOptions{}) 1361 if err != nil { 1362 t.Errorf("Failed to make a new Mounter: %v", err) 1363 } 1364 if mounter == nil { 1365 t.Errorf("Got a nil Mounter") 1366 } 1367 1368 volumePath := mounter.GetPath() 1369 if !strings.HasSuffix(volumePath, filepath.FromSlash("pods/test_pod_uid/volumes/kubernetes.io~projected/test_volume_name")) { 1370 t.Errorf("Got unexpected path: %s", volumePath) 1371 } 1372 1373 err = mounter.SetUp(volume.MounterArgs{}) 1374 if err != nil { 1375 t.Errorf("Failed to setup volume: %v", err) 1376 } 1377 if _, err := os.Stat(volumePath); err != nil { 1378 if os.IsNotExist(err) { 1379 t.Errorf("SetUp() failed, volume path not created: %s", volumePath) 1380 } else { 1381 t.Errorf("SetUp() failed: %v", err) 1382 } 1383 } 1384 1385 // secret volume should create its own empty wrapper path 1386 podWrapperMetadataDir := fmt.Sprintf("%v/pods/test_pod_uid/plugins/kubernetes.io~empty-dir/wrapped_test_volume_name", rootDir) 1387 1388 if _, err := os.Stat(podWrapperMetadataDir); err != nil { 1389 if os.IsNotExist(err) { 1390 t.Errorf("SetUp() failed, empty-dir wrapper path is not created: %s", podWrapperMetadataDir) 1391 } else { 1392 t.Errorf("SetUp() failed: %v", err) 1393 } 1394 } 1395 doTestSecretDataInVolume(volumePath, secret, t) 1396 defer doTestCleanAndTeardown(plugin, testPodUID, testVolumeName, volumePath, t) 1397 } 1398 1399 func makeVolumeSpec(volumeName, name string, defaultMode int32) *v1.Volume { 1400 return &v1.Volume{ 1401 Name: volumeName, 1402 VolumeSource: v1.VolumeSource{ 1403 Projected: makeProjection(name, utilptr.Int32Ptr(defaultMode), "secret"), 1404 }, 1405 } 1406 } 1407 1408 func makeSecret(namespace, name string) v1.Secret { 1409 return v1.Secret{ 1410 ObjectMeta: metav1.ObjectMeta{ 1411 Namespace: namespace, 1412 Name: name, 1413 }, 1414 Data: map[string][]byte{ 1415 "data-1": []byte("value-1"), 1416 "data-2": []byte("value-2"), 1417 "data-3": []byte("value-3"), 1418 }, 1419 } 1420 } 1421 1422 func makeProjection(name string, defaultMode *int32, kind string) *v1.ProjectedVolumeSource { 1423 var item v1.VolumeProjection 1424 1425 switch kind { 1426 case "configMap": 1427 item = v1.VolumeProjection{ 1428 ConfigMap: &v1.ConfigMapProjection{ 1429 LocalObjectReference: v1.LocalObjectReference{Name: name}, 1430 }, 1431 } 1432 case "secret": 1433 item = v1.VolumeProjection{ 1434 Secret: &v1.SecretProjection{ 1435 LocalObjectReference: v1.LocalObjectReference{Name: name}, 1436 }, 1437 } 1438 case "downwardAPI": 1439 item = v1.VolumeProjection{ 1440 DownwardAPI: &v1.DownwardAPIProjection{}, 1441 } 1442 case "serviceAccountToken": 1443 item = v1.VolumeProjection{ 1444 ServiceAccountToken: &v1.ServiceAccountTokenProjection{}, 1445 } 1446 } 1447 1448 return &v1.ProjectedVolumeSource{ 1449 Sources: []v1.VolumeProjection{item}, 1450 DefaultMode: defaultMode, 1451 } 1452 } 1453 1454 func doTestSecretDataInVolume(volumePath string, secret v1.Secret, t *testing.T) { 1455 for key, value := range secret.Data { 1456 secretDataHostPath := filepath.Join(volumePath, key) 1457 if _, err := os.Stat(secretDataHostPath); err != nil { 1458 t.Fatalf("SetUp() failed, couldn't find secret data on disk: %v", secretDataHostPath) 1459 } else { 1460 actualSecretBytes, err := os.ReadFile(secretDataHostPath) 1461 if err != nil { 1462 t.Fatalf("Couldn't read secret data from: %v", secretDataHostPath) 1463 } 1464 1465 actualSecretValue := string(actualSecretBytes) 1466 if string(value) != actualSecretValue { 1467 t.Errorf("Unexpected value; expected %q, got %q", value, actualSecretValue) 1468 } 1469 } 1470 } 1471 } 1472 1473 func doTestCleanAndTeardown(plugin volume.VolumePlugin, podUID types.UID, testVolumeName, volumePath string, t *testing.T) { 1474 unmounter, err := plugin.NewUnmounter(testVolumeName, podUID) 1475 if err != nil { 1476 t.Errorf("Failed to make a new Unmounter: %v", err) 1477 } 1478 if unmounter == nil { 1479 t.Errorf("Got a nil Unmounter") 1480 } 1481 1482 if err := unmounter.TearDown(); err != nil { 1483 t.Errorf("Expected success, got: %v", err) 1484 } 1485 if _, err := os.Stat(volumePath); err == nil { 1486 t.Errorf("TearDown() failed, volume path still exists: %s", volumePath) 1487 } else if !os.IsNotExist(err) { 1488 t.Errorf("TearDown() failed: %v", err) 1489 } 1490 } 1491 1492 func mustMakeRoot(t *testing.T, cn string) string { 1493 pub, priv, err := ed25519.GenerateKey(rand.Reader) 1494 if err != nil { 1495 t.Fatalf("Error while generating key: %v", err) 1496 } 1497 1498 template := &x509.Certificate{ 1499 SerialNumber: big.NewInt(0), 1500 Subject: pkix.Name{ 1501 CommonName: cn, 1502 }, 1503 IsCA: true, 1504 BasicConstraintsValid: true, 1505 } 1506 1507 cert, err := x509.CreateCertificate(rand.Reader, template, template, pub, priv) 1508 if err != nil { 1509 t.Fatalf("Error while making certificate: %v", err) 1510 } 1511 1512 return string(pem.EncodeToMemory(&pem.Block{ 1513 Type: "CERTIFICATE", 1514 Headers: nil, 1515 Bytes: cert, 1516 })) 1517 }