github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/state/apiserver/login_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package apiserver_test 5 6 import ( 7 "net" 8 "strconv" 9 "sync" 10 "time" 11 12 "github.com/juju/errors" 13 "github.com/juju/loggo" 14 jc "github.com/juju/testing/checkers" 15 "github.com/juju/utils" 16 gc "launchpad.net/gocheck" 17 18 jujutesting "github.com/juju/juju/juju/testing" 19 "github.com/juju/juju/network" 20 "github.com/juju/juju/state" 21 "github.com/juju/juju/state/api" 22 "github.com/juju/juju/state/api/params" 23 "github.com/juju/juju/state/apiserver" 24 coretesting "github.com/juju/juju/testing" 25 "github.com/juju/juju/testing/factory" 26 ) 27 28 type loginSuite struct { 29 jujutesting.JujuConnSuite 30 } 31 32 var _ = gc.Suite(&loginSuite{}) 33 34 var badLoginTests = []struct { 35 tag string 36 password string 37 err string 38 code string 39 }{{ 40 tag: "user-admin", 41 password: "wrong password", 42 err: "invalid entity name or password", 43 code: params.CodeUnauthorized, 44 }, { 45 tag: "user-foo", 46 password: "password", 47 err: "invalid entity name or password", 48 code: params.CodeUnauthorized, 49 }, { 50 tag: "bar", 51 password: "password", 52 err: `"bar" is not a valid tag`, 53 }} 54 55 func (s *loginSuite) setupServer(c *gc.C) (*api.Info, func()) { 56 return s.setupServerWithValidator(c, nil) 57 } 58 59 func (s *loginSuite) setupServerWithValidator(c *gc.C, validator apiserver.LoginValidator) (*api.Info, func()) { 60 srv, err := apiserver.NewServer( 61 s.State, 62 apiserver.ServerConfig{ 63 Addr: "localhost:0", 64 Cert: []byte(coretesting.ServerCert), 65 Key: []byte(coretesting.ServerKey), 66 Validator: validator, 67 }, 68 ) 69 c.Assert(err, gc.IsNil) 70 env, err := s.State.Environment() 71 c.Assert(err, gc.IsNil) 72 info := &api.Info{ 73 Tag: "", 74 Password: "", 75 EnvironTag: env.Tag(), 76 Addrs: []string{srv.Addr()}, 77 CACert: coretesting.CACert, 78 } 79 return info, func() { 80 err := srv.Stop() 81 c.Assert(err, gc.IsNil) 82 } 83 } 84 85 func (s *loginSuite) setupMachineAndServer(c *gc.C) (*api.Info, func()) { 86 machine, err := s.State.AddMachine("quantal", state.JobHostUnits) 87 c.Assert(err, gc.IsNil) 88 err = machine.SetProvisioned("foo", "fake_nonce", nil) 89 c.Assert(err, gc.IsNil) 90 password, err := utils.RandomPassword() 91 c.Assert(err, gc.IsNil) 92 err = machine.SetPassword(password) 93 c.Assert(err, gc.IsNil) 94 info, cleanup := s.setupServer(c) 95 info.Tag = machine.Tag() 96 info.Password = password 97 info.Nonce = "fake_nonce" 98 return info, cleanup 99 } 100 101 func (s *loginSuite) TestBadLogin(c *gc.C) { 102 // Start our own server so we can control when the first login 103 // happens. Otherwise in JujuConnSuite.SetUpTest api.Open is 104 // called with user-admin permissions automatically. 105 info, cleanup := s.setupServer(c) 106 defer cleanup() 107 108 for i, t := range badLoginTests { 109 c.Logf("test %d; entity %q; password %q", i, t.tag, t.password) 110 // Note that Open does not log in if the tag and password 111 // are empty. This allows us to test operations on the connection 112 // before calling Login, which we could not do if Open 113 // always logged in. 114 info.Tag = "" 115 info.Password = "" 116 func() { 117 st, err := api.Open(info, fastDialOpts) 118 c.Assert(err, gc.IsNil) 119 defer st.Close() 120 121 _, err = st.Machiner().Machine("0") 122 c.Assert(err, gc.ErrorMatches, `unknown object type "Machiner"`) 123 124 // Since these are user login tests, the nonce is empty. 125 err = st.Login(t.tag, t.password, "") 126 c.Assert(err, gc.ErrorMatches, t.err) 127 c.Assert(params.ErrCode(err), gc.Equals, t.code) 128 129 _, err = st.Machiner().Machine("0") 130 c.Assert(err, gc.ErrorMatches, `unknown object type "Machiner"`) 131 }() 132 } 133 } 134 135 func (s *loginSuite) TestLoginAsDeactivatedUser(c *gc.C) { 136 info, cleanup := s.setupServer(c) 137 defer cleanup() 138 139 info.Tag = "" 140 info.Password = "" 141 st, err := api.Open(info, fastDialOpts) 142 c.Assert(err, gc.IsNil) 143 defer st.Close() 144 password := "password" 145 u := s.Factory.MakeUser(factory.UserParams{Password: password}) 146 err = u.Deactivate() 147 c.Assert(err, gc.IsNil) 148 149 _, err = st.Client().Status([]string{}) 150 c.Assert(err, gc.ErrorMatches, `unknown object type "Client"`) 151 152 // Since these are user login tests, the nonce is empty. 153 err = st.Login(u.Tag(), password, "") 154 c.Assert(err, gc.ErrorMatches, "invalid entity name or password") 155 156 _, err = st.Client().Status([]string{}) 157 c.Assert(err, gc.ErrorMatches, `unknown object type "Client"`) 158 } 159 160 func (s *loginSuite) TestLoginSetsLogIdentifier(c *gc.C) { 161 info, cleanup := s.setupServer(c) 162 defer cleanup() 163 164 machineInState, err := s.State.AddMachine("quantal", state.JobHostUnits) 165 c.Assert(err, gc.IsNil) 166 err = machineInState.SetProvisioned("foo", "fake_nonce", nil) 167 c.Assert(err, gc.IsNil) 168 password, err := utils.RandomPassword() 169 c.Assert(err, gc.IsNil) 170 err = machineInState.SetPassword(password) 171 c.Assert(err, gc.IsNil) 172 c.Assert(machineInState.Tag(), gc.Equals, "machine-0") 173 174 tw := &loggo.TestWriter{} 175 c.Assert(loggo.RegisterWriter("login-tester", tw, loggo.DEBUG), gc.IsNil) 176 defer loggo.RemoveWriter("login-tester") 177 178 info.Tag = machineInState.Tag() 179 info.Password = password 180 info.Nonce = "fake_nonce" 181 182 apiConn, err := api.Open(info, fastDialOpts) 183 c.Assert(err, gc.IsNil) 184 apiMachine, err := apiConn.Machiner().Machine(machineInState.Tag()) 185 c.Assert(err, gc.IsNil) 186 c.Assert(apiMachine.Tag(), gc.Equals, machineInState.Tag()) 187 apiConn.Close() 188 189 c.Assert(tw.Log, jc.LogMatches, []string{ 190 `<- \[[0-9A-F]+\] <unknown> {"RequestId":1,"Type":"Admin","Request":"Login",` + 191 `"Params":{"AuthTag":"machine-0","Password":"[^"]*","Nonce":"fake_nonce"}` + 192 `}`, 193 // Now that we are logged in, we see the entity's tag 194 // [0-9.umns] is to handle timestamps that are ns, us, ms, or s 195 // long, though we expect it to be in the 'ms' range. 196 `-> \[[0-9A-F]+\] machine-0 [0-9.]+[umn]?s {"RequestId":1,"Response":.*} Admin\[""\].Login`, 197 `<- \[[0-9A-F]+\] machine-0 {"RequestId":2,"Type":"Machiner","Request":"Life","Params":{"Entities":\[{"Tag":"machine-0"}\]}}`, 198 `-> \[[0-9A-F]+\] machine-0 [0-9.umns]+ {"RequestId":2,"Response":{"Results":\[{"Life":"alive","Error":null}\]}} Machiner\[""\]\.Life`, 199 }) 200 } 201 202 func (s *loginSuite) TestLoginAddrs(c *gc.C) { 203 info, cleanup := s.setupMachineAndServer(c) 204 defer cleanup() 205 206 // Initially just the address we connect with is returned, 207 // despite there being no APIHostPorts in state. 208 connectedAddr, hostPorts := s.loginHostPorts(c, info) 209 connectedAddrHost, connectedAddrPortString, err := net.SplitHostPort(connectedAddr) 210 c.Assert(err, gc.IsNil) 211 connectedAddrPort, err := strconv.Atoi(connectedAddrPortString) 212 c.Assert(err, gc.IsNil) 213 connectedAddrHostPorts := [][]network.HostPort{ 214 []network.HostPort{{ 215 network.NewAddress(connectedAddrHost, network.ScopeUnknown), 216 connectedAddrPort, 217 }}, 218 } 219 c.Assert(hostPorts, gc.DeepEquals, connectedAddrHostPorts) 220 221 // After storing APIHostPorts in state, Login should store 222 // all of them and the address we connected with. 223 server1Addresses := []network.Address{{ 224 Value: "server-1", 225 Type: network.HostName, 226 Scope: network.ScopePublic, 227 }, { 228 Value: "10.0.0.1", 229 Type: network.IPv4Address, 230 NetworkName: "internal", 231 Scope: network.ScopeCloudLocal, 232 }} 233 server2Addresses := []network.Address{{ 234 Value: "::1", 235 Type: network.IPv6Address, 236 NetworkName: "loopback", 237 Scope: network.ScopeMachineLocal, 238 }} 239 stateAPIHostPorts := [][]network.HostPort{ 240 network.AddressesWithPort(server1Addresses, 123), 241 network.AddressesWithPort(server2Addresses, 456), 242 } 243 err = s.State.SetAPIHostPorts(stateAPIHostPorts) 244 c.Assert(err, gc.IsNil) 245 connectedAddr, hostPorts = s.loginHostPorts(c, info) 246 // Now that we connected, we add the other stateAPIHostPorts. However, 247 // the one we connected to comes first. 248 stateAPIHostPorts = append(connectedAddrHostPorts, stateAPIHostPorts...) 249 c.Assert(hostPorts, gc.DeepEquals, stateAPIHostPorts) 250 } 251 252 func (s *loginSuite) loginHostPorts(c *gc.C, info *api.Info) (connectedAddr string, hostPorts [][]network.HostPort) { 253 st, err := api.Open(info, fastDialOpts) 254 c.Assert(err, gc.IsNil) 255 defer st.Close() 256 return st.Addr(), st.APIHostPorts() 257 } 258 259 func startNLogins(c *gc.C, n int, info *api.Info) (chan error, *sync.WaitGroup) { 260 errResults := make(chan error, 100) 261 var doneWG sync.WaitGroup 262 var startedWG sync.WaitGroup 263 c.Logf("starting %d concurrent logins to %v", n, info.Addrs) 264 for i := 0; i < n; i++ { 265 i := i 266 c.Logf("starting login request %d", i) 267 startedWG.Add(1) 268 doneWG.Add(1) 269 go func() { 270 c.Logf("started login %d", i) 271 startedWG.Done() 272 st, err := api.Open(info, fastDialOpts) 273 errResults <- err 274 if err == nil { 275 st.Close() 276 } 277 doneWG.Done() 278 c.Logf("finished login %d: %v", i, err) 279 }() 280 } 281 startedWG.Wait() 282 return errResults, &doneWG 283 } 284 285 func (s *loginSuite) TestDelayLogins(c *gc.C) { 286 info, cleanup := s.setupMachineAndServer(c) 287 defer cleanup() 288 delayChan, cleanup := apiserver.DelayLogins() 289 defer cleanup() 290 291 // numConcurrentLogins is how many logins will fire off simultaneously. 292 // It doesn't really matter, as long as it is less than LoginRateLimit 293 const numConcurrentLogins = 5 294 c.Assert(numConcurrentLogins, jc.LessThan, apiserver.LoginRateLimit) 295 // Trigger a bunch of login requests 296 errResults, wg := startNLogins(c, numConcurrentLogins, info) 297 select { 298 case err := <-errResults: 299 c.Fatalf("we should not have gotten any logins yet: %v", err) 300 case <-time.After(coretesting.ShortWait): 301 } 302 // Allow one login to proceed 303 c.Logf("letting one login through") 304 select { 305 case delayChan <- struct{}{}: 306 default: 307 c.Fatalf("we should have been able to unblock a login") 308 } 309 select { 310 case err := <-errResults: 311 c.Check(err, gc.IsNil) 312 case <-time.After(coretesting.LongWait): 313 c.Fatalf("timed out while waiting for Login to finish") 314 } 315 c.Logf("checking no other logins succeeded") 316 // It should have only let 1 login through 317 select { 318 case err := <-errResults: 319 c.Fatalf("we should not have gotten more logins: %v", err) 320 case <-time.After(coretesting.ShortWait): 321 } 322 // Now allow the rest of the logins to proceed 323 c.Logf("letting %d logins through", numConcurrentLogins-1) 324 for i := 0; i < numConcurrentLogins-1; i++ { 325 delayChan <- struct{}{} 326 } 327 c.Logf("waiting for Logins to finish") 328 wg.Wait() 329 close(errResults) 330 successCount := 0 331 for err := range errResults { 332 c.Check(err, gc.IsNil) 333 if err == nil { 334 successCount += 1 335 } 336 } 337 // All the logins should succeed, they were just delayed after 338 // connecting. 339 c.Check(successCount, gc.Equals, numConcurrentLogins-1) 340 c.Logf("done") 341 } 342 343 func (s *loginSuite) TestLoginRateLimited(c *gc.C) { 344 info, cleanup := s.setupMachineAndServer(c) 345 defer cleanup() 346 delayChan, cleanup := apiserver.DelayLogins() 347 defer cleanup() 348 349 // Start enough concurrent Login requests so that we max out our 350 // LoginRateLimit 351 errResults, wg := startNLogins(c, apiserver.LoginRateLimit, info) 352 // All of them should have started, none of them should have succeeded 353 // (or failed) yet 354 select { 355 case err := <-errResults: 356 c.Fatalf("we should not have gotten any logins yet: %v", err) 357 case <-time.After(coretesting.ShortWait): 358 } 359 // We now have a bunch of pending Login requests, the next login 360 // request should be immediately bounced 361 _, err := api.Open(info, fastDialOpts) 362 c.Check(err, gc.ErrorMatches, "try again") 363 c.Check(err, jc.Satisfies, params.IsCodeTryAgain) 364 // Let one request through, we should see that it succeeds without 365 // error, and then be able to start a new request, but it will block 366 delayChan <- struct{}{} 367 select { 368 case err := <-errResults: 369 c.Check(err, gc.IsNil) 370 case <-time.After(coretesting.LongWait): 371 c.Fatalf("timed out expecting one login to succeed") 372 } 373 chOne := make(chan error, 1) 374 wg.Add(1) 375 go func() { 376 st, err := api.Open(info, fastDialOpts) 377 chOne <- err 378 if err == nil { 379 st.Close() 380 } 381 wg.Done() 382 }() 383 select { 384 case err := <-chOne: 385 c.Fatalf("the open request should not have completed: %v", err) 386 case <-time.After(coretesting.ShortWait): 387 } 388 // Let all the logins finish. We started with LoginRateLimit, let one 389 // proceed, but the issued another one, so there should be 390 // LoginRateLimit logins pending. 391 for i := 0; i < apiserver.LoginRateLimit; i++ { 392 delayChan <- struct{}{} 393 } 394 wg.Wait() 395 close(errResults) 396 for err := range errResults { 397 c.Check(err, gc.IsNil) 398 } 399 } 400 401 func (s *loginSuite) TestUsersLoginWhileRateLimited(c *gc.C) { 402 info, cleanup := s.setupMachineAndServer(c) 403 defer cleanup() 404 delayChan, cleanup := apiserver.DelayLogins() 405 defer cleanup() 406 407 // Start enough concurrent Login requests so that we max out our 408 // LoginRateLimit. Do one extra so we know we are in overload 409 machineResults, machineWG := startNLogins(c, apiserver.LoginRateLimit+1, info) 410 select { 411 case err := <-machineResults: 412 c.Check(err, jc.Satisfies, params.IsCodeTryAgain) 413 case <-time.After(coretesting.LongWait): 414 c.Fatalf("timed out waiting for login to get rejected.") 415 } 416 417 userInfo := *info 418 userInfo.Tag = "user-admin" 419 userInfo.Password = "dummy-secret" 420 userResults, userWG := startNLogins(c, apiserver.LoginRateLimit+1, &userInfo) 421 // all of them should have started, and none of them in TryAgain state 422 select { 423 case err := <-userResults: 424 c.Fatalf("we should not have gotten any logins yet: %v", err) 425 case <-time.After(coretesting.ShortWait): 426 } 427 totalLogins := apiserver.LoginRateLimit*2 + 1 428 for i := 0; i < totalLogins; i++ { 429 delayChan <- struct{}{} 430 } 431 machineWG.Wait() 432 close(machineResults) 433 userWG.Wait() 434 close(userResults) 435 machineCount := 0 436 for err := range machineResults { 437 machineCount += 1 438 c.Check(err, gc.IsNil) 439 } 440 c.Check(machineCount, gc.Equals, apiserver.LoginRateLimit) 441 userCount := 0 442 for err := range userResults { 443 userCount += 1 444 c.Check(err, gc.IsNil) 445 } 446 c.Check(userCount, gc.Equals, apiserver.LoginRateLimit+1) 447 } 448 449 func (s *loginSuite) TestUsersAreNotRateLimited(c *gc.C) { 450 info, cleanup := s.setupServer(c) 451 info.Tag = "user-admin" 452 info.Password = "dummy-secret" 453 defer cleanup() 454 delayChan, cleanup := apiserver.DelayLogins() 455 defer cleanup() 456 // We can login more than LoginRateLimit users 457 nLogins := apiserver.LoginRateLimit * 2 458 errResults, wg := startNLogins(c, nLogins, info) 459 select { 460 case err := <-errResults: 461 c.Fatalf("we should not have gotten any logins yet: %v", err) 462 case <-time.After(coretesting.ShortWait): 463 } 464 c.Logf("letting %d logins complete", nLogins) 465 for i := 0; i < nLogins; i++ { 466 delayChan <- struct{}{} 467 } 468 c.Logf("waiting for original requests to finish") 469 wg.Wait() 470 close(errResults) 471 for err := range errResults { 472 c.Check(err, gc.IsNil) 473 } 474 } 475 476 func (s *loginSuite) TestLoginReportsEnvironTag(c *gc.C) { 477 info, cleanup := s.setupServer(c) 478 defer cleanup() 479 // If we call api.Open without giving a username and password, then it 480 // won't call Login, so we can call it ourselves. 481 // We Login without passing an EnvironTag, to show that it still lets 482 // us in, and that we can find out the real EnvironTag from the 483 // response. 484 st, err := api.Open(info, fastDialOpts) 485 c.Assert(err, gc.IsNil) 486 var result params.LoginResult 487 creds := ¶ms.Creds{ 488 AuthTag: "user-admin", 489 Password: "dummy-secret", 490 } 491 err = st.Call("Admin", "", "Login", creds, &result) 492 c.Assert(err, gc.IsNil) 493 env, err := s.State.Environment() 494 c.Assert(err, gc.IsNil) 495 c.Assert(result.EnvironTag, gc.Equals, env.Tag()) 496 } 497 498 func (s *loginSuite) TestLoginValidationSuccess(c *gc.C) { 499 validator := func(_ params.Creds) error { 500 return nil 501 } 502 err := s.runLoginWithValidator(c, validator) 503 c.Assert(err, gc.IsNil) 504 } 505 506 func (s *loginSuite) TestLoginValidationFail(c *gc.C) { 507 validator := func(_ params.Creds) error { 508 return errors.New("Login not allowed") 509 } 510 err := s.runLoginWithValidator(c, validator) 511 c.Assert(err, gc.ErrorMatches, "Login not allowed") 512 } 513 514 func (s *loginSuite) runLoginWithValidator(c *gc.C, validator apiserver.LoginValidator) error { 515 info, cleanup := s.setupServerWithValidator(c, validator) 516 defer cleanup() 517 518 info.Tag = "" 519 info.Password = "" 520 521 st, err := api.Open(info, fastDialOpts) 522 c.Assert(err, gc.IsNil) 523 defer st.Close() 524 525 // Ensure not already logged in. 526 _, err = st.Machiner().Machine("0") 527 c.Assert(err, gc.ErrorMatches, `unknown object type "Machiner"`) 528 529 // Since these are user login tests, the nonce is empty. 530 return st.Login("user-admin", "dummy-secret", "") 531 }