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 }