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