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