...

Source file src/k8s.io/kubernetes/pkg/credentialprovider/keyring_test.go

Documentation: k8s.io/kubernetes/pkg/credentialprovider

     1  /*
     2  Copyright 2014 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 credentialprovider
    18  
    19  import (
    20  	"encoding/base64"
    21  	"fmt"
    22  	"reflect"
    23  	"testing"
    24  )
    25  
    26  func TestURLsMatch(t *testing.T) {
    27  	tests := []struct {
    28  		globURL       string
    29  		targetURL     string
    30  		matchExpected bool
    31  	}{
    32  		// match when there is no path component
    33  		{
    34  			globURL:       "*.kubernetes.io",
    35  			targetURL:     "prefix.kubernetes.io",
    36  			matchExpected: true,
    37  		},
    38  		{
    39  			globURL:       "prefix.*.io",
    40  			targetURL:     "prefix.kubernetes.io",
    41  			matchExpected: true,
    42  		},
    43  		{
    44  			globURL:       "prefix.kubernetes.*",
    45  			targetURL:     "prefix.kubernetes.io",
    46  			matchExpected: true,
    47  		},
    48  		{
    49  			globURL:       "*-good.kubernetes.io",
    50  			targetURL:     "prefix-good.kubernetes.io",
    51  			matchExpected: true,
    52  		},
    53  		// match with path components
    54  		{
    55  			globURL:       "*.kubernetes.io/blah",
    56  			targetURL:     "prefix.kubernetes.io/blah",
    57  			matchExpected: true,
    58  		},
    59  		{
    60  			globURL:       "prefix.*.io/foo",
    61  			targetURL:     "prefix.kubernetes.io/foo/bar",
    62  			matchExpected: true,
    63  		},
    64  		// match with path components and ports
    65  		{
    66  			globURL:       "*.kubernetes.io:1111/blah",
    67  			targetURL:     "prefix.kubernetes.io:1111/blah",
    68  			matchExpected: true,
    69  		},
    70  		{
    71  			globURL:       "prefix.*.io:1111/foo",
    72  			targetURL:     "prefix.kubernetes.io:1111/foo/bar",
    73  			matchExpected: true,
    74  		},
    75  		// no match when number of parts mismatch
    76  		{
    77  			globURL:       "*.kubernetes.io",
    78  			targetURL:     "kubernetes.io",
    79  			matchExpected: false,
    80  		},
    81  		{
    82  			globURL:       "*.*.kubernetes.io",
    83  			targetURL:     "prefix.kubernetes.io",
    84  			matchExpected: false,
    85  		},
    86  		{
    87  			globURL:       "*.*.kubernetes.io",
    88  			targetURL:     "kubernetes.io",
    89  			matchExpected: false,
    90  		},
    91  		{
    92  			globURL:       "*kubernetes.io",
    93  			targetURL:     "a.kubernetes.io",
    94  			matchExpected: false,
    95  		},
    96  		// match when number of parts match
    97  		{
    98  			globURL:       "*kubernetes.io",
    99  			targetURL:     "kubernetes.io",
   100  			matchExpected: true,
   101  		},
   102  		{
   103  			globURL:       "*.*.*.kubernetes.io",
   104  			targetURL:     "a.b.c.kubernetes.io",
   105  			matchExpected: true,
   106  		},
   107  		// no match when some parts mismatch
   108  		{
   109  			globURL:       "kubernetes.io",
   110  			targetURL:     "kubernetes.com",
   111  			matchExpected: false,
   112  		},
   113  		{
   114  			globURL:       "k*.io",
   115  			targetURL:     "quay.io",
   116  			matchExpected: false,
   117  		},
   118  		// no match when ports mismatch
   119  		{
   120  			globURL:       "*.kubernetes.io:1234/blah",
   121  			targetURL:     "prefix.kubernetes.io:1111/blah",
   122  			matchExpected: false,
   123  		},
   124  		{
   125  			globURL:       "prefix.*.io/foo",
   126  			targetURL:     "prefix.kubernetes.io:1111/foo/bar",
   127  			matchExpected: false,
   128  		},
   129  	}
   130  	for _, test := range tests {
   131  		matched, _ := URLsMatchStr(test.globURL, test.targetURL)
   132  		if matched != test.matchExpected {
   133  			t.Errorf("Expected match result of %s and %s to be %t, but was %t",
   134  				test.globURL, test.targetURL, test.matchExpected, matched)
   135  		}
   136  	}
   137  }
   138  
   139  func TestDockerKeyringForGlob(t *testing.T) {
   140  	tests := []struct {
   141  		globURL   string
   142  		targetURL string
   143  	}{
   144  		{
   145  			globURL:   "https://hello.kubernetes.io",
   146  			targetURL: "hello.kubernetes.io",
   147  		},
   148  		{
   149  			globURL:   "https://*.docker.io",
   150  			targetURL: "prefix.docker.io",
   151  		},
   152  		{
   153  			globURL:   "https://prefix.*.io",
   154  			targetURL: "prefix.docker.io",
   155  		},
   156  		{
   157  			globURL:   "https://prefix.docker.*",
   158  			targetURL: "prefix.docker.io",
   159  		},
   160  		{
   161  			globURL:   "https://*.docker.io/path",
   162  			targetURL: "prefix.docker.io/path",
   163  		},
   164  		{
   165  			globURL:   "https://prefix.*.io/path",
   166  			targetURL: "prefix.docker.io/path/subpath",
   167  		},
   168  		{
   169  			globURL:   "https://prefix.docker.*/path",
   170  			targetURL: "prefix.docker.io/path",
   171  		},
   172  		{
   173  			globURL:   "https://*.docker.io:8888",
   174  			targetURL: "prefix.docker.io:8888",
   175  		},
   176  		{
   177  			globURL:   "https://prefix.*.io:8888",
   178  			targetURL: "prefix.docker.io:8888",
   179  		},
   180  		{
   181  			globURL:   "https://prefix.docker.*:8888",
   182  			targetURL: "prefix.docker.io:8888",
   183  		},
   184  		{
   185  			globURL:   "https://*.docker.io/path:1111",
   186  			targetURL: "prefix.docker.io/path:1111",
   187  		},
   188  		{
   189  			globURL:   "https://*.docker.io/v1/",
   190  			targetURL: "prefix.docker.io/path:1111",
   191  		},
   192  		{
   193  			globURL:   "https://*.docker.io/v2/",
   194  			targetURL: "prefix.docker.io/path:1111",
   195  		},
   196  		{
   197  			globURL:   "https://prefix.docker.*/path:1111",
   198  			targetURL: "prefix.docker.io/path:1111",
   199  		},
   200  		{
   201  			globURL:   "prefix.docker.io:1111",
   202  			targetURL: "prefix.docker.io:1111/path",
   203  		},
   204  		{
   205  			globURL:   "*.docker.io:1111",
   206  			targetURL: "prefix.docker.io:1111/path",
   207  		},
   208  	}
   209  	for i, test := range tests {
   210  		email := "foo@bar.baz"
   211  		username := "foo"
   212  		password := "bar" // Fake value for testing.
   213  		auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password)))
   214  		sampleDockerConfig := fmt.Sprintf(`{
   215     "%s": {
   216       "email": %q,
   217       "auth": %q
   218     }
   219  }`, test.globURL, email, auth)
   220  
   221  		keyring := &BasicDockerKeyring{}
   222  		if cfg, err := ReadDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
   223  			t.Errorf("Error processing json blob %q, %v", sampleDockerConfig, err)
   224  		} else {
   225  			keyring.Add(cfg)
   226  		}
   227  
   228  		creds, ok := keyring.Lookup(test.targetURL + "/foo/bar")
   229  		if !ok {
   230  			t.Errorf("%d: Didn't find expected URL: %s", i, test.targetURL)
   231  			continue
   232  		}
   233  		val := creds[0]
   234  
   235  		if username != val.Username {
   236  			t.Errorf("Unexpected username value, want: %s, got: %s", username, val.Username)
   237  		}
   238  		if password != val.Password {
   239  			t.Errorf("Unexpected password value, want: %s, got: %s", password, val.Password)
   240  		}
   241  		if email != val.Email {
   242  			t.Errorf("Unexpected email value, want: %s, got: %s", email, val.Email)
   243  		}
   244  	}
   245  }
   246  
   247  func TestKeyringMiss(t *testing.T) {
   248  	tests := []struct {
   249  		globURL   string
   250  		lookupURL string
   251  	}{
   252  		{
   253  			globURL:   "https://hello.kubernetes.io",
   254  			lookupURL: "world.mesos.org/foo/bar",
   255  		},
   256  		{
   257  			globURL:   "https://*.docker.com",
   258  			lookupURL: "prefix.docker.io",
   259  		},
   260  		{
   261  			globURL:   "https://suffix.*.io",
   262  			lookupURL: "prefix.docker.io",
   263  		},
   264  		{
   265  			globURL:   "https://prefix.docker.c*",
   266  			lookupURL: "prefix.docker.io",
   267  		},
   268  		{
   269  			globURL:   "https://prefix.*.io/path:1111",
   270  			lookupURL: "prefix.docker.io/path/subpath:1111",
   271  		},
   272  		{
   273  			globURL:   "suffix.*.io",
   274  			lookupURL: "prefix.docker.io",
   275  		},
   276  	}
   277  	for _, test := range tests {
   278  		email := "foo@bar.baz"
   279  		username := "foo"
   280  		password := "bar" // Fake value for testing.
   281  		auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password)))
   282  		sampleDockerConfig := fmt.Sprintf(`{
   283     "%s": {
   284       "email": %q,
   285       "auth": %q
   286     }
   287  }`, test.globURL, email, auth)
   288  
   289  		keyring := &BasicDockerKeyring{}
   290  		if cfg, err := ReadDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
   291  			t.Errorf("Error processing json blob %q, %v", sampleDockerConfig, err)
   292  		} else {
   293  			keyring.Add(cfg)
   294  		}
   295  
   296  		_, ok := keyring.Lookup(test.lookupURL + "/foo/bar")
   297  		if ok {
   298  			t.Errorf("Expected not to find URL %s, but found", test.lookupURL)
   299  		}
   300  	}
   301  
   302  }
   303  
   304  func TestKeyringMissWithDockerHubCredentials(t *testing.T) {
   305  	url := defaultRegistryKey
   306  	email := "foo@bar.baz"
   307  	username := "foo"
   308  	password := "bar" // Fake value for testing.
   309  	auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password)))
   310  	sampleDockerConfig := fmt.Sprintf(`{
   311     "https://%s": {
   312       "email": %q,
   313       "auth": %q
   314     }
   315  }`, url, email, auth)
   316  
   317  	keyring := &BasicDockerKeyring{}
   318  	if cfg, err := ReadDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
   319  		t.Errorf("Error processing json blob %q, %v", sampleDockerConfig, err)
   320  	} else {
   321  		keyring.Add(cfg)
   322  	}
   323  
   324  	val, ok := keyring.Lookup("world.mesos.org/foo/bar")
   325  	if ok {
   326  		t.Errorf("Found unexpected credential: %+v", val)
   327  	}
   328  }
   329  
   330  func TestKeyringHitWithUnqualifiedDockerHub(t *testing.T) {
   331  	url := defaultRegistryKey
   332  	email := "foo@bar.baz"
   333  	username := "foo"
   334  	password := "bar" // Fake value for testing.
   335  	auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password)))
   336  	sampleDockerConfig := fmt.Sprintf(`{
   337     "https://%s": {
   338       "email": %q,
   339       "auth": %q
   340     }
   341  }`, url, email, auth)
   342  
   343  	keyring := &BasicDockerKeyring{}
   344  	if cfg, err := ReadDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
   345  		t.Errorf("Error processing json blob %q, %v", sampleDockerConfig, err)
   346  	} else {
   347  		keyring.Add(cfg)
   348  	}
   349  
   350  	creds, ok := keyring.Lookup("google/docker-registry")
   351  	if !ok {
   352  		t.Errorf("Didn't find expected URL: %s", url)
   353  		return
   354  	}
   355  	if len(creds) > 1 {
   356  		t.Errorf("Got more hits than expected: %s", creds)
   357  	}
   358  	val := creds[0]
   359  
   360  	if username != val.Username {
   361  		t.Errorf("Unexpected username value, want: %s, got: %s", username, val.Username)
   362  	}
   363  	if password != val.Password {
   364  		t.Errorf("Unexpected password value, want: %s, got: %s", password, val.Password)
   365  	}
   366  	if email != val.Email {
   367  		t.Errorf("Unexpected email value, want: %s, got: %s", email, val.Email)
   368  	}
   369  }
   370  
   371  func TestKeyringHitWithUnqualifiedLibraryDockerHub(t *testing.T) {
   372  	url := defaultRegistryKey
   373  	email := "foo@bar.baz"
   374  	username := "foo"
   375  	password := "bar" // Fake value for testing.
   376  	auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password)))
   377  	sampleDockerConfig := fmt.Sprintf(`{
   378     "https://%s": {
   379       "email": %q,
   380       "auth": %q
   381     }
   382  }`, url, email, auth)
   383  
   384  	keyring := &BasicDockerKeyring{}
   385  	if cfg, err := ReadDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
   386  		t.Errorf("Error processing json blob %q, %v", sampleDockerConfig, err)
   387  	} else {
   388  		keyring.Add(cfg)
   389  	}
   390  
   391  	creds, ok := keyring.Lookup("jenkins")
   392  	if !ok {
   393  		t.Errorf("Didn't find expected URL: %s", url)
   394  		return
   395  	}
   396  	if len(creds) > 1 {
   397  		t.Errorf("Got more hits than expected: %s", creds)
   398  	}
   399  	val := creds[0]
   400  
   401  	if username != val.Username {
   402  		t.Errorf("Unexpected username value, want: %s, got: %s", username, val.Username)
   403  	}
   404  	if password != val.Password {
   405  		t.Errorf("Unexpected password value, want: %s, got: %s", password, val.Password)
   406  	}
   407  	if email != val.Email {
   408  		t.Errorf("Unexpected email value, want: %s, got: %s", email, val.Email)
   409  	}
   410  }
   411  
   412  func TestKeyringHitWithQualifiedDockerHub(t *testing.T) {
   413  	url := defaultRegistryKey
   414  	email := "foo@bar.baz"
   415  	username := "foo"
   416  	password := "bar" // Fake value for testing.
   417  	auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password)))
   418  	sampleDockerConfig := fmt.Sprintf(`{
   419     "https://%s": {
   420       "email": %q,
   421       "auth": %q
   422     }
   423  }`, url, email, auth)
   424  
   425  	keyring := &BasicDockerKeyring{}
   426  	if cfg, err := ReadDockerConfigFileFromBytes([]byte(sampleDockerConfig)); err != nil {
   427  		t.Errorf("Error processing json blob %q, %v", sampleDockerConfig, err)
   428  	} else {
   429  		keyring.Add(cfg)
   430  	}
   431  
   432  	creds, ok := keyring.Lookup(url + "/google/docker-registry")
   433  	if !ok {
   434  		t.Errorf("Didn't find expected URL: %s", url)
   435  		return
   436  	}
   437  	if len(creds) > 2 {
   438  		t.Errorf("Got more hits than expected: %s", creds)
   439  	}
   440  	val := creds[0]
   441  
   442  	if username != val.Username {
   443  		t.Errorf("Unexpected username value, want: %s, got: %s", username, val.Username)
   444  	}
   445  	if password != val.Password {
   446  		t.Errorf("Unexpected password value, want: %s, got: %s", password, val.Password)
   447  	}
   448  	if email != val.Email {
   449  		t.Errorf("Unexpected email value, want: %s, got: %s", email, val.Email)
   450  	}
   451  }
   452  
   453  func TestIsDefaultRegistryMatch(t *testing.T) {
   454  	samples := []map[bool]string{
   455  		{true: "foo/bar"},
   456  		{true: "docker.io/foo/bar"},
   457  		{true: "index.docker.io/foo/bar"},
   458  		{true: "foo"},
   459  		{false: ""},
   460  		{false: "registry.tld/foo/bar"},
   461  		{false: "registry:5000/foo/bar"},
   462  		{false: "myhostdocker.io/foo/bar"},
   463  	}
   464  	for _, sample := range samples {
   465  		for expected, imageName := range sample {
   466  			if got := isDefaultRegistryMatch(imageName); got != expected {
   467  				t.Errorf("Expected '%s' to be %t, got %t", imageName, expected, got)
   468  			}
   469  		}
   470  	}
   471  }
   472  
   473  func TestProvidersDockerKeyring(t *testing.T) {
   474  	provider := &testProvider{
   475  		Count: 0,
   476  	}
   477  	keyring := &providersDockerKeyring{
   478  		Providers: []DockerConfigProvider{
   479  			provider,
   480  		},
   481  	}
   482  
   483  	if provider.Count != 0 {
   484  		t.Errorf("Unexpected number of Provide calls: %v", provider.Count)
   485  	}
   486  	keyring.Lookup("foo")
   487  	if provider.Count != 1 {
   488  		t.Errorf("Unexpected number of Provide calls: %v", provider.Count)
   489  	}
   490  	keyring.Lookup("foo")
   491  	if provider.Count != 2 {
   492  		t.Errorf("Unexpected number of Provide calls: %v", provider.Count)
   493  	}
   494  	keyring.Lookup("foo")
   495  	if provider.Count != 3 {
   496  		t.Errorf("Unexpected number of Provide calls: %v", provider.Count)
   497  	}
   498  }
   499  
   500  func TestDockerKeyringLookup(t *testing.T) {
   501  	ada := AuthConfig{
   502  		Username: "ada",
   503  		Password: "smash", // Fake value for testing.
   504  		Email:    "ada@example.com",
   505  	}
   506  
   507  	grace := AuthConfig{
   508  		Username: "grace",
   509  		Password: "squash", // Fake value for testing.
   510  		Email:    "grace@example.com",
   511  	}
   512  
   513  	dk := &BasicDockerKeyring{}
   514  	dk.Add(DockerConfig{
   515  		"bar.example.com/pong": DockerConfigEntry{
   516  			Username: grace.Username,
   517  			Password: grace.Password,
   518  			Email:    grace.Email,
   519  		},
   520  		"bar.example.com": DockerConfigEntry{
   521  			Username: ada.Username,
   522  			Password: ada.Password,
   523  			Email:    ada.Email,
   524  		},
   525  	})
   526  
   527  	tests := []struct {
   528  		image string
   529  		match []AuthConfig
   530  		ok    bool
   531  	}{
   532  		// direct match
   533  		{"bar.example.com", []AuthConfig{ada}, true},
   534  
   535  		// direct match deeper than other possible matches
   536  		{"bar.example.com/pong", []AuthConfig{grace, ada}, true},
   537  
   538  		// no direct match, deeper path ignored
   539  		{"bar.example.com/ping", []AuthConfig{ada}, true},
   540  
   541  		// match first part of path token
   542  		{"bar.example.com/pongz", []AuthConfig{grace, ada}, true},
   543  
   544  		// match regardless of sub-path
   545  		{"bar.example.com/pong/pang", []AuthConfig{grace, ada}, true},
   546  
   547  		// no host match
   548  		{"example.com", []AuthConfig{}, false},
   549  		{"foo.example.com", []AuthConfig{}, false},
   550  	}
   551  
   552  	for i, tt := range tests {
   553  		match, ok := dk.Lookup(tt.image)
   554  		if tt.ok != ok {
   555  			t.Errorf("case %d: expected ok=%t, got %t", i, tt.ok, ok)
   556  		}
   557  
   558  		if !reflect.DeepEqual(tt.match, match) {
   559  			t.Errorf("case %d: expected match=%#v, got %#v", i, tt.match, match)
   560  		}
   561  	}
   562  }
   563  
   564  // This validates that dockercfg entries with a scheme and url path are properly matched
   565  // by images that only match the hostname.
   566  // NOTE: the above covers the case of a more specific match trumping just hostname.
   567  func TestIssue3797(t *testing.T) {
   568  	rex := AuthConfig{
   569  		Username: "rex",
   570  		Password: "tiny arms", // Fake value for testing.
   571  		Email:    "rex@example.com",
   572  	}
   573  
   574  	dk := &BasicDockerKeyring{}
   575  	dk.Add(DockerConfig{
   576  		"https://quay.io/v1/": DockerConfigEntry{
   577  			Username: rex.Username,
   578  			Password: rex.Password,
   579  			Email:    rex.Email,
   580  		},
   581  	})
   582  
   583  	tests := []struct {
   584  		image string
   585  		match []AuthConfig
   586  		ok    bool
   587  	}{
   588  		// direct match
   589  		{"quay.io", []AuthConfig{rex}, true},
   590  
   591  		// partial matches
   592  		{"quay.io/foo", []AuthConfig{rex}, true},
   593  		{"quay.io/foo/bar", []AuthConfig{rex}, true},
   594  	}
   595  
   596  	for i, tt := range tests {
   597  		match, ok := dk.Lookup(tt.image)
   598  		if tt.ok != ok {
   599  			t.Errorf("case %d: expected ok=%t, got %t", i, tt.ok, ok)
   600  		}
   601  
   602  		if !reflect.DeepEqual(tt.match, match) {
   603  			t.Errorf("case %d: expected match=%#v, got %#v", i, tt.match, match)
   604  		}
   605  	}
   606  }
   607  

View as plain text