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