github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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 "context" 8 "fmt" 9 "sync" 10 "time" 11 12 "github.com/juju/clock" 13 "github.com/juju/errors" 14 "github.com/juju/names/v5" 15 "github.com/juju/rpcreflect" 16 17 "github.com/juju/juju/api" 18 "github.com/juju/juju/apiserver/authentication" 19 "github.com/juju/juju/apiserver/common" 20 apiservererrors "github.com/juju/juju/apiserver/errors" 21 "github.com/juju/juju/apiserver/facade" 22 "github.com/juju/juju/apiserver/observer" 23 "github.com/juju/juju/core/auditlog" 24 "github.com/juju/juju/core/network" 25 "github.com/juju/juju/core/permission" 26 "github.com/juju/juju/rpc" 27 "github.com/juju/juju/rpc/params" 28 "github.com/juju/juju/state" 29 jujuversion "github.com/juju/juju/version" 30 ) 31 32 type adminAPIFactory func(*Server, *apiHandler, observer.Observer) interface{} 33 34 // admin is the only object that unlogged-in clients can access. It holds any 35 // methods that are needed to log in. 36 type admin struct { 37 srv *Server 38 root *apiHandler 39 apiObserver observer.Observer 40 41 mu sync.Mutex 42 loggedIn bool 43 } 44 45 func newAdminAPIV3(srv *Server, root *apiHandler, apiObserver observer.Observer) interface{} { 46 return &admin{ 47 srv: srv, 48 root: root, 49 apiObserver: apiObserver, 50 } 51 } 52 53 // Admin returns an object that provides API access to methods that can be 54 // called even when not authenticated. 55 func (a *admin) Admin(id string) (*admin, error) { 56 if id != "" { 57 // Safeguard id for possible future use. 58 return nil, apiservererrors.ErrBadId 59 } 60 return a, nil 61 } 62 63 // Login logs in with the provided credentials. All subsequent requests on the 64 // connection will act as the authenticated user. 65 func (a *admin) Login(req params.LoginRequest) (params.LoginResult, error) { 66 return a.login(context.Background(), req, 3) 67 } 68 69 // RedirectInfo returns redirected host information for the model. 70 // In Juju it always returns an error because the Juju controller 71 // does not multiplex controllers. 72 func (a *admin) RedirectInfo() (params.RedirectInfoResult, error) { 73 return params.RedirectInfoResult{}, fmt.Errorf("not redirected") 74 } 75 76 var MaintenanceNoLoginError = errors.New("login failed - maintenance in progress") 77 var errAlreadyLoggedIn = errors.New("already logged in") 78 79 // login is the internal version of the Login API call. 80 func (a *admin) login(ctx context.Context, req params.LoginRequest, loginVersion int) (params.LoginResult, error) { 81 var fail params.LoginResult 82 83 a.mu.Lock() 84 defer a.mu.Unlock() 85 if a.loggedIn { 86 // This can only happen if Login is called concurrently. 87 return fail, errAlreadyLoggedIn 88 } 89 90 authResult, err := a.authenticate(ctx, req) 91 if err, ok := errors.Cause(err).(*apiservererrors.DischargeRequiredError); ok { 92 loginResult := params.LoginResult{ 93 DischargeRequired: err.LegacyMacaroon, 94 BakeryDischargeRequired: err.Macaroon, 95 DischargeRequiredReason: err.Error(), 96 } 97 logger.Infof("login failed with discharge-required error: %v", err) 98 return loginResult, nil 99 } 100 if err != nil { 101 return fail, errors.Trace(err) 102 } 103 104 // Fetch the API server addresses from state. 105 // If the login comes from a client, return all available addresses. 106 // Otherwise return the addresses suitable for agent use. 107 ctrlSt, err := a.root.shared.statePool.SystemState() 108 if err != nil { 109 return fail, errors.Trace(err) 110 } 111 getHostPorts := ctrlSt.APIHostPortsForAgents 112 if k, _ := names.TagKind(req.AuthTag); k == names.UserTagKind { 113 getHostPorts = ctrlSt.APIHostPortsForClients 114 } 115 hostPorts, err := getHostPorts() 116 if err != nil { 117 return fail, errors.Trace(err) 118 } 119 pServers := make([]network.HostPorts, len(hostPorts)) 120 for i, hps := range hostPorts { 121 pServers[i] = hps.HostPorts() 122 } 123 124 // apiRoot is the API root exposed to the client after login. 125 var apiRoot rpc.Root 126 apiRoot, err = newAPIRoot( 127 a.srv.clock, 128 a.srv.facades, 129 a.root, 130 httpRequestRecorderWrapper{ 131 collector: a.srv.metricsCollector, 132 modelUUID: a.root.model.UUID(), 133 }, 134 ) 135 if err != nil { 136 return fail, errors.Trace(err) 137 } 138 139 apiRoot, err = restrictAPIRoot( 140 a.srv, 141 apiRoot, 142 a.root.model, 143 *authResult, 144 ) 145 if err != nil { 146 return fail, errors.Trace(err) 147 } 148 149 var facadeFilters []facadeFilterFunc 150 var modelTag string 151 if authResult.anonymousLogin { 152 facadeFilters = append(facadeFilters, IsAnonymousFacade) 153 } 154 if authResult.controllerOnlyLogin { 155 facadeFilters = append(facadeFilters, IsControllerFacade) 156 } else { 157 facadeFilters = append(facadeFilters, IsModelFacade) 158 modelTag = a.root.model.Tag().String() 159 } 160 161 auditConfig := a.srv.GetAuditConfig() 162 auditRecorder, err := a.getAuditRecorder(req, authResult, auditConfig) 163 if err != nil { 164 return fail, errors.Trace(err) 165 } 166 167 recorderFactory := observer.NewRecorderFactory( 168 a.apiObserver, auditRecorder, auditConfig.CaptureAPIArgs, 169 ) 170 a.root.rpcConn.ServeRoot(apiRoot, recorderFactory, serverError) 171 return params.LoginResult{ 172 Servers: params.FromHostsPorts(pServers), 173 ControllerTag: a.root.model.ControllerTag().String(), 174 UserInfo: authResult.userInfo, 175 ServerVersion: jujuversion.Current.String(), 176 PublicDNSName: a.srv.publicDNSName(), 177 ModelTag: modelTag, 178 Facades: filterFacades(a.srv.facades, facadeFilters...), 179 }, nil 180 } 181 182 func (a *admin) getAuditRecorder(req params.LoginRequest, authResult *authResult, cfg auditlog.Config) (*auditlog.Recorder, error) { 183 if !authResult.userLogin || !cfg.Enabled { 184 return nil, nil 185 } 186 // Wrap the audit logger in a filter that prevents us from logging 187 // lots of readonly conversations (like "juju status" requests). 188 filter := observer.MakeInterestingRequestFilter(cfg.ExcludeMethods) 189 result, err := auditlog.NewRecorder( 190 observer.NewAuditLogFilter(cfg.Target, filter), 191 a.srv.clock, 192 auditlog.ConversationArgs{ 193 Who: a.root.authInfo.Entity.Tag().Id(), 194 What: req.CLIArgs, 195 ModelName: a.root.model.Name(), 196 ModelUUID: a.root.model.UUID(), 197 ConnectionID: a.root.connectionID, 198 }, 199 ) 200 if err != nil { 201 logger.Errorf("couldn't add login to audit log: %+v", err) 202 return nil, errors.Trace(err) 203 } 204 return result, nil 205 } 206 207 type authResult struct { 208 tag names.Tag // nil if external user login 209 anonymousLogin bool 210 userLogin bool // false if anonymous user 211 controllerOnlyLogin bool 212 controllerMachineLogin bool 213 userInfo *params.AuthUserInfo 214 } 215 216 func (a *admin) authenticate(ctx context.Context, req params.LoginRequest) (*authResult, error) { 217 result := &authResult{ 218 controllerOnlyLogin: a.root.modelUUID == "", 219 userLogin: true, 220 } 221 222 logger.Debugf("request authToken: %q", req.Token) 223 if req.Token == "" && req.AuthTag != "" { 224 tag, err := names.ParseTag(req.AuthTag) 225 if err == nil { 226 result.tag = tag 227 } 228 if err != nil || tag.Kind() != names.UserTagKind { 229 // Either the tag is invalid, or 230 // it's not a user; rate limit it. 231 a.srv.metricsCollector.LoginAttempts.Inc() 232 defer a.srv.metricsCollector.LoginAttempts.Dec() 233 234 // Users are not rate limited, all other entities are. 235 if err := a.srv.getAgentToken(); err != nil { 236 logger.Tracef("rate limiting for agent %s", req.AuthTag) 237 return nil, errors.Trace(err) 238 } 239 } 240 if err != nil { 241 return nil, errors.Trace(err) 242 } 243 } 244 245 // If the login attempt is for a migrated model, 246 // a.root.model will be nil as the model document does not exist on this 247 // controller and a.root.modelUUID cannot be resolved. 248 // In this case use the requested model UUID to check if we need to return 249 // a redirect error. 250 modelUUID := a.root.modelUUID 251 if a.root.model != nil { 252 modelUUID = a.root.model.UUID() 253 } 254 if err := a.maybeEmitRedirectError(modelUUID, result.tag); err != nil { 255 return nil, errors.Trace(err) 256 } 257 258 switch result.tag.(type) { 259 case nil: 260 // Macaroon logins are always for users. 261 case names.UserTag: 262 if result.tag.Id() == api.AnonymousUsername && len(req.Macaroons) == 0 { 263 result.anonymousLogin = true 264 result.userLogin = false 265 } 266 default: 267 result.userLogin = false 268 } 269 270 // Anonymous logins come from other controllers (in cross-model relations). 271 // We don't need to start pingers because we don't maintain presence 272 // information for them. 273 startPinger := !result.anonymousLogin 274 275 var authInfo authentication.AuthInfo 276 277 // controllerConn is used to indicate a connection from 278 // the controller to a non-controller model. 279 controllerConn := false 280 if !result.anonymousLogin { 281 authParams := authentication.AuthParams{ 282 AuthTag: result.tag, 283 Credentials: req.Credentials, 284 Nonce: req.Nonce, 285 Token: req.Token, 286 Macaroons: req.Macaroons, 287 BakeryVersion: req.BakeryVersion, 288 } 289 290 authenticated := false 291 for _, authenticator := range a.srv.loginAuthenticators { 292 var err error 293 authInfo, err = authenticator.AuthenticateLoginRequest(ctx, a.root.serverHost, modelUUID, authParams) 294 if errors.Is(err, errors.NotSupported) { 295 continue 296 } else if err != nil { 297 return nil, a.handleAuthError(err) 298 } 299 300 authenticated = true 301 a.root.authInfo = authInfo 302 result.controllerMachineLogin = authInfo.Controller 303 break 304 } 305 306 if !authenticated { 307 return nil, fmt.Errorf("failed to authenticate request: %w", errors.Unauthorized) 308 } 309 310 if result.controllerMachineLogin && !a.root.state.IsController() { 311 // We only need to run a pinger for controller machine 312 // agents when logging into the controller model. 313 startPinger = false 314 controllerConn = true 315 } 316 } else if a.root.model == nil { 317 // Anonymous login to unknown model. 318 // Hide the fact that the model does not exist. 319 return nil, errors.Unauthorizedf("invalid entity name or password") 320 } 321 // TODO(wallyworld) - we can't yet observe anonymous logins as entity must be non-nil 322 if !result.anonymousLogin { 323 a.apiObserver.Login(a.root.authInfo.Entity.Tag(), a.root.model.ModelTag(), controllerConn, req.UserData) 324 } 325 a.loggedIn = true 326 327 if startPinger { 328 if err := setupPingTimeoutDisconnect(a.srv.pingClock, a.root, a.root.authInfo.Entity); err != nil { 329 return nil, errors.Trace(err) 330 } 331 } 332 333 var lastConnection *time.Time 334 if err := a.fillLoginDetails(authInfo, result, lastConnection); err != nil { 335 return nil, errors.Trace(err) 336 } 337 return result, nil 338 } 339 340 func (a *admin) maybeEmitRedirectError(modelUUID string, authTag names.Tag) error { 341 userTag, ok := authTag.(names.UserTag) 342 if !ok { 343 return nil 344 } 345 346 st, err := a.root.shared.statePool.Get(modelUUID) 347 if err != nil { 348 return errors.Trace(err) 349 } 350 defer func() { _ = st.Release() }() 351 352 // If the model exists on this controller then no redirect is possible. 353 if _, err := st.Model(); err == nil || !errors.IsNotFound(err) { 354 return nil 355 } 356 357 // Check if the model was not found due to 358 // being migrated to another controller. 359 mig, err := st.CompletedMigration() 360 if err != nil && !errors.IsNotFound(err) { 361 return errors.Trace(err) 362 } 363 364 // If a user is trying to access a migrated model to which they are not 365 // granted access, do not return a redirect error. 366 // We need to return redirects if possible for anonymous logins in order 367 // to ensure post-migration operation of CMRs. 368 if mig == nil || (userTag.Id() != api.AnonymousUsername && mig.ModelUserAccess(userTag) == permission.NoAccess) { 369 return nil 370 } 371 372 target, err := mig.TargetInfo() 373 if err != nil { 374 return errors.Trace(err) 375 } 376 377 hps, err := network.ParseProviderHostPorts(target.Addrs...) 378 if err != nil { 379 return errors.Trace(err) 380 } 381 382 return &apiservererrors.RedirectError{ 383 Servers: []network.ProviderHostPorts{hps}, 384 CACert: target.CACert, 385 ControllerTag: target.ControllerTag, 386 ControllerAlias: target.ControllerAlias, 387 } 388 } 389 390 func (a *admin) handleAuthError(err error) error { 391 if err, ok := errors.Cause(err).(*apiservererrors.DischargeRequiredError); ok { 392 return err 393 } 394 if a.maintenanceInProgress() { 395 // An upgrade, migration or similar operation is in 396 // progress. It is possible for logins to fail until this 397 // is complete due to incomplete or updating data. Mask 398 // transitory and potentially confusing errors from failed 399 // logins with a more helpful one. 400 return errors.Wrap(err, MaintenanceNoLoginError) 401 } 402 return err 403 } 404 405 func (a *admin) fillLoginDetails(authInfo authentication.AuthInfo, result *authResult, lastConnection *time.Time) error { 406 // Send back user info if user 407 if result.userLogin { 408 var err error 409 result.userInfo, err = a.checkUserPermissions(authInfo, result.controllerOnlyLogin) 410 if err != nil { 411 return errors.Trace(err) 412 } 413 result.userInfo.LastConnection = lastConnection 414 } 415 if result.controllerOnlyLogin { 416 if result.anonymousLogin { 417 logger.Debugf(" anonymous controller login") 418 } else { 419 logger.Debugf("controller login: %s", a.root.authInfo.Entity.Tag()) 420 } 421 } else { 422 if result.anonymousLogin { 423 logger.Debugf("anonymous model login") 424 } else { 425 logger.Debugf("model login: %s for %s", a.root.authInfo.Entity.Tag(), a.root.model.ModelTag().Id()) 426 } 427 } 428 return nil 429 } 430 431 func (a *admin) checkUserPermissions(authInfo authentication.AuthInfo, controllerOnlyLogin bool) (*params.AuthUserInfo, error) { 432 userTag, ok := authInfo.Entity.Tag().(names.UserTag) 433 if !ok { 434 return nil, fmt.Errorf("establishing user tag from authenticated user entity") 435 } 436 437 modelAccess := permission.NoAccess 438 439 // TODO(perrito666) remove the following section about everyone group 440 // when groups are implemented, this accounts only for the lack of a local 441 // ControllerUser when logging in from an external user that has not been granted 442 // permissions on the controller but there are permissions for the special 443 // everyone group. 444 everyoneGroupAccess := permission.NoAccess 445 if !userTag.IsLocal() { 446 everyoneTag := names.NewUserTag(common.EveryoneTagName) 447 everyoneGroupUser, err := state.ControllerAccess(a.root.state, everyoneTag) 448 if err != nil && !errors.IsNotFound(err) { 449 return nil, errors.Annotatef(err, "obtaining ControllerUser for everyone group") 450 } 451 everyoneGroupAccess = everyoneGroupUser.Access 452 } 453 454 controllerAccess, err := authInfo.SubjectPermissions(a.root.state.ControllerTag()) 455 if errors.Is(err, errors.NotFound) { 456 controllerAccess = everyoneGroupAccess 457 } else if err != nil { 458 return nil, errors.Annotatef(err, "obtaining ControllerUser for logged in user %s", userTag.Id()) 459 } 460 461 if !controllerOnlyLogin { 462 // Only grab modelUser permissions if this is not a controller only 463 // login. In all situations, if the model user is not found, they have 464 // no authorisation to access this model, unless the user is controller 465 // admin. 466 467 var err error 468 modelAccess, err = authInfo.SubjectPermissions(a.root.model.ModelTag()) 469 if err != nil && controllerAccess != permission.SuperuserAccess { 470 return nil, errors.Wrap(err, apiservererrors.ErrPerm) 471 } 472 if err != nil && controllerAccess == permission.SuperuserAccess { 473 modelAccess = permission.AdminAccess 474 } 475 } 476 477 // It is possible that the everyoneGroup permissions are more capable than an 478 // individuals. If they are, use them. 479 if everyoneGroupAccess.GreaterControllerAccessThan(controllerAccess) { 480 controllerAccess = everyoneGroupAccess 481 } 482 if controllerOnlyLogin || !a.srv.allowModelAccess { 483 // We're either explicitly logging into the controller or 484 // we must check that the user has access to the controller 485 // even though they're logging into a model. 486 if controllerAccess == permission.NoAccess { 487 return nil, errors.Trace(apiservererrors.ErrPerm) 488 } 489 } 490 if controllerOnlyLogin { 491 logger.Debugf("controller login: user %s has %q access", userTag.Id(), controllerAccess) 492 } else { 493 logger.Debugf("model login: user %s has %q for controller; %q for model %s", 494 userTag.Id(), controllerAccess, modelAccess, a.root.model.ModelTag().Id()) 495 } 496 return ¶ms.AuthUserInfo{ 497 Identity: userTag.String(), 498 ControllerAccess: string(controllerAccess), 499 ModelAccess: string(modelAccess), 500 }, nil 501 } 502 503 type facadeFilterFunc func(name string) bool 504 505 func filterFacades(registry *facade.Registry, allowFacadeAllMustMatch ...facadeFilterFunc) []params.FacadeVersions { 506 allFacades := DescribeFacades(registry) 507 out := make([]params.FacadeVersions, 0, len(allFacades)) 508 for _, f := range allFacades { 509 allowed := false 510 for _, allowFacade := range allowFacadeAllMustMatch { 511 if allowed = allowFacade(f.Name); !allowed { 512 break 513 } 514 } 515 if allowed { 516 out = append(out, f) 517 } 518 } 519 return out 520 } 521 522 func (a *admin) maintenanceInProgress() bool { 523 return !a.srv.upgradeComplete() 524 } 525 526 func setupPingTimeoutDisconnect(clock clock.Clock, root *apiHandler, entity state.Entity) error { 527 tag := entity.Tag() 528 if tag.Kind() == names.UserTagKind { 529 return nil 530 } 531 532 // pingTimeout, by contrast, *is* used by the Pinger facade to 533 // stave off the call to action() that will shut down the agent 534 // connection if it gets lackadaisical about sending keepalive 535 // Pings. 536 // 537 // Do not confuse those (apiserver) Pings with those made by 538 // presence.Pinger (which *do* happen as a result of the former, 539 // but only as a relatively distant consequence). 540 // 541 // We should have picked better names... 542 action := func() { 543 logger.Debugf("closing connection due to ping timout") 544 if err := root.getRpcConn().Close(); err != nil { 545 logger.Errorf("error closing the RPC connection: %v", err) 546 } 547 } 548 pingTimeout := newPingTimeout(action, clock, maxClientPingInterval) 549 return root.Resources().RegisterNamed("pingTimeout", pingTimeout) 550 } 551 552 // errRoot implements the API that a client first sees 553 // when connecting to the API. It exposes the same API as initialRoot, except 554 // it returns the requested error when the client makes any request. 555 type errRoot struct { 556 err error 557 } 558 559 // FindMethod conforms to the same API as initialRoot, but we'll always return (nil, err) 560 func (r *errRoot) FindMethod(rootName string, version int, methodName string) (rpcreflect.MethodCaller, error) { 561 return nil, r.err 562 } 563 564 func (r *errRoot) Kill() { 565 }