k8s.io/kubernetes@v1.29.3/pkg/credentialprovider/keyring_test.go (about) 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 }