...

Source file src/k8s.io/kubernetes/pkg/credentialprovider/plugin/plugin_test.go

Documentation: k8s.io/kubernetes/pkg/credentialprovider/plugin

     1  /*
     2  Copyright 2020 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 plugin
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"reflect"
    23  	"sync"
    24  	"testing"
    25  	"time"
    26  
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/runtime/schema"
    30  	"k8s.io/apimachinery/pkg/util/rand"
    31  	"k8s.io/client-go/tools/cache"
    32  	credentialproviderapi "k8s.io/kubelet/pkg/apis/credentialprovider"
    33  	credentialproviderv1 "k8s.io/kubelet/pkg/apis/credentialprovider/v1"
    34  	credentialproviderv1alpha1 "k8s.io/kubelet/pkg/apis/credentialprovider/v1alpha1"
    35  	credentialproviderv1beta1 "k8s.io/kubelet/pkg/apis/credentialprovider/v1beta1"
    36  	"k8s.io/kubernetes/pkg/credentialprovider"
    37  	kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
    38  	"k8s.io/utils/clock"
    39  	testingclock "k8s.io/utils/clock/testing"
    40  )
    41  
    42  type fakeExecPlugin struct {
    43  	cacheKeyType  credentialproviderapi.PluginCacheKeyType
    44  	cacheDuration time.Duration
    45  
    46  	auth map[string]credentialproviderapi.AuthConfig
    47  }
    48  
    49  func (f *fakeExecPlugin) ExecPlugin(ctx context.Context, image string) (*credentialproviderapi.CredentialProviderResponse, error) {
    50  	return &credentialproviderapi.CredentialProviderResponse{
    51  		CacheKeyType: f.cacheKeyType,
    52  		CacheDuration: &metav1.Duration{
    53  			Duration: f.cacheDuration,
    54  		},
    55  		Auth: f.auth,
    56  	}, nil
    57  }
    58  
    59  func Test_Provide(t *testing.T) {
    60  	tclock := clock.RealClock{}
    61  	testcases := []struct {
    62  		name           string
    63  		pluginProvider *pluginProvider
    64  		image          string
    65  		dockerconfig   credentialprovider.DockerConfig
    66  	}{
    67  		{
    68  			name: "exact image match, with Registry cache key",
    69  			pluginProvider: &pluginProvider{
    70  				clock:          tclock,
    71  				lastCachePurge: tclock.Now(),
    72  				matchImages:    []string{"test.registry.io"},
    73  				cache:          cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: tclock}),
    74  				plugin: &fakeExecPlugin{
    75  					cacheKeyType: credentialproviderapi.RegistryPluginCacheKeyType,
    76  					auth: map[string]credentialproviderapi.AuthConfig{
    77  						"test.registry.io": {
    78  							Username: "user",
    79  							Password: "password",
    80  						},
    81  					},
    82  				},
    83  			},
    84  			image: "test.registry.io/foo/bar",
    85  			dockerconfig: credentialprovider.DockerConfig{
    86  				"test.registry.io": credentialprovider.DockerConfigEntry{
    87  					Username: "user",
    88  					Password: "password",
    89  				},
    90  			},
    91  		},
    92  		{
    93  			name: "exact image match, with Image cache key",
    94  			pluginProvider: &pluginProvider{
    95  				clock:          tclock,
    96  				lastCachePurge: tclock.Now(),
    97  				matchImages:    []string{"test.registry.io/foo/bar"},
    98  				cache:          cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: tclock}),
    99  				plugin: &fakeExecPlugin{
   100  					cacheKeyType: credentialproviderapi.ImagePluginCacheKeyType,
   101  					auth: map[string]credentialproviderapi.AuthConfig{
   102  						"test.registry.io/foo/bar": {
   103  							Username: "user",
   104  							Password: "password",
   105  						},
   106  					},
   107  				},
   108  			},
   109  			image: "test.registry.io/foo/bar",
   110  			dockerconfig: credentialprovider.DockerConfig{
   111  				"test.registry.io/foo/bar": credentialprovider.DockerConfigEntry{
   112  					Username: "user",
   113  					Password: "password",
   114  				},
   115  			},
   116  		},
   117  		{
   118  			name: "exact image match, with Global cache key",
   119  			pluginProvider: &pluginProvider{
   120  				clock:          tclock,
   121  				lastCachePurge: tclock.Now(),
   122  				matchImages:    []string{"test.registry.io"},
   123  				cache:          cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: tclock}),
   124  				plugin: &fakeExecPlugin{
   125  					cacheKeyType: credentialproviderapi.GlobalPluginCacheKeyType,
   126  					auth: map[string]credentialproviderapi.AuthConfig{
   127  						"test.registry.io": {
   128  							Username: "user",
   129  							Password: "password",
   130  						},
   131  					},
   132  				},
   133  			},
   134  			image: "test.registry.io",
   135  			dockerconfig: credentialprovider.DockerConfig{
   136  				"test.registry.io": credentialprovider.DockerConfigEntry{
   137  					Username: "user",
   138  					Password: "password",
   139  				},
   140  			},
   141  		},
   142  		{
   143  			name: "wild card image match, with Registry cache key",
   144  			pluginProvider: &pluginProvider{
   145  				clock:          tclock,
   146  				lastCachePurge: tclock.Now(),
   147  				matchImages:    []string{"*.registry.io:8080"},
   148  				cache:          cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: tclock}),
   149  				plugin: &fakeExecPlugin{
   150  					cacheKeyType: credentialproviderapi.RegistryPluginCacheKeyType,
   151  					auth: map[string]credentialproviderapi.AuthConfig{
   152  						"*.registry.io:8080": {
   153  							Username: "user",
   154  							Password: "password",
   155  						},
   156  					},
   157  				},
   158  			},
   159  			image: "test.registry.io:8080/foo",
   160  			dockerconfig: credentialprovider.DockerConfig{
   161  				"*.registry.io:8080": credentialprovider.DockerConfigEntry{
   162  					Username: "user",
   163  					Password: "password",
   164  				},
   165  			},
   166  		},
   167  		{
   168  			name: "wild card image match, with Image cache key",
   169  			pluginProvider: &pluginProvider{
   170  				clock:          tclock,
   171  				lastCachePurge: tclock.Now(),
   172  				matchImages:    []string{"*.*.registry.io"},
   173  				cache:          cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: tclock}),
   174  				plugin: &fakeExecPlugin{
   175  					cacheKeyType: credentialproviderapi.ImagePluginCacheKeyType,
   176  					auth: map[string]credentialproviderapi.AuthConfig{
   177  						"*.*.registry.io": {
   178  							Username: "user",
   179  							Password: "password",
   180  						},
   181  					},
   182  				},
   183  			},
   184  			image: "foo.bar.registry.io/foo/bar",
   185  			dockerconfig: credentialprovider.DockerConfig{
   186  				"*.*.registry.io": credentialprovider.DockerConfigEntry{
   187  					Username: "user",
   188  					Password: "password",
   189  				},
   190  			},
   191  		},
   192  		{
   193  			name: "wild card image match, with Global cache key",
   194  			pluginProvider: &pluginProvider{
   195  				clock:          tclock,
   196  				lastCachePurge: tclock.Now(),
   197  				matchImages:    []string{"*.registry.io"},
   198  				cache:          cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: tclock}),
   199  				plugin: &fakeExecPlugin{
   200  					cacheKeyType: credentialproviderapi.GlobalPluginCacheKeyType,
   201  					auth: map[string]credentialproviderapi.AuthConfig{
   202  						"*.registry.io": {
   203  							Username: "user",
   204  							Password: "password",
   205  						},
   206  					},
   207  				},
   208  			},
   209  			image: "test.registry.io",
   210  			dockerconfig: credentialprovider.DockerConfig{
   211  				"*.registry.io": credentialprovider.DockerConfigEntry{
   212  					Username: "user",
   213  					Password: "password",
   214  				},
   215  			},
   216  		},
   217  	}
   218  
   219  	for _, testcase := range testcases {
   220  		testcase := testcase
   221  		t.Run(testcase.name, func(t *testing.T) {
   222  			t.Parallel()
   223  			dockerconfig := testcase.pluginProvider.Provide(testcase.image)
   224  			if !reflect.DeepEqual(dockerconfig, testcase.dockerconfig) {
   225  				t.Logf("actual docker config: %v", dockerconfig)
   226  				t.Logf("expected docker config: %v", testcase.dockerconfig)
   227  				t.Error("unexpected docker config")
   228  			}
   229  		})
   230  	}
   231  }
   232  
   233  // This test calls Provide in parallel for different registries and images
   234  // The purpose of this is to detect any race conditions while cache rw.
   235  func Test_ProvideParallel(t *testing.T) {
   236  	tclock := clock.RealClock{}
   237  
   238  	testcases := []struct {
   239  		name     string
   240  		registry string
   241  	}{
   242  		{
   243  			name:     "provide for registry 1",
   244  			registry: "test1.registry.io",
   245  		},
   246  		{
   247  			name:     "provide for registry 2",
   248  			registry: "test2.registry.io",
   249  		},
   250  		{
   251  			name:     "provide for registry 3",
   252  			registry: "test3.registry.io",
   253  		},
   254  		{
   255  			name:     "provide for registry 4",
   256  			registry: "test4.registry.io",
   257  		},
   258  	}
   259  
   260  	pluginProvider := &pluginProvider{
   261  		clock:          tclock,
   262  		lastCachePurge: tclock.Now(),
   263  		matchImages:    []string{"test1.registry.io", "test2.registry.io", "test3.registry.io", "test4.registry.io"},
   264  		cache:          cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: tclock}),
   265  		plugin: &fakeExecPlugin{
   266  			cacheDuration: time.Minute * 1,
   267  			cacheKeyType:  credentialproviderapi.RegistryPluginCacheKeyType,
   268  			auth: map[string]credentialproviderapi.AuthConfig{
   269  				"test.registry.io": {
   270  					Username: "user",
   271  					Password: "password",
   272  				},
   273  			},
   274  		},
   275  	}
   276  
   277  	dockerconfig := credentialprovider.DockerConfig{
   278  		"test.registry.io": credentialprovider.DockerConfigEntry{
   279  			Username: "user",
   280  			Password: "password",
   281  		},
   282  	}
   283  
   284  	for _, testcase := range testcases {
   285  		testcase := testcase
   286  		t.Run(testcase.name, func(t *testing.T) {
   287  			t.Parallel()
   288  			var wg sync.WaitGroup
   289  			wg.Add(5)
   290  
   291  			for i := 0; i < 5; i++ {
   292  				go func(w *sync.WaitGroup) {
   293  					image := fmt.Sprintf(testcase.registry+"/%s", rand.String(5))
   294  					dockerconfigResponse := pluginProvider.Provide(image)
   295  					if !reflect.DeepEqual(dockerconfigResponse, dockerconfig) {
   296  						t.Logf("actual docker config: %v", dockerconfigResponse)
   297  						t.Logf("expected docker config: %v", dockerconfig)
   298  						t.Error("unexpected docker config")
   299  					}
   300  					w.Done()
   301  				}(&wg)
   302  			}
   303  			wg.Wait()
   304  
   305  		})
   306  	}
   307  }
   308  
   309  func Test_getCachedCredentials(t *testing.T) {
   310  	fakeClock := testingclock.NewFakeClock(time.Now())
   311  	p := &pluginProvider{
   312  		clock:          fakeClock,
   313  		lastCachePurge: fakeClock.Now(),
   314  		cache:          cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: fakeClock}),
   315  		plugin:         &fakeExecPlugin{},
   316  	}
   317  
   318  	testcases := []struct {
   319  		name             string
   320  		step             time.Duration
   321  		cacheEntry       cacheEntry
   322  		expectedResponse credentialprovider.DockerConfig
   323  		keyLength        int
   324  		getKey           string
   325  	}{
   326  		{
   327  			name:      "It should return not expired credential",
   328  			step:      1 * time.Second,
   329  			keyLength: 1,
   330  			getKey:    "image1",
   331  			expectedResponse: map[string]credentialprovider.DockerConfigEntry{
   332  				"image1": {
   333  					Username: "user1",
   334  					Password: "pass1",
   335  				},
   336  			},
   337  			cacheEntry: cacheEntry{
   338  				key:       "image1",
   339  				expiresAt: fakeClock.Now().Add(1 * time.Minute),
   340  				credentials: map[string]credentialprovider.DockerConfigEntry{
   341  					"image1": {
   342  						Username: "user1",
   343  						Password: "pass1",
   344  					},
   345  				},
   346  			},
   347  		},
   348  
   349  		{
   350  			name:      "It should not return expired credential",
   351  			step:      2 * time.Minute,
   352  			getKey:    "image2",
   353  			keyLength: 1,
   354  			cacheEntry: cacheEntry{
   355  				key:       "image2",
   356  				expiresAt: fakeClock.Now(),
   357  				credentials: map[string]credentialprovider.DockerConfigEntry{
   358  					"image2": {
   359  						Username: "user2",
   360  						Password: "pass2",
   361  					},
   362  				},
   363  			},
   364  		},
   365  
   366  		{
   367  			name:      "It should delete expired credential during purge",
   368  			step:      18 * time.Minute,
   369  			keyLength: 0,
   370  			// while get call for random, cache purge will be called and it will delete expired
   371  			// image3 credentials. We cannot use image3 as getKey here, as it will get deleted during
   372  			// get only, we will not be able verify the purge call.
   373  			getKey: "random",
   374  			cacheEntry: cacheEntry{
   375  				key:       "image3",
   376  				expiresAt: fakeClock.Now().Add(2 * time.Minute),
   377  				credentials: map[string]credentialprovider.DockerConfigEntry{
   378  					"image3": {
   379  						Username: "user3",
   380  						Password: "pass3",
   381  					},
   382  				},
   383  			},
   384  		},
   385  	}
   386  
   387  	for _, tc := range testcases {
   388  		t.Run(tc.name, func(t *testing.T) {
   389  			p.cache.Add(&tc.cacheEntry)
   390  			fakeClock.Step(tc.step)
   391  
   392  			// getCachedCredentials returns unexpired credentials.
   393  			res, _, err := p.getCachedCredentials(tc.getKey)
   394  			if err != nil {
   395  				t.Errorf("Unexpected error %v", err)
   396  			}
   397  			if !reflect.DeepEqual(res, tc.expectedResponse) {
   398  				t.Logf("response %v", res)
   399  				t.Logf("expected response %v", tc.expectedResponse)
   400  				t.Errorf("Unexpected response")
   401  			}
   402  
   403  			// Listkeys returns all the keys present in cache including expired keys.
   404  			if len(p.cache.ListKeys()) != tc.keyLength {
   405  				t.Errorf("Unexpected cache key length")
   406  			}
   407  		})
   408  	}
   409  }
   410  
   411  func Test_encodeRequest(t *testing.T) {
   412  	testcases := []struct {
   413  		name         string
   414  		apiVersion   schema.GroupVersion
   415  		request      *credentialproviderapi.CredentialProviderRequest
   416  		expectedData []byte
   417  		expectedErr  bool
   418  	}{
   419  		{
   420  			name:       "successful with v1alpha1",
   421  			apiVersion: credentialproviderv1alpha1.SchemeGroupVersion,
   422  			request: &credentialproviderapi.CredentialProviderRequest{
   423  				Image: "test.registry.io/foobar",
   424  			},
   425  			expectedData: []byte(`{"kind":"CredentialProviderRequest","apiVersion":"credentialprovider.kubelet.k8s.io/v1alpha1","image":"test.registry.io/foobar"}
   426  `),
   427  			expectedErr: false,
   428  		},
   429  		{
   430  			name:       "successful with v1beta1",
   431  			apiVersion: credentialproviderv1beta1.SchemeGroupVersion,
   432  			request: &credentialproviderapi.CredentialProviderRequest{
   433  				Image: "test.registry.io/foobar",
   434  			},
   435  			expectedData: []byte(`{"kind":"CredentialProviderRequest","apiVersion":"credentialprovider.kubelet.k8s.io/v1beta1","image":"test.registry.io/foobar"}
   436  `),
   437  			expectedErr: false,
   438  		},
   439  		{
   440  			name:       "successful with v1",
   441  			apiVersion: credentialproviderv1.SchemeGroupVersion,
   442  			request: &credentialproviderapi.CredentialProviderRequest{
   443  				Image: "test.registry.io/foobar",
   444  			},
   445  			expectedData: []byte(`{"kind":"CredentialProviderRequest","apiVersion":"credentialprovider.kubelet.k8s.io/v1","image":"test.registry.io/foobar"}
   446  `),
   447  			expectedErr: false,
   448  		},
   449  	}
   450  
   451  	for _, testcase := range testcases {
   452  		t.Run(testcase.name, func(t *testing.T) {
   453  			mediaType := "application/json"
   454  			info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType)
   455  			if !ok {
   456  				t.Fatalf("unsupported media type: %s", mediaType)
   457  			}
   458  
   459  			e := &execPlugin{
   460  				encoder: codecs.EncoderForVersion(info.Serializer, testcase.apiVersion),
   461  			}
   462  
   463  			data, err := e.encodeRequest(testcase.request)
   464  			if err != nil && !testcase.expectedErr {
   465  				t.Fatalf("unexpected error: %v", err)
   466  			}
   467  
   468  			if err == nil && testcase.expectedErr {
   469  				t.Fatalf("expected error %v but got nil", testcase.expectedErr)
   470  			}
   471  
   472  			if !reflect.DeepEqual(data, testcase.expectedData) {
   473  				t.Errorf("actual encoded data: %v", string(data))
   474  				t.Errorf("expected encoded data: %v", string(testcase.expectedData))
   475  				t.Errorf("unexpected encoded response")
   476  			}
   477  		})
   478  	}
   479  }
   480  
   481  func Test_decodeResponse(t *testing.T) {
   482  	testcases := []struct {
   483  		name             string
   484  		data             []byte
   485  		expectedResponse *credentialproviderapi.CredentialProviderResponse
   486  		expectedErr      bool
   487  	}{
   488  		{
   489  			name: "success with v1",
   490  			data: []byte(`{"kind":"CredentialProviderResponse","apiVersion":"credentialprovider.kubelet.k8s.io/v1","cacheKeyType":"Registry","cacheDuration":"1m","auth":{"*.registry.io":{"username":"user","password":"password"}}}`),
   491  			expectedResponse: &credentialproviderapi.CredentialProviderResponse{
   492  				CacheKeyType: credentialproviderapi.RegistryPluginCacheKeyType,
   493  				CacheDuration: &metav1.Duration{
   494  					Duration: time.Minute,
   495  				},
   496  				Auth: map[string]credentialproviderapi.AuthConfig{
   497  					"*.registry.io": {
   498  						Username: "user",
   499  						Password: "password",
   500  					},
   501  				},
   502  			},
   503  			expectedErr: false,
   504  		},
   505  		{
   506  			name: "success with v1beta1",
   507  			data: []byte(`{"kind":"CredentialProviderResponse","apiVersion":"credentialprovider.kubelet.k8s.io/v1beta1","cacheKeyType":"Registry","cacheDuration":"1m","auth":{"*.registry.io":{"username":"user","password":"password"}}}`),
   508  			expectedResponse: &credentialproviderapi.CredentialProviderResponse{
   509  				CacheKeyType: credentialproviderapi.RegistryPluginCacheKeyType,
   510  				CacheDuration: &metav1.Duration{
   511  					Duration: time.Minute,
   512  				},
   513  				Auth: map[string]credentialproviderapi.AuthConfig{
   514  					"*.registry.io": {
   515  						Username: "user",
   516  						Password: "password",
   517  					},
   518  				},
   519  			},
   520  			expectedErr: false,
   521  		},
   522  		{
   523  			name: "success with v1alpha1",
   524  			data: []byte(`{"kind":"CredentialProviderResponse","apiVersion":"credentialprovider.kubelet.k8s.io/v1alpha1","cacheKeyType":"Registry","cacheDuration":"1m","auth":{"*.registry.io":{"username":"user","password":"password"}}}`),
   525  			expectedResponse: &credentialproviderapi.CredentialProviderResponse{
   526  				CacheKeyType: credentialproviderapi.RegistryPluginCacheKeyType,
   527  				CacheDuration: &metav1.Duration{
   528  					Duration: time.Minute,
   529  				},
   530  				Auth: map[string]credentialproviderapi.AuthConfig{
   531  					"*.registry.io": {
   532  						Username: "user",
   533  						Password: "password",
   534  					},
   535  				},
   536  			},
   537  			expectedErr: false,
   538  		},
   539  		{
   540  			name:             "wrong Kind",
   541  			data:             []byte(`{"kind":"WrongKind","apiVersion":"credentialprovider.kubelet.k8s.io/v1beta1","cacheKeyType":"Registry","cacheDuration":"1m","auth":{"*.registry.io":{"username":"user","password":"password"}}}`),
   542  			expectedResponse: nil,
   543  			expectedErr:      true,
   544  		},
   545  		{
   546  			name:             "wrong Group",
   547  			data:             []byte(`{"kind":"CredentialProviderResponse","apiVersion":"foobar.kubelet.k8s.io/v1beta1","cacheKeyType":"Registry","cacheDuration":"1m","auth":{"*.registry.io":{"username":"user","password":"password"}}}`),
   548  			expectedResponse: nil,
   549  			expectedErr:      true,
   550  		},
   551  	}
   552  
   553  	for _, testcase := range testcases {
   554  		t.Run(testcase.name, func(t *testing.T) {
   555  			e := &execPlugin{}
   556  
   557  			decodedResponse, err := e.decodeResponse(testcase.data)
   558  			if err != nil && !testcase.expectedErr {
   559  				t.Fatalf("unexpected error: %v", err)
   560  			}
   561  
   562  			if err == nil && testcase.expectedErr {
   563  				t.Fatalf("expected error %v but not nil", testcase.expectedErr)
   564  			}
   565  
   566  			if !reflect.DeepEqual(decodedResponse, testcase.expectedResponse) {
   567  				t.Logf("actual decoded response: %#v", decodedResponse)
   568  				t.Logf("expected decoded response: %#v", testcase.expectedResponse)
   569  				t.Errorf("unexpected decoded response")
   570  			}
   571  		})
   572  	}
   573  }
   574  
   575  func Test_RegistryCacheKeyType(t *testing.T) {
   576  	tclock := clock.RealClock{}
   577  	pluginProvider := &pluginProvider{
   578  		clock:          tclock,
   579  		lastCachePurge: tclock.Now(),
   580  		matchImages:    []string{"*.registry.io"},
   581  		cache:          cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: tclock}),
   582  		plugin: &fakeExecPlugin{
   583  			cacheKeyType:  credentialproviderapi.RegistryPluginCacheKeyType,
   584  			cacheDuration: time.Hour,
   585  			auth: map[string]credentialproviderapi.AuthConfig{
   586  				"*.registry.io": {
   587  					Username: "user",
   588  					Password: "password",
   589  				},
   590  			},
   591  		},
   592  	}
   593  
   594  	expectedDockerConfig := credentialprovider.DockerConfig{
   595  		"*.registry.io": credentialprovider.DockerConfigEntry{
   596  			Username: "user",
   597  			Password: "password",
   598  		},
   599  	}
   600  
   601  	dockerConfig := pluginProvider.Provide("test.registry.io/foo/bar")
   602  	if !reflect.DeepEqual(dockerConfig, expectedDockerConfig) {
   603  		t.Logf("actual docker config: %v", dockerConfig)
   604  		t.Logf("expected docker config: %v", expectedDockerConfig)
   605  		t.Fatal("unexpected docker config")
   606  	}
   607  
   608  	expectedCacheKeys := []string{"test.registry.io"}
   609  	cacheKeys := pluginProvider.cache.ListKeys()
   610  
   611  	if !reflect.DeepEqual(cacheKeys, expectedCacheKeys) {
   612  		t.Logf("actual cache keys: %v", cacheKeys)
   613  		t.Logf("expected cache keys: %v", expectedCacheKeys)
   614  		t.Error("unexpected cache keys")
   615  	}
   616  
   617  	// nil out the exec plugin, this will test whether credentialproviderapi are fetched
   618  	// from cache, otherwise Provider should panic
   619  	pluginProvider.plugin = nil
   620  	dockerConfig = pluginProvider.Provide("test.registry.io/foo/bar")
   621  	if !reflect.DeepEqual(dockerConfig, expectedDockerConfig) {
   622  		t.Logf("actual docker config: %v", dockerConfig)
   623  		t.Logf("expected docker config: %v", expectedDockerConfig)
   624  		t.Fatal("unexpected docker config")
   625  	}
   626  }
   627  
   628  func Test_ImageCacheKeyType(t *testing.T) {
   629  	tclock := clock.RealClock{}
   630  	pluginProvider := &pluginProvider{
   631  		clock:          tclock,
   632  		lastCachePurge: tclock.Now(),
   633  		matchImages:    []string{"*.registry.io"},
   634  		cache:          cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: tclock}),
   635  		plugin: &fakeExecPlugin{
   636  			cacheKeyType:  credentialproviderapi.ImagePluginCacheKeyType,
   637  			cacheDuration: time.Hour,
   638  			auth: map[string]credentialproviderapi.AuthConfig{
   639  				"*.registry.io": {
   640  					Username: "user",
   641  					Password: "password",
   642  				},
   643  			},
   644  		},
   645  	}
   646  
   647  	expectedDockerConfig := credentialprovider.DockerConfig{
   648  		"*.registry.io": credentialprovider.DockerConfigEntry{
   649  			Username: "user",
   650  			Password: "password",
   651  		},
   652  	}
   653  
   654  	dockerConfig := pluginProvider.Provide("test.registry.io/foo/bar")
   655  	if !reflect.DeepEqual(dockerConfig, expectedDockerConfig) {
   656  		t.Logf("actual docker config: %v", dockerConfig)
   657  		t.Logf("expected docker config: %v", expectedDockerConfig)
   658  		t.Fatal("unexpected docker config")
   659  	}
   660  
   661  	expectedCacheKeys := []string{"test.registry.io/foo/bar"}
   662  	cacheKeys := pluginProvider.cache.ListKeys()
   663  
   664  	if !reflect.DeepEqual(cacheKeys, expectedCacheKeys) {
   665  		t.Logf("actual cache keys: %v", cacheKeys)
   666  		t.Logf("expected cache keys: %v", expectedCacheKeys)
   667  		t.Error("unexpected cache keys")
   668  	}
   669  
   670  	// nil out the exec plugin, this will test whether credentialproviderapi are fetched
   671  	// from cache, otherwise Provider should panic
   672  	pluginProvider.plugin = nil
   673  	dockerConfig = pluginProvider.Provide("test.registry.io/foo/bar")
   674  	if !reflect.DeepEqual(dockerConfig, expectedDockerConfig) {
   675  		t.Logf("actual docker config: %v", dockerConfig)
   676  		t.Logf("expected docker config: %v", expectedDockerConfig)
   677  		t.Fatal("unexpected docker config")
   678  	}
   679  }
   680  
   681  func Test_GlobalCacheKeyType(t *testing.T) {
   682  	tclock := clock.RealClock{}
   683  	pluginProvider := &pluginProvider{
   684  		clock:          tclock,
   685  		lastCachePurge: tclock.Now(),
   686  		matchImages:    []string{"*.registry.io"},
   687  		cache:          cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: tclock}),
   688  		plugin: &fakeExecPlugin{
   689  			cacheKeyType:  credentialproviderapi.GlobalPluginCacheKeyType,
   690  			cacheDuration: time.Hour,
   691  			auth: map[string]credentialproviderapi.AuthConfig{
   692  				"*.registry.io": {
   693  					Username: "user",
   694  					Password: "password",
   695  				},
   696  			},
   697  		},
   698  	}
   699  
   700  	expectedDockerConfig := credentialprovider.DockerConfig{
   701  		"*.registry.io": credentialprovider.DockerConfigEntry{
   702  			Username: "user",
   703  			Password: "password",
   704  		},
   705  	}
   706  
   707  	dockerConfig := pluginProvider.Provide("test.registry.io/foo/bar")
   708  	if !reflect.DeepEqual(dockerConfig, expectedDockerConfig) {
   709  		t.Logf("actual docker config: %v", dockerConfig)
   710  		t.Logf("expected docker config: %v", expectedDockerConfig)
   711  		t.Fatal("unexpected docker config")
   712  	}
   713  
   714  	expectedCacheKeys := []string{"global"}
   715  	cacheKeys := pluginProvider.cache.ListKeys()
   716  
   717  	if !reflect.DeepEqual(cacheKeys, expectedCacheKeys) {
   718  		t.Logf("actual cache keys: %v", cacheKeys)
   719  		t.Logf("expected cache keys: %v", expectedCacheKeys)
   720  		t.Error("unexpected cache keys")
   721  	}
   722  
   723  	// nil out the exec plugin, this will test whether credentialproviderapi are fetched
   724  	// from cache, otherwise Provider should panic
   725  	pluginProvider.plugin = nil
   726  	dockerConfig = pluginProvider.Provide("test.registry.io/foo/bar")
   727  	if !reflect.DeepEqual(dockerConfig, expectedDockerConfig) {
   728  		t.Logf("actual docker config: %v", dockerConfig)
   729  		t.Logf("expected docker config: %v", expectedDockerConfig)
   730  		t.Fatal("unexpected docker config")
   731  	}
   732  }
   733  
   734  func Test_NoCacheResponse(t *testing.T) {
   735  	tclock := clock.RealClock{}
   736  	pluginProvider := &pluginProvider{
   737  		clock:          tclock,
   738  		lastCachePurge: tclock.Now(),
   739  		matchImages:    []string{"*.registry.io"},
   740  		cache:          cache.NewExpirationStore(cacheKeyFunc, &cacheExpirationPolicy{clock: tclock}),
   741  		plugin: &fakeExecPlugin{
   742  			cacheKeyType:  credentialproviderapi.GlobalPluginCacheKeyType,
   743  			cacheDuration: 0, // no cache
   744  			auth: map[string]credentialproviderapi.AuthConfig{
   745  				"*.registry.io": {
   746  					Username: "user",
   747  					Password: "password",
   748  				},
   749  			},
   750  		},
   751  	}
   752  
   753  	expectedDockerConfig := credentialprovider.DockerConfig{
   754  		"*.registry.io": credentialprovider.DockerConfigEntry{
   755  			Username: "user",
   756  			Password: "password",
   757  		},
   758  	}
   759  
   760  	dockerConfig := pluginProvider.Provide("test.registry.io/foo/bar")
   761  	if !reflect.DeepEqual(dockerConfig, expectedDockerConfig) {
   762  		t.Logf("actual docker config: %v", dockerConfig)
   763  		t.Logf("expected docker config: %v", expectedDockerConfig)
   764  		t.Fatal("unexpected docker config")
   765  	}
   766  
   767  	expectedCacheKeys := []string{}
   768  	cacheKeys := pluginProvider.cache.ListKeys()
   769  	if !reflect.DeepEqual(cacheKeys, expectedCacheKeys) {
   770  		t.Logf("actual cache keys: %v", cacheKeys)
   771  		t.Logf("expected cache keys: %v", expectedCacheKeys)
   772  		t.Error("unexpected cache keys")
   773  	}
   774  }
   775  
   776  func Test_ExecPluginEnvVars(t *testing.T) {
   777  	testcases := []struct {
   778  		name            string
   779  		systemEnvVars   []string
   780  		execPlugin      *execPlugin
   781  		expectedEnvVars []string
   782  	}{
   783  		{
   784  			name:          "positive append system env vars",
   785  			systemEnvVars: []string{"HOME=/home/foo", "PATH=/usr/bin"},
   786  			execPlugin: &execPlugin{
   787  				envVars: []kubeletconfig.ExecEnvVar{
   788  					{
   789  						Name:  "SUPER_SECRET_STRONG_ACCESS_KEY",
   790  						Value: "123456789",
   791  					},
   792  				},
   793  			},
   794  			expectedEnvVars: []string{
   795  				"HOME=/home/foo",
   796  				"PATH=/usr/bin",
   797  				"SUPER_SECRET_STRONG_ACCESS_KEY=123456789",
   798  			},
   799  		},
   800  		{
   801  			name:          "positive no env vars provided in plugin",
   802  			systemEnvVars: []string{"HOME=/home/foo", "PATH=/usr/bin"},
   803  			execPlugin:    &execPlugin{},
   804  			expectedEnvVars: []string{
   805  				"HOME=/home/foo",
   806  				"PATH=/usr/bin",
   807  			},
   808  		},
   809  		{
   810  			name: "positive no system env vars but env vars are provided in plugin",
   811  			execPlugin: &execPlugin{
   812  				envVars: []kubeletconfig.ExecEnvVar{
   813  					{
   814  						Name:  "SUPER_SECRET_STRONG_ACCESS_KEY",
   815  						Value: "123456789",
   816  					},
   817  				},
   818  			},
   819  			expectedEnvVars: []string{
   820  				"SUPER_SECRET_STRONG_ACCESS_KEY=123456789",
   821  			},
   822  		},
   823  		{
   824  			name:            "positive no system or plugin provided env vars",
   825  			execPlugin:      &execPlugin{},
   826  			expectedEnvVars: nil,
   827  		},
   828  		{
   829  			name:          "positive plugin provided vars takes priority",
   830  			systemEnvVars: []string{"HOME=/home/foo", "PATH=/usr/bin", "SUPER_SECRET_STRONG_ACCESS_KEY=1111"},
   831  			execPlugin: &execPlugin{
   832  				envVars: []kubeletconfig.ExecEnvVar{
   833  					{
   834  						Name:  "SUPER_SECRET_STRONG_ACCESS_KEY",
   835  						Value: "123456789",
   836  					},
   837  				},
   838  			},
   839  			expectedEnvVars: []string{
   840  				"HOME=/home/foo",
   841  				"PATH=/usr/bin",
   842  				"SUPER_SECRET_STRONG_ACCESS_KEY=1111",
   843  				"SUPER_SECRET_STRONG_ACCESS_KEY=123456789",
   844  			},
   845  		},
   846  	}
   847  
   848  	for _, testcase := range testcases {
   849  		t.Run(testcase.name, func(t *testing.T) {
   850  			testcase.execPlugin.environ = func() []string {
   851  				return testcase.systemEnvVars
   852  			}
   853  
   854  			var configVars []string
   855  			for _, envVar := range testcase.execPlugin.envVars {
   856  				configVars = append(configVars, fmt.Sprintf("%s=%s", envVar.Name, envVar.Value))
   857  			}
   858  			merged := mergeEnvVars(testcase.systemEnvVars, configVars)
   859  
   860  			err := validate(testcase.expectedEnvVars, merged)
   861  			if err != nil {
   862  				t.Logf("unexpecged error %v", err)
   863  			}
   864  		})
   865  	}
   866  }
   867  
   868  func validate(expected, actual []string) error {
   869  	if len(actual) != len(expected) {
   870  		return fmt.Errorf("actual env var length [%d] and expected env var length [%d] don't match",
   871  			len(actual), len(expected))
   872  	}
   873  
   874  	for i := range actual {
   875  		if actual[i] != expected[i] {
   876  			return fmt.Errorf("mismatch in expected env var %s and actual env var %s", actual[i], expected[i])
   877  		}
   878  	}
   879  
   880  	return nil
   881  }
   882  

View as plain text