k8s.io/client-go@v0.22.2/tools/clientcmd/validation_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 clientcmd 18 19 import ( 20 "errors" 21 "fmt" 22 "io/ioutil" 23 "os" 24 "strings" 25 "testing" 26 27 utilerrors "k8s.io/apimachinery/pkg/util/errors" 28 clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 29 ) 30 31 func TestConfirmUsableBadInfoButOkConfig(t *testing.T) { 32 config := clientcmdapi.NewConfig() 33 config.Clusters["missing ca"] = &clientcmdapi.Cluster{ 34 Server: "anything", 35 CertificateAuthority: "missing", 36 } 37 config.AuthInfos["error"] = &clientcmdapi.AuthInfo{ 38 Username: "anything", 39 Token: "here", 40 } 41 config.Contexts["dirty"] = &clientcmdapi.Context{ 42 Cluster: "missing ca", 43 AuthInfo: "error", 44 } 45 config.Clusters["clean"] = &clientcmdapi.Cluster{ 46 Server: "anything", 47 } 48 config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{ 49 Token: "here", 50 } 51 config.Contexts["clean"] = &clientcmdapi.Context{ 52 Cluster: "clean", 53 AuthInfo: "clean", 54 } 55 56 badValidation := configValidationTest{ 57 config: config, 58 expectedErrorSubstring: []string{"unable to read certificate-authority"}, 59 } 60 okTest := configValidationTest{ 61 config: config, 62 } 63 64 okTest.testConfirmUsable("clean", t) 65 badValidation.testConfig(t) 66 } 67 68 func TestConfirmUsableBadInfoConfig(t *testing.T) { 69 config := clientcmdapi.NewConfig() 70 config.Clusters["missing ca"] = &clientcmdapi.Cluster{ 71 Server: "anything", 72 CertificateAuthority: "missing", 73 } 74 config.AuthInfos["error"] = &clientcmdapi.AuthInfo{ 75 Username: "anything", 76 Token: "here", 77 } 78 config.Contexts["first"] = &clientcmdapi.Context{ 79 Cluster: "missing ca", 80 AuthInfo: "error", 81 } 82 test := configValidationTest{ 83 config: config, 84 expectedErrorSubstring: []string{"unable to read certificate-authority"}, 85 } 86 87 test.testConfirmUsable("first", t) 88 } 89 90 func TestConfirmUsableEmptyConfig(t *testing.T) { 91 config := clientcmdapi.NewConfig() 92 test := configValidationTest{ 93 config: config, 94 expectedErrorSubstring: []string{"invalid configuration: no configuration has been provided"}, 95 } 96 97 test.testConfirmUsable("", t) 98 } 99 100 func TestConfirmUsableMissingConfig(t *testing.T) { 101 config := clientcmdapi.NewConfig() 102 test := configValidationTest{ 103 config: config, 104 expectedErrorSubstring: []string{"invalid configuration: no configuration has been provided"}, 105 } 106 107 test.testConfirmUsable("not-here", t) 108 } 109 110 func TestValidateEmptyConfig(t *testing.T) { 111 config := clientcmdapi.NewConfig() 112 test := configValidationTest{ 113 config: config, 114 expectedErrorSubstring: []string{"invalid configuration: no configuration has been provided"}, 115 } 116 117 test.testConfig(t) 118 } 119 120 func TestValidateMissingCurrentContextConfig(t *testing.T) { 121 config := clientcmdapi.NewConfig() 122 config.CurrentContext = "anything" 123 test := configValidationTest{ 124 config: config, 125 expectedErrorSubstring: []string{"context was not found for specified "}, 126 } 127 128 test.testConfig(t) 129 } 130 131 func TestIsContextNotFound(t *testing.T) { 132 config := clientcmdapi.NewConfig() 133 config.CurrentContext = "anything" 134 135 err := Validate(*config) 136 if !IsContextNotFound(err) { 137 t.Errorf("Expected context not found, but got %v", err) 138 } 139 if !IsConfigurationInvalid(err) { 140 t.Errorf("Expected configuration invalid, but got %v", err) 141 } 142 } 143 144 func TestIsEmptyConfig(t *testing.T) { 145 config := clientcmdapi.NewConfig() 146 147 err := Validate(*config) 148 if !IsEmptyConfig(err) { 149 t.Errorf("Expected context not found, but got %v", err) 150 } 151 if !IsConfigurationInvalid(err) { 152 t.Errorf("Expected configuration invalid, but got %v", err) 153 } 154 } 155 156 func TestIsConfigurationInvalid(t *testing.T) { 157 if newErrConfigurationInvalid([]error{}) != nil { 158 t.Errorf("unexpected error") 159 } 160 if newErrConfigurationInvalid([]error{ErrNoContext}) == ErrNoContext { 161 t.Errorf("unexpected error") 162 } 163 if newErrConfigurationInvalid([]error{ErrNoContext, ErrNoContext}) == nil { 164 t.Errorf("unexpected error") 165 } 166 if !IsConfigurationInvalid(newErrConfigurationInvalid([]error{ErrNoContext, ErrNoContext})) { 167 t.Errorf("unexpected error") 168 } 169 } 170 171 func TestValidateMissingReferencesConfig(t *testing.T) { 172 config := clientcmdapi.NewConfig() 173 config.CurrentContext = "anything" 174 config.Contexts["anything"] = &clientcmdapi.Context{Cluster: "missing", AuthInfo: "missing"} 175 test := configValidationTest{ 176 config: config, 177 expectedErrorSubstring: []string{"user \"missing\" was not found for context \"anything\"", "cluster \"missing\" was not found for context \"anything\""}, 178 } 179 180 test.testContext("anything", t) 181 test.testConfig(t) 182 } 183 184 func TestValidateEmptyContext(t *testing.T) { 185 config := clientcmdapi.NewConfig() 186 config.CurrentContext = "anything" 187 config.Contexts["anything"] = &clientcmdapi.Context{} 188 test := configValidationTest{ 189 config: config, 190 expectedErrorSubstring: []string{"user was not specified for context \"anything\"", "cluster was not specified for context \"anything\""}, 191 } 192 193 test.testContext("anything", t) 194 test.testConfig(t) 195 } 196 197 func TestValidateEmptyContextName(t *testing.T) { 198 config := clientcmdapi.NewConfig() 199 config.CurrentContext = "anything" 200 config.Contexts[""] = &clientcmdapi.Context{Cluster: "missing", AuthInfo: "missing"} 201 test := configValidationTest{ 202 config: config, 203 expectedErrorSubstring: []string{"empty context name", "is not allowed"}, 204 } 205 206 test.testContext("", t) 207 test.testConfig(t) 208 } 209 210 func TestValidateEmptyClusterInfo(t *testing.T) { 211 config := clientcmdapi.NewConfig() 212 config.Clusters["empty"] = clientcmdapi.NewCluster() 213 test := configValidationTest{ 214 config: config, 215 expectedErrorSubstring: []string{"cluster has no server defined"}, 216 } 217 218 test.testCluster("empty", t) 219 test.testConfig(t) 220 } 221 222 func TestValidateClusterInfoErrEmptyCluster(t *testing.T) { 223 cluster := clientcmdapi.NewCluster() 224 errs := validateClusterInfo("", *cluster) 225 226 if len(errs) != 1 { 227 t.Fatalf("unexpected errors: %v", errs) 228 } 229 if errs[0] != ErrEmptyCluster { 230 t.Errorf("unexpected error: %v", errs[0]) 231 } 232 } 233 234 func TestValidateMissingCAFileClusterInfo(t *testing.T) { 235 config := clientcmdapi.NewConfig() 236 config.Clusters["missing ca"] = &clientcmdapi.Cluster{ 237 Server: "anything", 238 CertificateAuthority: "missing", 239 } 240 test := configValidationTest{ 241 config: config, 242 expectedErrorSubstring: []string{"unable to read certificate-authority"}, 243 } 244 245 test.testCluster("missing ca", t) 246 test.testConfig(t) 247 } 248 249 func TestValidateCleanClusterInfo(t *testing.T) { 250 config := clientcmdapi.NewConfig() 251 config.Clusters["clean"] = &clientcmdapi.Cluster{ 252 Server: "anything", 253 } 254 test := configValidationTest{ 255 config: config, 256 } 257 258 test.testCluster("clean", t) 259 test.testConfig(t) 260 } 261 262 func TestValidateCleanWithCAClusterInfo(t *testing.T) { 263 tempFile, _ := ioutil.TempFile("", "") 264 defer os.Remove(tempFile.Name()) 265 266 config := clientcmdapi.NewConfig() 267 config.Clusters["clean"] = &clientcmdapi.Cluster{ 268 Server: "anything", 269 CertificateAuthority: tempFile.Name(), 270 } 271 test := configValidationTest{ 272 config: config, 273 } 274 275 test.testCluster("clean", t) 276 test.testConfig(t) 277 } 278 279 func TestValidateEmptyAuthInfo(t *testing.T) { 280 config := clientcmdapi.NewConfig() 281 config.AuthInfos["error"] = &clientcmdapi.AuthInfo{} 282 test := configValidationTest{ 283 config: config, 284 } 285 286 test.testAuthInfo("error", t) 287 test.testConfig(t) 288 } 289 290 func TestValidateCertFilesNotFoundAuthInfo(t *testing.T) { 291 config := clientcmdapi.NewConfig() 292 config.AuthInfos["error"] = &clientcmdapi.AuthInfo{ 293 ClientCertificate: "missing", 294 ClientKey: "missing", 295 } 296 test := configValidationTest{ 297 config: config, 298 expectedErrorSubstring: []string{"unable to read client-cert", "unable to read client-key"}, 299 } 300 301 test.testAuthInfo("error", t) 302 test.testConfig(t) 303 } 304 305 func TestValidateCertDataOverridesFiles(t *testing.T) { 306 tempFile, _ := ioutil.TempFile("", "") 307 defer os.Remove(tempFile.Name()) 308 309 config := clientcmdapi.NewConfig() 310 config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{ 311 ClientCertificate: tempFile.Name(), 312 ClientCertificateData: []byte("certdata"), 313 ClientKey: tempFile.Name(), 314 ClientKeyData: []byte("keydata"), 315 } 316 test := configValidationTest{ 317 config: config, 318 expectedErrorSubstring: []string{"client-cert-data and client-cert are both specified", "client-key-data and client-key are both specified"}, 319 } 320 321 test.testAuthInfo("clean", t) 322 test.testConfig(t) 323 } 324 325 func TestValidateCleanCertFilesAuthInfo(t *testing.T) { 326 tempFile, _ := ioutil.TempFile("", "") 327 defer os.Remove(tempFile.Name()) 328 329 config := clientcmdapi.NewConfig() 330 config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{ 331 ClientCertificate: tempFile.Name(), 332 ClientKey: tempFile.Name(), 333 } 334 test := configValidationTest{ 335 config: config, 336 } 337 338 test.testAuthInfo("clean", t) 339 test.testConfig(t) 340 } 341 342 func TestValidateCleanTokenAuthInfo(t *testing.T) { 343 config := clientcmdapi.NewConfig() 344 config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{ 345 Token: "any-value", 346 } 347 test := configValidationTest{ 348 config: config, 349 } 350 351 test.testAuthInfo("clean", t) 352 test.testConfig(t) 353 } 354 355 func TestValidateMultipleMethodsAuthInfo(t *testing.T) { 356 config := clientcmdapi.NewConfig() 357 config.AuthInfos["error"] = &clientcmdapi.AuthInfo{ 358 Token: "token", 359 Username: "username", 360 } 361 test := configValidationTest{ 362 config: config, 363 expectedErrorSubstring: []string{"more than one authentication method", "token", "basicAuth"}, 364 } 365 366 test.testAuthInfo("error", t) 367 test.testConfig(t) 368 } 369 370 func TestValidateAuthInfoExec(t *testing.T) { 371 config := clientcmdapi.NewConfig() 372 config.AuthInfos["user"] = &clientcmdapi.AuthInfo{ 373 Exec: &clientcmdapi.ExecConfig{ 374 Command: "/bin/example", 375 APIVersion: "clientauthentication.k8s.io/v1alpha1", 376 Args: []string{"hello", "world"}, 377 Env: []clientcmdapi.ExecEnvVar{ 378 {Name: "foo", Value: "bar"}, 379 }, 380 InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode, 381 }, 382 } 383 test := configValidationTest{ 384 config: config, 385 } 386 387 test.testAuthInfo("user", t) 388 test.testConfig(t) 389 } 390 391 func TestValidateAuthInfoExecNoVersion(t *testing.T) { 392 config := clientcmdapi.NewConfig() 393 config.AuthInfos["user"] = &clientcmdapi.AuthInfo{ 394 Exec: &clientcmdapi.ExecConfig{ 395 Command: "/bin/example", 396 InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode, 397 }, 398 } 399 test := configValidationTest{ 400 config: config, 401 expectedErrorSubstring: []string{ 402 "apiVersion must be specified for user to use exec authentication plugin", 403 }, 404 } 405 406 test.testAuthInfo("user", t) 407 test.testConfig(t) 408 } 409 410 func TestValidateAuthInfoExecNoCommand(t *testing.T) { 411 config := clientcmdapi.NewConfig() 412 config.AuthInfos["user"] = &clientcmdapi.AuthInfo{ 413 Exec: &clientcmdapi.ExecConfig{ 414 APIVersion: "clientauthentication.k8s.io/v1alpha1", 415 InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode, 416 }, 417 } 418 test := configValidationTest{ 419 config: config, 420 expectedErrorSubstring: []string{ 421 "command must be specified for user to use exec authentication plugin", 422 }, 423 } 424 425 test.testAuthInfo("user", t) 426 test.testConfig(t) 427 } 428 429 func TestValidateAuthInfoExecWithAuthProvider(t *testing.T) { 430 config := clientcmdapi.NewConfig() 431 config.AuthInfos["user"] = &clientcmdapi.AuthInfo{ 432 AuthProvider: &clientcmdapi.AuthProviderConfig{ 433 Name: "oidc", 434 }, 435 Exec: &clientcmdapi.ExecConfig{ 436 Command: "/bin/example", 437 APIVersion: "clientauthentication.k8s.io/v1alpha1", 438 InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode, 439 }, 440 } 441 test := configValidationTest{ 442 config: config, 443 expectedErrorSubstring: []string{ 444 "authProvider cannot be provided in combination with an exec plugin for user", 445 }, 446 } 447 448 test.testAuthInfo("user", t) 449 test.testConfig(t) 450 } 451 452 func TestValidateAuthInfoExecNoEnv(t *testing.T) { 453 config := clientcmdapi.NewConfig() 454 config.AuthInfos["user"] = &clientcmdapi.AuthInfo{ 455 Exec: &clientcmdapi.ExecConfig{ 456 Command: "/bin/example", 457 APIVersion: "clientauthentication.k8s.io/v1alpha1", 458 Env: []clientcmdapi.ExecEnvVar{ 459 {Name: "foo", Value: ""}, 460 }, 461 InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode, 462 }, 463 } 464 test := configValidationTest{ 465 config: config, 466 } 467 468 test.testAuthInfo("user", t) 469 test.testConfig(t) 470 } 471 472 func TestValidateAuthInfoExecInteractiveModeMissing(t *testing.T) { 473 config := clientcmdapi.NewConfig() 474 config.AuthInfos["user"] = &clientcmdapi.AuthInfo{ 475 Exec: &clientcmdapi.ExecConfig{ 476 Command: "/bin/example", 477 APIVersion: "clientauthentication.k8s.io/v1alpha1", 478 }, 479 } 480 test := configValidationTest{ 481 config: config, 482 expectedErrorSubstring: []string{ 483 "interactiveMode must be specified for user to use exec authentication plugin", 484 }, 485 } 486 487 test.testAuthInfo("user", t) 488 test.testConfig(t) 489 } 490 491 func TestValidateAuthInfoExecInteractiveModeInvalid(t *testing.T) { 492 config := clientcmdapi.NewConfig() 493 config.AuthInfos["user"] = &clientcmdapi.AuthInfo{ 494 Exec: &clientcmdapi.ExecConfig{ 495 Command: "/bin/example", 496 APIVersion: "clientauthentication.k8s.io/v1alpha1", 497 InteractiveMode: "invalid", 498 }, 499 } 500 test := configValidationTest{ 501 config: config, 502 expectedErrorSubstring: []string{ 503 `invalid interactiveMode for user: "invalid"`, 504 }, 505 } 506 507 test.testAuthInfo("user", t) 508 test.testConfig(t) 509 } 510 511 type configValidationTest struct { 512 config *clientcmdapi.Config 513 expectedErrorSubstring []string 514 } 515 516 func (c configValidationTest) testContext(contextName string, t *testing.T) { 517 errs := validateContext(contextName, *c.config.Contexts[contextName], *c.config) 518 519 if len(c.expectedErrorSubstring) != 0 { 520 if len(errs) == 0 { 521 t.Errorf("Expected error containing: %v", c.expectedErrorSubstring) 522 } 523 for _, curr := range c.expectedErrorSubstring { 524 if len(errs) != 0 && !strings.Contains(utilerrors.NewAggregate(errs).Error(), curr) { 525 t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, utilerrors.NewAggregate(errs)) 526 } 527 } 528 529 } else { 530 if len(errs) != 0 { 531 t.Errorf("Unexpected error: %v", utilerrors.NewAggregate(errs)) 532 } 533 } 534 } 535 536 func (c configValidationTest) testConfirmUsable(contextName string, t *testing.T) { 537 err := ConfirmUsable(*c.config, contextName) 538 539 if len(c.expectedErrorSubstring) != 0 { 540 if err == nil { 541 t.Errorf("Expected error containing: %v", c.expectedErrorSubstring) 542 } else { 543 for _, curr := range c.expectedErrorSubstring { 544 if err != nil && !strings.Contains(err.Error(), curr) { 545 t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, err) 546 } 547 } 548 } 549 } else { 550 if err != nil { 551 t.Errorf("Unexpected error: %v", err) 552 } 553 } 554 } 555 556 func (c configValidationTest) testConfig(t *testing.T) { 557 err := Validate(*c.config) 558 559 if len(c.expectedErrorSubstring) != 0 { 560 if err == nil { 561 t.Errorf("Expected error containing: %v", c.expectedErrorSubstring) 562 } else { 563 for _, curr := range c.expectedErrorSubstring { 564 if err != nil && !strings.Contains(err.Error(), curr) { 565 t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, err) 566 } 567 } 568 if !IsConfigurationInvalid(err) { 569 t.Errorf("all errors should be configuration invalid: %v", err) 570 } 571 } 572 } else { 573 if err != nil { 574 t.Errorf("Unexpected error: %v", err) 575 } 576 } 577 } 578 579 func (c configValidationTest) testCluster(clusterName string, t *testing.T) { 580 errs := validateClusterInfo(clusterName, *c.config.Clusters[clusterName]) 581 582 if len(c.expectedErrorSubstring) != 0 { 583 if len(errs) == 0 { 584 t.Errorf("Expected error containing: %v", c.expectedErrorSubstring) 585 } 586 for _, curr := range c.expectedErrorSubstring { 587 if len(errs) != 0 && !strings.Contains(utilerrors.NewAggregate(errs).Error(), curr) { 588 t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, utilerrors.NewAggregate(errs)) 589 } 590 } 591 592 } else { 593 if len(errs) != 0 { 594 t.Errorf("Unexpected error: %v", utilerrors.NewAggregate(errs)) 595 } 596 } 597 } 598 599 func (c configValidationTest) testAuthInfo(authInfoName string, t *testing.T) { 600 errs := validateAuthInfo(authInfoName, *c.config.AuthInfos[authInfoName]) 601 602 if len(c.expectedErrorSubstring) != 0 { 603 if len(errs) == 0 { 604 t.Errorf("Expected error containing: %v", c.expectedErrorSubstring) 605 } 606 for _, curr := range c.expectedErrorSubstring { 607 if len(errs) != 0 && !strings.Contains(utilerrors.NewAggregate(errs).Error(), curr) { 608 t.Errorf("Expected error containing: %v, but got %v", c.expectedErrorSubstring, utilerrors.NewAggregate(errs)) 609 } 610 } 611 612 } else { 613 if len(errs) != 0 { 614 t.Errorf("Unexpected error: %v", utilerrors.NewAggregate(errs)) 615 } 616 } 617 } 618 619 type alwaysMatchingError struct{} 620 621 func (_ alwaysMatchingError) Error() string { 622 return "error" 623 } 624 625 func (_ alwaysMatchingError) Is(_ error) bool { 626 return true 627 } 628 629 type someError struct{ msg string } 630 631 func (se someError) Error() string { 632 if se.msg != "" { 633 return se.msg 634 } 635 return "err" 636 } 637 638 func TestErrConfigurationInvalidWithErrorsIs(t *testing.T) { 639 testCases := []struct { 640 name string 641 err error 642 matchAgainst error 643 expectMatch bool 644 }{ 645 { 646 name: "no match", 647 err: errConfigurationInvalid{errors.New("my-error"), errors.New("my-other-error")}, 648 matchAgainst: fmt.Errorf("no entry %s", "here"), 649 }, 650 { 651 name: "match via .Is()", 652 err: errConfigurationInvalid{errors.New("forbidden"), alwaysMatchingError{}}, 653 matchAgainst: errors.New("unauthorized"), 654 expectMatch: true, 655 }, 656 { 657 name: "match via equality", 658 err: errConfigurationInvalid{errors.New("err"), someError{}}, 659 matchAgainst: someError{}, 660 expectMatch: true, 661 }, 662 { 663 name: "match via nested aggregate", 664 err: errConfigurationInvalid{errors.New("closed today"), errConfigurationInvalid{errConfigurationInvalid{someError{}}}}, 665 matchAgainst: someError{}, 666 expectMatch: true, 667 }, 668 { 669 name: "match via wrapped aggregate", 670 err: fmt.Errorf("wrap: %w", errConfigurationInvalid{errors.New("err"), someError{}}), 671 matchAgainst: someError{}, 672 expectMatch: true, 673 }, 674 } 675 676 for _, tc := range testCases { 677 t.Run(tc.name, func(t *testing.T) { 678 result := errors.Is(tc.err, tc.matchAgainst) 679 if result != tc.expectMatch { 680 t.Errorf("expected match: %t, got match: %t", tc.expectMatch, result) 681 } 682 }) 683 } 684 } 685 686 type accessTrackingError struct { 687 wasAccessed bool 688 } 689 690 func (accessTrackingError) Error() string { 691 return "err" 692 } 693 694 func (ate *accessTrackingError) Is(_ error) bool { 695 ate.wasAccessed = true 696 return true 697 } 698 699 var _ error = &accessTrackingError{} 700 701 func TestErrConfigurationInvalidWithErrorsIsShortCircuitsOnFirstMatch(t *testing.T) { 702 errC := errConfigurationInvalid{&accessTrackingError{}, &accessTrackingError{}} 703 _ = errors.Is(errC, &accessTrackingError{}) 704 705 var numAccessed int 706 for _, err := range errC { 707 if ate := err.(*accessTrackingError); ate.wasAccessed { 708 numAccessed++ 709 } 710 } 711 if numAccessed != 1 { 712 t.Errorf("expected exactly one error to get accessed, got %d", numAccessed) 713 } 714 }