Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare Cloudbeat for AWS Gov cloud #2050

Merged
merged 10 commits into from
Mar 26, 2024
36 changes: 29 additions & 7 deletions internal/flavors/benchmark/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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
romulets marked this conversation as resolved.
Show resolved Hide resolved
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")
Expand Down
39 changes: 30 additions & 9 deletions internal/flavors/benchmark/aws_org.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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")
Expand Down
34 changes: 24 additions & 10 deletions internal/resources/providers/awslib/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
}
Expand Down
13 changes: 9 additions & 4 deletions internal/resources/providers/awslib/iam/root_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
20 changes: 16 additions & 4 deletions internal/resources/providers/awslib/s3/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading