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 := &params.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  }