github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/apiserver/admin.go (about)

     1  // Copyright 2013, 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package apiserver
     5  
     6  import (
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/utils/clock"
    12  	"gopkg.in/juju/names.v2"
    13  
    14  	"github.com/juju/juju/apiserver/authentication"
    15  	"github.com/juju/juju/apiserver/common"
    16  	"github.com/juju/juju/apiserver/observer"
    17  	"github.com/juju/juju/apiserver/params"
    18  	"github.com/juju/juju/apiserver/presence"
    19  	"github.com/juju/juju/permission"
    20  	"github.com/juju/juju/rpc"
    21  	"github.com/juju/juju/rpc/rpcreflect"
    22  	"github.com/juju/juju/state"
    23  	statepresence "github.com/juju/juju/state/presence"
    24  	jujuversion "github.com/juju/juju/version"
    25  )
    26  
    27  type adminAPIFactory func(*Server, *apiHandler, observer.Observer) interface{}
    28  
    29  // admin is the only object that unlogged-in clients can access. It holds any
    30  // methods that are needed to log in.
    31  type admin struct {
    32  	srv         *Server
    33  	root        *apiHandler
    34  	apiObserver observer.Observer
    35  
    36  	mu       sync.Mutex
    37  	loggedIn bool
    38  }
    39  
    40  var AboutToRestoreError = errors.New("restore preparation in progress")
    41  var RestoreInProgressError = errors.New("restore in progress")
    42  var MaintenanceNoLoginError = errors.New("login failed - maintenance in progress")
    43  var errAlreadyLoggedIn = errors.New("already logged in")
    44  
    45  // login is the internal version of the Login API call.
    46  func (a *admin) login(req params.LoginRequest, loginVersion int) (params.LoginResult, error) {
    47  	var fail params.LoginResult
    48  
    49  	a.mu.Lock()
    50  	defer a.mu.Unlock()
    51  	if a.loggedIn {
    52  		// This can only happen if Login is called concurrently.
    53  		return fail, errAlreadyLoggedIn
    54  	}
    55  
    56  	// apiRoot is the API root exposed to the client after authentication.
    57  	var apiRoot rpc.Root = newAPIRoot(a.root.state, a.root.resources, a.root)
    58  
    59  	// Use the login validation function, if one was specified.
    60  	if a.srv.validator != nil {
    61  		err := a.srv.validator(req)
    62  		switch err {
    63  		case params.UpgradeInProgressError:
    64  			apiRoot = restrictRoot(apiRoot, upgradeMethodsOnly)
    65  		case AboutToRestoreError:
    66  			apiRoot = restrictRoot(apiRoot, aboutToRestoreMethodsOnly)
    67  		case RestoreInProgressError:
    68  			apiRoot = restrictAll(apiRoot, restoreInProgressError)
    69  		case nil:
    70  			// in this case no need to wrap authed api so we do nothing
    71  		default:
    72  			return fail, errors.Trace(err)
    73  		}
    74  	}
    75  
    76  	isUser := true
    77  	kind := names.UserTagKind
    78  	if req.AuthTag != "" {
    79  		var err error
    80  		kind, err = names.TagKind(req.AuthTag)
    81  		if err != nil || kind != names.UserTagKind {
    82  			isUser = false
    83  			// Users are not rate limited, all other entities are.
    84  			if !a.srv.limiter.Acquire() {
    85  				logger.Debugf("rate limiting for agent %s", req.AuthTag)
    86  				return fail, common.ErrTryAgain
    87  			}
    88  			defer a.srv.limiter.Release()
    89  		}
    90  	}
    91  
    92  	controllerOnlyLogin := a.root.modelUUID == ""
    93  	controllerMachineLogin := false
    94  
    95  	entity, lastConnection, err := a.checkCreds(req, isUser)
    96  	if err != nil {
    97  		if err, ok := errors.Cause(err).(*common.DischargeRequiredError); ok {
    98  			loginResult := params.LoginResult{
    99  				DischargeRequired:       err.Macaroon,
   100  				DischargeRequiredReason: err.Error(),
   101  			}
   102  			logger.Infof("login failed with discharge-required error: %v", err)
   103  			return loginResult, nil
   104  		}
   105  		if a.maintenanceInProgress() {
   106  			// An upgrade, restore or similar operation is in
   107  			// progress. It is possible for logins to fail until this
   108  			// is complete due to incomplete or updating data. Mask
   109  			// transitory and potentially confusing errors from failed
   110  			// logins with a more helpful one.
   111  			return fail, MaintenanceNoLoginError
   112  		}
   113  		// Here we have a special case.  The machine agents that manage
   114  		// models in the controller model need to be able to
   115  		// open API connections to other models.  In those cases, we
   116  		// need to look in the controller database to check the creds
   117  		// against the machine if and only if the entity tag is a machine tag,
   118  		// and the machine exists in the controller model, and the
   119  		// machine has the manage state job.  If all those parts are valid, we
   120  		// can then check the credentials against the controller model
   121  		// machine.
   122  		if kind != names.MachineTagKind {
   123  			return fail, errors.Trace(err)
   124  		}
   125  		if errors.Cause(err) != common.ErrBadCreds {
   126  			return fail, err
   127  		}
   128  		entity, err = a.checkControllerMachineCreds(req)
   129  		if err != nil {
   130  			return fail, errors.Trace(err)
   131  		}
   132  		// If we are here, then the entity will refer to a controller
   133  		// machine in the controller model, and we don't need a pinger
   134  		// for it as we already have one running in the machine agent api
   135  		// worker for the controller model.
   136  		controllerMachineLogin = true
   137  	}
   138  	a.root.entity = entity
   139  	a.apiObserver.Login(entity.Tag(), a.root.state.ModelTag(), controllerMachineLogin, req.UserData)
   140  
   141  	// We have authenticated the user; enable the appropriate API
   142  	// to serve to them.
   143  	a.loggedIn = true
   144  
   145  	if !controllerMachineLogin {
   146  		if err := startPingerIfAgent(a.srv.pingClock, a.root, entity); err != nil {
   147  			return fail, errors.Trace(err)
   148  		}
   149  	}
   150  
   151  	var maybeUserInfo *params.AuthUserInfo
   152  	// Send back user info if user
   153  	if isUser {
   154  		userTag := entity.Tag().(names.UserTag)
   155  		maybeUserInfo, err = a.checkUserPermissions(userTag, controllerOnlyLogin)
   156  		if err != nil {
   157  			return fail, errors.Trace(err)
   158  		}
   159  		maybeUserInfo.LastConnection = lastConnection
   160  	} else {
   161  		if controllerOnlyLogin {
   162  			logger.Debugf("controller login: %s", entity.Tag())
   163  		} else {
   164  			logger.Debugf("model login: %s for %s", entity.Tag(), a.root.state.ModelTag().Id())
   165  		}
   166  	}
   167  
   168  	// Fetch the API server addresses from state.
   169  	hostPorts, err := a.root.state.APIHostPorts()
   170  	if err != nil {
   171  		return fail, errors.Trace(err)
   172  	}
   173  
   174  	model, err := a.root.state.Model()
   175  	if err != nil {
   176  		return fail, errors.Trace(err)
   177  	}
   178  
   179  	if isUser && model.MigrationMode() == state.MigrationModeImporting {
   180  		apiRoot = restrictAll(apiRoot, errors.New("migration in progress, model is importing"))
   181  	}
   182  
   183  	loginResult := params.LoginResult{
   184  		Servers:       params.FromNetworkHostsPorts(hostPorts),
   185  		ControllerTag: model.ControllerTag().String(),
   186  		UserInfo:      maybeUserInfo,
   187  		ServerVersion: jujuversion.Current.String(),
   188  	}
   189  
   190  	if controllerOnlyLogin {
   191  		loginResult.Facades = filterFacades(isControllerFacade)
   192  		apiRoot = restrictRoot(apiRoot, controllerFacadesOnly)
   193  	} else {
   194  		loginResult.ModelTag = model.Tag().String()
   195  		loginResult.Facades = filterFacades(isModelFacade)
   196  		apiRoot = restrictRoot(apiRoot, modelFacadesOnly)
   197  	}
   198  
   199  	a.root.rpcConn.ServeRoot(apiRoot, serverError)
   200  
   201  	return loginResult, nil
   202  }
   203  
   204  func (a *admin) checkUserPermissions(userTag names.UserTag, controllerOnlyLogin bool) (*params.AuthUserInfo, error) {
   205  
   206  	modelAccess := permission.NoAccess
   207  	if !controllerOnlyLogin {
   208  		// Only grab modelUser permissions if this is not a controller only
   209  		// login. In all situations, if the model user is not found, they have
   210  		// no authorisation to access this model.
   211  		modelUser, err := a.root.state.UserAccess(userTag, a.root.state.ModelTag())
   212  		if err != nil {
   213  			return nil, errors.Wrap(err, common.ErrPerm)
   214  		}
   215  		modelAccess = modelUser.Access
   216  	}
   217  
   218  	// TODO(perrito666) remove the following section about everyone group
   219  	// when groups are implemented, this accounts only for the lack of a local
   220  	// ControllerUser when logging in from an external user that has not been granted
   221  	// permissions on the controller but there are permissions for the special
   222  	// everyone group.
   223  	everyoneGroupAccess := permission.NoAccess
   224  	if !userTag.IsLocal() {
   225  		everyoneTag := names.NewUserTag(common.EveryoneTagName)
   226  		everyoneGroupUser, err := state.ControllerAccess(a.root.state, everyoneTag)
   227  		if err != nil && !errors.IsNotFound(err) {
   228  			return nil, errors.Annotatef(err, "obtaining ControllerUser for everyone group")
   229  		}
   230  		everyoneGroupAccess = everyoneGroupUser.Access
   231  	}
   232  
   233  	controllerAccess := permission.NoAccess
   234  	if controllerUser, err := state.ControllerAccess(a.root.state, userTag); err == nil {
   235  		controllerAccess = controllerUser.Access
   236  	} else if errors.IsNotFound(err) {
   237  		controllerAccess = everyoneGroupAccess
   238  	} else {
   239  		return nil, errors.Annotatef(err, "obtaining ControllerUser for logged in user %s", userTag.Id())
   240  	}
   241  	// It is possible that the everyoneGroup permissions are more capable than an
   242  	// individuals. If they are, use them.
   243  	if everyoneGroupAccess.GreaterControllerAccessThan(controllerAccess) {
   244  		controllerAccess = everyoneGroupAccess
   245  	}
   246  	if controllerOnlyLogin || !a.srv.allowModelAccess {
   247  		// We're either explicitly logging into the controller or
   248  		// we must check that the user has access to the controller
   249  		// even though they're logging into a model.
   250  		if controllerAccess == permission.NoAccess {
   251  			return nil, errors.Trace(common.ErrPerm)
   252  		}
   253  	}
   254  	if controllerOnlyLogin {
   255  		logger.Debugf("controller login: user %s has %q access", userTag.Id(), controllerAccess)
   256  	} else {
   257  		logger.Debugf("model login: user %s has %q for controller; %q for model %s",
   258  			userTag.Id(), controllerAccess, modelAccess, a.root.state.ModelTag().Id())
   259  	}
   260  	return &params.AuthUserInfo{
   261  		Identity:         userTag.String(),
   262  		ControllerAccess: string(controllerAccess),
   263  		ModelAccess:      string(modelAccess),
   264  	}, nil
   265  }
   266  
   267  func filterFacades(allowFacade func(name string) bool) []params.FacadeVersions {
   268  	allFacades := DescribeFacades()
   269  	out := make([]params.FacadeVersions, 0, len(allFacades))
   270  	for _, facade := range allFacades {
   271  		if allowFacade(facade.Name) {
   272  			out = append(out, facade)
   273  		}
   274  	}
   275  	return out
   276  }
   277  
   278  func (a *admin) checkCreds(req params.LoginRequest, lookForModelUser bool) (state.Entity, *time.Time, error) {
   279  	return doCheckCreds(a.root.state, req, lookForModelUser, a.authenticator())
   280  }
   281  
   282  func (a *admin) checkControllerMachineCreds(req params.LoginRequest) (state.Entity, error) {
   283  	return checkControllerMachineCreds(a.srv.state, req, a.authenticator())
   284  }
   285  
   286  func (a *admin) authenticator() authentication.EntityAuthenticator {
   287  	return a.srv.authCtxt.authenticator(a.root.serverHost)
   288  }
   289  
   290  func (a *admin) maintenanceInProgress() bool {
   291  	if a.srv.validator == nil {
   292  		return false
   293  	}
   294  	// jujud's login validator will return an error for any user tag
   295  	// if jujud is upgrading or restoring. The tag of the entity
   296  	// trying to log in can't be used because jujud's login validator
   297  	// will always return nil for the local machine agent and here we
   298  	// need to know if maintenance is in progress irrespective of the
   299  	// the authenticating entity.
   300  	//
   301  	// TODO(mjs): 2014-09-29 bug 1375110
   302  	// This needs improving but I don't have the cycles right now.
   303  	req := params.LoginRequest{
   304  		AuthTag: names.NewUserTag("arbitrary").String(),
   305  	}
   306  	return a.srv.validator(req) != nil
   307  }
   308  
   309  var doCheckCreds = checkCreds
   310  
   311  // checkCreds validates the entities credentials in the current model.
   312  // If the entity is a user, and lookForModelUser is true, a model user must exist
   313  // for the model.  In the case of a user logging in to the controller, but
   314  // not a model, there is no env user needed.  While we have the env
   315  // user, if we do have it, update the last login time.
   316  //
   317  // Note that when logging in with lookForModelUser true, the returned
   318  // entity will be modelUserEntity, not *state.User (external users
   319  // don't have user entries) or *state.ModelUser (we
   320  // don't want to lose the local user information associated with that).
   321  func checkCreds(st *state.State, req params.LoginRequest, lookForModelUser bool, authenticator authentication.EntityAuthenticator) (state.Entity, *time.Time, error) {
   322  	var tag names.Tag
   323  	if req.AuthTag != "" {
   324  		var err error
   325  		tag, err = names.ParseTag(req.AuthTag)
   326  		if err != nil {
   327  			return nil, nil, errors.Trace(err)
   328  		}
   329  	}
   330  	var entityFinder authentication.EntityFinder = st
   331  	if lookForModelUser {
   332  		// When looking up model users, use a custom
   333  		// entity finder that looks up both the local user (if the user
   334  		// tag is in the local domain) and the model user.
   335  		entityFinder = modelUserEntityFinder{st}
   336  	}
   337  	entity, err := authenticator.Authenticate(entityFinder, tag, req)
   338  	if err != nil {
   339  		return nil, nil, errors.Trace(err)
   340  	}
   341  
   342  	// For user logins, update the last login time.
   343  	var lastLogin *time.Time
   344  	if entity, ok := entity.(loginEntity); ok {
   345  		userLastLogin, err := entity.LastLogin()
   346  		if err != nil && !state.IsNeverLoggedInError(err) {
   347  			return nil, nil, errors.Trace(err)
   348  		}
   349  		entity.UpdateLastLogin()
   350  		lastLogin = &userLastLogin
   351  	}
   352  	return entity, lastLogin, nil
   353  }
   354  
   355  // checkControllerMachineCreds checks the special case of a controller
   356  // machine creating an API connection for a different model so it can
   357  // run workers that act on behalf of a hosted model.
   358  func checkControllerMachineCreds(
   359  	controllerSt *state.State,
   360  	req params.LoginRequest,
   361  	authenticator authentication.EntityAuthenticator,
   362  ) (state.Entity, error) {
   363  	entity, _, err := doCheckCreds(controllerSt, req, false, authenticator)
   364  	if err != nil {
   365  		return nil, errors.Trace(err)
   366  	}
   367  	if machine, ok := entity.(*state.Machine); !ok {
   368  		return nil, errors.Errorf("entity should be a machine, but is %T", entity)
   369  	} else if !machine.IsManager() {
   370  		// The machine exists in the controller model, but it doesn't
   371  		// manage models, so reject it.
   372  		return nil, errors.Trace(common.ErrPerm)
   373  	}
   374  	return entity, nil
   375  }
   376  
   377  // loginEntity defines the interface needed to log in as a user.
   378  // Notable implementations are *state.User and *modelUserEntity.
   379  type loginEntity interface {
   380  	state.Entity
   381  	state.Authenticator
   382  	LastLogin() (time.Time, error)
   383  	UpdateLastLogin() error
   384  }
   385  
   386  // modelUserEntityFinder implements EntityFinder by returning a
   387  // loginEntity value for users, ensuring that the user exists in the
   388  // state's current model as well as retrieving more global
   389  // authentication details such as the password.
   390  type modelUserEntityFinder struct {
   391  	st *state.State
   392  }
   393  
   394  // FindEntity implements authentication.EntityFinder.FindEntity.
   395  func (f modelUserEntityFinder) FindEntity(tag names.Tag) (state.Entity, error) {
   396  	utag, ok := tag.(names.UserTag)
   397  	if !ok {
   398  		return f.st.FindEntity(tag)
   399  	}
   400  
   401  	modelUser, controllerUser, err := common.UserAccess(f.st, utag)
   402  	if err != nil {
   403  		return nil, errors.Trace(err)
   404  	}
   405  	u := &modelUserEntity{
   406  		st:             f.st,
   407  		modelUser:      modelUser,
   408  		controllerUser: controllerUser,
   409  	}
   410  	if utag.IsLocal() {
   411  		user, err := f.st.User(utag)
   412  		if err != nil {
   413  			return nil, errors.Trace(err)
   414  		}
   415  		u.user = user
   416  	}
   417  	return u, nil
   418  }
   419  
   420  var _ loginEntity = &modelUserEntity{}
   421  
   422  // modelUserEntity encapsulates an model user
   423  // and, if the user is local, the local state user
   424  // as well. This enables us to implement FindEntity
   425  // in such a way that the authentication mechanisms
   426  // can work without knowing these details.
   427  type modelUserEntity struct {
   428  	st *state.State
   429  
   430  	controllerUser permission.UserAccess
   431  	modelUser      permission.UserAccess
   432  	user           *state.User
   433  }
   434  
   435  // Refresh implements state.Authenticator.Refresh.
   436  func (u *modelUserEntity) Refresh() error {
   437  	if u.user == nil {
   438  		return nil
   439  	}
   440  	return u.user.Refresh()
   441  }
   442  
   443  // SetPassword implements state.Authenticator.SetPassword
   444  // by setting the password on the local user.
   445  func (u *modelUserEntity) SetPassword(pass string) error {
   446  	if u.user == nil {
   447  		return errors.New("cannot set password on external user")
   448  	}
   449  	return u.user.SetPassword(pass)
   450  }
   451  
   452  // PasswordValid implements state.Authenticator.PasswordValid.
   453  func (u *modelUserEntity) PasswordValid(pass string) bool {
   454  	if u.user == nil {
   455  		return false
   456  	}
   457  	return u.user.PasswordValid(pass)
   458  }
   459  
   460  // Tag implements state.Entity.Tag.
   461  func (u *modelUserEntity) Tag() names.Tag {
   462  	if u.user != nil {
   463  		return u.user.UserTag()
   464  	}
   465  	if !permission.IsEmptyUserAccess(u.modelUser) {
   466  		return u.modelUser.UserTag
   467  	}
   468  	return u.controllerUser.UserTag
   469  
   470  }
   471  
   472  // LastLogin implements loginEntity.LastLogin.
   473  func (u *modelUserEntity) LastLogin() (time.Time, error) {
   474  	// The last connection for the model takes precedence over
   475  	// the local user last login time.
   476  	var err error
   477  	var t time.Time
   478  	if !permission.IsEmptyUserAccess(u.modelUser) {
   479  		t, err = u.st.LastModelConnection(u.modelUser.UserTag)
   480  	} else {
   481  		err = state.NeverConnectedError("controller user")
   482  	}
   483  	if state.IsNeverConnectedError(err) || permission.IsEmptyUserAccess(u.modelUser) {
   484  		if u.user != nil {
   485  			// There's a global user, so use that login time instead.
   486  			return u.user.LastLogin()
   487  		}
   488  		// Since we're implementing LastLogin, we need
   489  		// to implement LastLogin error semantics too.
   490  		err = state.NeverLoggedInError(err.Error())
   491  	}
   492  	return t, errors.Trace(err)
   493  }
   494  
   495  // UpdateLastLogin implements loginEntity.UpdateLastLogin.
   496  func (u *modelUserEntity) UpdateLastLogin() error {
   497  	var err error
   498  
   499  	if !permission.IsEmptyUserAccess(u.modelUser) {
   500  		if u.modelUser.Object.Kind() != names.ModelTagKind {
   501  			return errors.NotValidf("%s as model user", u.modelUser.Object.Kind())
   502  		}
   503  
   504  		err = u.st.UpdateLastModelConnection(u.modelUser.UserTag)
   505  	}
   506  
   507  	if u.user != nil {
   508  		err1 := u.user.UpdateLastLogin()
   509  		if err == nil {
   510  			return err1
   511  		}
   512  	}
   513  	if err != nil {
   514  		return errors.Trace(err)
   515  	}
   516  	return nil
   517  }
   518  
   519  // presenceShim exists to represent a statepresence.Agent in a form
   520  // convenient to the apiserver/presence package, which exists to work
   521  // around the common.Resources infrastructure's lack of handling for
   522  // failed resources.
   523  type presenceShim struct {
   524  	agent statepresence.Agent
   525  }
   526  
   527  // Start starts and returns a running presence.Pinger. The caller is
   528  // responsible for stopping it when no longer required, and for handling
   529  // any errors returned from Wait.
   530  func (shim presenceShim) Start() (presence.Pinger, error) {
   531  	pinger, err := shim.agent.SetAgentPresence()
   532  	if err != nil {
   533  		return nil, errors.Trace(err)
   534  	}
   535  	return pinger, nil
   536  }
   537  
   538  func startPingerIfAgent(clock clock.Clock, root *apiHandler, entity state.Entity) error {
   539  	// worker runs presence.Pingers -- absence of which will cause
   540  	// embarrassing "agent is lost" messages to show up in status --
   541  	// until it's stopped. It's stored in resources purely for the
   542  	// side effects: we don't record its id, and nobody else
   543  	// retrieves it -- we just expect it to be stopped when the
   544  	// connection is shut down.
   545  	agent, ok := entity.(statepresence.Agent)
   546  	if !ok {
   547  		return nil
   548  	}
   549  	worker, err := presence.New(presence.Config{
   550  		Identity:   entity.Tag(),
   551  		Start:      presenceShim{agent}.Start,
   552  		Clock:      clock,
   553  		RetryDelay: 3 * time.Second,
   554  	})
   555  	if err != nil {
   556  		return err
   557  	}
   558  	root.getResources().Register(worker)
   559  
   560  	// pingTimeout, by contrast, *is* used by the Pinger facade to
   561  	// stave off the call to action() that will shut down the agent
   562  	// connection if it gets lackadaisical about sending keepalive
   563  	// Pings.
   564  	//
   565  	// Do not confuse those (apiserver) Pings with those made by
   566  	// presence.Pinger (which *do* happen as a result of the former,
   567  	// but only as a relatively distant consequence).
   568  	//
   569  	// We should have picked better names...
   570  	action := func() {
   571  		logger.Debugf("closing connection due to ping timout")
   572  		if err := root.getRpcConn().Close(); err != nil {
   573  			logger.Errorf("error closing the RPC connection: %v", err)
   574  		}
   575  	}
   576  	pingTimeout := newPingTimeout(action, clock, maxClientPingInterval)
   577  	return root.getResources().RegisterNamed("pingTimeout", pingTimeout)
   578  }
   579  
   580  // errRoot implements the API that a client first sees
   581  // when connecting to the API. It exposes the same API as initialRoot, except
   582  // it returns the requested error when the client makes any request.
   583  type errRoot struct {
   584  	err error
   585  }
   586  
   587  // FindMethod conforms to the same API as initialRoot, but we'll always return (nil, err)
   588  func (r *errRoot) FindMethod(rootName string, version int, methodName string) (rpcreflect.MethodCaller, error) {
   589  	return nil, r.err
   590  }
   591  
   592  func (r *errRoot) Kill() {
   593  }