github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/controller/register_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package controller_test
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/asn1"
     9  	"encoding/base64"
    10  	"encoding/json"
    11  	"io"
    12  	"io/ioutil"
    13  	"net/http"
    14  	"net/http/httptest"
    15  	"net/url"
    16  	"strings"
    17  
    18  	"github.com/juju/cmd"
    19  	"github.com/juju/errors"
    20  	jc "github.com/juju/testing/checkers"
    21  	"golang.org/x/crypto/nacl/secretbox"
    22  	gc "gopkg.in/check.v1"
    23  
    24  	"github.com/juju/juju/api"
    25  	"github.com/juju/juju/api/base"
    26  	"github.com/juju/juju/apiserver/params"
    27  	"github.com/juju/juju/cmd/juju/controller"
    28  	"github.com/juju/juju/jujuclient"
    29  	"github.com/juju/juju/jujuclient/jujuclienttesting"
    30  	"github.com/juju/juju/testing"
    31  )
    32  
    33  type RegisterSuite struct {
    34  	testing.FakeJujuXDGDataHomeSuite
    35  	apiConnection            *mockAPIConnection
    36  	store                    *jujuclienttesting.MemStore
    37  	apiOpenError             error
    38  	listModels               func(jujuclient.ClientStore, string, string) ([]base.UserModel, error)
    39  	listModelsControllerName string
    40  	listModelsUserName       string
    41  	server                   *httptest.Server
    42  	httpHandler              http.Handler
    43  }
    44  
    45  var _ = gc.Suite(&RegisterSuite{})
    46  
    47  func (s *RegisterSuite) SetUpTest(c *gc.C) {
    48  	s.FakeJujuXDGDataHomeSuite.SetUpTest(c)
    49  
    50  	s.apiOpenError = nil
    51  	s.httpHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
    52  	s.server = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    53  		s.httpHandler.ServeHTTP(w, r)
    54  	}))
    55  
    56  	serverURL, err := url.Parse(s.server.URL)
    57  	c.Assert(err, jc.ErrorIsNil)
    58  	s.apiConnection = &mockAPIConnection{
    59  		controllerTag: testing.ControllerTag,
    60  		addr:          serverURL.Host,
    61  	}
    62  	s.listModelsControllerName = ""
    63  	s.listModelsUserName = ""
    64  	s.listModels = func(_ jujuclient.ClientStore, controllerName, userName string) ([]base.UserModel, error) {
    65  		s.listModelsControllerName = controllerName
    66  		s.listModelsUserName = userName
    67  		return nil, nil
    68  	}
    69  
    70  	s.store = jujuclienttesting.NewMemStore()
    71  }
    72  
    73  func (s *RegisterSuite) TearDownTest(c *gc.C) {
    74  	s.server.Close()
    75  	s.FakeJujuXDGDataHomeSuite.TearDownTest(c)
    76  }
    77  
    78  func (s *RegisterSuite) apiOpen(info *api.Info, opts api.DialOpts) (api.Connection, error) {
    79  	if s.apiOpenError != nil {
    80  		return nil, s.apiOpenError
    81  	}
    82  	s.apiConnection.info = info
    83  	s.apiConnection.opts = opts
    84  	return s.apiConnection, nil
    85  }
    86  
    87  func (s *RegisterSuite) run(c *gc.C, stdin io.Reader, args ...string) (*cmd.Context, error) {
    88  	command := controller.NewRegisterCommandForTest(s.apiOpen, s.listModels, s.store)
    89  	err := testing.InitCommand(command, args)
    90  	c.Assert(err, jc.ErrorIsNil)
    91  	ctx := testing.Context(c)
    92  	ctx.Stdin = stdin
    93  	return ctx, command.Run(ctx)
    94  }
    95  
    96  func (s *RegisterSuite) encodeRegistrationData(c *gc.C, user string, secretKey []byte) string {
    97  	data, err := asn1.Marshal(jujuclient.RegistrationInfo{
    98  		User:      user,
    99  		Addrs:     []string{s.apiConnection.addr},
   100  		SecretKey: secretKey,
   101  	})
   102  	c.Assert(err, jc.ErrorIsNil)
   103  	// Append some junk to the end of the encoded data to
   104  	// ensure that, if we have to pad the data in add-user,
   105  	// register can still decode it.
   106  	data = append(data, 0, 0, 0)
   107  	return base64.URLEncoding.EncodeToString(data)
   108  }
   109  
   110  func (s *RegisterSuite) encodeRegistrationDataWithControllerName(c *gc.C, user string, secretKey []byte, controller string) string {
   111  	data, err := asn1.Marshal(jujuclient.RegistrationInfo{
   112  		User:           user,
   113  		Addrs:          []string{s.apiConnection.addr},
   114  		SecretKey:      secretKey,
   115  		ControllerName: controller,
   116  	})
   117  	c.Assert(err, jc.ErrorIsNil)
   118  	// Append some junk to the end of the encoded data to
   119  	// ensure that, if we have to pad the data in add-user,
   120  	// register can still decode it.
   121  	data = append(data, 0, 0, 0)
   122  	return base64.URLEncoding.EncodeToString(data)
   123  }
   124  
   125  func (s *RegisterSuite) seal(c *gc.C, message, key, nonce []byte) []byte {
   126  	var keyArray [32]byte
   127  	var nonceArray [24]byte
   128  	c.Assert(copy(keyArray[:], key), gc.Equals, len(keyArray))
   129  	c.Assert(copy(nonceArray[:], nonce), gc.Equals, len(nonceArray))
   130  	return secretbox.Seal(nil, message, &nonceArray, &keyArray)
   131  }
   132  
   133  func (s *RegisterSuite) TestInit(c *gc.C) {
   134  	registerCommand := controller.NewRegisterCommandForTest(nil, nil, nil)
   135  
   136  	err := testing.InitCommand(registerCommand, []string{})
   137  	c.Assert(err, gc.ErrorMatches, "registration data missing")
   138  
   139  	err = testing.InitCommand(registerCommand, []string{"foo"})
   140  	c.Assert(err, jc.ErrorIsNil)
   141  	c.Assert(registerCommand.EncodedData, gc.Equals, "foo")
   142  
   143  	err = testing.InitCommand(registerCommand, []string{"foo", "bar"})
   144  	c.Assert(err, gc.ErrorMatches, `unrecognized args: \["bar"\]`)
   145  }
   146  
   147  func (s *RegisterSuite) TestRegister(c *gc.C) {
   148  	ctx := s.testRegister(c, "")
   149  	c.Assert(s.listModelsControllerName, gc.Equals, "controller-name")
   150  	c.Assert(s.listModelsUserName, gc.Equals, "bob@local")
   151  	stderr := testing.Stderr(ctx)
   152  	c.Assert(stderr, gc.Equals, `
   153  Enter a name for this controller [controller-name]: 
   154  Enter a new password: 
   155  Confirm password: 
   156  
   157  Welcome, bob. You are now logged into "controller-name".
   158  
   159  There are no models available. You can add models with
   160  "juju add-model", or you can ask an administrator or owner
   161  of a model to grant access to that model with "juju grant".
   162  
   163  `[1:])
   164  }
   165  
   166  func (s *RegisterSuite) TestRegisterOneModel(c *gc.C) {
   167  	s.listModels = func(_ jujuclient.ClientStore, controllerName, userName string) ([]base.UserModel, error) {
   168  		return []base.UserModel{{
   169  			Name:  "theoneandonly",
   170  			Owner: "carol@local",
   171  			UUID:  "df136476-12e9-11e4-8a70-b2227cce2b54",
   172  		}}, nil
   173  	}
   174  	s.testRegister(c, "")
   175  	c.Assert(
   176  		s.store.Models["controller-name"].CurrentModel,
   177  		gc.Equals, "carol@local/theoneandonly",
   178  	)
   179  }
   180  
   181  func (s *RegisterSuite) TestRegisterMultipleModels(c *gc.C) {
   182  	s.listModels = func(_ jujuclient.ClientStore, controllerName, userName string) ([]base.UserModel, error) {
   183  		return []base.UserModel{{
   184  			Name:  "model1",
   185  			Owner: "bob@local",
   186  			UUID:  "df136476-12e9-11e4-8a70-b2227cce2b54",
   187  		}, {
   188  			Name:  "model2",
   189  			Owner: "bob@local",
   190  			UUID:  "df136476-12e9-11e4-8a70-b2227cce2b55",
   191  		}}, nil
   192  	}
   193  	ctx := s.testRegister(c, "")
   194  
   195  	// When there are multiple models, no current model will be set.
   196  	// Instead, the command will output the list of models and inform
   197  	// the user how to set the current model.
   198  	_, err := s.store.CurrentModel("controller-name")
   199  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   200  
   201  	stderr := testing.Stderr(ctx)
   202  	c.Assert(stderr, gc.Equals, `
   203  Enter a name for this controller [controller-name]: 
   204  Enter a new password: 
   205  Confirm password: 
   206  
   207  Welcome, bob. You are now logged into "controller-name".
   208  
   209  There are 2 models available. Use "juju switch" to select
   210  one of them:
   211    - juju switch model1
   212    - juju switch model2
   213  
   214  `[1:])
   215  }
   216  
   217  func (s *RegisterSuite) testRegister(c *gc.C, expectedError string) *cmd.Context {
   218  	secretKey := []byte(strings.Repeat("X", 32))
   219  	respNonce := []byte(strings.Repeat("X", 24))
   220  
   221  	var requests []*http.Request
   222  	var requestBodies [][]byte
   223  	const controllerUUID = "df136476-12e9-11e4-8a70-b2227cce2b54"
   224  	responsePayloadPlaintext, err := json.Marshal(params.SecretKeyLoginResponsePayload{
   225  		CACert:         testing.CACert,
   226  		ControllerUUID: controllerUUID,
   227  	})
   228  	c.Assert(err, jc.ErrorIsNil)
   229  	response, err := json.Marshal(params.SecretKeyLoginResponse{
   230  		Nonce:             respNonce,
   231  		PayloadCiphertext: s.seal(c, responsePayloadPlaintext, secretKey, respNonce),
   232  	})
   233  	c.Assert(err, jc.ErrorIsNil)
   234  	s.httpHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   235  		requests = append(requests, r)
   236  		requestBody, err := ioutil.ReadAll(requests[0].Body)
   237  		c.Check(err, jc.ErrorIsNil)
   238  		requestBodies = append(requestBodies, requestBody)
   239  		_, err = w.Write(response)
   240  		c.Check(err, jc.ErrorIsNil)
   241  	})
   242  
   243  	registrationData := s.encodeRegistrationDataWithControllerName(c, "bob", secretKey, "controller-name")
   244  	stdin := strings.NewReader("\nhunter2\nhunter2\n")
   245  	ctx, err := s.run(c, stdin, registrationData)
   246  	if expectedError != "" {
   247  		c.Assert(err, gc.ErrorMatches, expectedError)
   248  		return ctx
   249  	}
   250  	c.Assert(err, jc.ErrorIsNil)
   251  
   252  	// There should have been one POST command to "/register".
   253  	c.Assert(requests, gc.HasLen, 1)
   254  	c.Assert(requests[0].Method, gc.Equals, "POST")
   255  	c.Assert(requests[0].URL.Path, gc.Equals, "/register")
   256  	var request params.SecretKeyLoginRequest
   257  	err = json.Unmarshal(requestBodies[0], &request)
   258  	c.Assert(err, jc.ErrorIsNil)
   259  	c.Assert(request.User, jc.DeepEquals, "user-bob")
   260  	c.Assert(request.Nonce, gc.HasLen, 24)
   261  	requestPayloadPlaintext, err := json.Marshal(params.SecretKeyLoginRequestPayload{
   262  		"hunter2",
   263  	})
   264  	c.Assert(err, jc.ErrorIsNil)
   265  	expectedCiphertext := s.seal(c, requestPayloadPlaintext, secretKey, request.Nonce)
   266  	c.Assert(request.PayloadCiphertext, jc.DeepEquals, expectedCiphertext)
   267  
   268  	// The controller and account details should be recorded with
   269  	// the specified controller name ("controller-name") and user
   270  	// name from the registration string.
   271  
   272  	controller, err := s.store.ControllerByName("controller-name")
   273  	c.Assert(err, jc.ErrorIsNil)
   274  	c.Assert(controller, jc.DeepEquals, &jujuclient.ControllerDetails{
   275  		ControllerUUID: controllerUUID,
   276  		APIEndpoints:   []string{s.apiConnection.addr},
   277  		CACert:         testing.CACert,
   278  	})
   279  	account, err := s.store.AccountDetails("controller-name")
   280  	c.Assert(err, jc.ErrorIsNil)
   281  	c.Assert(account, jc.DeepEquals, &jujuclient.AccountDetails{
   282  		User:            "bob@local",
   283  		LastKnownAccess: "login",
   284  	})
   285  	return ctx
   286  }
   287  
   288  func (s *RegisterSuite) TestRegisterInvalidRegistrationData(c *gc.C) {
   289  	_, err := s.run(c, bytes.NewReader(nil), "not base64")
   290  	c.Assert(err, gc.ErrorMatches, "illegal base64 data at input byte 3")
   291  
   292  	_, err = s.run(c, bytes.NewReader(nil), "YXNuLjEK")
   293  	c.Assert(err, gc.ErrorMatches, "asn1: structure error: .*")
   294  }
   295  
   296  func (s *RegisterSuite) TestRegisterEmptyControllerName(c *gc.C) {
   297  	secretKey := []byte(strings.Repeat("X", 32))
   298  	registrationData := s.encodeRegistrationData(c, "bob", secretKey)
   299  	stdin := strings.NewReader("\n")
   300  	_, err := s.run(c, stdin, registrationData)
   301  	c.Assert(err, gc.ErrorMatches, "you must specify a non-empty controller name")
   302  }
   303  
   304  func (s *RegisterSuite) TestRegisterControllerNameExists(c *gc.C) {
   305  	err := s.store.AddController("controller-name", jujuclient.ControllerDetails{
   306  		ControllerUUID: "df136476-12e9-11e4-8a70-b2227cce2b54",
   307  		CACert:         testing.CACert,
   308  	})
   309  
   310  	secretKey := []byte(strings.Repeat("X", 32))
   311  	registrationData := s.encodeRegistrationData(c, "bob", secretKey)
   312  	stdin := strings.NewReader("controller-name\nhunter2\nhunter2\n")
   313  	_, err = s.run(c, stdin, registrationData)
   314  	c.Assert(err, gc.ErrorMatches, `controller "controller-name" already exists`)
   315  }
   316  
   317  func (s *RegisterSuite) TestControllerUUIDExists(c *gc.C) {
   318  	// Controller has the UUID from s.testRegister to mimic a user with
   319  	// this controller already registered (regardless of its name).
   320  	err := s.store.AddController("controller-name", jujuclient.ControllerDetails{
   321  		ControllerUUID: "df136476-12e9-11e4-8a70-b2227cce2b54",
   322  		CACert:         testing.CACert,
   323  	})
   324  
   325  	s.listModels = func(_ jujuclient.ClientStore, controllerName, userName string) ([]base.UserModel, error) {
   326  		return []base.UserModel{{
   327  			Name:  "model-name",
   328  			Owner: "bob@local",
   329  			UUID:  "df136476-12e9-11e4-8a70-b2227cce2b54",
   330  		}}, nil
   331  	}
   332  
   333  	s.testRegister(c, "you must specify a non-empty controller name")
   334  
   335  	secretKey := []byte(strings.Repeat("X", 32))
   336  	registrationData := s.encodeRegistrationDataWithControllerName(c, "bob", secretKey, "controller-name")
   337  
   338  	stdin := strings.NewReader("another-controller-name\nhunter2\nhunter2\n")
   339  	_, err = s.run(c, stdin, registrationData)
   340  	c.Assert(err, gc.ErrorMatches, "controller with UUID.*already exists")
   341  }
   342  
   343  func (s *RegisterSuite) TestProposedControllerNameExists(c *gc.C) {
   344  	// Controller does not have the UUID from s.testRegister, thereby
   345  	// mimicing a user with an already registered 'foreign' controller.
   346  	err := s.store.AddController("controller-name", jujuclient.ControllerDetails{
   347  		ControllerUUID: "0d75314a-5266-4f4f-8523-415be76f92dc",
   348  		CACert:         testing.CACert,
   349  	})
   350  
   351  	s.listModels = func(_ jujuclient.ClientStore, controllerName, userName string) ([]base.UserModel, error) {
   352  		return []base.UserModel{{
   353  			Name:  "model-name",
   354  			Owner: "bob@local",
   355  			UUID:  "df136476-12e9-11e4-8a70-b2227cce2b54",
   356  		}}, nil
   357  	}
   358  
   359  	ctx := s.testRegister(c, "you must specify a non-empty controller name")
   360  
   361  	secretKey := []byte(strings.Repeat("X", 32))
   362  	registrationData := s.encodeRegistrationDataWithControllerName(c, "bob", secretKey, "controller-name")
   363  
   364  	stdin := strings.NewReader("another-controller-name\nhunter2\nhunter2\n")
   365  	_, err = s.run(c, stdin, registrationData)
   366  	c.Assert(err, jc.ErrorIsNil)
   367  	stderr := testing.Stderr(ctx)
   368  	c.Assert(stderr, gc.Equals, `
   369  WARNING: You already have a controller registered with the name "controller-name". Please choose a different name for the new controller.
   370  
   371  Enter a name for this controller: 
   372  `[1:])
   373  
   374  }
   375  
   376  func (s *RegisterSuite) TestRegisterEmptyPassword(c *gc.C) {
   377  	secretKey := []byte(strings.Repeat("X", 32))
   378  	registrationData := s.encodeRegistrationData(c, "bob", secretKey)
   379  	stdin := strings.NewReader("controller-name\n\n")
   380  	_, err := s.run(c, stdin, registrationData)
   381  	c.Assert(err, gc.ErrorMatches, "you must specify a non-empty password")
   382  }
   383  
   384  func (s *RegisterSuite) TestRegisterPasswordMismatch(c *gc.C) {
   385  	secretKey := []byte(strings.Repeat("X", 32))
   386  	registrationData := s.encodeRegistrationData(c, "bob", secretKey)
   387  	stdin := strings.NewReader("controller-name\nhunter2\nhunter3\n")
   388  	_, err := s.run(c, stdin, registrationData)
   389  	c.Assert(err, gc.ErrorMatches, "passwords do not match")
   390  }
   391  
   392  func (s *RegisterSuite) TestAPIOpenError(c *gc.C) {
   393  	secretKey := []byte(strings.Repeat("X", 32))
   394  	registrationData := s.encodeRegistrationData(c, "bob", secretKey)
   395  	stdin := strings.NewReader("controller-name\nhunter2\nhunter2\n")
   396  	s.apiOpenError = errors.New("open failed")
   397  	_, err := s.run(c, stdin, registrationData)
   398  	c.Assert(err, gc.ErrorMatches, `open failed`)
   399  }
   400  
   401  func (s *RegisterSuite) TestRegisterServerError(c *gc.C) {
   402  	secretKey := []byte(strings.Repeat("X", 32))
   403  	response, err := json.Marshal(params.ErrorResult{
   404  		Error: &params.Error{Message: "xyz", Code: "123"},
   405  	})
   406  
   407  	s.httpHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   408  		w.WriteHeader(http.StatusInternalServerError)
   409  		_, err = w.Write(response)
   410  		c.Check(err, jc.ErrorIsNil)
   411  	})
   412  
   413  	registrationData := s.encodeRegistrationData(c, "bob", secretKey)
   414  	stdin := strings.NewReader("controller-name\nhunter2\nhunter2\n")
   415  	_, err = s.run(c, stdin, registrationData)
   416  	c.Assert(err, gc.ErrorMatches, "xyz")
   417  
   418  	_, err = s.store.ControllerByName("controller-name")
   419  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   420  }