github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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/names"
    12  	"github.com/juju/utils/clock"
    13  
    14  	"github.com/juju/juju/apiserver/authentication"
    15  	"github.com/juju/juju/apiserver/common"
    16  	"github.com/juju/juju/apiserver/params"
    17  	"github.com/juju/juju/apiserver/presence"
    18  	"github.com/juju/juju/rpc"
    19  	"github.com/juju/juju/rpc/rpcreflect"
    20  	"github.com/juju/juju/state"
    21  	statepresence "github.com/juju/juju/state/presence"
    22  	jujuversion "github.com/juju/juju/version"
    23  )
    24  
    25  type adminApiFactory func(srv *Server, root *apiHandler, reqNotifier *requestNotifier) interface{}
    26  
    27  // admin is the only object that unlogged-in clients can access. It holds any
    28  // methods that are needed to log in.
    29  type admin struct {
    30  	srv         *Server
    31  	root        *apiHandler
    32  	reqNotifier *requestNotifier
    33  
    34  	mu       sync.Mutex
    35  	loggedIn bool
    36  }
    37  
    38  var AboutToRestoreError = errors.New("restore preparation in progress")
    39  var RestoreInProgressError = errors.New("restore in progress")
    40  var MaintenanceNoLoginError = errors.New("login failed - maintenance in progress")
    41  var errAlreadyLoggedIn = errors.New("already logged in")
    42  
    43  func (a *admin) doLogin(req params.LoginRequest, loginVersion int) (params.LoginResultV1, error) {
    44  	var fail params.LoginResultV1
    45  
    46  	a.mu.Lock()
    47  	defer a.mu.Unlock()
    48  	if a.loggedIn {
    49  		// This can only happen if Login is called concurrently.
    50  		return fail, errAlreadyLoggedIn
    51  	}
    52  
    53  	// authedApi is the API method finder we'll use after getting logged in.
    54  	var authedApi rpc.MethodFinder = newApiRoot(a.root.state, a.root.resources, a.root)
    55  
    56  	// Use the login validation function, if one was specified.
    57  	if a.srv.validator != nil {
    58  		err := a.srv.validator(req)
    59  		switch err {
    60  		case params.UpgradeInProgressError:
    61  			authedApi = newUpgradingRoot(authedApi)
    62  		case AboutToRestoreError:
    63  			authedApi = newAboutToRestoreRoot(authedApi)
    64  		case RestoreInProgressError:
    65  			authedApi = newRestoreInProgressRoot(authedApi)
    66  		case nil:
    67  			// in this case no need to wrap authed api so we do nothing
    68  		default:
    69  			return fail, errors.Trace(err)
    70  		}
    71  	}
    72  
    73  	var agentPingerNeeded = true
    74  	isUser := true
    75  	kind := names.UserTagKind
    76  	if req.AuthTag != "" {
    77  		var err error
    78  		kind, err = names.TagKind(req.AuthTag)
    79  		if err != nil || kind != names.UserTagKind {
    80  			isUser = false
    81  			// Users are not rate limited, all other entities are.
    82  			if !a.srv.limiter.Acquire() {
    83  				logger.Debugf("rate limiting for agent %s", req.AuthTag)
    84  				return fail, common.ErrTryAgain
    85  			}
    86  			defer a.srv.limiter.Release()
    87  		}
    88  	}
    89  
    90  	serverOnlyLogin := a.root.modelUUID == ""
    91  
    92  	entity, lastConnection, err := doCheckCreds(a.root.state, req, !serverOnlyLogin, a.srv.authCtxt)
    93  	if err != nil {
    94  		if err, ok := errors.Cause(err).(*common.DischargeRequiredError); ok {
    95  			loginResult := params.LoginResultV1{
    96  				DischargeRequired:       err.Macaroon,
    97  				DischargeRequiredReason: err.Error(),
    98  			}
    99  			logger.Infof("login failed with discharge-required error: %v", err)
   100  			return loginResult, nil
   101  		}
   102  		if a.maintenanceInProgress() {
   103  			// An upgrade, restore or similar operation is in
   104  			// progress. It is possible for logins to fail until this
   105  			// is complete due to incomplete or updating data. Mask
   106  			// transitory and potentially confusing errors from failed
   107  			// logins with a more helpful one.
   108  			return fail, MaintenanceNoLoginError
   109  		}
   110  		// Here we have a special case.  The machine agents that manage
   111  		// models in the controller model need to be able to
   112  		// open API connections to other models.  In those cases, we
   113  		// need to look in the controller database to check the creds
   114  		// against the machine if and only if the entity tag is a machine tag,
   115  		// and the machine exists in the controller model, and the
   116  		// machine has the manage state job.  If all those parts are valid, we
   117  		// can then check the credentials against the controller model
   118  		// machine.
   119  		if kind != names.MachineTagKind {
   120  			return fail, errors.Trace(err)
   121  		}
   122  		entity, err = a.checkCredsOfControllerMachine(req)
   123  		if err != nil {
   124  			return fail, errors.Trace(err)
   125  		}
   126  		// If we are here, then the entity will refer to a controller
   127  		// machine in the controller model, and we don't need a pinger
   128  		// for it as we already have one running in the machine agent api
   129  		// worker for the controller model.
   130  		agentPingerNeeded = false
   131  	}
   132  	a.root.entity = entity
   133  
   134  	if a.reqNotifier != nil {
   135  		a.reqNotifier.login(entity.Tag().String())
   136  	}
   137  
   138  	// We have authenticated the user; enable the appropriate API
   139  	// to serve to them.
   140  	a.loggedIn = true
   141  
   142  	if agentPingerNeeded {
   143  		if err := startPingerIfAgent(a.root, entity); err != nil {
   144  			return fail, errors.Trace(err)
   145  		}
   146  	}
   147  
   148  	var maybeUserInfo *params.AuthUserInfo
   149  	var envUser *state.ModelUser
   150  	// Send back user info if user
   151  	if isUser && !serverOnlyLogin {
   152  		maybeUserInfo = &params.AuthUserInfo{
   153  			Identity:       entity.Tag().String(),
   154  			LastConnection: lastConnection,
   155  		}
   156  		envUser, err = a.root.state.ModelUser(entity.Tag().(names.UserTag))
   157  		if err != nil {
   158  			return fail, errors.Annotatef(err, "missing ModelUser for logged in user %s", entity.Tag())
   159  		}
   160  		if envUser.ReadOnly() {
   161  			logger.Debugf("model user %s is READ ONLY", entity.Tag())
   162  		}
   163  	}
   164  
   165  	// Fetch the API server addresses from state.
   166  	hostPorts, err := a.root.state.APIHostPorts()
   167  	if err != nil {
   168  		return fail, errors.Trace(err)
   169  	}
   170  	logger.Debugf("hostPorts: %v", hostPorts)
   171  
   172  	environ, err := a.root.state.Model()
   173  	if err != nil {
   174  		return fail, errors.Trace(err)
   175  	}
   176  
   177  	loginResult := params.LoginResultV1{
   178  		Servers:       params.FromNetworkHostsPorts(hostPorts),
   179  		ModelTag:      environ.Tag().String(),
   180  		ControllerTag: environ.ControllerTag().String(),
   181  		Facades:       DescribeFacades(),
   182  		UserInfo:      maybeUserInfo,
   183  		ServerVersion: jujuversion.Current.String(),
   184  	}
   185  
   186  	// For sufficiently modern login versions, stop serving the
   187  	// controller model at the root of the API.
   188  	if serverOnlyLogin {
   189  		authedApi = newRestrictedRoot(authedApi)
   190  		// Remove the ModelTag from the response as there is no
   191  		// model here.
   192  		loginResult.ModelTag = ""
   193  		// Strip out the facades that are not supported from the result.
   194  		var facades []params.FacadeVersions
   195  		for _, facade := range loginResult.Facades {
   196  			if restrictedRootNames.Contains(facade.Name) {
   197  				facades = append(facades, facade)
   198  			}
   199  		}
   200  		loginResult.Facades = facades
   201  	}
   202  
   203  	if envUser != nil {
   204  		authedApi = newClientAuthRoot(authedApi, envUser)
   205  	}
   206  
   207  	a.root.rpcConn.ServeFinder(authedApi, serverError)
   208  
   209  	return loginResult, nil
   210  }
   211  
   212  // checkCredsOfControllerMachine checks the special case of a controller
   213  // machine creating an API connection for a different model so it can
   214  // run API workers for that model to do things like provisioning
   215  // machines.
   216  func (a *admin) checkCredsOfControllerMachine(req params.LoginRequest) (state.Entity, error) {
   217  	entity, _, err := doCheckCreds(a.srv.state, req, false, a.srv.authCtxt)
   218  	if err != nil {
   219  		return nil, errors.Trace(err)
   220  	}
   221  	machine, ok := entity.(*state.Machine)
   222  	if !ok {
   223  		return nil, errors.Errorf("entity should be a machine, but is %T", entity)
   224  	}
   225  	for _, job := range machine.Jobs() {
   226  		if job == state.JobManageModel {
   227  			return entity, nil
   228  		}
   229  	}
   230  	// The machine does exist in the controller model, but it
   231  	// doesn't manage models, so reject it.
   232  	return nil, errors.Trace(common.ErrBadCreds)
   233  }
   234  
   235  func (a *admin) maintenanceInProgress() bool {
   236  	if a.srv.validator == nil {
   237  		return false
   238  	}
   239  	// jujud's login validator will return an error for any user tag
   240  	// if jujud is upgrading or restoring. The tag of the entity
   241  	// trying to log in can't be used because jujud's login validator
   242  	// will always return nil for the local machine agent and here we
   243  	// need to know if maintenance is in progress irrespective of the
   244  	// the authenticating entity.
   245  	//
   246  	// TODO(mjs): 2014-09-29 bug 1375110
   247  	// This needs improving but I don't have the cycles right now.
   248  	req := params.LoginRequest{
   249  		AuthTag: names.NewUserTag("arbitrary").String(),
   250  	}
   251  	return a.srv.validator(req) != nil
   252  }
   253  
   254  var doCheckCreds = checkCreds
   255  
   256  // checkCreds validates the entities credentials in the current model.
   257  // If the entity is a user, and lookForModelUser is true, a model user must exist
   258  // for the model.  In the case of a user logging in to the server, but
   259  // not a model, there is no env user needed.  While we have the env
   260  // user, if we do have it, update the last login time.
   261  //
   262  // Note that when logging in with lookForModelUser true, the returned
   263  // entity will be modelUserEntity, not *state.User (external users
   264  // don't have user entries) or *state.ModelUser (we
   265  // don't want to lose the local user information associated with that).
   266  func checkCreds(st *state.State, req params.LoginRequest, lookForModelUser bool, authenticator authentication.EntityAuthenticator) (state.Entity, *time.Time, error) {
   267  	var tag names.Tag
   268  	if req.AuthTag != "" {
   269  		var err error
   270  		tag, err = names.ParseTag(req.AuthTag)
   271  		if err != nil {
   272  			return nil, nil, errors.Trace(err)
   273  		}
   274  	}
   275  	var entityFinder authentication.EntityFinder = st
   276  	if lookForModelUser {
   277  		// When looking up model users, use a custom
   278  		// entity finder that looks up both the local user (if the user
   279  		// tag is in the local domain) and the model user.
   280  		entityFinder = modelUserEntityFinder{st}
   281  	}
   282  	entity, err := authenticator.Authenticate(entityFinder, tag, req)
   283  	if err != nil {
   284  		return nil, nil, errors.Trace(err)
   285  	}
   286  
   287  	// For user logins, update the last login time.
   288  	var lastLogin *time.Time
   289  	if entity, ok := entity.(loginEntity); ok {
   290  		userLastLogin, err := entity.LastLogin()
   291  		if err != nil && !state.IsNeverLoggedInError(err) {
   292  			return nil, nil, errors.Trace(err)
   293  		}
   294  		entity.UpdateLastLogin()
   295  		lastLogin = &userLastLogin
   296  	}
   297  	return entity, lastLogin, nil
   298  }
   299  
   300  // loginEntity defines the interface needed to log in as a user.
   301  // Notable implementations are *state.User and *modelUserEntity.
   302  type loginEntity interface {
   303  	state.Entity
   304  	state.Authenticator
   305  	LastLogin() (time.Time, error)
   306  	UpdateLastLogin() error
   307  }
   308  
   309  // modelUserEntityFinder implements EntityFinder by returning a
   310  // loginEntity value for users, ensuring that the user exists in the
   311  // state's current model as well as retrieving more global
   312  // authentication details such as the password.
   313  type modelUserEntityFinder struct {
   314  	st *state.State
   315  }
   316  
   317  // FindEntity implements authentication.EntityFinder.FindEntity.
   318  func (f modelUserEntityFinder) FindEntity(tag names.Tag) (state.Entity, error) {
   319  	utag, ok := tag.(names.UserTag)
   320  	if !ok {
   321  		return f.st.FindEntity(tag)
   322  	}
   323  	modelUser, err := f.st.ModelUser(utag)
   324  	if err != nil {
   325  		return nil, err
   326  	}
   327  	u := &modelUserEntity{
   328  		modelUser: modelUser,
   329  	}
   330  	if utag.IsLocal() {
   331  		user, err := f.st.User(utag)
   332  		if err != nil {
   333  			return nil, err
   334  		}
   335  		u.user = user
   336  	}
   337  	return u, nil
   338  }
   339  
   340  var _ loginEntity = &modelUserEntity{}
   341  
   342  // modelUserEntity encapsulates an model user
   343  // and, if the user is local, the local state user
   344  // as well. This enables us to implement FindEntity
   345  // in such a way that the authentication mechanisms
   346  // can work without knowing these details.
   347  type modelUserEntity struct {
   348  	modelUser *state.ModelUser
   349  	user      *state.User
   350  }
   351  
   352  // Refresh implements state.Authenticator.Refresh.
   353  func (u *modelUserEntity) Refresh() error {
   354  	if u.user == nil {
   355  		return nil
   356  	}
   357  	return u.user.Refresh()
   358  }
   359  
   360  // SetPassword implements state.Authenticator.SetPassword
   361  // by setting the password on the local user.
   362  func (u *modelUserEntity) SetPassword(pass string) error {
   363  	if u.user == nil {
   364  		return errors.New("cannot set password on external user")
   365  	}
   366  	return u.user.SetPassword(pass)
   367  }
   368  
   369  // PasswordValid implements state.Authenticator.PasswordValid.
   370  func (u *modelUserEntity) PasswordValid(pass string) bool {
   371  	if u.user == nil {
   372  		return false
   373  	}
   374  	return u.user.PasswordValid(pass)
   375  }
   376  
   377  // Tag implements state.Entity.Tag.
   378  func (u *modelUserEntity) Tag() names.Tag {
   379  	return u.modelUser.UserTag()
   380  }
   381  
   382  // LastLogin implements loginEntity.LastLogin.
   383  func (u *modelUserEntity) LastLogin() (time.Time, error) {
   384  	// The last connection for the model takes precedence over
   385  	// the local user last login time.
   386  	t, err := u.modelUser.LastConnection()
   387  	if state.IsNeverConnectedError(err) {
   388  		if u.user != nil {
   389  			// There's a global user, so use that login time instead.
   390  			return u.user.LastLogin()
   391  		}
   392  		// Since we're implementing LastLogin, we need
   393  		// to implement LastLogin error semantics too.
   394  		err = state.NeverLoggedInError(err.Error())
   395  	}
   396  	return t, err
   397  }
   398  
   399  // UpdateLastLogin implements loginEntity.UpdateLastLogin.
   400  func (u *modelUserEntity) UpdateLastLogin() error {
   401  	err := u.modelUser.UpdateLastConnection()
   402  	if u.user != nil {
   403  		err1 := u.user.UpdateLastLogin()
   404  		if err == nil {
   405  			err = err1
   406  		}
   407  	}
   408  	return err
   409  }
   410  
   411  // presenceShim exists to represent a statepresence.Agent in a form
   412  // convenient to the apiserver/presence package, which exists to work
   413  // around the common.Resources infrastructure's lack of handling for
   414  // failed resources.
   415  type presenceShim struct {
   416  	agent statepresence.Agent
   417  }
   418  
   419  // Start starts and returns a running presence.Pinger. The caller is
   420  // responsible for stopping it when no longer required, and for handling
   421  // any errors returned from Wait.
   422  func (shim presenceShim) Start() (presence.Pinger, error) {
   423  	pinger, err := shim.agent.SetAgentPresence()
   424  	if err != nil {
   425  		return nil, errors.Trace(err)
   426  	}
   427  	return pinger, nil
   428  }
   429  
   430  func startPingerIfAgent(root *apiHandler, entity state.Entity) error {
   431  	// worker runs presence.Pingers -- absence of which will cause
   432  	// embarrassing "agent is lost" messages to show up in status --
   433  	// until it's stopped. It's stored in resources purely for the
   434  	// side effects: we don't record its id, and nobody else
   435  	// retrieves it -- we just expect it to be stopped when the
   436  	// connection is shut down.
   437  	agent, ok := entity.(statepresence.Agent)
   438  	if !ok {
   439  		return nil
   440  	}
   441  	worker, err := presence.New(presence.Config{
   442  		Identity:   entity.Tag(),
   443  		Start:      presenceShim{agent}.Start,
   444  		Clock:      clock.WallClock,
   445  		RetryDelay: 3 * time.Second,
   446  	})
   447  	if err != nil {
   448  		return err
   449  	}
   450  	root.getResources().Register(worker)
   451  
   452  	// pingTimeout, by contrast, *is* used by the Pinger facade to
   453  	// stave off the call to action() that will shut down the agent
   454  	// connection if it gets lackadaisical about sending keepalive
   455  	// Pings.
   456  	//
   457  	// Do not confuse those (apiserver) Pings with those made by
   458  	// presence.Pinger (which *do* happen as a result of the former,
   459  	// but only as a relatively distant consequence).
   460  	//
   461  	// We should have picked better names...
   462  	action := func() {
   463  		if err := root.getRpcConn().Close(); err != nil {
   464  			logger.Errorf("error closing the RPC connection: %v", err)
   465  		}
   466  	}
   467  	pingTimeout := newPingTimeout(action, maxClientPingInterval)
   468  	return root.getResources().RegisterNamed("pingTimeout", pingTimeout)
   469  }
   470  
   471  // errRoot implements the API that a client first sees
   472  // when connecting to the API. It exposes the same API as initialRoot, except
   473  // it returns the requested error when the client makes any request.
   474  type errRoot struct {
   475  	err error
   476  }
   477  
   478  // FindMethod conforms to the same API as initialRoot, but we'll always return (nil, err)
   479  func (r *errRoot) FindMethod(rootName string, version int, methodName string) (rpcreflect.MethodCaller, error) {
   480  	return nil, r.err
   481  }