github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/client/cloud/cloud.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // Package cloud defines an API end point for functions dealing with
     5  // the controller's cloud definition, and cloud credentials.
     6  package cloud
     7  
     8  import (
     9  	"fmt"
    10  	"sort"
    11  	"strings"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/loggo"
    15  	"github.com/juju/txn"
    16  	"gopkg.in/juju/names.v2"
    17  
    18  	"github.com/juju/juju/apiserver/common"
    19  	"github.com/juju/juju/apiserver/common/credentialcommon"
    20  	"github.com/juju/juju/apiserver/facade"
    21  	"github.com/juju/juju/apiserver/params"
    22  	"github.com/juju/juju/cloud"
    23  	"github.com/juju/juju/environs"
    24  	environscontext "github.com/juju/juju/environs/context"
    25  	"github.com/juju/juju/permission"
    26  	"github.com/juju/juju/state"
    27  )
    28  
    29  var logger = loggo.GetLogger("juju.apiserver.cloud")
    30  
    31  // CloudV3 defines the methods on the cloud API facade, version 3.
    32  type CloudV3 interface {
    33  	AddCloud(cloudArgs params.AddCloudArgs) error
    34  	AddCredentials(args params.TaggedCredentials) (params.ErrorResults, error)
    35  	CheckCredentialsModels(args params.TaggedCredentials) (params.UpdateCredentialResults, error)
    36  	Cloud(args params.Entities) (params.CloudResults, error)
    37  	Clouds() (params.CloudsResult, error)
    38  	Credential(args params.Entities) (params.CloudCredentialResults, error)
    39  	CredentialContents(credentialArgs params.CloudCredentialArgs) (params.CredentialContentResults, error)
    40  	DefaultCloud() (params.StringResult, error)
    41  	ModifyCloudAccess(args params.ModifyCloudAccessRequest) (params.ErrorResults, error)
    42  	RevokeCredentialsCheckModels(args params.RevokeCredentialArgs) (params.ErrorResults, error)
    43  	UpdateCredentialsCheckModels(args params.UpdateCredentialArgs) (params.UpdateCredentialResults, error)
    44  	UserCredentials(args params.UserClouds) (params.StringsResults, error)
    45  }
    46  
    47  // CloudV2 defines the methods on the cloud API facade, version 2.
    48  type CloudV2 interface {
    49  	AddCloud(cloudArgs params.AddCloudArgs) error
    50  	AddCredentials(args params.TaggedCredentials) (params.ErrorResults, error)
    51  	Cloud(args params.Entities) (params.CloudResults, error)
    52  	Clouds() (params.CloudsResult, error)
    53  	Credential(args params.Entities) (params.CloudCredentialResults, error)
    54  	CredentialContents(credentialArgs params.CloudCredentialArgs) (params.CredentialContentResults, error)
    55  	DefaultCloud() (params.StringResult, error)
    56  	RemoveClouds(args params.Entities) (params.ErrorResults, error)
    57  	RevokeCredentials(args params.Entities) (params.ErrorResults, error)
    58  	UpdateCredentials(args params.TaggedCredentials) (params.ErrorResults, error)
    59  	UserCredentials(args params.UserClouds) (params.StringsResults, error)
    60  }
    61  
    62  // CloudV1 defines the methods on the cloud API facade, version 1.
    63  type CloudV1 interface {
    64  	Cloud(args params.Entities) (params.CloudResults, error)
    65  	Clouds() (params.CloudsResult, error)
    66  	Credential(args params.Entities) (params.CloudCredentialResults, error)
    67  	DefaultCloud() (params.StringResult, error)
    68  	RevokeCredentials(args params.Entities) (params.ErrorResults, error)
    69  	UpdateCredentials(args params.TaggedCredentials) (params.ErrorResults, error)
    70  	UserCredentials(args params.UserClouds) (params.StringsResults, error)
    71  }
    72  
    73  // CloudAPI implements the cloud interface and is the concrete implementation
    74  // of the api end point.
    75  type CloudAPI struct {
    76  	backend                Backend
    77  	ctlrBackend            Backend
    78  	authorizer             facade.Authorizer
    79  	apiUser                names.UserTag
    80  	getCredentialsAuthFunc common.GetAuthFunc
    81  	callContext            environscontext.ProviderCallContext
    82  	pool                   ModelPoolBackend
    83  }
    84  
    85  // CloudAPIV2 provides a way to wrap the different calls
    86  // between version 2 and version 3 of the cloud API.
    87  type CloudAPIV2 struct {
    88  	*CloudAPI
    89  }
    90  
    91  // CloudAPIV1 provides a way to wrap the different calls
    92  // between version 1 and version 2 of the cloud API.
    93  type CloudAPIV1 struct {
    94  	*CloudAPIV2
    95  }
    96  
    97  var (
    98  	_ CloudV3 = (*CloudAPI)(nil)
    99  	_ CloudV2 = (*CloudAPIV2)(nil)
   100  	_ CloudV1 = (*CloudAPIV1)(nil)
   101  )
   102  
   103  // NewFacadeV3 is used for API registration.
   104  func NewFacadeV3(context facade.Context) (*CloudAPI, error) {
   105  	st := NewStateBackend(context.State())
   106  	pool := NewModelPoolBackend(context.StatePool())
   107  	ctlrSt := NewStateBackend(pool.SystemState())
   108  	return NewCloudAPI(st, ctlrSt, pool, context.Auth(), state.CallContext(context.State()))
   109  }
   110  
   111  // NewFacadeV2 is used for API registration.
   112  func NewFacadeV2(context facade.Context) (*CloudAPIV2, error) {
   113  	v3, err := NewFacadeV3(context)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  	return &CloudAPIV2{v3}, nil
   118  }
   119  
   120  // NewFacadeV1 is used for API registration.
   121  func NewFacadeV1(context facade.Context) (*CloudAPIV1, error) {
   122  	v2, err := NewFacadeV2(context)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	return &CloudAPIV1{v2}, nil
   127  }
   128  
   129  // NewCloudAPI creates a new API server endpoint for managing the controller's
   130  // cloud definition and cloud credentials.
   131  func NewCloudAPI(backend, ctlrBackend Backend, pool ModelPoolBackend, authorizer facade.Authorizer, callCtx environscontext.ProviderCallContext) (*CloudAPI, error) {
   132  	if !authorizer.AuthClient() {
   133  		return nil, common.ErrPerm
   134  	}
   135  
   136  	authUser, _ := authorizer.GetAuthTag().(names.UserTag)
   137  	getUserAuthFunc := func() (common.AuthFunc, error) {
   138  		isAdmin, err := authorizer.HasPermission(permission.SuperuserAccess, backend.ControllerTag())
   139  		if err != nil && !errors.IsNotFound(err) {
   140  			return nil, err
   141  		}
   142  		return func(tag names.Tag) bool {
   143  			userTag, ok := tag.(names.UserTag)
   144  			if !ok {
   145  				return false
   146  			}
   147  			return isAdmin || userTag == authUser
   148  		}, nil
   149  	}
   150  	return &CloudAPI{
   151  		backend:                backend,
   152  		ctlrBackend:            ctlrBackend,
   153  		authorizer:             authorizer,
   154  		getCredentialsAuthFunc: getUserAuthFunc,
   155  		apiUser:                authUser,
   156  		callContext:            callCtx,
   157  		pool:                   pool,
   158  	}, nil
   159  }
   160  
   161  func (api *CloudAPI) canAccessCloud(cloud string, user names.UserTag, access permission.Access) (bool, error) {
   162  	perm, err := api.ctlrBackend.GetCloudAccess(cloud, user)
   163  	if errors.IsNotFound(err) {
   164  		return false, nil
   165  	}
   166  	if err != nil {
   167  		return false, errors.Trace(err)
   168  	}
   169  	return perm.EqualOrGreaterCloudAccessThan(access), nil
   170  }
   171  
   172  // Clouds returns the definitions of all clouds supported by the controller
   173  // that the logged in user can see.
   174  func (api *CloudAPI) Clouds() (params.CloudsResult, error) {
   175  	var result params.CloudsResult
   176  	clouds, err := api.backend.Clouds()
   177  	if err != nil {
   178  		return result, err
   179  	}
   180  	isAdmin, err := api.authorizer.HasPermission(permission.SuperuserAccess, api.ctlrBackend.ControllerTag())
   181  	if err != nil && !errors.IsNotFound(err) {
   182  		return result, errors.Trace(err)
   183  	}
   184  	result.Clouds = make(map[string]params.Cloud)
   185  	for tag, aCloud := range clouds {
   186  		// Ensure user has permission to see the cloud.
   187  		if !isAdmin {
   188  			canAccess, err := api.canAccessCloud(tag.Id(), api.apiUser, permission.AddModelAccess)
   189  			if err != nil {
   190  				return result, err
   191  			}
   192  			if !canAccess {
   193  				continue
   194  			}
   195  		}
   196  		paramsCloud := common.CloudToParams(aCloud)
   197  		result.Clouds[tag.String()] = paramsCloud
   198  	}
   199  	return result, nil
   200  }
   201  
   202  // Cloud returns the cloud definitions for the specified clouds.
   203  func (api *CloudAPI) Cloud(args params.Entities) (params.CloudResults, error) {
   204  	results := params.CloudResults{
   205  		Results: make([]params.CloudResult, len(args.Entities)),
   206  	}
   207  	isAdmin, err := api.authorizer.HasPermission(permission.SuperuserAccess, api.ctlrBackend.ControllerTag())
   208  	if err != nil && !errors.IsNotFound(err) {
   209  		return results, errors.Trace(err)
   210  	}
   211  	one := func(arg params.Entity) (*params.Cloud, error) {
   212  		tag, err := names.ParseCloudTag(arg.Tag)
   213  		if err != nil {
   214  			return nil, err
   215  		}
   216  		// Ensure user has permission to see the cloud.
   217  		if !isAdmin {
   218  			canAccess, err := api.canAccessCloud(tag.Id(), api.apiUser, permission.AddModelAccess)
   219  			if err != nil {
   220  				return nil, err
   221  			}
   222  			if !canAccess {
   223  				return nil, errors.NotFoundf("cloud %q", tag.Id())
   224  			}
   225  		}
   226  		aCloud, err := api.backend.Cloud(tag.Id())
   227  		if err != nil {
   228  			return nil, err
   229  		}
   230  		paramsCloud := common.CloudToParams(aCloud)
   231  		return &paramsCloud, nil
   232  	}
   233  	for i, arg := range args.Entities {
   234  		aCloud, err := one(arg)
   235  		if err != nil {
   236  			results.Results[i].Error = common.ServerError(err)
   237  		} else {
   238  			results.Results[i].Cloud = aCloud
   239  		}
   240  	}
   241  	return results, nil
   242  }
   243  
   244  // CloudInfo returns information about the specified clouds.
   245  func (api *CloudAPI) CloudInfo(args params.Entities) (params.CloudInfoResults, error) {
   246  	results := params.CloudInfoResults{
   247  		Results: make([]params.CloudInfoResult, len(args.Entities)),
   248  	}
   249  
   250  	oneCloudInfo := func(arg params.Entity) (*params.CloudInfo, error) {
   251  		tag, err := names.ParseCloudTag(arg.Tag)
   252  		if err != nil {
   253  			return nil, errors.Trace(err)
   254  		}
   255  		return api.getCloudInfo(tag)
   256  	}
   257  
   258  	for i, arg := range args.Entities {
   259  		cloudInfo, err := oneCloudInfo(arg)
   260  		if err != nil {
   261  			results.Results[i].Error = common.ServerError(err)
   262  			continue
   263  		}
   264  		results.Results[i].Result = cloudInfo
   265  	}
   266  	return results, nil
   267  }
   268  
   269  func cloudToParams(cloud cloud.Cloud) params.CloudDetails {
   270  	authTypes := make([]string, len(cloud.AuthTypes))
   271  	for i, authType := range cloud.AuthTypes {
   272  		authTypes[i] = string(authType)
   273  	}
   274  	regions := make([]params.CloudRegion, len(cloud.Regions))
   275  	for i, region := range cloud.Regions {
   276  		regions[i] = params.CloudRegion{
   277  			Name:             region.Name,
   278  			Endpoint:         region.Endpoint,
   279  			IdentityEndpoint: region.IdentityEndpoint,
   280  			StorageEndpoint:  region.StorageEndpoint,
   281  		}
   282  	}
   283  	return params.CloudDetails{
   284  		Type:             cloud.Type,
   285  		AuthTypes:        authTypes,
   286  		Endpoint:         cloud.Endpoint,
   287  		IdentityEndpoint: cloud.IdentityEndpoint,
   288  		StorageEndpoint:  cloud.StorageEndpoint,
   289  		Regions:          regions,
   290  	}
   291  }
   292  
   293  func (api *CloudAPI) getCloudInfo(tag names.CloudTag) (*params.CloudInfo, error) {
   294  	isAdmin, err := api.authorizer.HasPermission(permission.SuperuserAccess, api.ctlrBackend.ControllerTag())
   295  	if err != nil && !errors.IsNotFound(err) {
   296  		return nil, errors.Trace(err)
   297  	}
   298  	// If not a controller admin, check for cloud admin.
   299  	if !isAdmin {
   300  		perm, err := api.ctlrBackend.GetCloudAccess(tag.Id(), api.apiUser)
   301  		if err != nil && !errors.IsNotFound(err) {
   302  			return nil, errors.Trace(err)
   303  		}
   304  		isAdmin = perm == permission.AdminAccess
   305  	}
   306  
   307  	aCloud, err := api.backend.Cloud(tag.Id())
   308  	if err != nil {
   309  		return nil, errors.Trace(err)
   310  	}
   311  	info := params.CloudInfo{
   312  		CloudDetails: cloudToParams(aCloud),
   313  	}
   314  
   315  	cloudUsers, err := api.ctlrBackend.GetCloudUsers(tag.Id())
   316  	if err != nil {
   317  		return nil, errors.Trace(err)
   318  	}
   319  	for userId, perm := range cloudUsers {
   320  		if !isAdmin && api.apiUser.Id() != userId {
   321  			// The authenticated user is neither the a controller
   322  			// superuser, a cloud administrator, nor a cloud user, so
   323  			// has no business knowing about the cloud user.
   324  			continue
   325  		}
   326  		userTag := names.NewUserTag(userId)
   327  		displayName := userId
   328  		if userTag.IsLocal() {
   329  			u, err := api.backend.User(userTag)
   330  			if err != nil {
   331  				if _, ok := err.(state.DeletedUserError); !ok {
   332  					// We ignore deleted users for now. So if it is not a
   333  					// DeletedUserError we return the error.
   334  					return nil, errors.Trace(err)
   335  				}
   336  				continue
   337  			}
   338  			displayName = u.DisplayName()
   339  		}
   340  
   341  		userInfo := params.CloudUserInfo{
   342  			UserName:    userId,
   343  			DisplayName: displayName,
   344  			Access:      string(perm),
   345  		}
   346  
   347  		if err != nil {
   348  			return nil, errors.Trace(err)
   349  		}
   350  		info.Users = append(info.Users, userInfo)
   351  	}
   352  
   353  	if len(info.Users) == 0 {
   354  		// No users, which means the authenticated user doesn't
   355  		// have access to the cloud.
   356  		return nil, errors.Trace(common.ErrPerm)
   357  	}
   358  	return &info, nil
   359  }
   360  
   361  // ListCloudInfo returns clouds that the specified user has access to.
   362  // Controller admins (superuser) can list clouds for any user.
   363  // Other users can only ask about their own clouds.
   364  func (api *CloudAPI) ListCloudInfo(req params.ListCloudsRequest) (params.ListCloudInfoResults, error) {
   365  	result := params.ListCloudInfoResults{}
   366  
   367  	userTag, err := names.ParseUserTag(req.UserTag)
   368  	if err != nil {
   369  		return result, errors.Trace(err)
   370  	}
   371  
   372  	cloudInfos, err := api.ctlrBackend.CloudsForUser(userTag, req.All)
   373  	if err != nil {
   374  		return result, errors.Trace(err)
   375  	}
   376  
   377  	for _, ci := range cloudInfos {
   378  		info := &params.ListCloudInfo{
   379  			CloudDetails: cloudToParams(ci.Cloud),
   380  			Access:       string(ci.Access),
   381  		}
   382  		result.Results = append(result.Results, params.ListCloudInfoResult{Result: info})
   383  	}
   384  	return result, nil
   385  }
   386  
   387  // DefaultCloud returns the tag of the cloud that models will be
   388  // created in by default.
   389  func (api *CloudAPI) DefaultCloud() (params.StringResult, error) {
   390  	controllerModel, err := api.ctlrBackend.Model()
   391  	if err != nil {
   392  		return params.StringResult{}, err
   393  	}
   394  	return params.StringResult{
   395  		Result: names.NewCloudTag(controllerModel.Cloud()).String(),
   396  	}, nil
   397  }
   398  
   399  // UserCredentials returns the cloud credentials for a set of users.
   400  func (api *CloudAPI) UserCredentials(args params.UserClouds) (params.StringsResults, error) {
   401  	results := params.StringsResults{
   402  		Results: make([]params.StringsResult, len(args.UserClouds)),
   403  	}
   404  	authFunc, err := api.getCredentialsAuthFunc()
   405  	if err != nil {
   406  		return results, err
   407  	}
   408  	for i, arg := range args.UserClouds {
   409  		userTag, err := names.ParseUserTag(arg.UserTag)
   410  		if err != nil {
   411  			results.Results[i].Error = common.ServerError(err)
   412  			continue
   413  		}
   414  		if !authFunc(userTag) {
   415  			results.Results[i].Error = common.ServerError(common.ErrPerm)
   416  			continue
   417  		}
   418  		cloudTag, err := names.ParseCloudTag(arg.CloudTag)
   419  		if err != nil {
   420  			results.Results[i].Error = common.ServerError(err)
   421  			continue
   422  		}
   423  		cloudCredentials, err := api.backend.CloudCredentials(userTag, cloudTag.Id())
   424  		if err != nil {
   425  			results.Results[i].Error = common.ServerError(err)
   426  			continue
   427  		}
   428  		out := make([]string, 0, len(cloudCredentials))
   429  		for tagId := range cloudCredentials {
   430  			out = append(out, names.NewCloudCredentialTag(tagId).String())
   431  		}
   432  		results.Results[i].Result = out
   433  	}
   434  	return results, nil
   435  }
   436  
   437  // AddCredentials adds new credentials.
   438  // In contrast to UpdateCredentials() below, the new credentials can be
   439  // for a cloud that the controller does not manage (this is required
   440  // for CAAS models)
   441  func (api *CloudAPI) AddCredentials(args params.TaggedCredentials) (params.ErrorResults, error) {
   442  	results := params.ErrorResults{
   443  		Results: make([]params.ErrorResult, len(args.Credentials)),
   444  	}
   445  
   446  	authFunc, err := api.getCredentialsAuthFunc()
   447  	if err != nil {
   448  		return results, err
   449  	}
   450  	for i, arg := range args.Credentials {
   451  		tag, err := names.ParseCloudCredentialTag(arg.Tag)
   452  		if err != nil {
   453  			results.Results[i].Error = common.ServerError(err)
   454  			continue
   455  		}
   456  		// NOTE(axw) if we add ACLs for cloud credentials, we'll need
   457  		// to change this auth check.
   458  		if !authFunc(tag.Owner()) {
   459  			results.Results[i].Error = common.ServerError(common.ErrPerm)
   460  			continue
   461  		}
   462  
   463  		in := cloud.NewCredential(
   464  			cloud.AuthType(arg.Credential.AuthType),
   465  			arg.Credential.Attributes,
   466  		)
   467  		if err := api.backend.UpdateCloudCredential(tag, in); err != nil {
   468  			results.Results[i].Error = common.ServerError(err)
   469  			continue
   470  		}
   471  	}
   472  	return results, nil
   473  }
   474  
   475  // CheckCredentialsModels validates supplied cloud credentials' content against
   476  // models that currently use these credentials.
   477  // If there are any models that are using a credential and these models or their
   478  // cloud instances are not going to be accessible with corresponding credential,
   479  // there will be detailed validation errors per model.
   480  func (api *CloudAPI) CheckCredentialsModels(args params.TaggedCredentials) (params.UpdateCredentialResults, error) {
   481  	return api.commonUpdateCredentials(false, false, args)
   482  }
   483  
   484  // UpdateCredentialsCheckModels updates a set of cloud credentials' content.
   485  // If there are any models that are using a credential and these models
   486  // are not going to be visible with updated credential content,
   487  // there will be detailed validation errors per model.
   488  // Controller admins can 'force' an update of the credential
   489  // regardless of whether it is deemed valid or not.
   490  func (api *CloudAPI) UpdateCredentialsCheckModels(args params.UpdateCredentialArgs) (params.UpdateCredentialResults, error) {
   491  	return api.commonUpdateCredentials(true, args.Force, params.TaggedCredentials{args.Credentials})
   492  }
   493  
   494  func (api *CloudAPI) commonUpdateCredentials(update bool, force bool, args params.TaggedCredentials) (params.UpdateCredentialResults, error) {
   495  	if force {
   496  		// Only controller admins can ask for an update to be forced.
   497  		isControllerAdmin, err := api.authorizer.HasPermission(permission.SuperuserAccess, api.ctlrBackend.ControllerTag())
   498  		if err != nil && !errors.IsNotFound(err) {
   499  			return params.UpdateCredentialResults{}, errors.Trace(err)
   500  		}
   501  		if !isControllerAdmin {
   502  			return params.UpdateCredentialResults{}, errors.Annotatef(common.ErrBadRequest, "unexpected force specified")
   503  		}
   504  	}
   505  
   506  	authFunc, err := api.getCredentialsAuthFunc()
   507  	if err != nil {
   508  		return params.UpdateCredentialResults{}, err
   509  	}
   510  
   511  	results := make([]params.UpdateCredentialResult, len(args.Credentials))
   512  	for i, arg := range args.Credentials {
   513  		results[i].CredentialTag = arg.Tag
   514  		tag, err := names.ParseCloudCredentialTag(arg.Tag)
   515  		if err != nil {
   516  			results[i].Error = common.ServerError(err)
   517  			continue
   518  		}
   519  		// NOTE(axw) if we add ACLs for cloud credentials, we'll need
   520  		// to change this auth check.
   521  		if !authFunc(tag.Owner()) {
   522  			results[i].Error = common.ServerError(common.ErrPerm)
   523  			continue
   524  		}
   525  		in := cloud.NewCredential(
   526  			cloud.AuthType(arg.Credential.AuthType),
   527  			arg.Credential.Attributes,
   528  		)
   529  
   530  		models, err := api.credentialModels(tag)
   531  		if err != nil {
   532  			results[i].Error = common.ServerError(err)
   533  			if !force {
   534  				// Could not determine if credential has models - do not continue updating this credential...
   535  				continue
   536  			}
   537  		}
   538  
   539  		var modelsErred bool
   540  		if len(models) > 0 {
   541  			var modelsResult []params.UpdateCredentialModelResult
   542  			for uuid, name := range models {
   543  				model := params.UpdateCredentialModelResult{
   544  					ModelUUID: uuid,
   545  					ModelName: name,
   546  				}
   547  				model.Errors = api.validateCredentialForModel(uuid, tag, &in)
   548  				modelsResult = append(modelsResult, model)
   549  				if len(model.Errors) > 0 {
   550  					modelsErred = true
   551  				}
   552  			}
   553  			// since we get a map above, for consistency ensure that models are added
   554  			// sorted by model uuid.
   555  			sort.Slice(modelsResult, func(i, j int) bool {
   556  				return modelsResult[i].ModelUUID < modelsResult[j].ModelUUID
   557  			})
   558  			results[i].Models = modelsResult
   559  		}
   560  
   561  		if modelsErred {
   562  			results[i].Error = common.ServerError(errors.New("some models are no longer visible"))
   563  			if !force {
   564  				// Some models that use this credential do not like the new content, do not update the credential...
   565  				continue
   566  			}
   567  		}
   568  
   569  		if update {
   570  			if err := api.backend.UpdateCloudCredential(tag, in); err != nil {
   571  				if errors.IsNotFound(err) {
   572  					err = errors.Errorf(
   573  						"cannot update credential %q: controller does not manage cloud %q",
   574  						tag.Name(), tag.Cloud().Id())
   575  				}
   576  				results[i].Error = common.ServerError(err)
   577  			}
   578  		}
   579  	}
   580  	return params.UpdateCredentialResults{results}, nil
   581  }
   582  
   583  func (api *CloudAPI) credentialModels(tag names.CloudCredentialTag) (map[string]string, error) {
   584  	models, err := api.backend.CredentialModels(tag)
   585  	if err != nil && !errors.IsNotFound(err) {
   586  		return nil, errors.Trace(err)
   587  	}
   588  	return models, nil
   589  }
   590  
   591  func (api *CloudAPI) validateCredentialForModel(modelUUID string, tag names.CloudCredentialTag, credential *cloud.Credential) []params.ErrorResult {
   592  	var result []params.ErrorResult
   593  
   594  	modelState, err := api.pool.Get(modelUUID)
   595  	if err != nil {
   596  		return append(result, params.ErrorResult{common.ServerError(err)})
   597  	}
   598  	defer modelState.Release()
   599  
   600  	modelErrors, err := validateNewCredentialForModelFunc(
   601  		modelState.Model(),
   602  		api.callContext,
   603  		tag,
   604  		credential,
   605  	)
   606  	if err != nil {
   607  		return append(result, params.ErrorResult{common.ServerError(err)})
   608  	}
   609  	if len(modelErrors.Results) > 0 {
   610  		return append(result, modelErrors.Results...)
   611  	}
   612  	return result
   613  }
   614  
   615  var validateNewCredentialForModelFunc = credentialcommon.ValidateNewModelCredential
   616  
   617  // Mask out old methods from the new API versions. The API reflection
   618  // code in rpc/rpcreflect/type.go:newMethod skips 2-argument methods,
   619  // so this removes the method as far as the RPC machinery is concerned.
   620  // UpdateCredentials was dropped in V3, replaced with UpdateCredentialsCheckModels.
   621  func (*CloudAPI) UpdateCredentials(_, _ struct{}) {}
   622  
   623  // Mask out old methods from the new API versions. The API reflection
   624  // code in rpc/rpcreflect/type.go:newMethod skips 2-argument methods,
   625  // so this removes the method as far as the RPC machinery is concerned.
   626  //
   627  // CheckCredentialsModels did not exist before V3.
   628  func (*CloudAPIV2) CheckCredentialsModels(_, _ struct{}) {}
   629  
   630  // UpdateCredentials updates a set of cloud credentials' content.
   631  func (api *CloudAPIV2) UpdateCredentials(args params.TaggedCredentials) (params.ErrorResults, error) {
   632  	results := params.ErrorResults{
   633  		Results: make([]params.ErrorResult, len(args.Credentials)),
   634  	}
   635  
   636  	updateResults, err := api.commonUpdateCredentials(true, false, args)
   637  	if err != nil {
   638  		return results, err
   639  	}
   640  
   641  	// If there are any models that are using a credential and these models
   642  	// are not going to be visible with updated credential content,
   643  	// there will be detailed validation errors per model.
   644  	// However, old return parameter structure could not hold this much detail and,
   645  	// thus, per-model-per-credential errors are squashed into per-credential errors.
   646  	for i, result := range updateResults.Results {
   647  		var resultErrors []params.ErrorResult
   648  		if result.Error != nil {
   649  			resultErrors = append(resultErrors, params.ErrorResult{result.Error})
   650  		}
   651  		for _, m := range result.Models {
   652  			if len(m.Errors) > 0 {
   653  				modelErors := params.ErrorResults{m.Errors}
   654  				combined := errors.Annotatef(modelErors.Combine(), "model %q (uuid %v)", m.ModelName, m.ModelUUID)
   655  				resultErrors = append(resultErrors, params.ErrorResult{common.ServerError(combined)})
   656  			}
   657  		}
   658  		if len(resultErrors) == 1 {
   659  			results.Results[i].Error = resultErrors[0].Error
   660  			continue
   661  		}
   662  		if len(resultErrors) > 1 {
   663  			credentialError := params.ErrorResults{resultErrors}
   664  			results.Results[i].Error = common.ServerError(credentialError.Combine())
   665  		}
   666  	}
   667  	return results, nil
   668  }
   669  
   670  // Mask out old methods from the new API versions. The API reflection
   671  // code in rpc/rpcreflect/type.go:newMethod skips 2-argument methods,
   672  // so this removes the method as far as the RPC machinery is concerned.
   673  //
   674  // RevokeCredentials was dropped in V3, replaced with RevokeCredentialsCheckModel.
   675  func (*CloudAPI) RevokeCredentials(_, _ struct{}) {}
   676  
   677  // UpdateCredentials updates a set of cloud credentials' content.
   678  func (api *CloudAPIV2) RevokeCredentials(args params.Entities) (params.ErrorResults, error) {
   679  	results := params.ErrorResults{
   680  		Results: make([]params.ErrorResult, len(args.Entities)),
   681  	}
   682  	authFunc, err := api.getCredentialsAuthFunc()
   683  	if err != nil {
   684  		return results, err
   685  	}
   686  	for i, arg := range args.Entities {
   687  		tag, err := names.ParseCloudCredentialTag(arg.Tag)
   688  		if err != nil {
   689  			results.Results[i].Error = common.ServerError(err)
   690  			continue
   691  		}
   692  		// NOTE(axw) if we add ACLs for cloud credentials, we'll need
   693  		// to change this auth check.
   694  		if !authFunc(tag.Owner()) {
   695  			results.Results[i].Error = common.ServerError(common.ErrPerm)
   696  			continue
   697  		}
   698  
   699  		models, err := api.credentialModels(tag)
   700  		if err != nil {
   701  			logger.Warningf("could not get models that use credential %v: %v", tag, err)
   702  		}
   703  		if len(models) > 0 {
   704  			// For backward compatibility, we must proceed here regardless of whether the credential is used by any models,
   705  			// but, at least, let's log it.
   706  			logger.Warningf("credential %v will be deleted but it is still used by model%v", tag, modelsPretty(models))
   707  		}
   708  
   709  		if err := api.backend.RemoveCloudCredential(tag); err != nil {
   710  			results.Results[i].Error = common.ServerError(err)
   711  		}
   712  	}
   713  	return results, nil
   714  }
   715  
   716  // Mask out old methods from the new API versions. The API reflection
   717  // code in rpc/rpcreflect/type.go:newMethod skips 2-argument methods,
   718  // so this removes the method as far as the RPC machinery is concerned.
   719  //
   720  // RevokeCredentialsCheckModels did not exist before V3.
   721  func (*CloudAPIV2) RevokeCredentialsCheckModels(_, _ struct{}) {}
   722  
   723  func plural(length int) string {
   724  	if length == 1 {
   725  		return ""
   726  	}
   727  	return "s"
   728  }
   729  
   730  func modelsPretty(in map[string]string) string {
   731  	// map keys are notoriously randomly ordered
   732  	uuids := []string{}
   733  	for uuid := range in {
   734  		uuids = append(uuids, uuid)
   735  	}
   736  	sort.Strings(uuids)
   737  
   738  	firstLine := ":\n- "
   739  	if len(uuids) == 1 {
   740  		firstLine = " "
   741  	}
   742  
   743  	return fmt.Sprintf("%v%v%v",
   744  		plural(len(in)),
   745  		firstLine,
   746  		strings.Join(uuids, "\n- "),
   747  	)
   748  }
   749  
   750  // RevokeCredentialsCheckModels revokes a set of cloud credentials.
   751  // If the credentials are used by any of the models, the credential deletion will be aborted.
   752  // If credential-in-use needs to be revoked nonetheless, this method allows the use of force.
   753  func (api *CloudAPI) RevokeCredentialsCheckModels(args params.RevokeCredentialArgs) (params.ErrorResults, error) {
   754  	// TODO (anastasiamac 2018-11-13) the behavior here needs to be changed to performed promised models checks and authorise use of force.
   755  	results := params.ErrorResults{
   756  		Results: make([]params.ErrorResult, len(args.Credentials)),
   757  	}
   758  	authFunc, err := api.getCredentialsAuthFunc()
   759  	if err != nil {
   760  		return results, err
   761  	}
   762  
   763  	opMessage := func(force bool) string {
   764  		if force {
   765  			return "will be deleted but"
   766  		}
   767  		return "cannot be deleted as"
   768  	}
   769  
   770  	for i, arg := range args.Credentials {
   771  		tag, err := names.ParseCloudCredentialTag(arg.Tag)
   772  		if err != nil {
   773  			results.Results[i].Error = common.ServerError(err)
   774  			continue
   775  		}
   776  		// NOTE(axw) if we add ACLs for cloud credentials, we'll need
   777  		// to change this auth check.
   778  		if !authFunc(tag.Owner()) {
   779  			results.Results[i].Error = common.ServerError(common.ErrPerm)
   780  			continue
   781  		}
   782  
   783  		models, err := api.credentialModels(tag)
   784  		if err != nil {
   785  			if !arg.Force {
   786  				// Could not determine if credential has models - do not continue updating this credential...
   787  				results.Results[i].Error = common.ServerError(err)
   788  				continue
   789  			}
   790  			logger.Warningf("could not get models that use credential %v: %v", tag, err)
   791  		}
   792  		if len(models) != 0 {
   793  			logger.Warningf("credential %v %v it is used by model%v",
   794  				tag,
   795  				opMessage(arg.Force),
   796  				modelsPretty(models),
   797  			)
   798  			if !arg.Force {
   799  				// Some models still use this credential - do not delete this credential...
   800  				results.Results[i].Error = common.ServerError(errors.Errorf("cannot delete credential %v: it is still used by %d model%v", tag, len(models), plural(len(models))))
   801  				continue
   802  			}
   803  		}
   804  
   805  		if err := api.backend.RemoveCloudCredential(tag); err != nil {
   806  			results.Results[i].Error = common.ServerError(err)
   807  		}
   808  	}
   809  	return results, nil
   810  }
   811  
   812  // Credential returns the specified cloud credential for each tag, minus secrets.
   813  func (api *CloudAPI) Credential(args params.Entities) (params.CloudCredentialResults, error) {
   814  	results := params.CloudCredentialResults{
   815  		Results: make([]params.CloudCredentialResult, len(args.Entities)),
   816  	}
   817  	authFunc, err := api.getCredentialsAuthFunc()
   818  	if err != nil {
   819  		return results, err
   820  	}
   821  
   822  	for i, arg := range args.Entities {
   823  		credentialTag, err := names.ParseCloudCredentialTag(arg.Tag)
   824  		if err != nil {
   825  			results.Results[i].Error = common.ServerError(err)
   826  			continue
   827  		}
   828  		if !authFunc(credentialTag.Owner()) {
   829  			results.Results[i].Error = common.ServerError(common.ErrPerm)
   830  			continue
   831  		}
   832  
   833  		// Helper to look up and cache credential schemas for clouds.
   834  		schemaCache := make(map[string]map[cloud.AuthType]cloud.CredentialSchema)
   835  		credentialSchemas := func() (map[cloud.AuthType]cloud.CredentialSchema, error) {
   836  			cloudName := credentialTag.Cloud().Id()
   837  			if s, ok := schemaCache[cloudName]; ok {
   838  				return s, nil
   839  			}
   840  			aCloud, err := api.backend.Cloud(cloudName)
   841  			if err != nil {
   842  				return nil, err
   843  			}
   844  			provider, err := environs.Provider(aCloud.Type)
   845  			if err != nil {
   846  				return nil, err
   847  			}
   848  			schema := provider.CredentialSchemas()
   849  			schemaCache[cloudName] = schema
   850  			return schema, nil
   851  		}
   852  		cloudCredentials, err := api.backend.CloudCredentials(credentialTag.Owner(), credentialTag.Cloud().Id())
   853  		if err != nil {
   854  			results.Results[i].Error = common.ServerError(err)
   855  			continue
   856  		}
   857  
   858  		cred, ok := cloudCredentials[credentialTag.Id()]
   859  		if !ok {
   860  			results.Results[i].Error = common.ServerError(errors.NotFoundf("credential %q", credentialTag.Name()))
   861  			continue
   862  		}
   863  
   864  		schemas, err := credentialSchemas()
   865  		if err != nil {
   866  			results.Results[i].Error = common.ServerError(err)
   867  			continue
   868  		}
   869  
   870  		attrs := cred.Attributes
   871  		var redacted []string
   872  		// Mask out the secrets.
   873  		if s, ok := schemas[cloud.AuthType(cred.AuthType)]; ok {
   874  			for _, attr := range s {
   875  				if attr.Hidden {
   876  					delete(attrs, attr.Name)
   877  					redacted = append(redacted, attr.Name)
   878  				}
   879  			}
   880  		}
   881  		results.Results[i].Result = &params.CloudCredential{
   882  			AuthType:   cred.AuthType,
   883  			Attributes: attrs,
   884  			Redacted:   redacted,
   885  		}
   886  	}
   887  	return results, nil
   888  }
   889  
   890  // AddCloud adds a new cloud, different from the one managed by the controller.
   891  func (api *CloudAPI) AddCloud(cloudArgs params.AddCloudArgs) error {
   892  	err := api.backend.AddCloud(common.CloudFromParams(cloudArgs.Name, cloudArgs.Cloud), api.apiUser.Name())
   893  	if err != nil {
   894  		return err
   895  	}
   896  	return nil
   897  }
   898  
   899  // RemoveClouds removes the specified clouds from the controller.
   900  // If a cloud is in use (has models deployed to it), the removal will fail.
   901  func (api *CloudAPI) RemoveClouds(args params.Entities) (params.ErrorResults, error) {
   902  	result := params.ErrorResults{
   903  		Results: make([]params.ErrorResult, len(args.Entities)),
   904  	}
   905  	isAdmin, err := api.authorizer.HasPermission(permission.SuperuserAccess, api.ctlrBackend.ControllerTag())
   906  	if err != nil && !errors.IsNotFound(err) {
   907  		return result, errors.Trace(err)
   908  	}
   909  	for i, entity := range args.Entities {
   910  		tag, err := names.ParseCloudTag(entity.Tag)
   911  		if err != nil {
   912  			result.Results[i].Error = common.ServerError(err)
   913  			continue
   914  		}
   915  		// Ensure user has permission to remove the cloud.
   916  		if !isAdmin {
   917  			canAccess, err := api.canAccessCloud(tag.Id(), api.apiUser, permission.AdminAccess)
   918  			if err != nil {
   919  				result.Results[i].Error = common.ServerError(err)
   920  				continue
   921  			}
   922  			if !canAccess {
   923  				result.Results[i].Error = common.ServerError(common.ErrPerm)
   924  				continue
   925  			}
   926  		}
   927  		err = api.backend.RemoveCloud(tag.Id())
   928  		result.Results[i].Error = common.ServerError(err)
   929  	}
   930  	return result, nil
   931  }
   932  
   933  // CredentialContents returns the specified cloud credentials,
   934  // including the secrets if requested.
   935  // If no specific credential name/cloud was passed in, all credentials for this user
   936  // are returned.
   937  // Only credential owner can see its contents as well as what models use it.
   938  // Controller admin has no special superpowers here and is treated the same as all other users.
   939  func (api *CloudAPI) CredentialContents(args params.CloudCredentialArgs) (params.CredentialContentResults, error) {
   940  	// Helper to look up and cache credential schemas for clouds.
   941  	schemaCache := make(map[string]map[cloud.AuthType]cloud.CredentialSchema)
   942  	credentialSchemas := func(cloudName string) (map[cloud.AuthType]cloud.CredentialSchema, error) {
   943  		if s, ok := schemaCache[cloudName]; ok {
   944  			return s, nil
   945  		}
   946  		aCloud, err := api.backend.Cloud(cloudName)
   947  		if err != nil {
   948  			return nil, err
   949  		}
   950  		provider, err := environs.Provider(aCloud.Type)
   951  		if err != nil {
   952  			return nil, err
   953  		}
   954  		schema := provider.CredentialSchemas()
   955  		schemaCache[cloudName] = schema
   956  		return schema, nil
   957  	}
   958  
   959  	// Helper to parse state.Credential into an expected result item.
   960  	stateIntoParam := func(credential state.Credential, includeSecrets bool) params.CredentialContentResult {
   961  		schemas, err := credentialSchemas(credential.Cloud)
   962  		if err != nil {
   963  			return params.CredentialContentResult{Error: common.ServerError(err)}
   964  		}
   965  		attrs := map[string]string{}
   966  		// Filter out the secrets.
   967  		if s, ok := schemas[cloud.AuthType(credential.AuthType)]; ok {
   968  			for _, attr := range s {
   969  				if value, exists := credential.Attributes[attr.Name]; exists {
   970  					if attr.Hidden && !includeSecrets {
   971  						continue
   972  					}
   973  					attrs[attr.Name] = value
   974  				}
   975  			}
   976  		}
   977  		info := params.ControllerCredentialInfo{
   978  			Content: params.CredentialContent{
   979  				Name:       credential.Name,
   980  				AuthType:   credential.AuthType,
   981  				Attributes: attrs,
   982  				Cloud:      credential.Cloud,
   983  			},
   984  		}
   985  
   986  		// get models
   987  		tag, err := credential.CloudCredentialTag()
   988  		if err != nil {
   989  			return params.CredentialContentResult{Error: common.ServerError(err)}
   990  		}
   991  
   992  		models, err := api.backend.CredentialModelsAndOwnerAccess(tag)
   993  		if err != nil && !errors.IsNotFound(err) {
   994  			return params.CredentialContentResult{Error: common.ServerError(err)}
   995  		}
   996  		info.Models = make([]params.ModelAccess, len(models))
   997  		for i, m := range models {
   998  			info.Models[i] = params.ModelAccess{m.ModelName, string(m.OwnerAccess)}
   999  		}
  1000  
  1001  		return params.CredentialContentResult{Result: &info}
  1002  	}
  1003  
  1004  	var result []params.CredentialContentResult
  1005  	if len(args.Credentials) == 0 {
  1006  		credentials, err := api.backend.AllCloudCredentials(api.apiUser)
  1007  		if err != nil {
  1008  			return params.CredentialContentResults{}, errors.Trace(err)
  1009  		}
  1010  		result = make([]params.CredentialContentResult, len(credentials))
  1011  		for i, credential := range credentials {
  1012  			result[i] = stateIntoParam(credential, args.IncludeSecrets)
  1013  		}
  1014  	} else {
  1015  		// Helper to construct credential tag from cloud and name.
  1016  		credId := func(cloudName, credentialName string) string {
  1017  			return fmt.Sprintf("%s/%s/%s",
  1018  				cloudName, api.apiUser.Id(), credentialName,
  1019  			)
  1020  		}
  1021  
  1022  		result = make([]params.CredentialContentResult, len(args.Credentials))
  1023  		for i, given := range args.Credentials {
  1024  			id := credId(given.CloudName, given.CredentialName)
  1025  			tag := names.NewCloudCredentialTag(id)
  1026  			credential, err := api.backend.CloudCredential(tag)
  1027  			if err != nil {
  1028  				result[i] = params.CredentialContentResult{
  1029  					Error: common.ServerError(err),
  1030  				}
  1031  				continue
  1032  			}
  1033  			result[i] = stateIntoParam(credential, args.IncludeSecrets)
  1034  		}
  1035  	}
  1036  	return params.CredentialContentResults{result}, nil
  1037  }
  1038  
  1039  // ModifyCloudAccess changes the model access granted to users.
  1040  func (c *CloudAPI) ModifyCloudAccess(args params.ModifyCloudAccessRequest) (params.ErrorResults, error) {
  1041  	result := params.ErrorResults{
  1042  		Results: make([]params.ErrorResult, len(args.Changes)),
  1043  	}
  1044  	if len(args.Changes) == 0 {
  1045  		return result, nil
  1046  	}
  1047  
  1048  	for i, arg := range args.Changes {
  1049  		cloudTag, err := names.ParseCloudTag(arg.CloudTag)
  1050  		if err != nil {
  1051  			result.Results[i].Error = common.ServerError(err)
  1052  			continue
  1053  		}
  1054  		_, err = c.backend.Cloud(cloudTag.Id())
  1055  		if err != nil {
  1056  			result.Results[i].Error = common.ServerError(err)
  1057  			continue
  1058  		}
  1059  		if c.apiUser.String() == arg.UserTag {
  1060  			result.Results[i].Error = common.ServerError(errors.New("cannot change your own cloud access"))
  1061  			continue
  1062  		}
  1063  
  1064  		isAdmin, err := c.authorizer.HasPermission(permission.SuperuserAccess, c.backend.ControllerTag())
  1065  		if err != nil {
  1066  			result.Results[i].Error = common.ServerError(err)
  1067  			continue
  1068  		}
  1069  		if !isAdmin {
  1070  			callerAccess, err := c.backend.GetCloudAccess(cloudTag.Id(), c.apiUser)
  1071  			if err != nil {
  1072  				result.Results[i].Error = common.ServerError(err)
  1073  				continue
  1074  			}
  1075  			if callerAccess != permission.AdminAccess {
  1076  				result.Results[i].Error = common.ServerError(common.ErrPerm)
  1077  				continue
  1078  			}
  1079  		}
  1080  
  1081  		cloudAccess := permission.Access(arg.Access)
  1082  		if err := permission.ValidateCloudAccess(cloudAccess); err != nil {
  1083  			result.Results[i].Error = common.ServerError(err)
  1084  			continue
  1085  		}
  1086  
  1087  		targetUserTag, err := names.ParseUserTag(arg.UserTag)
  1088  		if err != nil {
  1089  			result.Results[i].Error = common.ServerError(errors.Annotate(err, "could not modify cloud access"))
  1090  			continue
  1091  		}
  1092  
  1093  		result.Results[i].Error = common.ServerError(
  1094  			ChangeCloudAccess(c.backend, cloudTag.Id(), targetUserTag, arg.Action, cloudAccess))
  1095  	}
  1096  	return result, nil
  1097  }
  1098  
  1099  // ChangeCloudAccess performs the requested access grant or revoke action for the
  1100  // specified user on the cloud.
  1101  func ChangeCloudAccess(backend Backend, cloud string, targetUserTag names.UserTag, action params.CloudAction, access permission.Access) error {
  1102  	switch action {
  1103  	case params.GrantCloudAccess:
  1104  		err := grantCloudAccess(backend, cloud, targetUserTag, access)
  1105  		if err != nil {
  1106  			return errors.Annotate(err, "could not grant cloud access")
  1107  		}
  1108  		return nil
  1109  	case params.RevokeCloudAccess:
  1110  		return revokeCloudAccess(backend, cloud, targetUserTag, access)
  1111  	default:
  1112  		return errors.Errorf("unknown action %q", action)
  1113  	}
  1114  }
  1115  
  1116  func grantCloudAccess(backend Backend, cloud string, targetUserTag names.UserTag, access permission.Access) error {
  1117  	err := backend.CreateCloudAccess(cloud, targetUserTag, access)
  1118  	if errors.IsAlreadyExists(err) {
  1119  		cloudAccess, err := backend.GetCloudAccess(cloud, targetUserTag)
  1120  		if errors.IsNotFound(err) {
  1121  			// Conflicts with prior check, must be inconsistent state.
  1122  			err = txn.ErrExcessiveContention
  1123  		}
  1124  		if err != nil {
  1125  			return errors.Annotate(err, "could not look up cloud access for user")
  1126  		}
  1127  
  1128  		// Only set access if greater access is being granted.
  1129  		if cloudAccess.EqualOrGreaterCloudAccessThan(access) {
  1130  			return errors.Errorf("user already has %q access or greater", access)
  1131  		}
  1132  		if err = backend.UpdateCloudAccess(cloud, targetUserTag, access); err != nil {
  1133  			return errors.Annotate(err, "could not set cloud access for user")
  1134  		}
  1135  		return nil
  1136  
  1137  	}
  1138  	if err != nil {
  1139  		return errors.Trace(err)
  1140  	}
  1141  	return nil
  1142  }
  1143  
  1144  func revokeCloudAccess(backend Backend, cloud string, targetUserTag names.UserTag, access permission.Access) error {
  1145  	switch access {
  1146  	case permission.AddModelAccess:
  1147  		// Revoking add-model access removes all access.
  1148  		err := backend.RemoveCloudAccess(cloud, targetUserTag)
  1149  		return errors.Annotate(err, "could not revoke cloud access")
  1150  	case permission.AdminAccess:
  1151  		// Revoking admin sets add-model.
  1152  		err := backend.UpdateCloudAccess(cloud, targetUserTag, permission.AddModelAccess)
  1153  		return errors.Annotate(err, "could not set cloud access to add-model")
  1154  
  1155  	default:
  1156  		return errors.Errorf("don't know how to revoke %q access", access)
  1157  	}
  1158  }