diff --git a/internal/flavors/benchmark/aws.go b/internal/flavors/benchmark/aws.go index 11b0942584..848160e51a 100644 --- a/internal/flavors/benchmark/aws.go +++ b/internal/flavors/benchmark/aws.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" + awssdk "github.com/aws/aws-sdk-go-v2/aws" "github.com/elastic/beats/v7/x-pack/libbeat/common/aws" "github.com/elastic/elastic-agent-libs/logp" @@ -59,23 +60,44 @@ func (a *AWS) initialize(ctx context.Context, log *logp.Logger, cfg *config.Conf return nil, nil, nil, err } - // TODO: make this mock-able - awsConfig, err := aws.InitializeAWSConfig(cfg.CloudConfig.Aws.Cred) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to initialize AWS credentials: %w", err) + var ( + awsConfig *awssdk.Config + awsIdentity *cloud.Identity + err error + ) + + awsConfig, awsIdentity, err = a.getIdentity(ctx, cfg) + if err != nil && cfg.CloudConfig.Aws.Cred.DefaultRegion == "" { + log.Warn("failed to initialize identity; retrying to check AWS Gov Cloud regions") + cfg.CloudConfig.Aws.Cred.DefaultRegion = awslib.DefaultGovRegion + awsConfig, awsIdentity, err = a.getIdentity(ctx, cfg) } - awsIdentity, err := a.IdentityProvider.GetIdentity(ctx, awsConfig) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to get AWS identity: %w", err) + return nil, nil, nil, fmt.Errorf("failed to get AWS Identity: %w", err) } + log.Info("successfully retrieved AWS Identity") return registry.NewRegistry( log, - registry.WithFetchersMap(preset.NewCisAwsFetchers(log, awsConfig, ch, awsIdentity)), + registry.WithFetchersMap(preset.NewCisAwsFetchers(log, *awsConfig, ch, awsIdentity)), ), cloud.NewDataProvider(cloud.WithAccount(*awsIdentity)), nil, nil } +func (a *AWS) getIdentity(ctx context.Context, cfg *config.Config) (*awssdk.Config, *cloud.Identity, error) { + awsConfig, err := aws.InitializeAWSConfig(cfg.CloudConfig.Aws.Cred) + if err != nil { + return nil, nil, fmt.Errorf("failed to initialize AWS credentials: %w", err) + } + + awsIdentity, err := a.IdentityProvider.GetIdentity(ctx, awsConfig) + if err != nil { + return nil, nil, fmt.Errorf("failed to get AWS identity: %w", err) + } + + return &awsConfig, awsIdentity, nil +} + func (a *AWS) checkDependencies() error { if a.IdentityProvider == nil { return errors.New("aws identity provider is uninitialized") diff --git a/internal/flavors/benchmark/aws_org.go b/internal/flavors/benchmark/aws_org.go index 814d5e30ee..b44a458847 100644 --- a/internal/flavors/benchmark/aws_org.go +++ b/internal/flavors/benchmark/aws_org.go @@ -71,23 +71,30 @@ func (a *AWSOrg) initialize(ctx context.Context, log *logp.Logger, cfg *config.C return nil, nil, nil, err } - // TODO: make this mock-able - awsConfig, err := aws.InitializeAWSConfig(cfg.CloudConfig.Aws.Cred) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to initialize AWS credentials: %w", err) - } + var ( + awsConfig *awssdk.Config + awsIdentity *cloud.Identity + err error + ) - a.IAMProvider = iam.NewIAMProvider(log, awsConfig, nil) + awsConfig, awsIdentity, err = a.getIdentity(ctx, cfg) + if err != nil && cfg.CloudConfig.Aws.Cred.DefaultRegion == "" { + log.Warn("failed to initialize identity; retrying to check AWS Gov Cloud regions") + cfg.CloudConfig.Aws.Cred.DefaultRegion = awslib.DefaultGovRegion + awsConfig, awsIdentity, err = a.getIdentity(ctx, cfg) + } - awsIdentity, err := a.IdentityProvider.GetIdentity(ctx, awsConfig) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to get AWS identity: %w", err) + return nil, nil, nil, fmt.Errorf("failed to get AWS Identity: %w", err) } + log.Info("successfully retrieved AWS Identity") + + a.IAMProvider = iam.NewIAMProvider(log, *awsConfig, nil) cache := make(map[string]registry.FetchersMap) reg := registry.NewRegistry(log, registry.WithUpdater( func() (registry.FetchersMap, error) { - accounts, err := a.getAwsAccounts(ctx, log, awsConfig, awsIdentity) + accounts, err := a.getAwsAccounts(ctx, log, *awsConfig, awsIdentity) if err != nil { return nil, fmt.Errorf("failed to get AWS accounts: %w", err) } @@ -211,6 +218,20 @@ func (a *AWSOrg) pickManagementAccountRole(ctx context.Context, log *logp.Logger return config, nil } +func (a *AWSOrg) getIdentity(ctx context.Context, cfg *config.Config) (*awssdk.Config, *cloud.Identity, error) { + awsConfig, err := aws.InitializeAWSConfig(cfg.CloudConfig.Aws.Cred) + if err != nil { + return nil, nil, fmt.Errorf("failed to initialize AWS credentials: %w", err) + } + + awsIdentity, err := a.IdentityProvider.GetIdentity(ctx, awsConfig) + if err != nil { + return nil, nil, fmt.Errorf("failed to get AWS identity: %w", err) + } + + return &awsConfig, awsIdentity, nil +} + func (a *AWSOrg) checkDependencies() error { if a.IAMProvider == nil { return errors.New("aws iam provider is uninitialized") diff --git a/internal/resources/providers/awslib/aws.go b/internal/resources/providers/awslib/aws.go index 51942b8bf5..0a18c536d5 100644 --- a/internal/resources/providers/awslib/aws.go +++ b/internal/resources/providers/awslib/aws.go @@ -19,11 +19,14 @@ package awslib import ( "errors" + + "github.com/elastic/cloudbeat/internal/resources/utils/pointers" ) const ( - DefaultRegion = "us-east-1" - GlobalRegion = "global" + DefaultRegion = "us-east-1" + DefaultGovRegion = "us-gov-east-1" + GlobalRegion = "global" ) var ErrClientNotFound = errors.New("aws client not found") @@ -35,18 +38,29 @@ type AwsResource interface { GetRegion() string } -func GetClient[T any](region *string, list map[string]T) (T, error) { - c, ok := list[getRegion(region)] - if !ok { - return c, ErrClientNotFound +func GetDefaultClient[T any](list map[string]T) (T, error) { + c, ok := list[DefaultRegion] + if ok { + return c, nil } - return c, nil + + c, ok = list[DefaultGovRegion] + if ok { + return c, nil + } + + return c, ErrClientNotFound } -func getRegion(region *string) string { +func GetClient[T any](region *string, list map[string]T) (T, error) { if region == nil { - return DefaultRegion + return GetDefaultClient(list) } - return *region + c, ok := list[pointers.Deref(region)] + if !ok { + return c, ErrClientNotFound + } + + return c, nil } diff --git a/internal/resources/providers/awslib/cloudtrail/provider.go b/internal/resources/providers/awslib/cloudtrail/provider.go index 183a3c401c..acb8c7488b 100644 --- a/internal/resources/providers/awslib/cloudtrail/provider.go +++ b/internal/resources/providers/awslib/cloudtrail/provider.go @@ -19,6 +19,7 @@ package cloudtrail import ( "context" + "fmt" "github.com/aws/aws-sdk-go-v2/service/cloudtrail" "github.com/aws/aws-sdk-go-v2/service/cloudtrail/types" @@ -40,7 +41,11 @@ type Client interface { func (p Provider) DescribeTrails(ctx context.Context) ([]TrailInfo, error) { input := cloudtrail.DescribeTrailsInput{} - output, err := p.clients[awslib.DefaultRegion].DescribeTrails(ctx, &input) + defaultClient, err := awslib.GetDefaultClient(p.clients) + if err != nil { + return nil, fmt.Errorf("could not select default region client: %w", err) + } + output, err := defaultClient.DescribeTrails(ctx, &input) if err != nil { return nil, err } diff --git a/internal/resources/providers/awslib/iam/root_account.go b/internal/resources/providers/awslib/iam/root_account.go index 4bee6dcc4c..a2400bf23d 100644 --- a/internal/resources/providers/awslib/iam/root_account.go +++ b/internal/resources/providers/awslib/iam/root_account.go @@ -39,10 +39,15 @@ func (p Provider) getRootAccountUser(rootAccount *CredentialReport) *types.User return nil } - pwdLastUsed, err := time.Parse(time.RFC3339, rootAccount.PasswordLastUsed) - if err != nil { - p.log.Errorf("fail to parse root account password last used, error: %v", err) - return nil + pwdLastUsed := time.Time{} + // "no_information" if never used, "N/A" if user has no password + // Docs: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_getting-report.html + if rootAccount.PasswordLastUsed != "no_information" && rootAccount.PasswordLastUsed != "N/A" { + pwdLastUsed, err = time.Parse(time.RFC3339, rootAccount.PasswordLastUsed) + if err != nil { + p.log.Errorf("fail to parse root account password last used, error: %v", err) + return nil + } } return &types.User{ diff --git a/internal/resources/providers/awslib/s3/provider.go b/internal/resources/providers/awslib/s3/provider.go index 67f64c0f82..d75f290abe 100644 --- a/internal/resources/providers/awslib/s3/provider.go +++ b/internal/resources/providers/awslib/s3/provider.go @@ -59,7 +59,11 @@ func NewProvider(log *logp.Logger, cfg aws.Config, factory awslib.CrossRegionFac } func (p Provider) DescribeBuckets(ctx context.Context) ([]awslib.AwsResource, error) { - clientBuckets, err := p.clients[awslib.DefaultRegion].ListBuckets(ctx, &s3Client.ListBucketsInput{}) + defaultClient, err := awslib.GetDefaultClient(p.clients) + if err != nil { + return nil, fmt.Errorf("could not select default region client: %w", err) + } + clientBuckets, err := defaultClient.ListBuckets(ctx, &s3Client.ListBucketsInput{}) if err != nil { p.log.Errorf("Could not list s3 buckets: %v", err) return nil, err @@ -225,15 +229,23 @@ func (p Provider) getBucketEncryptionAlgorithm(ctx context.Context, bucketName * } func (p Provider) getBucketRegion(ctx context.Context, bucketName *string) (string, error) { - location, err := p.clients[awslib.DefaultRegion].GetBucketLocation(ctx, &s3Client.GetBucketLocationInput{Bucket: bucketName}) + defaultClient, err := awslib.GetDefaultClient(p.clients) + if err != nil { + return "", fmt.Errorf("could not select default region client: %w", err) + } + location, err := defaultClient.GetBucketLocation(ctx, &s3Client.GetBucketLocationInput{Bucket: bucketName}) if err != nil { return "", err } region := string(location.LocationConstraint) - // Region us-east-1 have a LocationConstraint of null. + // Region us-east-1 have a LocationConstraint of null... if region == "" { - region = "us-east-1" + region = awslib.DefaultRegion + // ...but check if it's not the AWS GovCloud partition + if _, ok := p.clients[awslib.DefaultRegion]; !ok { + region = awslib.DefaultGovRegion + } } return region, nil