internal/resources/providers/awslib/account_provider.go (105 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package awslib import ( "context" "errors" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/organizations" "github.com/aws/aws-sdk-go-v2/service/organizations/types" "github.com/elastic/cloudbeat/internal/dataprovider/providers/cloud" "github.com/elastic/cloudbeat/internal/infra/clog" "github.com/elastic/cloudbeat/internal/resources/utils/pointers" ) type AccountProviderAPI interface { ListAccounts(ctx context.Context, log *clog.Logger, cfg aws.Config) ([]cloud.Identity, error) } type organizationsAPI interface { organizations.ListAccountsAPIClient organizations.ListParentsAPIClient DescribeOrganizationalUnit(context.Context, *organizations.DescribeOrganizationalUnitInput, ...func(*organizations.Options)) (*organizations.DescribeOrganizationalUnitOutput, error) } type AccountProvider struct{} func (a AccountProvider) ListAccounts(ctx context.Context, log *clog.Logger, cfg aws.Config) ([]cloud.Identity, error) { return listAccounts(ctx, log, organizations.NewFromConfig(cfg)) } func listAccounts(ctx context.Context, log *clog.Logger, client organizationsAPI) ([]cloud.Identity, error) { organizationIdToName := make(map[string]string) input := organizations.ListAccountsInput{} var accounts []cloud.Identity for { o, err := client.ListAccounts(ctx, &input) if err != nil { return nil, err } for _, account := range o.Accounts { if account.Status != types.AccountStatusActive || account.Id == nil { continue } organization, err := getOUInfoForAccount(ctx, client, organizationIdToName, account.Id) if err != nil { log.Errorf("failed to get organizational unit info for account %s: %v", *account.Id, err) } accounts = append(accounts, cloud.Identity{ Provider: "aws", Account: *account.Id, AccountAlias: pointers.Deref(account.Name), OrganizationId: organization.id, OrganizationName: organization.name, }) } if o.NextToken == nil { break } input.NextToken = o.NextToken } return accounts, nil } type organizationalUnitInfo struct { id string name string } func getOUInfoForAccount(ctx context.Context, client organizationsAPI, cache map[string]string, accountId *string) (organizationalUnitInfo, error) { // We need a paginator, according to the AWS docs: // These operations can occasionally return an empty set of results even when there are more results available. paginator := organizations.NewListParentsPaginator(client, &organizations.ListParentsInput{ChildId: accountId}) for paginator.HasMorePages() { o, err := paginator.NextPage(ctx) if err != nil { return organizationalUnitInfo{}, err } if len(o.Parents) == 0 { continue } // According to AWS, in the current release, a child can have only a single parent. parent := o.Parents[0] if parent.Type == types.ParentTypeRoot { return organizationalUnitInfo{ id: *parent.Id, name: "Root", }, nil } return describeOU(ctx, client, cache, parent.Id) } return organizationalUnitInfo{}, errors.New("empty response") } func describeOU(ctx context.Context, client organizationsAPI, cache map[string]string, id *string) (organizationalUnitInfo, error) { if id == nil { return organizationalUnitInfo{}, errors.New("nil id") } if savedName, ok := cache[*id]; ok { return organizationalUnitInfo{ id: *id, name: savedName, }, nil } o, err := client.DescribeOrganizationalUnit(ctx, &organizations.DescribeOrganizationalUnitInput{ OrganizationalUnitId: id, }) if err != nil { return organizationalUnitInfo{id: *id}, err } name := pointers.Deref(o.OrganizationalUnit.Name) if cache != nil { cache[*id] = name } return organizationalUnitInfo{ id: *id, name: name, }, nil }