github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/cloud/credentials_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package cloud_test 5 6 import ( 7 "io/ioutil" 8 "path/filepath" 9 "regexp" 10 11 "github.com/juju/errors" 12 jc "github.com/juju/testing/checkers" 13 gc "gopkg.in/check.v1" 14 15 "github.com/juju/juju/cloud" 16 "github.com/juju/juju/testing" 17 ) 18 19 type credentialsSuite struct { 20 testing.FakeJujuXDGDataHomeSuite 21 } 22 23 var _ = gc.Suite(&credentialsSuite{}) 24 25 func (s *credentialsSuite) TestMarshalAccessKey(c *gc.C) { 26 creds := map[string]cloud.CloudCredential{ 27 "aws": { 28 DefaultCredential: "default-cred", 29 DefaultRegion: "us-west-2", 30 AuthCredentials: map[string]cloud.Credential{ 31 "peter": cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{ 32 "access-key": "key", 33 "secret-key": "secret", 34 }), 35 // TODO(wallyworld) - add anther credential once goyaml.v2 supports inline MapSlice. 36 //"paul": &cloud.AccessKeyCredentials{ 37 // Key: "paulkey", 38 // Secret: "paulsecret", 39 //}, 40 }, 41 }, 42 } 43 out, err := cloud.MarshalCredentials(creds) 44 c.Assert(err, jc.ErrorIsNil) 45 c.Assert(string(out), gc.Equals, ` 46 credentials: 47 aws: 48 default-credential: default-cred 49 default-region: us-west-2 50 peter: 51 auth-type: access-key 52 access-key: key 53 secret-key: secret 54 `[1:]) 55 } 56 57 func (s *credentialsSuite) TestMarshalOpenstackAccessKey(c *gc.C) { 58 creds := map[string]cloud.CloudCredential{ 59 "openstack": { 60 DefaultCredential: "default-cred", 61 DefaultRegion: "region-a", 62 AuthCredentials: map[string]cloud.Credential{ 63 "peter": cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{ 64 "access-key": "key", 65 "secret-key": "secret", 66 "tenant-name": "tenant", 67 }), 68 }, 69 }, 70 } 71 out, err := cloud.MarshalCredentials(creds) 72 c.Assert(err, jc.ErrorIsNil) 73 c.Assert(string(out), gc.Equals, ` 74 credentials: 75 openstack: 76 default-credential: default-cred 77 default-region: region-a 78 peter: 79 auth-type: access-key 80 access-key: key 81 secret-key: secret 82 tenant-name: tenant 83 `[1:]) 84 } 85 86 func (s *credentialsSuite) TestMarshalOpenstackUserPass(c *gc.C) { 87 creds := map[string]cloud.CloudCredential{ 88 "openstack": { 89 DefaultCredential: "default-cred", 90 DefaultRegion: "region-a", 91 AuthCredentials: map[string]cloud.Credential{ 92 "peter": cloud.NewCredential(cloud.UserPassAuthType, map[string]string{ 93 "username": "user", 94 "password": "secret", 95 "tenant-name": "tenant", 96 }), 97 }, 98 }, 99 } 100 out, err := cloud.MarshalCredentials(creds) 101 c.Assert(err, jc.ErrorIsNil) 102 c.Assert(string(out), gc.Equals, ` 103 credentials: 104 openstack: 105 default-credential: default-cred 106 default-region: region-a 107 peter: 108 auth-type: userpass 109 password: secret 110 tenant-name: tenant 111 username: user 112 `[1:]) 113 } 114 115 func (s *credentialsSuite) TestMarshalAzureCredntials(c *gc.C) { 116 creds := map[string]cloud.CloudCredential{ 117 "azure": { 118 DefaultCredential: "default-cred", 119 DefaultRegion: "Central US", 120 AuthCredentials: map[string]cloud.Credential{ 121 "peter": cloud.NewCredential(cloud.UserPassAuthType, map[string]string{ 122 "application-id": "app-id", 123 "application-password": "app-secret", 124 "subscription-id": "subscription-id", 125 "tenant-id": "tenant-id", 126 }), 127 }, 128 }, 129 } 130 out, err := cloud.MarshalCredentials(creds) 131 c.Assert(err, jc.ErrorIsNil) 132 c.Assert(string(out), gc.Equals, ` 133 credentials: 134 azure: 135 default-credential: default-cred 136 default-region: Central US 137 peter: 138 auth-type: userpass 139 application-id: app-id 140 application-password: app-secret 141 subscription-id: subscription-id 142 tenant-id: tenant-id 143 `[1:]) 144 } 145 146 func (s *credentialsSuite) TestMarshalOAuth1(c *gc.C) { 147 creds := map[string]cloud.CloudCredential{ 148 "maas": { 149 DefaultCredential: "default-cred", 150 DefaultRegion: "region-default", 151 AuthCredentials: map[string]cloud.Credential{ 152 "peter": cloud.NewCredential(cloud.OAuth1AuthType, map[string]string{ 153 "consumer-key": "consumer-key", 154 "consumer-secret": "consumer-secret", 155 "access-token": "access-token", 156 "token-secret": "token-secret", 157 }), 158 }, 159 }, 160 } 161 out, err := cloud.MarshalCredentials(creds) 162 c.Assert(err, jc.ErrorIsNil) 163 c.Assert(string(out), gc.Equals, ` 164 credentials: 165 maas: 166 default-credential: default-cred 167 default-region: region-default 168 peter: 169 auth-type: oauth1 170 access-token: access-token 171 consumer-key: consumer-key 172 consumer-secret: consumer-secret 173 token-secret: token-secret 174 `[1:]) 175 } 176 177 func (s *credentialsSuite) TestMarshalOAuth2(c *gc.C) { 178 creds := map[string]cloud.CloudCredential{ 179 "google": { 180 DefaultCredential: "default-cred", 181 DefaultRegion: "West US", 182 AuthCredentials: map[string]cloud.Credential{ 183 "peter": cloud.NewCredential(cloud.OAuth2AuthType, map[string]string{ 184 "client-id": "client-id", 185 "client-email": "client-email", 186 "private-key": "secret", 187 }), 188 }, 189 }, 190 } 191 out, err := cloud.MarshalCredentials(creds) 192 c.Assert(err, jc.ErrorIsNil) 193 c.Assert(string(out), gc.Equals, ` 194 credentials: 195 google: 196 default-credential: default-cred 197 default-region: West US 198 peter: 199 auth-type: oauth2 200 client-email: client-email 201 client-id: client-id 202 private-key: secret 203 `[1:]) 204 } 205 206 func (s *credentialsSuite) TestParseCredentials(c *gc.C) { 207 s.testParseCredentials(c, []byte(` 208 credentials: 209 aws: 210 default-credential: peter 211 default-region: us-east-2 212 peter: 213 auth-type: access-key 214 access-key: key 215 secret-key: secret 216 aws-china: 217 default-credential: zhu8jie 218 zhu8jie: 219 auth-type: access-key 220 access-key: key 221 secret-key: secret 222 sun5kong: 223 auth-type: access-key 224 access-key: quay 225 secret-key: sekrit 226 aws-gov: 227 default-region: us-gov-west-1 228 supersekrit: 229 auth-type: access-key 230 access-key: super 231 secret-key: sekrit 232 `[1:]), map[string]cloud.CloudCredential{ 233 "aws": cloud.CloudCredential{ 234 DefaultCredential: "peter", 235 DefaultRegion: "us-east-2", 236 AuthCredentials: map[string]cloud.Credential{ 237 "peter": cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{ 238 "access-key": "key", 239 "secret-key": "secret", 240 }), 241 }, 242 }, 243 "aws-china": cloud.CloudCredential{ 244 DefaultCredential: "zhu8jie", 245 AuthCredentials: map[string]cloud.Credential{ 246 "zhu8jie": cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{ 247 "access-key": "key", 248 "secret-key": "secret", 249 }), 250 "sun5kong": cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{ 251 "access-key": "quay", 252 "secret-key": "sekrit", 253 }), 254 }, 255 }, 256 "aws-gov": cloud.CloudCredential{ 257 DefaultRegion: "us-gov-west-1", 258 AuthCredentials: map[string]cloud.Credential{ 259 "supersekrit": cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{ 260 "access-key": "super", 261 "secret-key": "sekrit", 262 }), 263 }, 264 }, 265 }) 266 } 267 268 func (s *credentialsSuite) TestParseCredentialsUnknownAuthType(c *gc.C) { 269 // Unknown auth-type is not validated by ParseCredentials. 270 // Validation is deferred to FinalizeCredential. 271 s.testParseCredentials(c, []byte(` 272 credentials: 273 cloud-name: 274 credential-name: 275 auth-type: woop 276 `[1:]), map[string]cloud.CloudCredential{ 277 "cloud-name": cloud.CloudCredential{ 278 AuthCredentials: map[string]cloud.Credential{ 279 "credential-name": cloud.NewCredential("woop", nil), 280 }, 281 }, 282 }) 283 } 284 285 func (s *credentialsSuite) testParseCredentials(c *gc.C, input []byte, expect map[string]cloud.CloudCredential) { 286 output, err := cloud.ParseCredentials(input) 287 c.Assert(err, jc.ErrorIsNil) 288 c.Assert(output, jc.DeepEquals, expect) 289 } 290 291 func (s *credentialsSuite) TestParseCredentialsMissingAuthType(c *gc.C) { 292 s.testParseCredentialsError(c, []byte(` 293 credentials: 294 cloud-name: 295 credential-name: 296 doesnt: really-matter 297 `[1:]), "credentials.cloud-name.credential-name: missing auth-type") 298 } 299 300 func (s *credentialsSuite) TestParseCredentialsNonStringValue(c *gc.C) { 301 s.testParseCredentialsError(c, []byte(` 302 credentials: 303 cloud-name: 304 credential-name: 305 non-string-value: 123 306 `[1:]), `credentials\.cloud-name\.credential-name\.non-string-value: expected string, got int\(123\)`) 307 } 308 309 func (s *credentialsSuite) testParseCredentialsError(c *gc.C, input []byte, expect string) { 310 _, err := cloud.ParseCredentials(input) 311 c.Assert(err, gc.ErrorMatches, expect) 312 } 313 314 func (s *credentialsSuite) TestFinalizeCredential(c *gc.C) { 315 cred := cloud.NewCredential( 316 cloud.UserPassAuthType, 317 map[string]string{ 318 "key": "value", 319 }, 320 ) 321 schema := cloud.CredentialSchema{ 322 { 323 "key", 324 cloud.CredentialAttr{ 325 Description: "key credential", 326 Hidden: true, 327 }, 328 }, 329 } 330 _, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ 331 cloud.UserPassAuthType: schema, 332 }, readFileNotSupported) 333 c.Assert(err, jc.ErrorIsNil) 334 } 335 336 func (s *credentialsSuite) TestFinalizeCredentialFileAttr(c *gc.C) { 337 cred := cloud.NewCredential( 338 cloud.UserPassAuthType, 339 map[string]string{ 340 "key-file": "path", 341 "quay": "value", 342 }, 343 ) 344 schema := cloud.CredentialSchema{ 345 { 346 "key", 347 cloud.CredentialAttr{ 348 Description: "key credential", 349 Hidden: true, 350 FileAttr: "key-file", 351 }, 352 }, { 353 "quay", cloud.CredentialAttr{FileAttr: "quay-file"}, 354 }, 355 } 356 readFile := func(s string) ([]byte, error) { 357 c.Assert(s, gc.Equals, "path") 358 return []byte("file-value"), nil 359 } 360 newCred, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ 361 cloud.UserPassAuthType: schema, 362 }, readFile) 363 c.Assert(err, jc.ErrorIsNil) 364 c.Assert(newCred.Attributes(), jc.DeepEquals, map[string]string{ 365 "key": "file-value", 366 "quay": "value", 367 }) 368 } 369 370 func (s *credentialsSuite) TestFinalizeCredentialFileEmpty(c *gc.C) { 371 cred := cloud.NewCredential( 372 cloud.UserPassAuthType, 373 map[string]string{ 374 "key-file": "path", 375 }, 376 ) 377 schema := cloud.CredentialSchema{ 378 { 379 "key", 380 cloud.CredentialAttr{ 381 Description: "key credential", 382 Hidden: true, 383 FileAttr: "key-file", 384 }, 385 }, 386 } 387 readFile := func(string) ([]byte, error) { 388 return nil, nil 389 } 390 _, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ 391 cloud.UserPassAuthType: schema, 392 }, readFile) 393 c.Assert(err, gc.ErrorMatches, `empty file for "key" not valid`) 394 } 395 396 func (s *credentialsSuite) TestFinalizeCredentialFileAttrNeither(c *gc.C) { 397 cred := cloud.NewCredential( 398 cloud.UserPassAuthType, 399 map[string]string{}, 400 ) 401 schema := cloud.CredentialSchema{ 402 { 403 "key", 404 cloud.CredentialAttr{ 405 Description: "key credential", 406 Hidden: true, 407 FileAttr: "key-file", 408 }, 409 }, 410 } 411 _, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ 412 cloud.UserPassAuthType: schema, 413 }, readFileNotSupported) 414 c.Assert(err, gc.ErrorMatches, `either "key" or "key-file" must be specified`) 415 } 416 417 func (s *credentialsSuite) TestFinalizeCredentialFileAttrBoth(c *gc.C) { 418 cred := cloud.NewCredential( 419 cloud.UserPassAuthType, 420 map[string]string{ 421 "key": "value", 422 "key-file": "path", 423 }, 424 ) 425 schema := cloud.CredentialSchema{ 426 { 427 "key", 428 cloud.CredentialAttr{ 429 Description: "key credential", 430 Hidden: true, 431 FileAttr: "key-file", 432 }, 433 }, 434 } 435 _, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ 436 cloud.UserPassAuthType: schema, 437 }, readFileNotSupported) 438 c.Assert(err, gc.ErrorMatches, `specifying both "key" and "key-file" not valid`) 439 } 440 441 func (s *credentialsSuite) TestFinalizeCredentialInvalid(c *gc.C) { 442 cred := cloud.NewCredential( 443 cloud.UserPassAuthType, 444 map[string]string{}, 445 ) 446 schema := cloud.CredentialSchema{ 447 { 448 "key", 449 cloud.CredentialAttr{ 450 Description: "key credential", 451 Hidden: true, 452 }, 453 }, 454 } 455 _, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ 456 cloud.UserPassAuthType: schema, 457 }, readFileNotSupported) 458 c.Assert(err, gc.ErrorMatches, "key: expected string, got nothing") 459 } 460 461 func (s *credentialsSuite) TestFinalizeCredentialNotSupported(c *gc.C) { 462 cred := cloud.NewCredential( 463 cloud.OAuth2AuthType, 464 map[string]string{}, 465 ) 466 _, err := cloud.FinalizeCredential( 467 cred, map[cloud.AuthType]cloud.CredentialSchema{}, readFileNotSupported, 468 ) 469 c.Assert(err, jc.Satisfies, errors.IsNotSupported) 470 c.Assert(err, gc.ErrorMatches, `auth-type "oauth2" not supported`) 471 } 472 473 func readFileNotSupported(f string) ([]byte, error) { 474 return nil, errors.NotSupportedf("reading file %q", f) 475 } 476 477 func (s *credentialsSuite) TestFinalizeCredentialMandatoryFieldMissing(c *gc.C) { 478 cred := cloud.NewCredential( 479 cloud.UserPassAuthType, 480 map[string]string{ 481 "password": "secret", 482 "domain": "domain", 483 }, 484 ) 485 schema := cloud.CredentialSchema{ 486 { 487 "username", cloud.CredentialAttr{Optional: false}, 488 }, { 489 "password", cloud.CredentialAttr{Hidden: true}, 490 }, { 491 "domain", cloud.CredentialAttr{}, 492 }, 493 } 494 _, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ 495 cloud.UserPassAuthType: schema, 496 }, nil) 497 c.Assert(err, gc.ErrorMatches, "username: expected string, got nothing") 498 } 499 500 func (s *credentialsSuite) TestFinalizeCredentialMandatoryFieldFromFile(c *gc.C) { 501 cred := cloud.NewCredential( 502 cloud.UserPassAuthType, 503 map[string]string{ 504 "key-file": "path", 505 }, 506 ) 507 schema := cloud.CredentialSchema{ 508 { 509 "key", 510 cloud.CredentialAttr{ 511 Description: "key credential", 512 Optional: false, 513 FileAttr: "key-file", 514 }, 515 }, 516 } 517 readFile := func(s string) ([]byte, error) { 518 c.Assert(s, gc.Equals, "path") 519 return []byte("file-value"), nil 520 } 521 newCred, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ 522 cloud.UserPassAuthType: schema, 523 }, readFile) 524 c.Assert(err, jc.ErrorIsNil) 525 c.Assert(newCred.Attributes(), jc.DeepEquals, map[string]string{ 526 "key": "file-value", 527 }) 528 } 529 530 func (s *credentialsSuite) TestFinalizeCredentialExtraField(c *gc.C) { 531 cred := cloud.NewCredential( 532 cloud.UserPassAuthType, 533 map[string]string{ 534 "username": "user", 535 "password": "secret", 536 "domain": "domain", 537 "access-key": "access-key", 538 }, 539 ) 540 schema := cloud.CredentialSchema{ 541 { 542 "username", cloud.CredentialAttr{Optional: false}, 543 }, { 544 "password", cloud.CredentialAttr{Hidden: true}, 545 }, { 546 "domain", cloud.CredentialAttr{}, 547 }, 548 } 549 _, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ 550 cloud.UserPassAuthType: schema, 551 }, nil) 552 c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(`unknown key "access-key" (value "access-key")`)) 553 } 554 555 func (s *credentialsSuite) TestFinalizeCredentialInvalidChoice(c *gc.C) { 556 cred := cloud.NewCredential( 557 cloud.UserPassAuthType, 558 map[string]string{ 559 "username": "user", 560 "password": "secret", 561 "algorithm": "foo", 562 }, 563 ) 564 schema := cloud.CredentialSchema{ 565 { 566 "username", cloud.CredentialAttr{Optional: false}, 567 }, { 568 "password", cloud.CredentialAttr{Hidden: true}, 569 }, { 570 "algorithm", cloud.CredentialAttr{Options: []interface{}{"bar", "foobar"}}, 571 }, 572 } 573 _, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ 574 cloud.UserPassAuthType: schema, 575 }, nil) 576 c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(`algorithm: expected one of [bar foobar], got "foo"`)) 577 } 578 579 func (s *credentialsSuite) TestFinalizeCredentialFilePath(c *gc.C) { 580 dir := c.MkDir() 581 filename := filepath.Join(dir, "filename") 582 err := ioutil.WriteFile(filename, []byte{}, 0600) 583 c.Assert(err, jc.ErrorIsNil) 584 585 cred := cloud.NewCredential( 586 cloud.JSONFileAuthType, 587 map[string]string{ 588 "file": filename, 589 }, 590 ) 591 schema := cloud.CredentialSchema{ 592 { 593 "file", cloud.CredentialAttr{FilePath: true}, 594 }, 595 } 596 newCred, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ 597 cloud.JSONFileAuthType: schema, 598 }, nil) 599 c.Assert(err, jc.ErrorIsNil) 600 c.Assert(newCred.Attributes(), jc.DeepEquals, map[string]string{ 601 "file": filename, 602 }) 603 } 604 605 func (s *credentialsSuite) TestFinalizeCredentialInvalidFilePath(c *gc.C) { 606 cred := cloud.NewCredential( 607 cloud.JSONFileAuthType, 608 map[string]string{ 609 "file": filepath.Join(c.MkDir(), "somefile"), 610 }, 611 ) 612 schema := cloud.CredentialSchema{ 613 { 614 "file", cloud.CredentialAttr{FilePath: true}, 615 }, 616 } 617 _, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ 618 cloud.JSONFileAuthType: schema, 619 }, nil) 620 c.Assert(err, gc.ErrorMatches, "invalid file path: .*") 621 } 622 623 func (s *credentialsSuite) TestFinalizeCredentialRelativeFilePath(c *gc.C) { 624 cred := cloud.NewCredential( 625 cloud.JSONFileAuthType, 626 map[string]string{ 627 "file": "file", 628 }, 629 ) 630 schema := cloud.CredentialSchema{ 631 { 632 "file", cloud.CredentialAttr{FilePath: true}, 633 }, 634 } 635 _, err := cloud.FinalizeCredential(cred, map[cloud.AuthType]cloud.CredentialSchema{ 636 cloud.JSONFileAuthType: schema, 637 }, nil) 638 c.Assert(err, gc.ErrorMatches, "file path must be an absolute path: file") 639 } 640 641 func (s *credentialsSuite) TestRemoveSecrets(c *gc.C) { 642 cred := cloud.NewCredential( 643 cloud.UserPassAuthType, 644 map[string]string{ 645 "username": "user", 646 "password": "secret", 647 }, 648 ) 649 schema := cloud.CredentialSchema{ 650 { 651 "username", cloud.CredentialAttr{}, 652 }, { 653 "password", cloud.CredentialAttr{Hidden: true}, 654 }, 655 } 656 sanitisedCred, err := cloud.RemoveSecrets(cred, map[cloud.AuthType]cloud.CredentialSchema{ 657 cloud.UserPassAuthType: schema, 658 }) 659 c.Assert(err, jc.ErrorIsNil) 660 c.Assert(sanitisedCred.Attributes(), jc.DeepEquals, map[string]string{ 661 "username": "user", 662 }) 663 }