yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/aws/organizations.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 package aws 16 17 import ( 18 "fmt" 19 "strings" 20 "time" 21 22 "github.com/aws/aws-sdk-go/service/organizations" 23 24 "yunion.io/x/jsonutils" 25 "yunion.io/x/log" 26 "yunion.io/x/pkg/errors" 27 28 api "yunion.io/x/cloudmux/pkg/apis/compute" 29 "yunion.io/x/cloudmux/pkg/cloudprovider" 30 ) 31 32 /* 33 * {"arn":"arn:aws:organizations::285906155448:account/o-vgh74bqhdw/285906155448","email":"swordqiu@gmail.com","id":"285906155448","joined_method":"INVITED","joined_timestamp":"2021-02-09T03:55:27.724000Z","name":"qiu jian","status":"ACTIVE"} 34 */ 35 type SAccount struct { 36 ID string `json:"id"` 37 Name string `json:"name"` 38 Arn string `json:"arn"` 39 Email string `json:"email"` 40 Status string `json:"status"` 41 42 JoinedMethod string `json:"joined_method"` 43 JoinedTimestamp time.Time `json:"joined_timestamp"` 44 45 IsMaster bool `json:"is_master"` 46 } 47 48 /* 49 * { 50 * Arn: "arn:aws:organizations::031871565791:policy/o-gn75phg8ge/service_control_policy/p-4l9recev", 51 * AwsManaged: false, 52 * Description: "Create Preventive SCP Guardrails", 53 * Id: "p-4l9recev", 54 * Name: "SCP-PREVENTIVE-GUARDRAILS", 55 * Type: "SERVICE_CONTROL_POLICY" 56 * } 57 */ 58 type SOrgPolicy struct { 59 Arn string `json:"arn"` 60 AwsManaged bool `json:"aws_managed"` 61 Description string `json:"description"` 62 Id string `json:"id"` 63 Name string `json:"name"` 64 Type string `json:"type"` 65 } 66 67 const ( 68 SERVICE_CONTROL_POLICY = "SERVICE_CONTROL_POLICY" 69 TAG_POLICY = "TAG_POLICY" 70 BACKUP_POLICY = "BACKUP_POLICY" 71 AISERVICES_OPT_OUT_POLICY = "AISERVICES_OPT_OUT_POLICY" 72 ) 73 74 func (r *SRegion) ListPolicies(filter string) ([]SOrgPolicy, error) { 75 orgCli, err := r.getOrganizationClient() 76 if err != nil { 77 return nil, errors.Wrap(err, "GetOrganizationClient") 78 } 79 var nextToken *string 80 policies := make([]SOrgPolicy, 0) 81 82 for { 83 input := organizations.ListPoliciesInput{} 84 input.SetFilter(filter) 85 if nextToken != nil { 86 input.SetNextToken(*nextToken) 87 } 88 parts, err := orgCli.ListPolicies(&input) 89 if err != nil { 90 return nil, errors.Wrap(err, "ListPolicies") 91 } 92 for _, pPtr := range parts.Policies { 93 p := SOrgPolicy{ 94 Arn: *pPtr.Arn, 95 AwsManaged: *pPtr.AwsManaged, 96 Description: *pPtr.Description, 97 Id: *pPtr.Id, 98 Name: *pPtr.Name, 99 Type: *pPtr.Type, 100 } 101 policies = append(policies, p) 102 } 103 if parts.NextToken == nil || len(*parts.NextToken) == 0 { 104 break 105 } else { 106 nextToken = parts.NextToken 107 } 108 } 109 return policies, nil 110 } 111 112 func (r *SRegion) ListPoliciesForTarget(filter string, targetId string) ([]SOrgPolicy, error) { 113 orgCli, err := r.getOrganizationClient() 114 if err != nil { 115 return nil, errors.Wrap(err, "GetOrganizationClient") 116 } 117 var nextToken *string 118 policies := make([]SOrgPolicy, 0) 119 120 for { 121 input := organizations.ListPoliciesForTargetInput{} 122 input.SetFilter(filter) 123 input.SetTargetId(targetId) 124 if nextToken != nil { 125 input.SetNextToken(*nextToken) 126 } 127 parts, err := orgCli.ListPoliciesForTarget(&input) 128 if err != nil { 129 return nil, errors.Wrap(err, "ListPoliciesForTarget") 130 } 131 for _, pPtr := range parts.Policies { 132 p := SOrgPolicy{ 133 Arn: *pPtr.Arn, 134 AwsManaged: *pPtr.AwsManaged, 135 Description: *pPtr.Description, 136 Id: *pPtr.Id, 137 Name: *pPtr.Name, 138 Type: *pPtr.Type, 139 } 140 policies = append(policies, p) 141 } 142 if parts.NextToken == nil || len(*parts.NextToken) == 0 { 143 break 144 } else { 145 nextToken = parts.NextToken 146 } 147 } 148 return policies, nil 149 } 150 151 func (r *SRegion) DescribeOrgPolicy(pId string) (jsonutils.JSONObject, error) { 152 orgCli, err := r.getOrganizationClient() 153 if err != nil { 154 return nil, errors.Wrap(err, "GetOrganizationClient") 155 } 156 input := organizations.DescribePolicyInput{} 157 input.SetPolicyId(pId) 158 output, err := orgCli.DescribePolicy(&input) 159 if err != nil { 160 return nil, errors.Wrap(err, "DescribePolicy") 161 } 162 content, err := jsonutils.ParseString(*output.Policy.Content) 163 if err != nil { 164 return nil, errors.Wrap(err, "ParseJSON") 165 } 166 return content, nil 167 } 168 169 func (r *SRegion) ListAccounts() ([]SAccount, error) { 170 orgCli, err := r.getOrganizationClient() 171 if err != nil { 172 return nil, errors.Wrap(err, "GetOrganizationClient") 173 } 174 input := organizations.DescribeOrganizationInput{} 175 orgOutput, err := orgCli.DescribeOrganization(&input) 176 if err != nil { 177 log.Errorf("%#v", err) 178 return nil, errors.Wrap(err, "DescribeOrganization") 179 } 180 181 var nextToken *string 182 accounts := make([]SAccount, 0) 183 for { 184 input := organizations.ListAccountsInput{} 185 if nextToken != nil { 186 input.NextToken = nextToken 187 } 188 parts, err := orgCli.ListAccounts(&input) 189 if err != nil { 190 return nil, errors.Wrap(err, "ListAccounts") 191 } 192 for _, actPtr := range parts.Accounts { 193 account := SAccount{ 194 ID: *actPtr.Id, 195 Name: *actPtr.Name, 196 Arn: *actPtr.Arn, 197 Email: *actPtr.Email, 198 Status: *actPtr.Status, 199 JoinedMethod: *actPtr.JoinedMethod, 200 JoinedTimestamp: *actPtr.JoinedTimestamp, 201 } 202 if *orgOutput.Organization.MasterAccountId == *actPtr.Id { 203 account.IsMaster = true 204 } 205 accounts = append(accounts, account) 206 } 207 if parts.NextToken == nil || len(*parts.NextToken) == 0 { 208 break 209 } else { 210 nextToken = parts.NextToken 211 } 212 } 213 return accounts, nil 214 } 215 216 func (self *SAwsClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) { 217 defRegion, err := self.getDefaultRegion() 218 if err != nil { 219 return nil, errors.Wrapf(err, "getDefaultRegion") 220 } 221 accounts, err := defRegion.ListAccounts() 222 if err != nil { 223 // find errors 224 if strings.Contains(err.Error(), "AWSOrganizationsNotInUseException") || strings.Contains(err.Error(), "AccessDeniedException") { 225 // permission denied, fall back to single account mode 226 subAccount := cloudprovider.SSubAccount{} 227 subAccount.Name = self.cpcfg.Name 228 subAccount.Account = self.accessKey 229 subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL 230 return []cloudprovider.SSubAccount{subAccount}, nil 231 } else { 232 return nil, errors.Wrap(err, "ListAccounts") 233 } 234 } else { 235 // check if caller is a root caller 236 caller, _ := self.GetCallerIdentity() 237 isRootAccount := false 238 // arn:aws:iam::285906155448:root 239 if caller != nil && strings.HasSuffix(caller.Arn, ":root") { 240 log.Debugf("root %s", caller.Arn) 241 isRootAccount = true 242 } 243 subAccounts := []cloudprovider.SSubAccount{} 244 for _, account := range accounts { 245 subAccount := cloudprovider.SSubAccount{} 246 if account.Status == "ACTIVE" { 247 subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL 248 } else { 249 subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_SUSPENDED 250 } 251 if account.IsMaster { 252 subAccount.Name = fmt.Sprintf("%s/%s", account.Name, self.cpcfg.Name) 253 subAccount.Account = self.accessKey 254 } else { 255 if isRootAccount { 256 log.Warningf("Cannot access non-master account with root account!!") 257 subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NO_PERMISSION 258 } 259 subAccount.Name = fmt.Sprintf("%s/%s", account.Name, account.ID) 260 subAccount.Account = fmt.Sprintf("%s/%s", self.accessKey, account.ID) 261 } 262 subAccounts = append(subAccounts, subAccount) 263 } 264 return subAccounts, nil 265 } 266 } 267 268 func (r *SRegion) ListParents(childId string) error { 269 orgCli, err := r.getOrganizationClient() 270 if err != nil { 271 return errors.Wrap(err, "GetOrganizationClient") 272 } 273 input := organizations.ListParentsInput{} 274 input.SetChildId(childId) 275 parents, err := orgCli.ListParents(&input) 276 if err != nil { 277 return errors.Wrap(err, "ListParents") 278 } 279 log.Debugf("%#v", parents) 280 return nil 281 } 282 283 func (r *SRegion) DescribeOrganizationalUnit(ouId string) error { 284 orgCli, err := r.getOrganizationClient() 285 if err != nil { 286 return errors.Wrap(err, "GetOrganizationClient") 287 } 288 input := organizations.DescribeOrganizationalUnitInput{} 289 input.SetOrganizationalUnitId(ouId) 290 output, err := orgCli.DescribeOrganizationalUnit(&input) 291 if err != nil { 292 return errors.Wrap(err, "DescribeOrganizationUnit") 293 } 294 log.Debugf("%#v", output) 295 return nil 296 }