// APIHostPorts returns the API host/port addresses stored in state. func (c *Client) APIHostPorts() (result params.APIHostPortsResult, err error) { var servers [][]network.HostPort if servers, err = c.api.state.APIHostPorts(); err != nil { return params.APIHostPortsResult{}, err } result.Servers = params.FromNetworkHostsPorts(servers) return result, nil }
// APIHostPorts returns the API server addresses. func (api *APIAddresser) APIHostPorts() (params.APIHostPortsResult, error) { servers, err := api.getter.APIHostPorts() if err != nil { return params.APIHostPortsResult{}, err } return params.APIHostPortsResult{ Servers: params.FromNetworkHostsPorts(servers), }, nil }
// APIHostPorts returns the API host/port addresses stored in state. func (c *Client) APIHostPorts() (result params.APIHostPortsResult, err error) { if err := c.checkCanWrite(); err != nil { return result, err } var servers [][]network.HostPort if servers, err = c.api.stateAccessor.APIHostPorts(); err != nil { return params.APIHostPortsResult{}, err } result.Servers = params.FromNetworkHostsPorts(servers) return result, nil }
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 params.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, errors.Trace(err) } } var agentPingerNeeded = true isUser := true kind := names.UserTagKind if req.AuthTag != "" { var err error kind, err = names.TagKind(req.AuthTag) if err != nil || kind != names.UserTagKind { isUser = false // 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() } } serverOnlyLogin := a.root.modelUUID == "" entity, lastConnection, err := doCheckCreds(a.root.state, req, !serverOnlyLogin, a.srv.authCtxt) if err != nil { if err, ok := errors.Cause(err).(*common.DischargeRequiredError); ok { loginResult := params.LoginResultV1{ DischargeRequired: err.Macaroon, DischargeRequiredReason: err.Error(), } logger.Infof("login failed with discharge-required error: %v", err) return loginResult, 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 // models in the controller model need to be able to // open API connections to other models. In those cases, we // need to look in the controller 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 controller model, and the // machine has the manage state job. If all those parts are valid, we // can then check the credentials against the controller model // machine. if kind != names.MachineTagKind { return fail, errors.Trace(err) } entity, err = a.checkCredsOfControllerMachine(req) if err != nil { return fail, errors.Trace(err) } // If we are here, then the entity will refer to a controller // machine in the controller model, and we don't need a pinger // for it as we already have one running in the machine agent api // worker for the controller model. 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, errors.Trace(err) } } var maybeUserInfo *params.AuthUserInfo var envUser *state.ModelUser // Send back user info if user if isUser && !serverOnlyLogin { maybeUserInfo = ¶ms.AuthUserInfo{ Identity: entity.Tag().String(), LastConnection: lastConnection, } envUser, err = a.root.state.ModelUser(entity.Tag().(names.UserTag)) if err != nil { return fail, errors.Annotatef(err, "missing ModelUser for logged in user %s", entity.Tag()) } if envUser.ReadOnly() { logger.Debugf("model user %s is READ ONLY", entity.Tag()) } } // Fetch the API server addresses from state. hostPorts, err := a.root.state.APIHostPorts() if err != nil { return fail, errors.Trace(err) } logger.Debugf("hostPorts: %v", hostPorts) environ, err := a.root.state.Model() if err != nil { return fail, errors.Trace(err) } loginResult := params.LoginResultV1{ Servers: params.FromNetworkHostsPorts(hostPorts), ModelTag: environ.Tag().String(), ControllerTag: environ.ControllerTag().String(), Facades: DescribeFacades(), UserInfo: maybeUserInfo, ServerVersion: jujuversion.Current.String(), } // For sufficiently modern login versions, stop serving the // controller model at the root of the API. if serverOnlyLogin { authedApi = newRestrictedRoot(authedApi) // Remove the ModelTag from the response as there is no // model here. loginResult.ModelTag = "" // 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 } if envUser != nil { authedApi = newClientAuthRoot(authedApi, envUser) } a.root.rpcConn.ServeFinder(authedApi, serverError) return loginResult, nil }
// login is the internal version of the Login API call. func (a *admin) login(req params.LoginRequest, loginVersion int) (params.LoginResult, error) { var fail params.LoginResult a.mu.Lock() defer a.mu.Unlock() if a.loggedIn { // This can only happen if Login is called concurrently. return fail, errAlreadyLoggedIn } // apiRoot is the API root exposed to the client after authentication. var apiRoot rpc.Root = 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 params.UpgradeInProgressError: apiRoot = restrictRoot(apiRoot, upgradeMethodsOnly) case AboutToRestoreError: apiRoot = restrictRoot(apiRoot, aboutToRestoreMethodsOnly) case RestoreInProgressError: apiRoot = restrictAll(apiRoot, restoreInProgressError) case nil: // in this case no need to wrap authed api so we do nothing default: return fail, errors.Trace(err) } } isUser := true kind := names.UserTagKind if req.AuthTag != "" { var err error kind, err = names.TagKind(req.AuthTag) if err != nil || kind != names.UserTagKind { isUser = false // 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() } } controllerOnlyLogin := a.root.modelUUID == "" controllerMachineLogin := false entity, lastConnection, err := a.checkCreds(req, isUser) if err != nil { if err, ok := errors.Cause(err).(*common.DischargeRequiredError); ok { loginResult := params.LoginResult{ DischargeRequired: err.Macaroon, DischargeRequiredReason: err.Error(), } logger.Infof("login failed with discharge-required error: %v", err) return loginResult, 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 // models in the controller model need to be able to // open API connections to other models. In those cases, we // need to look in the controller 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 controller model, and the // machine has the manage state job. If all those parts are valid, we // can then check the credentials against the controller model // machine. if kind != names.MachineTagKind { return fail, errors.Trace(err) } if errors.Cause(err) != common.ErrBadCreds { return fail, err } entity, err = a.checkControllerMachineCreds(req) if err != nil { return fail, errors.Trace(err) } // If we are here, then the entity will refer to a controller // machine in the controller model, and we don't need a pinger // for it as we already have one running in the machine agent api // worker for the controller model. controllerMachineLogin = true } a.root.entity = entity a.apiObserver.Login(entity.Tag(), a.root.state.ModelTag(), controllerMachineLogin, req.UserData) // We have authenticated the user; enable the appropriate API // to serve to them. a.loggedIn = true if !controllerMachineLogin { if err := startPingerIfAgent(a.srv.clock, a.root, entity); err != nil { return fail, errors.Trace(err) } } var maybeUserInfo *params.AuthUserInfo var modelUser permission.UserAccess var everyoneGroupUser permission.UserAccess // Send back user info if user if isUser { maybeUserInfo = ¶ms.AuthUserInfo{ Identity: entity.Tag().String(), LastConnection: lastConnection, } userTag := entity.Tag().(names.UserTag) // TODO(perrito666) remove the following section about everyone group // when groups are implemented, this accounts only for the lack of a local // ControllerUser when logging in from an external user that has not been granted // permissions on the controller but there are permissions for the special // everyone group. if !userTag.IsLocal() { everyoneTag := names.NewUserTag(common.EveryoneTagName) everyoneGroupUser, err = state.ControllerAccess(a.root.state, everyoneTag) if err != nil && !errors.IsNotFound(err) { return fail, errors.Annotatef(err, "obtaining ControllerUser for everyone group") } } modelUser, err = a.root.state.UserAccess(userTag, a.root.state.ModelTag()) if err != nil && !errors.IsNotFound(err) { return fail, errors.Annotatef(err, "obtaining ModelUser for logged in user %s", entity.Tag()) } controllerUser, err := state.ControllerAccess(a.root.state, entity.Tag()) if err != nil && !errors.IsNotFound(err) { return fail, errors.Annotatef(err, "obtaining ControllerUser for logged in user %s", entity.Tag()) } if permission.IsEmptyUserAccess(modelUser) && permission.IsEmptyUserAccess(controllerUser) && permission.IsEmptyUserAccess(everyoneGroupUser) { return fail, errors.NotFoundf("model or controller access for logged in user %q", userTag.Canonical()) } maybeUserInfo.ControllerAccess = string(controllerUser.Access) maybeUserInfo.ModelAccess = string(modelUser.Access) logger.Tracef("controller user %s has %v", entity.Tag(), controllerUser.Access) logger.Tracef("model user %s has %s", entity.Tag(), modelUser.Access) } // Fetch the API server addresses from state. hostPorts, err := a.root.state.APIHostPorts() if err != nil { return fail, errors.Trace(err) } logger.Debugf("hostPorts: %v", hostPorts) model, err := a.root.state.Model() if err != nil { return fail, errors.Trace(err) } if isUser && model.MigrationMode() == state.MigrationModeImporting { apiRoot = restrictAll(apiRoot, errors.New("migration in progress, model is importing")) } loginResult := params.LoginResult{ Servers: params.FromNetworkHostsPorts(hostPorts), ControllerTag: model.ControllerTag().String(), UserInfo: maybeUserInfo, ServerVersion: jujuversion.Current.String(), } if controllerOnlyLogin { loginResult.Facades = filterFacades(isControllerFacade) apiRoot = restrictRoot(apiRoot, controllerFacadesOnly) } else { loginResult.ModelTag = model.Tag().String() loginResult.Facades = filterFacades(isModelFacade) apiRoot = restrictRoot(apiRoot, modelFacadesOnly) } a.root.rpcConn.ServeRoot(apiRoot, serverError) return loginResult, nil }