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