yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/google/iampolicy.go (about)

     1  // Copyright 2019 Yunion
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Copyright 2019 Yunion
    16  
    17  //
    18  // Licensed under the Apache License, Version 2.0 (the "License");
    19  // you may not use this file except in compliance with the License.
    20  // You may obtain a copy of the License at
    21  //
    22  //     http://www.apache.org/licenses/LICENSE-2.0
    23  //
    24  // Unless required by applicable law or agreed to in writing, software
    25  // distributed under the License is distributed on an "AS IS" BASIS,
    26  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    27  // See the License for the specific language governing permissions and
    28  // limitations under the License.
    29  
    30  package google
    31  
    32  import (
    33  	"fmt"
    34  	"strings"
    35  
    36  	"yunion.io/x/jsonutils"
    37  	"yunion.io/x/pkg/errors"
    38  	"yunion.io/x/pkg/util/stringutils"
    39  	"yunion.io/x/pkg/utils"
    40  
    41  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    42  	"yunion.io/x/cloudmux/pkg/multicloud"
    43  )
    44  
    45  type SBinding struct {
    46  	Role    string
    47  	Members []string
    48  }
    49  
    50  type SIamPolicy struct {
    51  	client   *SGoogleClient
    52  	Version  int
    53  	Etag     string
    54  	Bindings []SBinding
    55  }
    56  
    57  func (self *SGoogleClient) GetIamPolicy() (*SIamPolicy, error) {
    58  	resource := fmt.Sprintf("projects/%s:getIamPolicy", self.projectId)
    59  	resp, err := self.managerPost(resource, nil, nil)
    60  	if err != nil {
    61  		return nil, errors.Wrap(err, "managerPost")
    62  	}
    63  	policy := &SIamPolicy{client: self}
    64  	err = resp.Unmarshal(policy)
    65  	if err != nil {
    66  		return nil, errors.Wrap(err, "Unmarshal")
    67  	}
    68  	return policy, nil
    69  }
    70  
    71  func (self *SGoogleClient) TestIam(permissions []string) ([]string, error) {
    72  	resource := fmt.Sprintf("projects/%s:testIamPermissions", self.projectId)
    73  	body := jsonutils.Marshal(map[string]interface{}{"permissions": permissions})
    74  	resp, err := self.managerPost(resource, nil, body)
    75  	if err != nil {
    76  		return nil, errors.Wrap(err, "testIamPermissions")
    77  	}
    78  	ret := []string{}
    79  	err = resp.Unmarshal(&ret, "permissions")
    80  	if err != nil {
    81  		return nil, errors.Wrap(err, "resp.Unmarshal")
    82  	}
    83  	return ret, nil
    84  }
    85  
    86  func (self *SGoogleClient) IsSupportCloudId() bool {
    87  	permissions, err := self.TestIam([]string{"resourcemanager.projects.setIamPolicy"})
    88  	if err != nil {
    89  		return false
    90  	}
    91  	return len(permissions) == 1
    92  }
    93  
    94  func (self *SGoogleClient) SetIamPlicy(policy *SIamPolicy) error {
    95  	resource := fmt.Sprintf("projects/%s:setIamPolicy", self.projectId)
    96  	body := jsonutils.Marshal(map[string]interface{}{"policy": map[string]interface{}{"bindings": policy.Bindings}})
    97  	_, err := self.managerPost(resource, nil, body)
    98  	if err != nil {
    99  		return errors.Wrap(err, "managerPost")
   100  	}
   101  	return nil
   102  }
   103  
   104  func (self *SGoogleClient) CreateICloudpolicy(opts *cloudprovider.SCloudpolicyCreateOptions) (cloudprovider.ICloudpolicy, error) {
   105  	permission := struct {
   106  		IncludedPermissions []string
   107  	}{}
   108  	err := opts.Document.Unmarshal(&permission)
   109  	if err != nil {
   110  		return nil, errors.Wrapf(err, "Document.Unmarshal(")
   111  	}
   112  	return self.CreateRole(permission.IncludedPermissions, opts.Name, opts.Desc)
   113  }
   114  
   115  func (self *SGoogleClient) CreateRole(permissions []string, name, desc string) (*SRole, error) {
   116  	resource := fmt.Sprintf("projects/%s/roles", self.projectId)
   117  	params := map[string]interface{}{
   118  		"roleId": strings.ReplaceAll(stringutils.UUID4(), "-", "_"),
   119  		"role": map[string]interface{}{
   120  			"title":               name,
   121  			"description":         desc,
   122  			"includedPermissions": permissions,
   123  			"stage":               "GA",
   124  		},
   125  	}
   126  	resp, err := self.iamPost(resource, nil, jsonutils.Marshal(params))
   127  	if err != nil {
   128  		return nil, errors.Wrap(err, "managerPost")
   129  	}
   130  	role := SRole{client: self}
   131  	err = resp.Unmarshal(&role)
   132  	if err != nil {
   133  		return nil, errors.Wrap(err, "resp.Unmarshal")
   134  	}
   135  	return &role, nil
   136  }
   137  
   138  type SClouduser struct {
   139  	multicloud.SBaseClouduser
   140  
   141  	policy *SIamPolicy
   142  	Name   string
   143  	Roles  []string
   144  }
   145  
   146  func (self *SClouduser) GetEmailAddr() string {
   147  	return ""
   148  }
   149  
   150  func (self *SClouduser) GetInviteUrl() string {
   151  	return ""
   152  }
   153  
   154  func (self *SGoogleClient) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) {
   155  	roles, err := self.GetRoles("")
   156  	if err != nil {
   157  		return nil, errors.Wrap(err, "GetRoles")
   158  	}
   159  	ret := []cloudprovider.ICloudpolicy{}
   160  	for i := range roles {
   161  		roles[i].client = self
   162  		ret = append(ret, &roles[i])
   163  	}
   164  	return ret, nil
   165  }
   166  
   167  func (self *SGoogleClient) GetICustomCloudpolicies() ([]cloudprovider.ICloudpolicy, error) {
   168  	roles, err := self.GetRoles(self.projectId)
   169  	if err != nil {
   170  		return nil, errors.Wrapf(err, "GetRoles for project %s", self.projectId)
   171  	}
   172  	ret := []cloudprovider.ICloudpolicy{}
   173  	for i := range roles {
   174  		roles[i].client = self
   175  		ret = append(ret, &roles[i])
   176  	}
   177  	return ret, nil
   178  }
   179  
   180  type SRole struct {
   181  	client *SGoogleClient
   182  
   183  	Name                string
   184  	Title               string
   185  	Description         string
   186  	IncludedPermissions []string
   187  	Stage               string
   188  	Etag                string
   189  }
   190  
   191  func (role *SRole) GetName() string {
   192  	return role.Title
   193  }
   194  
   195  func (role *SRole) GetGlobalId() string {
   196  	return role.Name
   197  }
   198  
   199  func (role *SRole) GetDescription() string {
   200  	return role.Description
   201  }
   202  
   203  func (role *SRole) UpdateDocument(document *jsonutils.JSONDict) error {
   204  	permissions := struct {
   205  		IncludedPermissions []string
   206  	}{}
   207  	err := document.Unmarshal(&permissions)
   208  	if err != nil {
   209  		return errors.Wrapf(err, "document.Unmarshal")
   210  	}
   211  	return role.client.UpdateRole(role.Name, permissions.IncludedPermissions)
   212  }
   213  
   214  func (role *SRole) Delete() error {
   215  	return role.client.DeleteRole(role.Name)
   216  }
   217  
   218  func (role *SRole) GetDocument() (*jsonutils.JSONDict, error) {
   219  	permissions := jsonutils.Marshal(role.IncludedPermissions)
   220  	result := jsonutils.NewDict()
   221  	result.Add(permissions, "includedPermissions")
   222  	return result, nil
   223  }
   224  
   225  func (self *SGoogleClient) GetRole(roleId string) (*SRole, error) {
   226  	role := &SRole{}
   227  	resp, err := self.iamGet(roleId)
   228  	if err != nil {
   229  		return nil, errors.Wrapf(err, "iamGet(%s)", roleId)
   230  	}
   231  	err = resp.Unmarshal(role)
   232  	if err != nil {
   233  		return nil, errors.Wrap(err, "resp.Unmarshal")
   234  	}
   235  	return role, nil
   236  }
   237  
   238  // https://cloud.google.com/iam/docs/reference/rest/v1/roles/list
   239  func (self *SGoogleClient) GetRoles(projectId string) ([]SRole, error) {
   240  	roles := []SRole{}
   241  	params := map[string]string{"view": "FULL"}
   242  	resource := "roles"
   243  	if len(projectId) > 0 {
   244  		resource = fmt.Sprintf("projects/%s/roles", projectId)
   245  	}
   246  	err := self.iamListAll(resource, params, &roles)
   247  	if err != nil {
   248  		return nil, errors.Wrap(err, "iamListAll.roles")
   249  	}
   250  	return roles, nil
   251  }
   252  
   253  func (user *SClouduser) GetGlobalId() string {
   254  	return user.Name
   255  }
   256  
   257  func (user *SClouduser) GetName() string {
   258  	return user.Name
   259  }
   260  
   261  func (user *SClouduser) IsConsoleLogin() bool {
   262  	return true
   263  }
   264  
   265  func (user *SClouduser) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) {
   266  	ret := []cloudprovider.ICloudpolicy{}
   267  	for _, roleStr := range user.Roles {
   268  		if strings.HasPrefix(roleStr, "roles/") {
   269  			role, err := user.policy.client.GetRole(roleStr)
   270  			if err != nil {
   271  				return nil, errors.Wrap(err, "GetRole")
   272  			}
   273  			ret = append(ret, role)
   274  		}
   275  	}
   276  	return ret, nil
   277  }
   278  
   279  func (user *SClouduser) GetICustomCloudpolicies() ([]cloudprovider.ICloudpolicy, error) {
   280  	ret := []cloudprovider.ICloudpolicy{}
   281  	for _, roleStr := range user.Roles {
   282  		if strings.HasPrefix(roleStr, "projects/") {
   283  			role, err := user.policy.client.GetRole(roleStr)
   284  			if err != nil {
   285  				return nil, errors.Wrap(err, "GetRole")
   286  			}
   287  			ret = append(ret, role)
   288  		}
   289  	}
   290  	return ret, nil
   291  }
   292  
   293  var getUserName = func(user string) string {
   294  	if strings.HasSuffix(user, "gserviceaccount.com") {
   295  		return "serviceAccount:" + user
   296  	}
   297  	return "user:" + user
   298  }
   299  
   300  func (policy *SIamPolicy) AttachPolicy(user string, roles []string) error {
   301  	for _, role := range roles {
   302  		find := false
   303  		for i := range policy.Bindings {
   304  			if policy.Bindings[i].Role == role {
   305  				if !utils.IsInStringArray(getUserName(user), policy.Bindings[i].Members) {
   306  					policy.Bindings[i].Members = append(policy.Bindings[i].Members, getUserName(user))
   307  					find = true
   308  				}
   309  			}
   310  		}
   311  		if !find {
   312  			policy.Bindings = append(policy.Bindings, SBinding{Role: role, Members: []string{getUserName(user)}})
   313  		}
   314  	}
   315  	return policy.client.SetIamPlicy(policy)
   316  }
   317  
   318  func (user *SClouduser) AttachSystemPolicy(role string) error {
   319  	return user.policy.AttachPolicy(user.Name, []string{role})
   320  }
   321  
   322  func (user *SClouduser) AttachCustomPolicy(role string) error {
   323  	return user.policy.AttachPolicy(user.Name, []string{role})
   324  }
   325  
   326  func (policy *SIamPolicy) DetachPolicy(user, role string) error {
   327  	change := false
   328  	for i := range policy.Bindings {
   329  		if policy.Bindings[i].Role == role && utils.IsInStringArray(getUserName(user), policy.Bindings[i].Members) {
   330  			change = true
   331  			members := []string{}
   332  			for _, member := range policy.Bindings[i].Members {
   333  				if member != getUserName(user) {
   334  					members = append(members, member)
   335  				}
   336  			}
   337  			policy.Bindings[i].Members = members
   338  		}
   339  	}
   340  	if change {
   341  		return policy.client.SetIamPlicy(policy)
   342  	}
   343  	return nil
   344  }
   345  
   346  func (client *SGoogleClient) CreateIClouduser(conf *cloudprovider.SClouduserCreateConfig) (cloudprovider.IClouduser, error) {
   347  	policy, err := client.GetIamPolicy()
   348  	if err != nil {
   349  		return nil, errors.Wrap(err, "GetIamPolicy")
   350  	}
   351  	if len(conf.ExternalPolicyIds) == 0 {
   352  		return nil, fmt.Errorf("missing policy info")
   353  	}
   354  	err = policy.AttachPolicy(conf.Name, conf.ExternalPolicyIds)
   355  	if err != nil {
   356  		return nil, errors.Wrap(err, "policy.AttachPolicy")
   357  	}
   358  	return client.GetIClouduserByName(conf.Name)
   359  }
   360  
   361  func (client *SGoogleClient) GetIClouduserByName(name string) (cloudprovider.IClouduser, error) {
   362  	policy, err := client.GetIamPolicy()
   363  	if err != nil {
   364  		return nil, errors.Wrap(err, "GetIamPolicy")
   365  	}
   366  	users, err := policy.GetICloudusers()
   367  	if err != nil {
   368  		return nil, errors.Wrap(err, "policy.GetICloudusers")
   369  	}
   370  	for i := range users {
   371  		if users[i].GetName() == name {
   372  			return users[i], nil
   373  		}
   374  	}
   375  	return &SClouduser{policy: policy, Name: name, Roles: []string{}}, nil
   376  }
   377  
   378  func (user *SClouduser) DetachSystemPolicy(role string) error {
   379  	return user.policy.DetachPolicy(user.Name, role)
   380  }
   381  
   382  func (user *SClouduser) DetachCustomPolicy(role string) error {
   383  	return user.policy.DetachPolicy(user.Name, role)
   384  }
   385  
   386  func (policy *SIamPolicy) DeleteUser(user string) error {
   387  	change := false
   388  	for i := range policy.Bindings {
   389  		if utils.IsInStringArray(getUserName(user), policy.Bindings[i].Members) {
   390  			change = true
   391  			members := []string{}
   392  			for _, member := range policy.Bindings[i].Members {
   393  				if member != getUserName(user) {
   394  					members = append(members, member)
   395  				}
   396  			}
   397  			policy.Bindings[i].Members = members
   398  		}
   399  	}
   400  	if change {
   401  		return policy.client.SetIamPlicy(policy)
   402  	}
   403  	return nil
   404  }
   405  
   406  func (user *SClouduser) Delete() error {
   407  	return user.policy.DeleteUser(user.Name)
   408  }
   409  
   410  func (user *SClouduser) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) {
   411  	return []cloudprovider.ICloudgroup{}, nil
   412  }
   413  
   414  func (user *SClouduser) ResetPassword(password string) error {
   415  	return cloudprovider.ErrNotSupported
   416  }
   417  
   418  func (client *SGoogleClient) GetICloudusers() ([]cloudprovider.IClouduser, error) {
   419  	policy, err := client.GetIamPolicy()
   420  	if err != nil {
   421  		return nil, errors.Wrap(err, "GetIamPolicy")
   422  	}
   423  	return policy.GetICloudusers()
   424  }
   425  
   426  func (policy *SIamPolicy) GetICloudusers() ([]cloudprovider.IClouduser, error) {
   427  	users := map[string]*SClouduser{}
   428  	for _, binding := range policy.Bindings {
   429  		for _, member := range binding.Members {
   430  			if strings.HasPrefix(member, "user:") {
   431  				user := strings.TrimPrefix(member, "user:")
   432  				if _, ok := users[user]; !ok {
   433  					users[user] = &SClouduser{Name: user, Roles: []string{}}
   434  				}
   435  				roles := users[user].Roles
   436  				if !utils.IsInStringArray(binding.Role, roles) {
   437  					roles = append(roles, binding.Role)
   438  				}
   439  				users[user].Roles = roles
   440  			}
   441  		}
   442  	}
   443  	cloudusers := []cloudprovider.IClouduser{}
   444  	for i := range users {
   445  		users[i].policy = policy
   446  		cloudusers = append(cloudusers, users[i])
   447  	}
   448  	return cloudusers, nil
   449  }
   450  
   451  func (self *SGoogleClient) DeleteRole(id string) error {
   452  	return self.iamDelete(id, nil)
   453  }
   454  
   455  func (self *SGoogleClient) UpdateRole(id string, permissions []string) error {
   456  	query := map[string]string{
   457  		"updateMask": "includedPermissions",
   458  	}
   459  	params := map[string]interface{}{
   460  		"includedPermissions": permissions,
   461  	}
   462  	_, err := self.iamPatch(id, query, jsonutils.Marshal(params))
   463  	return err
   464  }