func (a *admin) doLogin(req params.LoginRequest, loginVersion int) (params.LoginResultV1, error) { var fail params.LoginResultV1 a.mu.Lock() defer a.mu.Unlock() if a.loggedIn { // This can only happen if Login is called concurrently. return fail, errAlreadyLoggedIn } // authedApi is the API method finder we'll use after getting logged in. var authedApi rpc.MethodFinder = newApiRoot(a.root.state, a.root.resources, a.root) // Use the login validation function, if one was specified. if a.srv.validator != nil { err := a.srv.validator(req) switch err { case UpgradeInProgressError: authedApi = newUpgradingRoot(authedApi) case AboutToRestoreError: authedApi = newAboutToRestoreRoot(authedApi) case RestoreInProgressError: authedApi = newRestoreInProgressRoot(authedApi) case nil: // in this case no need to wrap authed api so we do nothing default: return fail, err } } var agentPingerNeeded = true var isUser bool kind, err := names.TagKind(req.AuthTag) if err != nil || kind != names.UserTagKind { // Users are not rate limited, all other entities are if !a.srv.limiter.Acquire() { logger.Debugf("rate limiting for agent %s", req.AuthTag) return fail, common.ErrTryAgain } defer a.srv.limiter.Release() } else { isUser = true } serverOnlyLogin := loginVersion > 1 && a.root.envUUID == "" entity, lastConnection, err := doCheckCreds(a.root.state, req, !serverOnlyLogin) if err != nil { if a.maintenanceInProgress() { // An upgrade, restore or similar operation is in // progress. It is possible for logins to fail until this // is complete due to incomplete or updating data. Mask // transitory and potentially confusing errors from failed // logins with a more helpful one. return fail, MaintenanceNoLoginError } // Here we have a special case. The machine agents that manage // environments in the state server environment need to be able to // open API connections to other environments. In those cases, we // need to look in the state server database to check the creds // against the machine if and only if the entity tag is a machine tag, // and the machine exists in the state server environment, and the // machine has the manage state job. If all those parts are valid, we // can then check the credentials against the state server environment // machine. if kind != names.MachineTagKind { return fail, err } entity, err = a.checkCredsOfStateServerMachine(req) if err != nil { return fail, err } // If we are here, then the entity will refer to a state server // machine in the state server environment, and we don't need a pinger // for it as we already have one running in the machine agent api // worker for the state server environment. agentPingerNeeded = false } a.root.entity = entity if a.reqNotifier != nil { a.reqNotifier.login(entity.Tag().String()) } // We have authenticated the user; enable the appropriate API // to serve to them. a.loggedIn = true if agentPingerNeeded { if err := startPingerIfAgent(a.root, entity); err != nil { return fail, err } } var maybeUserInfo *params.AuthUserInfo // Send back user info if user if isUser { maybeUserInfo = ¶ms.AuthUserInfo{ Identity: entity.Tag().String(), LastConnection: lastConnection, } } // Fetch the API server addresses from state. hostPorts, err := a.root.state.APIHostPorts() if err != nil { return fail, err } logger.Debugf("hostPorts: %v", hostPorts) environ, err := a.root.state.Environment() if err != nil { return fail, err } loginResult := params.LoginResultV1{ Servers: params.FromNetworkHostsPorts(hostPorts), EnvironTag: environ.Tag().String(), ServerTag: environ.ServerTag().String(), Facades: DescribeFacades(), UserInfo: maybeUserInfo, ServerVersion: version.Current.Number.String(), } // For sufficiently modern login versions, stop serving the // state server environment at the root of the API. if serverOnlyLogin { authedApi = newRestrictedRoot(authedApi) // Remove the EnvironTag from the response as there is no // environment here. loginResult.EnvironTag = "" // Strip out the facades that are not supported from the result. var facades []params.FacadeVersions for _, facade := range loginResult.Facades { if restrictedRootNames.Contains(facade.Name) { facades = append(facades, facade) } } loginResult.Facades = facades } a.root.rpcConn.ServeFinder(authedApi, serverError) return loginResult, nil }