github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/apiserver/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  	"github.com/juju/errors"
    10  	"gopkg.in/juju/names.v2"
    11  
    12  	"github.com/juju/juju/apiserver/common"
    13  	"github.com/juju/juju/apiserver/facade"
    14  	"github.com/juju/juju/apiserver/params"
    15  	"github.com/juju/juju/cloud"
    16  	"github.com/juju/juju/environs"
    17  	"github.com/juju/juju/permission"
    18  	"github.com/juju/juju/state"
    19  )
    20  
    21  func init() {
    22  	common.RegisterStandardFacade("Cloud", 1, newFacade)
    23  }
    24  
    25  // CloudAPI implements the model manager interface and is
    26  // the concrete implementation of the api end point.
    27  type CloudAPI struct {
    28  	backend                Backend
    29  	authorizer             facade.Authorizer
    30  	apiUser                names.UserTag
    31  	getCredentialsAuthFunc common.GetAuthFunc
    32  }
    33  
    34  func newFacade(st *state.State, _ facade.Resources, auth facade.Authorizer) (*CloudAPI, error) {
    35  	return NewCloudAPI(NewStateBackend(st), auth)
    36  }
    37  
    38  // NewCloudAPI creates a new API server endpoint for managing the controller's
    39  // cloud definition and cloud credentials.
    40  func NewCloudAPI(backend Backend, authorizer facade.Authorizer) (*CloudAPI, error) {
    41  
    42  	if !authorizer.AuthClient() {
    43  		return nil, common.ErrPerm
    44  	}
    45  
    46  	getUserAuthFunc := func() (common.AuthFunc, error) {
    47  		authUser, _ := authorizer.GetAuthTag().(names.UserTag)
    48  		isAdmin, err := authorizer.HasPermission(permission.SuperuserAccess, backend.ControllerTag())
    49  		if err != nil && !errors.IsNotFound(err) {
    50  			return nil, err
    51  		}
    52  		return func(tag names.Tag) bool {
    53  			userTag, ok := tag.(names.UserTag)
    54  			if !ok {
    55  				return false
    56  			}
    57  			return isAdmin || userTag.Canonical() == authUser.Canonical()
    58  		}, nil
    59  	}
    60  	return &CloudAPI{
    61  		backend:                backend,
    62  		authorizer:             authorizer,
    63  		getCredentialsAuthFunc: getUserAuthFunc,
    64  	}, nil
    65  }
    66  
    67  // Clouds returns the definitions of all clouds supported by the controller.
    68  func (api *CloudAPI) Clouds() (params.CloudsResult, error) {
    69  	var result params.CloudsResult
    70  	clouds, err := api.backend.Clouds()
    71  	if err != nil {
    72  		return result, err
    73  	}
    74  	result.Clouds = make(map[string]params.Cloud)
    75  	for tag, cloud := range clouds {
    76  		paramsCloud := cloudToParams(cloud)
    77  		result.Clouds[tag.String()] = paramsCloud
    78  	}
    79  	return result, nil
    80  }
    81  
    82  // Cloud returns the cloud definitions for the specified clouds.
    83  func (api *CloudAPI) Cloud(args params.Entities) (params.CloudResults, error) {
    84  	results := params.CloudResults{
    85  		Results: make([]params.CloudResult, len(args.Entities)),
    86  	}
    87  	one := func(arg params.Entity) (*params.Cloud, error) {
    88  		tag, err := names.ParseCloudTag(arg.Tag)
    89  		if err != nil {
    90  			return nil, err
    91  		}
    92  		cloud, err := api.backend.Cloud(tag.Id())
    93  		if err != nil {
    94  			return nil, err
    95  		}
    96  		paramsCloud := cloudToParams(cloud)
    97  		return &paramsCloud, nil
    98  	}
    99  	for i, arg := range args.Entities {
   100  		cloud, err := one(arg)
   101  		if err != nil {
   102  			results.Results[i].Error = common.ServerError(err)
   103  		} else {
   104  			results.Results[i].Cloud = cloud
   105  		}
   106  	}
   107  	return results, nil
   108  }
   109  
   110  func cloudToParams(cloud cloud.Cloud) params.Cloud {
   111  	authTypes := make([]string, len(cloud.AuthTypes))
   112  	for i, authType := range cloud.AuthTypes {
   113  		authTypes[i] = string(authType)
   114  	}
   115  	regions := make([]params.CloudRegion, len(cloud.Regions))
   116  	for i, region := range cloud.Regions {
   117  		regions[i] = params.CloudRegion{
   118  			Name:             region.Name,
   119  			Endpoint:         region.Endpoint,
   120  			IdentityEndpoint: region.IdentityEndpoint,
   121  			StorageEndpoint:  region.StorageEndpoint,
   122  		}
   123  	}
   124  	return params.Cloud{
   125  		Type:             cloud.Type,
   126  		AuthTypes:        authTypes,
   127  		Endpoint:         cloud.Endpoint,
   128  		IdentityEndpoint: cloud.IdentityEndpoint,
   129  		StorageEndpoint:  cloud.StorageEndpoint,
   130  		Regions:          regions,
   131  	}
   132  }
   133  
   134  // DefaultCloud returns the tag of the cloud that models will be
   135  // created in by default.
   136  func (api *CloudAPI) DefaultCloud() (params.StringResult, error) {
   137  	controllerModel, err := api.backend.ControllerModel()
   138  	if err != nil {
   139  		return params.StringResult{}, err
   140  	}
   141  
   142  	return params.StringResult{
   143  		Result: names.NewCloudTag(controllerModel.Cloud()).String(),
   144  	}, nil
   145  }
   146  
   147  // UserCredentials returns the cloud credentials for a set of users.
   148  func (api *CloudAPI) UserCredentials(args params.UserClouds) (params.StringsResults, error) {
   149  	results := params.StringsResults{
   150  		Results: make([]params.StringsResult, len(args.UserClouds)),
   151  	}
   152  	authFunc, err := api.getCredentialsAuthFunc()
   153  	if err != nil {
   154  		return results, err
   155  	}
   156  	for i, arg := range args.UserClouds {
   157  		userTag, err := names.ParseUserTag(arg.UserTag)
   158  		if err != nil {
   159  			results.Results[i].Error = common.ServerError(err)
   160  			continue
   161  		}
   162  		if !authFunc(userTag) {
   163  			results.Results[i].Error = common.ServerError(common.ErrPerm)
   164  			continue
   165  		}
   166  		cloudTag, err := names.ParseCloudTag(arg.CloudTag)
   167  		if err != nil {
   168  			results.Results[i].Error = common.ServerError(err)
   169  			continue
   170  		}
   171  		cloudCredentials, err := api.backend.CloudCredentials(userTag, cloudTag.Id())
   172  		if err != nil {
   173  			results.Results[i].Error = common.ServerError(err)
   174  			continue
   175  		}
   176  		out := make([]string, 0, len(cloudCredentials))
   177  		for tagId := range cloudCredentials {
   178  			out = append(out, names.NewCloudCredentialTag(tagId).String())
   179  		}
   180  		results.Results[i].Result = out
   181  	}
   182  	return results, nil
   183  }
   184  
   185  // UpdateCredentials updates a set of cloud credentials.
   186  func (api *CloudAPI) UpdateCredentials(args params.UpdateCloudCredentials) (params.ErrorResults, error) {
   187  	results := params.ErrorResults{
   188  		Results: make([]params.ErrorResult, len(args.Credentials)),
   189  	}
   190  	authFunc, err := api.getCredentialsAuthFunc()
   191  	if err != nil {
   192  		return results, err
   193  	}
   194  	for i, arg := range args.Credentials {
   195  		tag, err := names.ParseCloudCredentialTag(arg.Tag)
   196  		if err != nil {
   197  			results.Results[i].Error = common.ServerError(err)
   198  			continue
   199  		}
   200  		// NOTE(axw) if we add ACLs for cloud credentials, we'll need
   201  		// to change this auth check.
   202  		if !authFunc(tag.Owner()) {
   203  			results.Results[i].Error = common.ServerError(common.ErrPerm)
   204  			continue
   205  		}
   206  		in := cloud.NewCredential(
   207  			cloud.AuthType(arg.Credential.AuthType),
   208  			arg.Credential.Attributes,
   209  		)
   210  		if err := api.backend.UpdateCloudCredential(tag, in); err != nil {
   211  			results.Results[i].Error = common.ServerError(err)
   212  			continue
   213  		}
   214  	}
   215  	return results, nil
   216  }
   217  
   218  // RevokeCredentials revokes a set of cloud credentials.
   219  func (api *CloudAPI) RevokeCredentials(args params.Entities) (params.ErrorResults, error) {
   220  	results := params.ErrorResults{
   221  		Results: make([]params.ErrorResult, len(args.Entities)),
   222  	}
   223  	authFunc, err := api.getCredentialsAuthFunc()
   224  	if err != nil {
   225  		return results, err
   226  	}
   227  	for i, arg := range args.Entities {
   228  		tag, err := names.ParseCloudCredentialTag(arg.Tag)
   229  		if err != nil {
   230  			results.Results[i].Error = common.ServerError(err)
   231  			continue
   232  		}
   233  		// NOTE(axw) if we add ACLs for cloud credentials, we'll need
   234  		// to change this auth check.
   235  		if !authFunc(tag.Owner()) {
   236  			results.Results[i].Error = common.ServerError(common.ErrPerm)
   237  			continue
   238  		}
   239  		if err := api.backend.RemoveCloudCredential(tag); err != nil {
   240  			results.Results[i].Error = common.ServerError(err)
   241  		}
   242  	}
   243  	return results, nil
   244  }
   245  
   246  // Credential returns the specified cloud credential for each tag, minus secrets.
   247  func (api *CloudAPI) Credential(args params.Entities) (params.CloudCredentialResults, error) {
   248  	results := params.CloudCredentialResults{
   249  		Results: make([]params.CloudCredentialResult, len(args.Entities)),
   250  	}
   251  	authFunc, err := api.getCredentialsAuthFunc()
   252  	if err != nil {
   253  		return results, err
   254  	}
   255  
   256  	for i, arg := range args.Entities {
   257  		credentialTag, err := names.ParseCloudCredentialTag(arg.Tag)
   258  		if err != nil {
   259  			results.Results[i].Error = common.ServerError(err)
   260  			continue
   261  		}
   262  		if !authFunc(credentialTag.Owner()) {
   263  			results.Results[i].Error = common.ServerError(common.ErrPerm)
   264  			continue
   265  		}
   266  
   267  		// Helper to look up and cache credential schemas for clouds.
   268  		schemaCache := make(map[string]map[cloud.AuthType]cloud.CredentialSchema)
   269  		credentialSchemas := func() (map[cloud.AuthType]cloud.CredentialSchema, error) {
   270  			cloudName := credentialTag.Cloud().Id()
   271  			if s, ok := schemaCache[cloudName]; ok {
   272  				return s, nil
   273  			}
   274  			cloud, err := api.backend.Cloud(cloudName)
   275  			if err != nil {
   276  				return nil, err
   277  			}
   278  			provider, err := environs.Provider(cloud.Type)
   279  			if err != nil {
   280  				return nil, err
   281  			}
   282  			schema := provider.CredentialSchemas()
   283  			schemaCache[cloudName] = schema
   284  			return schema, nil
   285  		}
   286  		cloudCredentials, err := api.backend.CloudCredentials(credentialTag.Owner(), credentialTag.Cloud().Id())
   287  		if err != nil {
   288  			results.Results[i].Error = common.ServerError(err)
   289  			continue
   290  		}
   291  
   292  		cred, ok := cloudCredentials[credentialTag.Canonical()]
   293  		if !ok {
   294  			results.Results[i].Error = common.ServerError(errors.NotFoundf("credential %q", credentialTag.Name()))
   295  			continue
   296  		}
   297  
   298  		schemas, err := credentialSchemas()
   299  		if err != nil {
   300  			results.Results[i].Error = common.ServerError(err)
   301  			continue
   302  		}
   303  
   304  		attrs := cred.Attributes()
   305  		var redacted []string
   306  		// Mask out the secrets.
   307  		if s, ok := schemas[cred.AuthType()]; ok {
   308  			for _, attr := range s {
   309  				if attr.Hidden {
   310  					delete(attrs, attr.Name)
   311  					redacted = append(redacted, attr.Name)
   312  				}
   313  			}
   314  		}
   315  		results.Results[i].Result = &params.CloudCredential{
   316  			AuthType:   string(cred.AuthType()),
   317  			Attributes: attrs,
   318  			Redacted:   redacted,
   319  		}
   320  	}
   321  	return results, nil
   322  }