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
37 changes: 31 additions & 6 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,21 +60,45 @@ func (a *AWS) initialize(ctx context.Context, log *logp.Logger, cfg *config.Conf
return nil, nil, nil, err
}

var (
awsConfig *awssdk.Config
awsIdentity *cloud.Identity
err error
)

awsConfig, awsIdentity, err = a.getIdentity(ctx, cfg)
// TODO(kuba): Ask when the DefaultRegion is empty. Is there a chance it is
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be solved before merging, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, exactly. I would love someone more knowledgable would comment on how CloudConfig.Aws is populated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

someone more knowledgable would comment on how CloudConfig.Aws is populated.

I don't qualify as very knowledgable, but afaik this configuration comes from the configuration yaml file + injected via agent.

I don't think overwriting is good idea.

But @amirbenun or @jeniawhite are better suited to explain how it's populated and if it's wise to overwrite it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kubasobon @romulets
The configuration comes from the agent and is based on the populated config during the installation of the AWS CSPM integration.
so it appears that the DefaultRegion field is not being populated.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect, so there is no chance of getting a collision. Thank you.

// set, but incorrectly - e.g. "us-east-1"?
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)
}

if err != nil {
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)),
), cloud.NewDataProvider(cloud.WithAccount(*awsIdentity)), nil, nil
}

func (a *AWS) getIdentity(ctx context.Context, cfg *config.Config) (*awssdk.Config, *cloud.Identity, error) {
// 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)
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, nil, fmt.Errorf("failed to get AWS identity: %w", err)
return nil, nil, fmt.Errorf("failed to get AWS identity: %w", err)
}

return registry.NewRegistry(
log,
registry.WithFetchersMap(preset.NewCisAwsFetchers(log, awsConfig, ch, awsIdentity)),
), cloud.NewDataProvider(cloud.WithAccount(*awsIdentity)), nil, nil
return &awsConfig, awsIdentity, nil
}

func (a *AWS) checkDependencies() error {
Expand Down
40 changes: 31 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,21 @@ 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) {
// TODO: make this mock-able
kubasobon marked this conversation as resolved.
Show resolved Hide resolved
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