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 &params.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  }