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  }