...

Source file src/k8s.io/kubernetes/pkg/kubelet/token/token_manager_test.go

Documentation: k8s.io/kubernetes/pkg/kubelet/token

     1  /*
     2  Copyright 2018 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 token
    18  
    19  import (
    20  	"fmt"
    21  	"testing"
    22  	"time"
    23  
    24  	authenticationv1 "k8s.io/api/authentication/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/types"
    27  	testingclock "k8s.io/utils/clock/testing"
    28  )
    29  
    30  func TestTokenCachingAndExpiration(t *testing.T) {
    31  	type suite struct {
    32  		clock *testingclock.FakeClock
    33  		tg    *fakeTokenGetter
    34  		mgr   *Manager
    35  	}
    36  
    37  	cases := []struct {
    38  		name string
    39  		exp  time.Duration
    40  		f    func(t *testing.T, s *suite)
    41  	}{
    42  		{
    43  			name: "rotate hour token expires in the last 12 minutes",
    44  			exp:  time.Hour,
    45  			f: func(t *testing.T, s *suite) {
    46  				s.clock.SetTime(s.clock.Now().Add(50 * time.Minute))
    47  				if _, err := s.mgr.GetServiceAccountToken("a", "b", getTokenRequest()); err != nil {
    48  					t.Fatalf("unexpected error: %v", err)
    49  				}
    50  				if s.tg.count != 2 {
    51  					t.Fatalf("expected token to be refreshed: call count was %d", s.tg.count)
    52  				}
    53  			},
    54  		},
    55  		{
    56  			name: "rotate 24 hour token that expires in 40 hours",
    57  			exp:  40 * time.Hour,
    58  			f: func(t *testing.T, s *suite) {
    59  				s.clock.SetTime(s.clock.Now().Add(25 * time.Hour))
    60  				if _, err := s.mgr.GetServiceAccountToken("a", "b", getTokenRequest()); err != nil {
    61  					t.Fatalf("unexpected error: %v", err)
    62  				}
    63  				if s.tg.count != 2 {
    64  					t.Fatalf("expected token to be refreshed: call count was %d", s.tg.count)
    65  				}
    66  			},
    67  		},
    68  		{
    69  			name: "rotate hour token fails, old token is still valid, doesn't error",
    70  			exp:  time.Hour,
    71  			f: func(t *testing.T, s *suite) {
    72  				s.clock.SetTime(s.clock.Now().Add(50 * time.Minute))
    73  				tg := &fakeTokenGetter{
    74  					err: fmt.Errorf("err"),
    75  				}
    76  				s.mgr.getToken = tg.getToken
    77  				tr, err := s.mgr.GetServiceAccountToken("a", "b", getTokenRequest())
    78  				if err != nil {
    79  					t.Fatalf("unexpected error: %v", err)
    80  				}
    81  				if tr.Status.Token != "foo" {
    82  					t.Fatalf("unexpected token: %v", tr.Status.Token)
    83  				}
    84  			},
    85  		},
    86  	}
    87  
    88  	for _, c := range cases {
    89  		t.Run(c.name, func(t *testing.T) {
    90  			clock := testingclock.NewFakeClock(time.Time{}.Add(30 * 24 * time.Hour))
    91  			expSecs := int64(c.exp.Seconds())
    92  			s := &suite{
    93  				clock: clock,
    94  				mgr:   NewManager(nil),
    95  				tg: &fakeTokenGetter{
    96  					tr: &authenticationv1.TokenRequest{
    97  						Spec: authenticationv1.TokenRequestSpec{
    98  							ExpirationSeconds: &expSecs,
    99  						},
   100  						Status: authenticationv1.TokenRequestStatus{
   101  							Token:               "foo",
   102  							ExpirationTimestamp: metav1.Time{Time: clock.Now().Add(c.exp)},
   103  						},
   104  					},
   105  				},
   106  			}
   107  			s.mgr.getToken = s.tg.getToken
   108  			s.mgr.clock = s.clock
   109  			if _, err := s.mgr.GetServiceAccountToken("a", "b", getTokenRequest()); err != nil {
   110  				t.Fatalf("unexpected error: %v", err)
   111  			}
   112  			if s.tg.count != 1 {
   113  				t.Fatalf("unexpected client call, got: %d, want: 1", s.tg.count)
   114  			}
   115  
   116  			if _, err := s.mgr.GetServiceAccountToken("a", "b", getTokenRequest()); err != nil {
   117  				t.Fatalf("unexpected error: %v", err)
   118  			}
   119  			if s.tg.count != 1 {
   120  				t.Fatalf("expected token to be served from cache: saw %d", s.tg.count)
   121  			}
   122  
   123  			c.f(t, s)
   124  		})
   125  	}
   126  }
   127  
   128  func TestRequiresRefresh(t *testing.T) {
   129  	start := time.Now()
   130  	cases := []struct {
   131  		now, exp      time.Time
   132  		expectRefresh bool
   133  		requestTweaks func(*authenticationv1.TokenRequest)
   134  	}{
   135  		{
   136  			now:           start.Add(10 * time.Minute),
   137  			exp:           start.Add(60 * time.Minute),
   138  			expectRefresh: false,
   139  		},
   140  		{
   141  			now:           start.Add(50 * time.Minute),
   142  			exp:           start.Add(60 * time.Minute),
   143  			expectRefresh: true,
   144  		},
   145  		{
   146  			now:           start.Add(25 * time.Hour),
   147  			exp:           start.Add(60 * time.Hour),
   148  			expectRefresh: true,
   149  		},
   150  		{
   151  			now:           start.Add(70 * time.Minute),
   152  			exp:           start.Add(60 * time.Minute),
   153  			expectRefresh: true,
   154  		},
   155  		{
   156  			// expiry will be overwritten by the tweak below.
   157  			now:           start.Add(0 * time.Minute),
   158  			exp:           start.Add(60 * time.Minute),
   159  			expectRefresh: false,
   160  			requestTweaks: func(tr *authenticationv1.TokenRequest) {
   161  				tr.Spec.ExpirationSeconds = nil
   162  			},
   163  		},
   164  	}
   165  
   166  	for i, c := range cases {
   167  		t.Run(fmt.Sprint(i), func(t *testing.T) {
   168  			clock := testingclock.NewFakeClock(c.now)
   169  			secs := int64(c.exp.Sub(start).Seconds())
   170  			tr := &authenticationv1.TokenRequest{
   171  				Spec: authenticationv1.TokenRequestSpec{
   172  					ExpirationSeconds: &secs,
   173  				},
   174  				Status: authenticationv1.TokenRequestStatus{
   175  					ExpirationTimestamp: metav1.Time{Time: c.exp},
   176  				},
   177  			}
   178  
   179  			if c.requestTweaks != nil {
   180  				c.requestTweaks(tr)
   181  			}
   182  
   183  			mgr := NewManager(nil)
   184  			mgr.clock = clock
   185  
   186  			rr := mgr.requiresRefresh(tr)
   187  			if rr != c.expectRefresh {
   188  				t.Fatalf("unexpected requiresRefresh result, got: %v, want: %v", rr, c.expectRefresh)
   189  			}
   190  		})
   191  	}
   192  }
   193  
   194  func TestDeleteServiceAccountToken(t *testing.T) {
   195  	type request struct {
   196  		name, namespace string
   197  		tr              authenticationv1.TokenRequest
   198  		shouldFail      bool
   199  	}
   200  
   201  	cases := []struct {
   202  		name         string
   203  		requestIndex []int
   204  		deletePodUID []types.UID
   205  		expLeftIndex []int
   206  	}{
   207  		{
   208  			name:         "delete none with all success requests",
   209  			requestIndex: []int{0, 1, 2},
   210  			expLeftIndex: []int{0, 1, 2},
   211  		},
   212  		{
   213  			name:         "delete one with all success requests",
   214  			requestIndex: []int{0, 1, 2},
   215  			deletePodUID: []types.UID{"fake-uid-1"},
   216  			expLeftIndex: []int{1, 2},
   217  		},
   218  		{
   219  			name:         "delete two with all success requests",
   220  			requestIndex: []int{0, 1, 2},
   221  			deletePodUID: []types.UID{"fake-uid-1", "fake-uid-3"},
   222  			expLeftIndex: []int{1},
   223  		},
   224  		{
   225  			name:         "delete all with all success requests",
   226  			requestIndex: []int{0, 1, 2},
   227  			deletePodUID: []types.UID{"fake-uid-1", "fake-uid-2", "fake-uid-3"},
   228  		},
   229  		{
   230  			name:         "delete no pod with failed requests",
   231  			requestIndex: []int{0, 1, 2, 3},
   232  			deletePodUID: []types.UID{},
   233  			expLeftIndex: []int{0, 1, 2},
   234  		},
   235  		{
   236  			name:         "delete other pod with failed requests",
   237  			requestIndex: []int{0, 1, 2, 3},
   238  			deletePodUID: []types.UID{"fake-uid-2"},
   239  			expLeftIndex: []int{0, 2},
   240  		},
   241  		{
   242  			name:         "delete no pod with request which success after failure",
   243  			requestIndex: []int{0, 1, 2, 3, 4},
   244  			deletePodUID: []types.UID{},
   245  			expLeftIndex: []int{0, 1, 2, 4},
   246  		},
   247  		{
   248  			name:         "delete the pod which success after failure",
   249  			requestIndex: []int{0, 1, 2, 3, 4},
   250  			deletePodUID: []types.UID{"fake-uid-4"},
   251  			expLeftIndex: []int{0, 1, 2},
   252  		},
   253  		{
   254  			name:         "delete other pod with request which success after failure",
   255  			requestIndex: []int{0, 1, 2, 3, 4},
   256  			deletePodUID: []types.UID{"fake-uid-1"},
   257  			expLeftIndex: []int{1, 2, 4},
   258  		},
   259  		{
   260  			name:         "delete some pod not in the set",
   261  			requestIndex: []int{0, 1, 2},
   262  			deletePodUID: []types.UID{"fake-uid-100", "fake-uid-200"},
   263  			expLeftIndex: []int{0, 1, 2},
   264  		},
   265  	}
   266  
   267  	for _, c := range cases {
   268  		t.Run(c.name, func(t *testing.T) {
   269  			requests := []request{
   270  				{
   271  					name:      "fake-name-1",
   272  					namespace: "fake-namespace-1",
   273  					tr: authenticationv1.TokenRequest{
   274  						Spec: authenticationv1.TokenRequestSpec{
   275  							BoundObjectRef: &authenticationv1.BoundObjectReference{
   276  								UID:  "fake-uid-1",
   277  								Name: "fake-name-1",
   278  							},
   279  						},
   280  					},
   281  					shouldFail: false,
   282  				},
   283  				{
   284  					name:      "fake-name-2",
   285  					namespace: "fake-namespace-2",
   286  					tr: authenticationv1.TokenRequest{
   287  						Spec: authenticationv1.TokenRequestSpec{
   288  							BoundObjectRef: &authenticationv1.BoundObjectReference{
   289  								UID:  "fake-uid-2",
   290  								Name: "fake-name-2",
   291  							},
   292  						},
   293  					},
   294  					shouldFail: false,
   295  				},
   296  				{
   297  					name:      "fake-name-3",
   298  					namespace: "fake-namespace-3",
   299  					tr: authenticationv1.TokenRequest{
   300  						Spec: authenticationv1.TokenRequestSpec{
   301  							BoundObjectRef: &authenticationv1.BoundObjectReference{
   302  								UID:  "fake-uid-3",
   303  								Name: "fake-name-3",
   304  							},
   305  						},
   306  					},
   307  					shouldFail: false,
   308  				},
   309  				{
   310  					name:      "fake-name-4",
   311  					namespace: "fake-namespace-4",
   312  					tr: authenticationv1.TokenRequest{
   313  						Spec: authenticationv1.TokenRequestSpec{
   314  							BoundObjectRef: &authenticationv1.BoundObjectReference{
   315  								UID:  "fake-uid-4",
   316  								Name: "fake-name-4",
   317  							},
   318  						},
   319  					},
   320  					shouldFail: true,
   321  				},
   322  				{
   323  					//exactly the same with last one, besides it will success
   324  					name:      "fake-name-4",
   325  					namespace: "fake-namespace-4",
   326  					tr: authenticationv1.TokenRequest{
   327  						Spec: authenticationv1.TokenRequestSpec{
   328  							BoundObjectRef: &authenticationv1.BoundObjectReference{
   329  								UID:  "fake-uid-4",
   330  								Name: "fake-name-4",
   331  							},
   332  						},
   333  					},
   334  					shouldFail: false,
   335  				},
   336  			}
   337  			testMgr := NewManager(nil)
   338  			testMgr.clock = testingclock.NewFakeClock(time.Time{}.Add(30 * 24 * time.Hour))
   339  
   340  			successGetToken := func(_, _ string, tr *authenticationv1.TokenRequest) (*authenticationv1.TokenRequest, error) {
   341  				tr.Status = authenticationv1.TokenRequestStatus{
   342  					ExpirationTimestamp: metav1.Time{Time: testMgr.clock.Now().Add(10 * time.Hour)},
   343  				}
   344  				return tr, nil
   345  			}
   346  			failGetToken := func(_, _ string, tr *authenticationv1.TokenRequest) (*authenticationv1.TokenRequest, error) {
   347  				return nil, fmt.Errorf("fail tr")
   348  			}
   349  
   350  			for _, index := range c.requestIndex {
   351  				req := requests[index]
   352  				if req.shouldFail {
   353  					testMgr.getToken = failGetToken
   354  				} else {
   355  					testMgr.getToken = successGetToken
   356  				}
   357  				testMgr.GetServiceAccountToken(req.namespace, req.name, &req.tr)
   358  			}
   359  
   360  			for _, uid := range c.deletePodUID {
   361  				testMgr.DeleteServiceAccountToken(uid)
   362  			}
   363  			if len(c.expLeftIndex) != len(testMgr.cache) {
   364  				t.Errorf("%s got unexpected result: expected left cache size is %d, got %d", c.name, len(c.expLeftIndex), len(testMgr.cache))
   365  			}
   366  			for _, leftIndex := range c.expLeftIndex {
   367  				r := requests[leftIndex]
   368  				_, ok := testMgr.get(keyFunc(r.name, r.namespace, &r.tr))
   369  				if !ok {
   370  					t.Errorf("%s got unexpected result: expected token request %v exist in cache, but not", c.name, r)
   371  				}
   372  			}
   373  		})
   374  	}
   375  }
   376  
   377  type fakeTokenGetter struct {
   378  	count int
   379  	tr    *authenticationv1.TokenRequest
   380  	err   error
   381  }
   382  
   383  func (ftg *fakeTokenGetter) getToken(name, namespace string, tr *authenticationv1.TokenRequest) (*authenticationv1.TokenRequest, error) {
   384  	ftg.count++
   385  	return ftg.tr, ftg.err
   386  }
   387  
   388  func TestCleanup(t *testing.T) {
   389  	cases := []struct {
   390  		name              string
   391  		relativeExp       time.Duration
   392  		expectedCacheSize int
   393  	}{
   394  		{
   395  			name:              "don't cleanup unexpired tokens",
   396  			relativeExp:       -1 * time.Hour,
   397  			expectedCacheSize: 0,
   398  		},
   399  		{
   400  			name:              "cleanup expired tokens",
   401  			relativeExp:       time.Hour,
   402  			expectedCacheSize: 1,
   403  		},
   404  	}
   405  	for _, c := range cases {
   406  		t.Run(c.name, func(t *testing.T) {
   407  			clock := testingclock.NewFakeClock(time.Time{}.Add(24 * time.Hour))
   408  			mgr := NewManager(nil)
   409  			mgr.clock = clock
   410  
   411  			mgr.set("key", &authenticationv1.TokenRequest{
   412  				Status: authenticationv1.TokenRequestStatus{
   413  					ExpirationTimestamp: metav1.Time{Time: mgr.clock.Now().Add(c.relativeExp)},
   414  				},
   415  			})
   416  			mgr.cleanup()
   417  			if got, want := len(mgr.cache), c.expectedCacheSize; got != want {
   418  				t.Fatalf("unexpected number of cache entries after cleanup, got: %d, want: %d", got, want)
   419  			}
   420  		})
   421  	}
   422  }
   423  
   424  func TestKeyFunc(t *testing.T) {
   425  	type tokenRequestUnit struct {
   426  		name      string
   427  		namespace string
   428  		tr        *authenticationv1.TokenRequest
   429  	}
   430  	getKeyFunc := func(u tokenRequestUnit) string {
   431  		return keyFunc(u.name, u.namespace, u.tr)
   432  	}
   433  
   434  	cases := []struct {
   435  		name   string
   436  		trus   []tokenRequestUnit
   437  		target tokenRequestUnit
   438  
   439  		shouldHit bool
   440  	}{
   441  		{
   442  			name: "hit",
   443  			trus: []tokenRequestUnit{
   444  				{
   445  					name:      "foo-sa",
   446  					namespace: "foo-ns",
   447  					tr: &authenticationv1.TokenRequest{
   448  						Spec: authenticationv1.TokenRequestSpec{
   449  							Audiences:         []string{"foo1", "foo2"},
   450  							ExpirationSeconds: getInt64Point(2000),
   451  							BoundObjectRef: &authenticationv1.BoundObjectReference{
   452  								Kind: "pod",
   453  								Name: "foo-pod",
   454  								UID:  "foo-uid",
   455  							},
   456  						},
   457  					},
   458  				},
   459  				{
   460  					name:      "ame-sa",
   461  					namespace: "ame-ns",
   462  					tr: &authenticationv1.TokenRequest{
   463  						Spec: authenticationv1.TokenRequestSpec{
   464  							Audiences:         []string{"ame1", "ame2"},
   465  							ExpirationSeconds: getInt64Point(2000),
   466  							BoundObjectRef: &authenticationv1.BoundObjectReference{
   467  								Kind: "pod",
   468  								Name: "ame-pod",
   469  								UID:  "ame-uid",
   470  							},
   471  						},
   472  					},
   473  				},
   474  			},
   475  			target: tokenRequestUnit{
   476  				name:      "foo-sa",
   477  				namespace: "foo-ns",
   478  				tr: &authenticationv1.TokenRequest{
   479  					Spec: authenticationv1.TokenRequestSpec{
   480  						Audiences:         []string{"foo1", "foo2"},
   481  						ExpirationSeconds: getInt64Point(2000),
   482  						BoundObjectRef: &authenticationv1.BoundObjectReference{
   483  							Kind: "pod",
   484  							Name: "foo-pod",
   485  							UID:  "foo-uid",
   486  						},
   487  					},
   488  				},
   489  			},
   490  			shouldHit: true,
   491  		},
   492  		{
   493  			name: "not hit due to different ExpirationSeconds",
   494  			trus: []tokenRequestUnit{
   495  				{
   496  					name:      "foo-sa",
   497  					namespace: "foo-ns",
   498  					tr: &authenticationv1.TokenRequest{
   499  						Spec: authenticationv1.TokenRequestSpec{
   500  							Audiences:         []string{"foo1", "foo2"},
   501  							ExpirationSeconds: getInt64Point(2000),
   502  							BoundObjectRef: &authenticationv1.BoundObjectReference{
   503  								Kind: "pod",
   504  								Name: "foo-pod",
   505  								UID:  "foo-uid",
   506  							},
   507  						},
   508  					},
   509  				},
   510  			},
   511  			target: tokenRequestUnit{
   512  				name:      "foo-sa",
   513  				namespace: "foo-ns",
   514  				tr: &authenticationv1.TokenRequest{
   515  					Spec: authenticationv1.TokenRequestSpec{
   516  						Audiences: []string{"foo1", "foo2"},
   517  						//everthing is same besides ExpirationSeconds
   518  						ExpirationSeconds: getInt64Point(2001),
   519  						BoundObjectRef: &authenticationv1.BoundObjectReference{
   520  							Kind: "pod",
   521  							Name: "foo-pod",
   522  							UID:  "foo-uid",
   523  						},
   524  					},
   525  				},
   526  			},
   527  			shouldHit: false,
   528  		},
   529  		{
   530  			name: "not hit due to different BoundObjectRef",
   531  			trus: []tokenRequestUnit{
   532  				{
   533  					name:      "foo-sa",
   534  					namespace: "foo-ns",
   535  					tr: &authenticationv1.TokenRequest{
   536  						Spec: authenticationv1.TokenRequestSpec{
   537  							Audiences:         []string{"foo1", "foo2"},
   538  							ExpirationSeconds: getInt64Point(2000),
   539  							BoundObjectRef: &authenticationv1.BoundObjectReference{
   540  								Kind: "pod",
   541  								Name: "foo-pod",
   542  								UID:  "foo-uid",
   543  							},
   544  						},
   545  					},
   546  				},
   547  			},
   548  			target: tokenRequestUnit{
   549  				name:      "foo-sa",
   550  				namespace: "foo-ns",
   551  				tr: &authenticationv1.TokenRequest{
   552  					Spec: authenticationv1.TokenRequestSpec{
   553  						Audiences:         []string{"foo1", "foo2"},
   554  						ExpirationSeconds: getInt64Point(2000),
   555  						BoundObjectRef: &authenticationv1.BoundObjectReference{
   556  							Kind: "pod",
   557  							//everthing is same besides BoundObjectRef.Name
   558  							Name: "diff-pod",
   559  							UID:  "foo-uid",
   560  						},
   561  					},
   562  				},
   563  			},
   564  			shouldHit: false,
   565  		},
   566  	}
   567  
   568  	for _, c := range cases {
   569  		t.Run(c.name, func(t *testing.T) {
   570  			mgr := NewManager(nil)
   571  			mgr.clock = testingclock.NewFakeClock(time.Time{}.Add(30 * 24 * time.Hour))
   572  			for _, tru := range c.trus {
   573  				mgr.set(getKeyFunc(tru), &authenticationv1.TokenRequest{
   574  					Status: authenticationv1.TokenRequestStatus{
   575  						//make sure the token cache would not be cleaned by token manager clenaup func
   576  						ExpirationTimestamp: metav1.Time{Time: mgr.clock.Now().Add(50 * time.Minute)},
   577  					},
   578  				})
   579  			}
   580  			_, hit := mgr.get(getKeyFunc(c.target))
   581  
   582  			if hit != c.shouldHit {
   583  				t.Errorf("%s got unexpected hit result: expected to be %t, got %t", c.name, c.shouldHit, hit)
   584  			}
   585  		})
   586  	}
   587  
   588  }
   589  
   590  func getTokenRequest() *authenticationv1.TokenRequest {
   591  	return &authenticationv1.TokenRequest{
   592  		Spec: authenticationv1.TokenRequestSpec{
   593  			Audiences:         []string{"foo1", "foo2"},
   594  			ExpirationSeconds: getInt64Point(2000),
   595  			BoundObjectRef: &authenticationv1.BoundObjectReference{
   596  				Kind: "pod",
   597  				Name: "foo-pod",
   598  				UID:  "foo-uid",
   599  			},
   600  		},
   601  	}
   602  }
   603  
   604  func getInt64Point(v int64) *int64 {
   605  	return &v
   606  }
   607  

View as plain text