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 = ¶ms.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 }