Skip to content

Commit 902fbfc

Browse files
Aws bugs fixes and enhancements (fix 4+m initialization of aws shell/scans) (#6454)
* Race condition fix * Filter propagation * Skip child discovery * S3 Optimization * Retry logic to reduce bad connection time * Documentation * fix unintended bug * rds cluster / instance description * simplify nil handling for child filters * address review s3 optimization * add tests for discovery and filter propagation * propagation adressed by review * fix and add tests for parser handeling * fix s3 client side initalization
1 parent d6117a5 commit 902fbfc

File tree

9 files changed

+476
-61
lines changed

9 files changed

+476
-61
lines changed

providers-sdk/v1/inventory/inventory.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package inventory
55

66
import (
7+
"maps"
78
"os"
89
"os/user"
910
"path/filepath"
@@ -455,9 +456,7 @@ func (cfg *Config) Clone(opts ...CloneOption) *Config {
455456
clonedObject.Discover = &Discovery{}
456457
}
457458
clonedObject.Discover.Filter = make(map[string]string)
458-
for k, v := range cfg.Discover.Filter {
459-
clonedObject.Discover.Filter[k] = v
460-
}
459+
maps.Copy(clonedObject.Discover.Filter, cfg.Discover.GetFilter())
461460
}
462461

463462
return clonedObject

providers/aws/config/config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Examples:
4646
cnspec scan aws ec2 instance-connect <user@host>
4747
cnspec scan aws ec2 instance-connect <user@host> --identity-file <path>
4848
cnspec scan aws ec2 ebs <snapshot-id>
49-
cnspec scan aws --filters region=ap-south-1
49+
cnspec scan aws --filters regions=ap-south-1,us-east-1
5050
5151
Notes:
5252
If you set the AWS_PROFILE environment variable, you can omit the profile flag.
@@ -131,7 +131,7 @@ Notes:
131131
Long: "filters",
132132
Type: plugin.FlagType_KeyValue,
133133
Default: "",
134-
Desc: "Filter options, e.g., --filters region=us-east-2, --filters ec2:instance-ids=i-093439483935",
134+
Desc: "Filter options, e.g., --filters regions=us-east-2,us-east-1 , --filters ec2:instance-ids=i-093439483935",
135135
},
136136
},
137137
},

providers/aws/connection/connection.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"maps"
1212
"net/http"
1313
"slices"
14+
"time"
1415

1516
"github.com/aws/aws-sdk-go-v2/aws"
1617
"github.com/aws/aws-sdk-go-v2/config"
@@ -75,9 +76,11 @@ func NewAwsConnection(id uint32, asset *inventory.Asset, conf *inventory.Config)
7576
for _, opt := range opts {
7677
opt(c)
7778
}
78-
// custom retry client
79+
// custom retry client with reduced retries and shorter backoff
80+
// to avoid excessive delays when regions are unreachable
7981
retryClient := retryablehttp.NewClient()
80-
retryClient.RetryMax = 5
82+
retryClient.RetryMax = 2 // reduced from 5 to avoid long delays on unreachable regions
83+
retryClient.RetryWaitMax = 10 * time.Second // cap at 10s instead of 30s
8184
retryClient.Logger = zerologadapter.New(log.Logger)
8285
c.awsConfigOptions = append(c.awsConfigOptions, config.WithHTTPClient(retryClient.StandardClient()))
8386

@@ -328,6 +331,10 @@ func (h *AwsConnection) Regions() ([]string, error) {
328331
svc := h.Ec2(h.cfg.Region)
329332
ctx := context.Background()
330333

334+
// DescribeRegions works to get the list of enabled regions for the account ( each account of organization)
335+
// but this does not mean the respective service endpoint is available in that region. They will timeout instead of failing fast
336+
// (e.g. EKS,KMS,Sagemaker is for example not available in ap-southeast-1 etc)
337+
// This also does not cover SCPs that might block access to certain regions.
331338
res, err := svc.DescribeRegions(ctx, &ec2.DescribeRegionsInput{})
332339
if err != nil {
333340
log.Warn().Err(err).Msg("unable to describe regions")

providers/aws/provider/provider.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,6 @@ func (s *Service) ParseCLI(req *plugin.ParseCLIReq) (*plugin.ParseCLIRes, error)
5454
return &plugin.ParseCLIRes{Asset: asset}, nil
5555
}
5656

57-
inventoryConfig := &inventory.Config{
58-
Type: req.Connector,
59-
}
6057
// discovery flags
6158
discoverTargets := []string{}
6259
if x, ok := flags["discover"]; ok && len(x.Array) != 0 {
@@ -67,7 +64,14 @@ func (s *Service) ParseCLI(req *plugin.ParseCLIReq) (*plugin.ParseCLIRes, error)
6764
}
6865
filterOpts := parseFlagsToFiltersOpts(flags)
6966

70-
inventoryConfig.Discover = &inventory.Discovery{Targets: discoverTargets, Filter: filterOpts}
67+
if len(discoverTargets) == 0 {
68+
discoverTargets = []string{resources.DiscoveryAuto}
69+
}
70+
71+
inventoryConfig := &inventory.Config{
72+
Type: req.Connector,
73+
Discover: &inventory.Discovery{Targets: discoverTargets, Filter: filterOpts},
74+
}
7175
asset := inventory.Asset{
7276
Connections: []*inventory.Config{inventoryConfig},
7377
Options: opts,
@@ -289,7 +293,7 @@ func (s *Service) detect(asset *inventory.Asset, conn plugin.Connection) error {
289293
}
290294

291295
func (s *Service) discover(conn *connection.AwsConnection) (*inventory.Inventory, error) {
292-
if conn.Conf.Discover == nil {
296+
if len(conn.Conf.GetDiscover().GetTargets()) == 0 {
293297
return nil, nil
294298
}
295299

providers/aws/resources/aws.lr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2739,7 +2739,7 @@ aws.rds.dbcluster @defaults("id region engine engineVersion") {
27392739
engine string
27402740
// Version of the database engine for this DB cluster
27412741
engineVersion string
2742-
// Whether the cluster is publicly accessible
2742+
// Whether the cluster is publicly accessible. Note: This will only return a value for non-Aurora Multi-AZ DB clusters
27432743
publiclyAccessible bool
27442744
// Whether the cluster is a Multi-AZ deployment
27452745
multiAZ bool
@@ -2847,7 +2847,7 @@ aws.rds.dbinstance @defaults("id region engine engineVersion") {
28472847
region string
28482848
// Availability zone where the instance exists
28492849
availabilityZone string
2850-
// Whether the instance is publicly accessible. Note: This will only return a value for non-Aurora Multi-AZ DB clusters
2850+
// Whether the instance is publicly accessible
28512851
publiclyAccessible bool
28522852
// List of log types the instance is configured to export to CloudWatch logs
28532853
enabledCloudwatchLogsExports []string

providers/aws/resources/aws_s3.go

Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -57,51 +57,48 @@ func (a *mqlAwsS3) id() (string, error) {
5757

5858
func (a *mqlAwsS3) buckets() ([]any, error) {
5959
conn := a.MqlRuntime.Connection.(*connection.AwsConnection)
60-
61-
svc := conn.S3("")
6260
ctx := context.Background()
6361

64-
totalBuckets := make([]s3types.Bucket, 0)
65-
params := &s3.ListBucketsInput{}
66-
paginator := s3.NewListBucketsPaginator(svc, params, func(o *s3.ListBucketsPaginatorOptions) {
67-
o.Limit = 100
68-
})
69-
for paginator.HasMorePages() {
70-
output, err := paginator.NextPage(ctx)
71-
if err != nil {
72-
return nil, err
73-
}
74-
totalBuckets = append(totalBuckets, output.Buckets...)
62+
configuredRegions, err := conn.Regions()
63+
if err != nil {
64+
return nil, err
7565
}
7666

77-
res := []any{}
78-
for _, bucket := range totalBuckets {
79-
location, err := svc.GetBucketLocation(ctx, &s3.GetBucketLocationInput{
80-
Bucket: bucket.Name,
67+
type bucketWithRegion struct {
68+
bucket s3types.Bucket
69+
region string
70+
}
71+
var bucketsWithRegions []bucketWithRegion
72+
73+
for _, region := range configuredRegions {
74+
svc := conn.S3(region)
75+
log.Debug().Str("region", region).Msg("listing S3 buckets in region")
76+
params := &s3.ListBucketsInput{BucketRegion: aws.String(region)}
77+
paginator := s3.NewListBucketsPaginator(svc, params, func(o *s3.ListBucketsPaginatorOptions) {
78+
o.Limit = 100
8179
})
82-
if err != nil {
83-
log.Error().Err(err).Str("bucket", *bucket.Name).Msg("Could not get bucket location")
84-
continue
85-
}
86-
if location == nil {
87-
log.Error().Err(err).Str("bucket", *bucket.Name).Msg("Could not get bucket location (returned null)")
88-
continue
80+
for paginator.HasMorePages() {
81+
output, err := paginator.NextPage(ctx)
82+
if err != nil {
83+
log.Warn().Err(err).Str("region", region).Msg("could not list S3 buckets in region")
84+
break
85+
}
86+
for _, bucket := range output.Buckets {
87+
bucketsWithRegions = append(bucketsWithRegions, bucketWithRegion{bucket: bucket, region: region})
88+
}
8989
}
90+
}
9091

91-
region := string(location.LocationConstraint)
92-
// us-east-1 returns "" therefore we set it explicitly
93-
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLocation.html#API_GetBucketLocation_ResponseSyntax
94-
if region == "" {
95-
region = "us-east-1"
96-
}
92+
res := []any{}
93+
for _, bwr := range bucketsWithRegions {
9794
mqlS3Bucket, err := CreateResource(a.MqlRuntime, ResourceAwsS3Bucket,
9895
map[string]*llx.RawData{
99-
"name": llx.StringDataPtr(bucket.Name),
100-
"arn": llx.StringData(fmt.Sprintf(s3ArnPattern, convert.ToValue(bucket.Name))),
96+
"name": llx.StringDataPtr(bwr.bucket.Name),
97+
"arn": llx.StringData(fmt.Sprintf(s3ArnPattern, convert.ToValue(bwr.bucket.Name))),
10198
"exists": llx.BoolData(true),
102-
"location": llx.StringData(region),
103-
"createdTime": llx.TimeDataPtr(bucket.CreationDate),
104-
"createdAt": llx.TimeDataPtr(bucket.CreationDate),
99+
"location": llx.StringData(bwr.region),
100+
"createdTime": llx.TimeDataPtr(bwr.bucket.CreationDate),
101+
"createdAt": llx.TimeDataPtr(bwr.bucket.CreationDate),
105102
})
106103
if err != nil {
107104
return nil, err
@@ -115,7 +112,6 @@ func (a *mqlAwsS3) buckets() ([]any, error) {
115112
}
116113

117114
if conn.Filters.General.IsFilteredOutByTags(mapStringInterfaceToStringString(tags)) {
118-
log.Debug().Interface("log_group", bucket.Name).Msg("excluding log group due to filters")
119115
continue
120116
}
121117
}

providers/aws/resources/discovery.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,7 @@ func Discover(runtime *plugin.Runtime) (*inventory.Inventory, error) {
151151
}
152152

153153
func getDiscoveryTargets(config *inventory.Config) []string {
154-
targets := config.Discover.Targets
155-
156-
if len(targets) == 0 {
157-
return Auto
158-
}
154+
targets := config.GetDiscover().GetTargets()
159155

160156
if stringx.Contains(targets, DiscoveryAll) {
161157
// return the All list + All Api Resources list

providers/aws/resources/discovery_conversion.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,17 @@ func MqlObjectToAsset(account string, mqlObject mqlObject, conn *connection.AwsC
7575
return nil
7676
}
7777
platformid := MondooObjectID(mqlObject.awsObject)
78-
t := conn.Conf
79-
t.PlatformId = platformid
78+
// Clone first to avoid mutating the parent connection's config (race condition)
79+
clonedConfig := conn.Conf.Clone(inventory.WithoutDiscovery(), inventory.WithParentConnectionId(conn.Conf.Id), inventory.WithFilters())
80+
clonedConfig.PlatformId = platformid
8081
platformIds := []string{platformid, mqlObject.awsObject.arn}
8182
platformIds = append(platformIds, mqlObject.platformIds...)
8283
return &inventory.Asset{
8384
PlatformIds: platformIds,
8485
Name: mqlObject.name,
8586
Platform: connection.GetPlatformForObject(platformName, account),
8687
Labels: mqlObject.labels,
87-
Connections: []*inventory.Config{t.Clone(inventory.WithoutDiscovery(), inventory.WithParentConnectionId(t.Id))},
88+
Connections: []*inventory.Config{clonedConfig},
8889
Options: conn.ConnectionOptions(),
8990
}
9091
}
@@ -226,11 +227,14 @@ func accountAsset(conn *connection.AwsConnection, awsAccount *mqlAwsAccount) *in
226227

227228
id := "//platformid.api.mondoo.app/runtime/aws/accounts/" + accountId
228229
accountArn := "arn:aws:sts::" + accountId
230+
// Clone first to avoid mutating the parent connection's config
231+
clonedConfig := conn.Conf.Clone(inventory.WithoutDiscovery(), inventory.WithParentConnectionId(conn.Conf.Id), inventory.WithFilters())
232+
clonedConfig.PlatformId = id
229233
return &inventory.Asset{
230234
PlatformIds: []string{id, accountArn},
231235
Name: name,
232236
Platform: connection.GetPlatformForObject("", accountId),
233-
Connections: []*inventory.Config{conn.Conf.Clone(inventory.WithoutDiscovery(), inventory.WithParentConnectionId(conn.Conf.Id), inventory.WithFilters())},
237+
Connections: []*inventory.Config{clonedConfig},
234238
Options: conn.ConnectionOptions(),
235239
}
236240
}

0 commit comments

Comments
 (0)