From 3167b15908c1cf96fad2d32d66de9157dff587b4 Mon Sep 17 00:00:00 2001 From: Mike Danese Date: Tue, 13 Nov 2018 13:08:56 -0800 Subject: [PATCH] TokenRequestProjections should allow API server to default empty audience --- pkg/volume/projected/BUILD | 7 ++ pkg/volume/projected/projected.go | 9 +- pkg/volume/projected/projected_test.go | 118 +++++++++++++++++++++++++ 3 files changed, 131 insertions(+), 3 deletions(-) diff --git a/pkg/volume/projected/BUILD b/pkg/volume/projected/BUILD index 1d7df6fe8ff6e..65883f906b386 100644 --- a/pkg/volume/projected/BUILD +++ b/pkg/volume/projected/BUILD @@ -11,15 +11,22 @@ go_test( srcs = ["projected_test.go"], embed = [":go_default_library"], deps = [ + "//pkg/apis/authentication/v1:go_default_library", + "//pkg/apis/core/v1:go_default_library", "//pkg/volume:go_default_library", "//pkg/volume/emptydir:go_default_library", "//pkg/volume/testing:go_default_library", "//pkg/volume/util:go_default_library", + "//staging/src/k8s.io/api/authentication/v1:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//staging/src/k8s.io/client-go/kubernetes:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library", + "//staging/src/k8s.io/client-go/testing:go_default_library", ], ) diff --git a/pkg/volume/projected/projected.go b/pkg/volume/projected/projected.go index efffb178cd8ac..e93588ce6767d 100644 --- a/pkg/volume/projected/projected.go +++ b/pkg/volume/projected/projected.go @@ -326,11 +326,14 @@ func (s *projectedVolumeMounter) collectData() (map[string]volumeutil.FileProjec continue } tp := source.ServiceAccountToken + + var auds []string + if len(tp.Audience) != 0 { + auds = []string{tp.Audience} + } tr, err := s.plugin.getServiceAccountToken(s.pod.Namespace, s.pod.Spec.ServiceAccountName, &authenticationv1.TokenRequest{ Spec: authenticationv1.TokenRequestSpec{ - Audiences: []string{ - tp.Audience, - }, + Audiences: auds, ExpirationSeconds: tp.ExpirationSeconds, BoundObjectRef: &authenticationv1.BoundObjectReference{ APIVersion: "v1", diff --git a/pkg/volume/projected/projected_test.go b/pkg/volume/projected/projected_test.go index 10c7b14fa5d03..932e2fe992c89 100644 --- a/pkg/volume/projected/projected_test.go +++ b/pkg/volume/projected/projected_test.go @@ -25,11 +25,18 @@ import ( "strings" "testing" + authenticationv1 "k8s.io/api/authentication/v1" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/diff" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" + clitesting "k8s.io/client-go/testing" + pkgauthenticationv1 "k8s.io/kubernetes/pkg/apis/authentication/v1" + pkgcorev1 "k8s.io/kubernetes/pkg/apis/core/v1" "k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume/emptydir" volumetest "k8s.io/kubernetes/pkg/volume/testing" @@ -699,6 +706,113 @@ func TestCollectDataWithDownwardAPI(t *testing.T) { } } +func TestCollectDataWithServiceAccountToken(t *testing.T) { + scheme := runtime.NewScheme() + utilruntime.Must(pkgauthenticationv1.RegisterDefaults(scheme)) + utilruntime.Must(pkgcorev1.RegisterDefaults(scheme)) + + minute := int64(60) + cases := []struct { + name string + svcacct string + audience string + expiration *int64 + path string + + payload map[string]util.FileProjection + }{ + { + name: "test good service account", + audience: "https://example.com", + path: "token", + expiration: &minute, + + payload: map[string]util.FileProjection{ + "token": {Data: []byte("test_projected_namespace:foo:60:[https://example.com]"), Mode: 0600}, + }, + }, + { + name: "test good service account other path", + audience: "https://example.com", + path: "other-token", + expiration: &minute, + + payload: map[string]util.FileProjection{ + "other-token": {Data: []byte("test_projected_namespace:foo:60:[https://example.com]"), Mode: 0600}, + }, + }, + { + name: "test good service account defaults audience", + path: "token", + expiration: &minute, + + payload: map[string]util.FileProjection{ + "token": {Data: []byte("test_projected_namespace:foo:60:[https://api]"), Mode: 0600}, + }, + }, + { + name: "test good service account defaults expiration", + audience: "https://example.com", + path: "token", + + payload: map[string]util.FileProjection{ + "token": {Data: []byte("test_projected_namespace:foo:3600:[https://example.com]"), Mode: 0600}, + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + testNamespace := "test_projected_namespace" + source := makeProjection(tc.name, 0600, "serviceAccountToken") + source.Sources[0].ServiceAccountToken.Audience = tc.audience + source.Sources[0].ServiceAccountToken.ExpirationSeconds = tc.expiration + source.Sources[0].ServiceAccountToken.Path = tc.path + + testPodUID := types.UID("test_pod_uid") + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, UID: testPodUID}, + Spec: v1.PodSpec{ServiceAccountName: "foo"}, + } + scheme.Default(pod) + + client := &fake.Clientset{} + client.AddReactor("create", "serviceaccounts", clitesting.ReactionFunc(func(action clitesting.Action) (bool, runtime.Object, error) { + tr := action.(clitesting.CreateAction).GetObject().(*authenticationv1.TokenRequest) + scheme.Default(tr) + if len(tr.Spec.Audiences) == 0 { + tr.Spec.Audiences = []string{"https://api"} + } + tr.Status.Token = fmt.Sprintf("%v:%v:%d:%v", action.GetNamespace(), "foo", *tr.Spec.ExpirationSeconds, tr.Spec.Audiences) + return true, tr, nil + })) + + _, host := newTestHost(t, client) + + var myVolumeMounter = projectedVolumeMounter{ + projectedVolume: &projectedVolume{ + sources: source.Sources, + podUID: pod.UID, + plugin: &projectedPlugin{ + host: host, + getServiceAccountToken: host.GetServiceAccountTokenFunc(), + }, + }, + source: *source, + pod: pod, + } + + actualPayload, err := myVolumeMounter.collectData() + if err != nil { + t.Fatalf("unexpected failure making payload: %v", err) + } + if e, a := tc.payload, actualPayload; !reflect.DeepEqual(e, a) { + t.Errorf("expected and actual payload do not match:\n%s", diff.ObjectReflectDiff(e, a)) + } + }) + } +} + func newTestHost(t *testing.T, clientset clientset.Interface) (string, volume.VolumeHost) { tempDir, err := ioutil.TempDir("/tmp", "projected_volume_test.") if err != nil { @@ -1113,6 +1227,10 @@ func makeProjection(name string, defaultMode int32, kind string) *v1.ProjectedVo item = v1.VolumeProjection{ DownwardAPI: &v1.DownwardAPIProjection{}, } + case "serviceAccountToken": + item = v1.VolumeProjection{ + ServiceAccountToken: &v1.ServiceAccountTokenProjection{}, + } } return &v1.ProjectedVolumeSource{