github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/apiserver/admin_test.go (about) 1 // Copyright 2012-2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package apiserver_test 5 6 import ( 7 "fmt" 8 "net" 9 "strconv" 10 "sync" 11 "time" 12 13 "github.com/juju/errors" 14 "github.com/juju/loggo" 15 "github.com/juju/names" 16 jc "github.com/juju/testing/checkers" 17 "github.com/juju/utils" 18 gc "gopkg.in/check.v1" 19 20 "github.com/juju/juju/api" 21 apimachiner "github.com/juju/juju/api/machiner" 22 apitesting "github.com/juju/juju/api/testing" 23 "github.com/juju/juju/apiserver" 24 "github.com/juju/juju/apiserver/params" 25 jujutesting "github.com/juju/juju/juju/testing" 26 "github.com/juju/juju/network" 27 "github.com/juju/juju/rpc" 28 "github.com/juju/juju/state" 29 coretesting "github.com/juju/juju/testing" 30 "github.com/juju/juju/testing/factory" 31 ) 32 33 type baseLoginSuite struct { 34 jujutesting.JujuConnSuite 35 setAdminApi func(*apiserver.Server) 36 } 37 38 type loginSuite struct { 39 baseLoginSuite 40 } 41 42 var _ = gc.Suite(&loginSuite{ 43 baseLoginSuite{ 44 setAdminApi: func(srv *apiserver.Server) { 45 apiserver.SetAdminApiVersions(srv, 3) 46 }, 47 }, 48 }) 49 50 func (s *baseLoginSuite) SetUpTest(c *gc.C) { 51 s.JujuConnSuite.SetUpTest(c) 52 loggo.GetLogger("juju.apiserver").SetLogLevel(loggo.TRACE) 53 } 54 55 func (s *baseLoginSuite) setupServer(c *gc.C) (api.Connection, func()) { 56 return s.setupServerForEnvironment(c, s.State.ModelTag()) 57 } 58 59 func (s *baseLoginSuite) setupServerForEnvironment(c *gc.C, modelTag names.ModelTag) (api.Connection, func()) { 60 info, cleanup := s.setupServerForEnvironmentWithValidator(c, modelTag, nil) 61 st, err := api.Open(info, fastDialOpts) 62 c.Assert(err, jc.ErrorIsNil) 63 return st, func() { 64 st.Close() 65 cleanup() 66 } 67 } 68 69 func (s *baseLoginSuite) setupMachineAndServer(c *gc.C) (*api.Info, func()) { 70 machine, password := s.Factory.MakeMachineReturningPassword( 71 c, &factory.MachineParams{Nonce: "fake_nonce"}) 72 info, cleanup := s.setupServerWithValidator(c, nil) 73 info.Tag = machine.Tag() 74 info.Password = password 75 info.Nonce = "fake_nonce" 76 return info, cleanup 77 } 78 79 func (s *loginSuite) TestLoginWithInvalidTag(c *gc.C) { 80 info := s.APIInfo(c) 81 info.Tag = nil 82 info.Password = "" 83 st := s.openAPIWithoutLogin(c, info) 84 defer st.Close() 85 86 request := ¶ms.LoginRequest{ 87 AuthTag: "bar", 88 Credentials: "password", 89 } 90 91 var response params.LoginResult 92 err := st.APICall("Admin", 3, "", "Login", request, &response) 93 c.Assert(err, gc.ErrorMatches, `.*"bar" is not a valid tag.*`) 94 } 95 96 func (s *loginSuite) TestBadLogin(c *gc.C) { 97 // Start our own server so we can control when the first login 98 // happens. Otherwise in JujuConnSuite.SetUpTest api.Open is 99 // called with user-admin permissions automatically. 100 info, cleanup := s.setupServerWithValidator(c, nil) 101 defer cleanup() 102 103 adminUser := s.AdminUserTag(c) 104 105 for i, t := range []struct { 106 tag names.Tag 107 password string 108 err error 109 code string 110 }{{ 111 tag: adminUser, 112 password: "wrong password", 113 err: &rpc.RequestError{ 114 Message: "invalid entity name or password", 115 Code: "unauthorized access", 116 }, 117 code: params.CodeUnauthorized, 118 }, { 119 tag: names.NewUserTag("unknown"), 120 password: "password", 121 err: &rpc.RequestError{ 122 Message: "invalid entity name or password", 123 Code: "unauthorized access", 124 }, 125 code: params.CodeUnauthorized, 126 }} { 127 c.Logf("test %d; entity %q; password %q", i, t.tag, t.password) 128 func() { 129 // Open the API without logging in, so we can perform 130 // operations on the connection before calling Login. 131 st := s.openAPIWithoutLogin(c, info) 132 defer st.Close() 133 134 _, err := apimachiner.NewState(st).Machine(names.NewMachineTag("0")) 135 c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ 136 Message: `unknown object type "Machiner"`, 137 Code: "not implemented", 138 }) 139 140 // Since these are user login tests, the nonce is empty. 141 err = st.Login(t.tag, t.password, "", nil) 142 c.Assert(errors.Cause(err), gc.DeepEquals, t.err) 143 c.Assert(params.ErrCode(err), gc.Equals, t.code) 144 145 _, err = apimachiner.NewState(st).Machine(names.NewMachineTag("0")) 146 c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ 147 Message: `unknown object type "Machiner"`, 148 Code: "not implemented", 149 }) 150 }() 151 } 152 } 153 154 func (s *loginSuite) TestLoginAsDeactivatedUser(c *gc.C) { 155 info, cleanup := s.setupServerWithValidator(c, nil) 156 defer cleanup() 157 158 st := s.openAPIWithoutLogin(c, info) 159 defer st.Close() 160 password := "password" 161 u := s.Factory.MakeUser(c, &factory.UserParams{Password: password, Disabled: true}) 162 163 _, err := st.Client().Status([]string{}) 164 c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ 165 Message: `unknown object type "Client"`, 166 Code: "not implemented", 167 }) 168 169 // Since these are user login tests, the nonce is empty. 170 err = st.Login(u.Tag(), password, "", nil) 171 c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ 172 Message: "invalid entity name or password", 173 Code: "unauthorized access", 174 }) 175 176 _, err = st.Client().Status([]string{}) 177 c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ 178 Message: `unknown object type "Client"`, 179 Code: "not implemented", 180 }) 181 } 182 183 func (s *baseLoginSuite) runLoginSetsLogIdentifier(c *gc.C) { 184 info, cleanup := s.setupServerWithValidator(c, nil) 185 defer cleanup() 186 187 machine, password := s.Factory.MakeMachineReturningPassword( 188 c, &factory.MachineParams{Nonce: "fake_nonce"}) 189 190 info.Tag = machine.Tag() 191 info.Password = password 192 info.Nonce = "fake_nonce" 193 194 apiConn, err := api.Open(info, fastDialOpts) 195 c.Assert(err, jc.ErrorIsNil) 196 defer apiConn.Close() 197 198 apiMachine, err := apimachiner.NewState(apiConn).Machine(machine.MachineTag()) 199 c.Assert(err, jc.ErrorIsNil) 200 c.Assert(apiMachine.Tag(), gc.Equals, machine.Tag()) 201 } 202 203 func (s *loginSuite) TestLoginAddrs(c *gc.C) { 204 info, cleanup := s.setupMachineAndServer(c) 205 defer cleanup() 206 207 err := s.State.SetAPIHostPorts(nil) 208 c.Assert(err, jc.ErrorIsNil) 209 210 // Initially just the address we connect with is returned, 211 // despite there being no APIHostPorts in state. 212 connectedAddr, hostPorts := s.loginHostPorts(c, info) 213 connectedAddrHost, connectedAddrPortString, err := net.SplitHostPort(connectedAddr) 214 c.Assert(err, jc.ErrorIsNil) 215 connectedAddrPort, err := strconv.Atoi(connectedAddrPortString) 216 c.Assert(err, jc.ErrorIsNil) 217 connectedAddrHostPorts := [][]network.HostPort{ 218 network.NewHostPorts(connectedAddrPort, connectedAddrHost), 219 } 220 c.Assert(hostPorts, gc.DeepEquals, connectedAddrHostPorts) 221 222 // After storing APIHostPorts in state, Login should store 223 // all of them and the address we connected with. 224 server1Addresses := []network.Address{{ 225 Value: "server-1", 226 Type: network.HostName, 227 Scope: network.ScopePublic, 228 }, { 229 Value: "10.0.0.1", 230 Type: network.IPv4Address, 231 Scope: network.ScopeCloudLocal, 232 }} 233 server2Addresses := []network.Address{{ 234 Value: "::1", 235 Type: network.IPv6Address, 236 Scope: network.ScopeMachineLocal, 237 }} 238 stateAPIHostPorts := [][]network.HostPort{ 239 network.AddressesWithPort(server1Addresses, 123), 240 network.AddressesWithPort(server2Addresses, 456), 241 } 242 err = s.State.SetAPIHostPorts(stateAPIHostPorts) 243 c.Assert(err, jc.ErrorIsNil) 244 _, hostPorts = s.loginHostPorts(c, info) 245 // Now that we connected, we add the other stateAPIHostPorts. However, 246 // the one we connected to comes first. 247 stateAPIHostPorts = append(connectedAddrHostPorts, stateAPIHostPorts...) 248 c.Assert(hostPorts, gc.DeepEquals, stateAPIHostPorts) 249 } 250 251 func (s *baseLoginSuite) loginHostPorts(c *gc.C, info *api.Info) (connectedAddr string, hostPorts [][]network.HostPort) { 252 st, err := api.Open(info, fastDialOpts) 253 c.Assert(err, jc.ErrorIsNil) 254 defer st.Close() 255 return st.Addr(), st.APIHostPorts() 256 } 257 258 func startNLogins(c *gc.C, n int, info *api.Info) (chan error, *sync.WaitGroup) { 259 errResults := make(chan error, 100) 260 var doneWG sync.WaitGroup 261 var startedWG sync.WaitGroup 262 c.Logf("starting %d concurrent logins to %v", n, info.Addrs) 263 for i := 0; i < n; i++ { 264 i := i 265 c.Logf("starting login request %d", i) 266 startedWG.Add(1) 267 doneWG.Add(1) 268 go func() { 269 c.Logf("started login %d", i) 270 startedWG.Done() 271 st, err := api.Open(info, fastDialOpts) 272 errResults <- err 273 if err == nil { 274 st.Close() 275 } 276 doneWG.Done() 277 c.Logf("finished login %d: %v", i, err) 278 }() 279 } 280 startedWG.Wait() 281 return errResults, &doneWG 282 } 283 284 func (s *loginSuite) TestDelayLogins(c *gc.C) { 285 info, cleanup := s.setupMachineAndServer(c) 286 defer cleanup() 287 delayChan, cleanup := apiserver.DelayLogins() 288 defer cleanup() 289 290 // numConcurrentLogins is how many logins will fire off simultaneously. 291 // It doesn't really matter, as long as it is less than LoginRateLimit 292 const numConcurrentLogins = 5 293 c.Assert(numConcurrentLogins, jc.LessThan, apiserver.LoginRateLimit) 294 // Trigger a bunch of login requests 295 errResults, wg := startNLogins(c, numConcurrentLogins, info) 296 select { 297 case err := <-errResults: 298 c.Fatalf("we should not have gotten any logins yet: %v", err) 299 case <-time.After(coretesting.ShortWait): 300 } 301 // Allow one login to proceed 302 c.Logf("letting one login through") 303 select { 304 case delayChan <- struct{}{}: 305 default: 306 c.Fatalf("we should have been able to unblock a login") 307 } 308 select { 309 case err := <-errResults: 310 c.Check(err, jc.ErrorIsNil) 311 case <-time.After(coretesting.LongWait): 312 c.Fatalf("timed out while waiting for Login to finish") 313 } 314 c.Logf("checking no other logins succeeded") 315 // It should have only let 1 login through 316 select { 317 case err := <-errResults: 318 c.Fatalf("we should not have gotten more logins: %v", err) 319 case <-time.After(coretesting.ShortWait): 320 } 321 // Now allow the rest of the logins to proceed 322 c.Logf("letting %d logins through", numConcurrentLogins-1) 323 for i := 0; i < numConcurrentLogins-1; i++ { 324 delayChan <- struct{}{} 325 } 326 c.Logf("waiting for Logins to finish") 327 wg.Wait() 328 close(errResults) 329 successCount := 0 330 for err := range errResults { 331 c.Check(err, jc.ErrorIsNil) 332 if err == nil { 333 successCount += 1 334 } 335 } 336 // All the logins should succeed, they were just delayed after 337 // connecting. 338 c.Check(successCount, gc.Equals, numConcurrentLogins-1) 339 c.Logf("done") 340 } 341 342 func (s *loginSuite) TestLoginRateLimited(c *gc.C) { 343 info, cleanup := s.setupMachineAndServer(c) 344 defer cleanup() 345 delayChan, cleanup := apiserver.DelayLogins() 346 defer cleanup() 347 348 // Start enough concurrent Login requests so that we max out our 349 // LoginRateLimit. Do one extra so we know we are in overload 350 errResults, wg := startNLogins(c, apiserver.LoginRateLimit+1, info) 351 select { 352 case err := <-errResults: 353 c.Check(err, jc.Satisfies, params.IsCodeTryAgain) 354 case <-time.After(coretesting.LongWait): 355 c.Fatalf("timed out waiting for login to get rejected.") 356 } 357 358 // Let one request through, we should see that it succeeds without 359 // error, and then be able to start a new request, but it will block 360 delayChan <- struct{}{} 361 select { 362 case err := <-errResults: 363 c.Check(err, jc.ErrorIsNil) 364 case <-time.After(coretesting.LongWait): 365 c.Fatalf("timed out expecting one login to succeed") 366 } 367 chOne := make(chan error, 1) 368 wg.Add(1) 369 go func() { 370 st, err := api.Open(info, fastDialOpts) 371 chOne <- err 372 if err == nil { 373 st.Close() 374 } 375 wg.Done() 376 }() 377 select { 378 case err := <-chOne: 379 c.Fatalf("the open request should not have completed: %v", err) 380 case <-time.After(coretesting.ShortWait): 381 } 382 // Let all the logins finish. We started with LoginRateLimit, let one 383 // proceed, but we issued another one, so there should be 384 // LoginRateLimit logins pending. 385 for i := 0; i < apiserver.LoginRateLimit; i++ { 386 delayChan <- struct{}{} 387 } 388 wg.Wait() 389 close(errResults) 390 for err := range errResults { 391 c.Check(err, jc.ErrorIsNil) 392 } 393 } 394 395 func (s *loginSuite) TestUsersLoginWhileRateLimited(c *gc.C) { 396 info, cleanup := s.setupMachineAndServer(c) 397 defer cleanup() 398 delayChan, cleanup := apiserver.DelayLogins() 399 defer cleanup() 400 401 // Start enough concurrent Login requests so that we max out our 402 // LoginRateLimit. Do one extra so we know we are in overload 403 machineResults, machineWG := startNLogins(c, apiserver.LoginRateLimit+1, info) 404 select { 405 case err := <-machineResults: 406 c.Check(err, jc.Satisfies, params.IsCodeTryAgain) 407 case <-time.After(coretesting.LongWait): 408 c.Fatalf("timed out waiting for login to get rejected.") 409 } 410 411 userInfo := *info 412 userInfo.Tag = s.AdminUserTag(c) 413 userInfo.Password = "dummy-secret" 414 userResults, userWG := startNLogins(c, apiserver.LoginRateLimit+1, &userInfo) 415 // all of them should have started, and none of them in TryAgain state 416 select { 417 case err := <-userResults: 418 c.Fatalf("we should not have gotten any logins yet: %v", err) 419 case <-time.After(coretesting.ShortWait): 420 } 421 totalLogins := apiserver.LoginRateLimit*2 + 1 422 for i := 0; i < totalLogins; i++ { 423 delayChan <- struct{}{} 424 } 425 machineWG.Wait() 426 close(machineResults) 427 userWG.Wait() 428 close(userResults) 429 machineCount := 0 430 for err := range machineResults { 431 machineCount += 1 432 c.Check(err, jc.ErrorIsNil) 433 } 434 c.Check(machineCount, gc.Equals, apiserver.LoginRateLimit) 435 userCount := 0 436 for err := range userResults { 437 userCount += 1 438 c.Check(err, jc.ErrorIsNil) 439 } 440 c.Check(userCount, gc.Equals, apiserver.LoginRateLimit+1) 441 } 442 443 func (s *loginSuite) TestUsersAreNotRateLimited(c *gc.C) { 444 info, cleanup := s.setupServerWithValidator(c, nil) 445 info.Tag = s.AdminUserTag(c) 446 info.Password = "dummy-secret" 447 defer cleanup() 448 delayChan, cleanup := apiserver.DelayLogins() 449 defer cleanup() 450 // We can login more than LoginRateLimit users 451 nLogins := apiserver.LoginRateLimit * 2 452 errResults, wg := startNLogins(c, nLogins, info) 453 select { 454 case err := <-errResults: 455 c.Fatalf("we should not have gotten any logins yet: %v", err) 456 case <-time.After(coretesting.ShortWait): 457 } 458 c.Logf("letting %d logins complete", nLogins) 459 for i := 0; i < nLogins; i++ { 460 delayChan <- struct{}{} 461 } 462 c.Logf("waiting for original requests to finish") 463 wg.Wait() 464 close(errResults) 465 for err := range errResults { 466 c.Check(err, jc.ErrorIsNil) 467 } 468 } 469 470 func (s *loginSuite) TestNonEnvironUserLoginFails(c *gc.C) { 471 info, cleanup := s.setupServerWithValidator(c, nil) 472 defer cleanup() 473 user := s.Factory.MakeUser(c, &factory.UserParams{Password: "dummy-password", NoModelUser: true}) 474 info.Password = "dummy-password" 475 info.Tag = user.UserTag() 476 _, err := api.Open(info, fastDialOpts) 477 c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ 478 Message: "invalid entity name or password", 479 Code: "unauthorized access", 480 }) 481 } 482 483 func (s *loginSuite) TestLoginValidationSuccess(c *gc.C) { 484 validator := func(params.LoginRequest) error { 485 return nil 486 } 487 checker := func(c *gc.C, loginErr error, st api.Connection) { 488 c.Assert(loginErr, gc.IsNil) 489 490 // Ensure an API call that would be restricted during 491 // upgrades works after a normal login. 492 err := st.APICall("Client", 1, "", "DestroyModel", nil, nil) 493 c.Assert(err, jc.ErrorIsNil) 494 } 495 s.checkLoginWithValidator(c, validator, checker) 496 } 497 498 func (s *loginSuite) TestLoginValidationFail(c *gc.C) { 499 validator := func(params.LoginRequest) error { 500 return errors.New("Login not allowed") 501 } 502 checker := func(c *gc.C, loginErr error, _ api.Connection) { 503 // error is wrapped in API server 504 c.Assert(loginErr, gc.ErrorMatches, "Login not allowed") 505 } 506 s.checkLoginWithValidator(c, validator, checker) 507 } 508 509 func (s *loginSuite) TestLoginValidationDuringUpgrade(c *gc.C) { 510 validator := func(params.LoginRequest) error { 511 return params.UpgradeInProgressError 512 } 513 checker := func(c *gc.C, loginErr error, st api.Connection) { 514 c.Assert(loginErr, gc.IsNil) 515 516 var statusResult params.FullStatus 517 err := st.APICall("Client", 1, "", "FullStatus", params.StatusParams{}, &statusResult) 518 c.Assert(err, jc.ErrorIsNil) 519 520 err = st.APICall("Client", 1, "", "DestroyModel", nil, nil) 521 c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{Message: params.CodeUpgradeInProgress, Code: params.CodeUpgradeInProgress}) 522 } 523 s.checkLoginWithValidator(c, validator, checker) 524 } 525 526 func (s *loginSuite) TestFailedLoginDuringMaintenance(c *gc.C) { 527 validator := func(params.LoginRequest) error { 528 return errors.New("something") 529 } 530 info, cleanup := s.setupServerWithValidator(c, validator) 531 defer cleanup() 532 533 checkLogin := func(tag names.Tag) { 534 st := s.openAPIWithoutLogin(c, info) 535 defer st.Close() 536 err := st.Login(tag, "dummy-secret", "nonce", nil) 537 c.Assert(err, gc.ErrorMatches, "something") 538 } 539 checkLogin(names.NewUserTag("definitelywontexist")) 540 checkLogin(names.NewMachineTag("99999")) 541 } 542 543 type validationChecker func(c *gc.C, err error, st api.Connection) 544 545 func (s *baseLoginSuite) checkLoginWithValidator(c *gc.C, validator apiserver.LoginValidator, checker validationChecker) { 546 info, cleanup := s.setupServerWithValidator(c, validator) 547 defer cleanup() 548 549 st := s.openAPIWithoutLogin(c, info) 550 defer st.Close() 551 552 // Ensure not already logged in. 553 _, err := apimachiner.NewState(st).Machine(names.NewMachineTag("0")) 554 c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ 555 Message: `unknown object type "Machiner"`, 556 Code: "not implemented", 557 }) 558 559 adminUser := s.AdminUserTag(c) 560 // Since these are user login tests, the nonce is empty. 561 err = st.Login(adminUser, "dummy-secret", "", nil) 562 563 checker(c, err, st) 564 } 565 566 func (s *baseLoginSuite) setupServerWithValidator(c *gc.C, validator apiserver.LoginValidator) (*api.Info, func()) { 567 env, err := s.State.Model() 568 c.Assert(err, jc.ErrorIsNil) 569 return s.setupServerForEnvironmentWithValidator(c, env.ModelTag(), validator) 570 } 571 572 func (s *baseLoginSuite) setupServerForEnvironmentWithValidator(c *gc.C, modelTag names.ModelTag, validator apiserver.LoginValidator) (*api.Info, func()) { 573 listener, err := net.Listen("tcp", "127.0.0.1:0") 574 c.Assert(err, jc.ErrorIsNil) 575 srv, err := apiserver.NewServer( 576 s.State, 577 listener, 578 apiserver.ServerConfig{ 579 Cert: []byte(coretesting.ServerCert), 580 Key: []byte(coretesting.ServerKey), 581 Validator: validator, 582 Tag: names.NewMachineTag("0"), 583 LogDir: c.MkDir(), 584 }, 585 ) 586 c.Assert(err, jc.ErrorIsNil) 587 c.Assert(s.setAdminApi, gc.NotNil) 588 s.setAdminApi(srv) 589 info := &api.Info{ 590 Tag: nil, 591 Password: "", 592 ModelTag: modelTag, 593 Addrs: []string{srv.Addr().String()}, 594 CACert: coretesting.CACert, 595 } 596 return info, func() { 597 err := srv.Stop() 598 c.Assert(err, jc.ErrorIsNil) 599 } 600 } 601 602 func (s *baseLoginSuite) openAPIWithoutLogin(c *gc.C, info *api.Info) api.Connection { 603 info.Tag = nil 604 info.Password = "" 605 info.SkipLogin = true 606 st, err := api.Open(info, fastDialOpts) 607 c.Assert(err, jc.ErrorIsNil) 608 return st 609 } 610 611 func (s *loginSuite) TestControllerModel(c *gc.C) { 612 info, cleanup := s.setupServerWithValidator(c, nil) 613 defer cleanup() 614 615 c.Assert(info.ModelTag, gc.Equals, s.State.ModelTag()) 616 st := s.openAPIWithoutLogin(c, info) 617 defer st.Close() 618 619 adminUser := s.AdminUserTag(c) 620 err := st.Login(adminUser, "dummy-secret", "", nil) 621 c.Assert(err, jc.ErrorIsNil) 622 623 s.assertRemoteEnvironment(c, st, s.State.ModelTag()) 624 } 625 626 func (s *loginSuite) TestControllerModelBadCreds(c *gc.C) { 627 info, cleanup := s.setupServerWithValidator(c, nil) 628 defer cleanup() 629 630 c.Assert(info.ModelTag, gc.Equals, s.State.ModelTag()) 631 st := s.openAPIWithoutLogin(c, info) 632 defer st.Close() 633 634 adminUser := s.AdminUserTag(c) 635 err := st.Login(adminUser, "bad-password", "", nil) 636 c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ 637 Message: `invalid entity name or password`, 638 Code: "unauthorized access", 639 }) 640 } 641 642 func (s *loginSuite) TestNonExistentEnvironment(c *gc.C) { 643 info, cleanup := s.setupServerWithValidator(c, nil) 644 defer cleanup() 645 646 uuid, err := utils.NewUUID() 647 c.Assert(err, jc.ErrorIsNil) 648 info.ModelTag = names.NewModelTag(uuid.String()) 649 st := s.openAPIWithoutLogin(c, info) 650 defer st.Close() 651 652 adminUser := s.AdminUserTag(c) 653 err = st.Login(adminUser, "dummy-secret", "", nil) 654 c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ 655 Message: fmt.Sprintf("unknown model: %q", uuid), 656 Code: "not found", 657 }) 658 } 659 660 func (s *loginSuite) TestInvalidEnvironment(c *gc.C) { 661 info, cleanup := s.setupServerWithValidator(c, nil) 662 defer cleanup() 663 664 info.ModelTag = names.NewModelTag("rubbish") 665 st := s.openAPIWithoutLogin(c, info) 666 defer st.Close() 667 668 adminUser := s.AdminUserTag(c) 669 err := st.Login(adminUser, "dummy-secret", "", nil) 670 c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ 671 Message: `unknown model: "rubbish"`, 672 Code: "not found", 673 }) 674 } 675 676 func (s *loginSuite) TestOtherEnvironment(c *gc.C) { 677 info, cleanup := s.setupServerWithValidator(c, nil) 678 defer cleanup() 679 680 envOwner := s.Factory.MakeUser(c, nil) 681 envState := s.Factory.MakeModel(c, &factory.ModelParams{ 682 Owner: envOwner.UserTag(), 683 }) 684 defer envState.Close() 685 info.ModelTag = envState.ModelTag() 686 st := s.openAPIWithoutLogin(c, info) 687 defer st.Close() 688 689 err := st.Login(envOwner.UserTag(), "password", "", nil) 690 c.Assert(err, jc.ErrorIsNil) 691 s.assertRemoteEnvironment(c, st, envState.ModelTag()) 692 } 693 694 func (s *loginSuite) TestMachineLoginOtherEnvironment(c *gc.C) { 695 // User credentials are checked against a global user list. 696 // Machine credentials are checked against environment specific 697 // machines, so this makes sure that the credential checking is 698 // using the correct state connection. 699 info, cleanup := s.setupServerWithValidator(c, nil) 700 defer cleanup() 701 702 envOwner := s.Factory.MakeUser(c, nil) 703 envState := s.Factory.MakeModel(c, &factory.ModelParams{ 704 Owner: envOwner.UserTag(), 705 ConfigAttrs: map[string]interface{}{ 706 "controller": false, 707 }, 708 }) 709 defer envState.Close() 710 711 f2 := factory.NewFactory(envState) 712 machine, password := f2.MakeMachineReturningPassword(c, &factory.MachineParams{ 713 Nonce: "nonce", 714 }) 715 716 info.ModelTag = envState.ModelTag() 717 st := s.openAPIWithoutLogin(c, info) 718 defer st.Close() 719 720 err := st.Login(machine.Tag(), password, "nonce", nil) 721 c.Assert(err, jc.ErrorIsNil) 722 } 723 724 func (s *loginSuite) TestOtherEnvironmentFromController(c *gc.C) { 725 info, cleanup := s.setupServerWithValidator(c, nil) 726 defer cleanup() 727 728 machine, password := s.Factory.MakeMachineReturningPassword(c, &factory.MachineParams{ 729 Jobs: []state.MachineJob{state.JobManageModel}, 730 }) 731 732 envState := s.Factory.MakeModel(c, nil) 733 defer envState.Close() 734 info.ModelTag = envState.ModelTag() 735 st := s.openAPIWithoutLogin(c, info) 736 defer st.Close() 737 738 err := st.Login(machine.Tag(), password, "nonce", nil) 739 c.Assert(err, jc.ErrorIsNil) 740 } 741 742 func (s *loginSuite) TestOtherEnvironmentWhenNotController(c *gc.C) { 743 info, cleanup := s.setupServerWithValidator(c, nil) 744 defer cleanup() 745 746 machine, password := s.Factory.MakeMachineReturningPassword(c, nil) 747 748 envState := s.Factory.MakeModel(c, nil) 749 defer envState.Close() 750 info.ModelTag = envState.ModelTag() 751 st := s.openAPIWithoutLogin(c, info) 752 defer st.Close() 753 754 err := st.Login(machine.Tag(), password, "nonce", nil) 755 c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ 756 Message: "invalid entity name or password", 757 Code: "unauthorized access", 758 }) 759 } 760 761 func (s *loginSuite) assertRemoteEnvironment(c *gc.C, st api.Connection, expected names.ModelTag) { 762 // Look at what the api thinks it has. 763 tag, err := st.ModelTag() 764 c.Assert(err, jc.ErrorIsNil) 765 c.Assert(tag, gc.Equals, expected) 766 // Look at what the api Client thinks it has. 767 client := st.Client() 768 769 // ModelUUID looks at the env tag on the api state connection. 770 c.Assert(client.ModelUUID(), gc.Equals, expected.Id()) 771 772 // ModelInfo calls a remote method that looks up the environment. 773 info, err := client.ModelInfo() 774 c.Assert(err, jc.ErrorIsNil) 775 c.Assert(info.UUID, gc.Equals, expected.Id()) 776 } 777 778 func (s *loginSuite) TestLoginUpdatesLastLoginAndConnection(c *gc.C) { 779 _, cleanup := s.setupServerWithValidator(c, nil) 780 defer cleanup() 781 782 // Since the login and connection times truncate time to the second, 783 // we need to make sure our start time is just before now. 784 startTime := time.Now().Add(-time.Second) 785 786 password := "shhh..." 787 user := s.Factory.MakeUser(c, &factory.UserParams{ 788 Password: password, 789 }) 790 791 info := s.APIInfo(c) 792 info.Tag = user.Tag() 793 info.Password = password 794 apiState, err := api.Open(info, api.DialOpts{}) 795 c.Assert(err, jc.ErrorIsNil) 796 defer apiState.Close() 797 798 // The user now has last login updated. 799 err = user.Refresh() 800 c.Assert(err, jc.ErrorIsNil) 801 lastLogin, err := user.LastLogin() 802 c.Assert(err, jc.ErrorIsNil) 803 c.Assert(lastLogin, gc.NotNil) 804 c.Assert(lastLogin.After(startTime), jc.IsTrue) 805 806 // The env user is also updated. 807 modelUser, err := s.State.ModelUser(user.UserTag()) 808 c.Assert(err, jc.ErrorIsNil) 809 when, err := modelUser.LastConnection() 810 c.Assert(err, jc.ErrorIsNil) 811 c.Assert(when, gc.NotNil) 812 c.Assert(when.After(startTime), jc.IsTrue) 813 } 814 815 var _ = gc.Suite(&macaroonLoginSuite{}) 816 817 type macaroonLoginSuite struct { 818 apitesting.MacaroonSuite 819 } 820 821 func (s *macaroonLoginSuite) TestLoginToController(c *gc.C) { 822 // Note that currently we cannot use macaroon auth 823 // to log into the controller rather than an environment 824 // because there's no place to store the fact that 825 // a given external user is allowed access to the controller. 826 s.DischargerLogin = func() string { 827 return "test@somewhere" 828 } 829 info := s.APIInfo(c) 830 831 // Zero the environment tag so that we log into the controller 832 // not the environment. 833 info.ModelTag = names.ModelTag{} 834 835 client, err := api.Open(info, api.DialOpts{}) 836 c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ 837 Message: "invalid entity name or password", 838 Code: "unauthorized access", 839 }) 840 c.Assert(client, gc.Equals, nil) 841 } 842 843 func (s *macaroonLoginSuite) TestLoginToEnvironmentSuccess(c *gc.C) { 844 s.AddModelUser(c, "test@somewhere") 845 s.DischargerLogin = func() string { 846 return "test@somewhere" 847 } 848 client, err := api.Open(s.APIInfo(c), api.DialOpts{}) 849 c.Assert(err, jc.ErrorIsNil) 850 defer client.Close() 851 852 // The auth tag has been correctly returned by the server. 853 c.Assert(client.AuthTag(), gc.Equals, names.NewUserTag("test@somewhere")) 854 } 855 856 func (s *macaroonLoginSuite) TestFailedToObtainDischargeLogin(c *gc.C) { 857 s.DischargerLogin = func() string { 858 return "" 859 } 860 client, err := api.Open(s.APIInfo(c), api.DialOpts{}) 861 c.Assert(err, gc.ErrorMatches, `cannot get discharge from "https://.*": third party refused discharge: cannot discharge: login denied by discharger`) 862 c.Assert(client, gc.Equals, nil) 863 } 864 865 func (s *macaroonLoginSuite) TestUnknownUserLogin(c *gc.C) { 866 s.DischargerLogin = func() string { 867 return "testUnknown@somewhere" 868 } 869 client, err := api.Open(s.APIInfo(c), api.DialOpts{}) 870 c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ 871 Message: "invalid entity name or password", 872 Code: "unauthorized access", 873 }) 874 c.Assert(client, gc.Equals, nil) 875 }