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

Implement support for GuardDuty Detectors #320

Merged
merged 3 commits into from
Jul 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ The currently supported functionality includes:
- Inspecting and deleting all IAM OpenID Connect Providers
- Inspecting and deleting all Customer managed keys from Key Management Service in an AWS account
- Inspecting and deleting all CloudWatch Log Groups in an AWS Account
- Inspecting and deleting all GuardDuty Detectors in an AWS Account

### BEWARE!

Expand Down
37 changes: 25 additions & 12 deletions aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func getRandomRegionWithExclusions(regionsToExclude []string) (string, error) {
rand.Seed(time.Now().UnixNano())

// exclude from "allRegions"
var exclusions = make(map[string]string)
exclusions := make(map[string]string)
for _, region := range regionsToExclude {
exclusions[region] = region
}
Expand Down Expand Up @@ -217,7 +217,7 @@ func GetAllResources(targetRegions []string, excludeAfter time.Time, resourceTyp

count := 1
totalRegions := len(targetRegions)
var resourcesCache = map[string]map[string][]*string{}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Switched to https://github.com/mvdan/gofumpt for auto-formatting, so that's why some of these vars and extra lines are getting cleaned up.

resourcesCache := map[string]map[string][]*string{}

for _, region := range targetRegions {
// The "global" region case is handled outside this loop
Expand All @@ -228,9 +228,9 @@ func GetAllResources(targetRegions []string, excludeAfter time.Time, resourceTyp
logging.Logger.Infof("Checking region [%d/%d]: %s", count, totalRegions, region)

session, err := session.NewSession(&awsgo.Config{
Region: awsgo.String(region)},
Region: awsgo.String(region),
},
)

if err != nil {
return nil, errors.WithStackTrace(err)
}
Expand Down Expand Up @@ -517,7 +517,6 @@ func GetAllResources(targetRegions []string, excludeAfter time.Time, resourceTyp
dbInstances := DBInstances{}
if IsNukeable(dbInstances.ResourceName(), resourceTypes) {
instanceNames, err := getAllRdsInstances(session, excludeAfter)

if err != nil {
return nil, errors.WithStackTrace(err)
}
Expand All @@ -535,7 +534,6 @@ func GetAllResources(targetRegions []string, excludeAfter time.Time, resourceTyp
dbClusters := DBClusters{}
if IsNukeable(dbClusters.ResourceName(), resourceTypes) {
clustersNames, err := getAllRdsClusters(session, excludeAfter)

if err != nil {
return nil, errors.WithStackTrace(err)
}
Expand All @@ -551,7 +549,6 @@ func GetAllResources(targetRegions []string, excludeAfter time.Time, resourceTyp
lambdaFunctions := LambdaFunctions{}
if IsNukeable(lambdaFunctions.ResourceName(), resourceTypes) {
lambdaFunctionNames, err := getAllLambdaFunctions(session, excludeAfter, configObj, lambdaFunctions.MaxBatchSize())

if err != nil {
return nil, errors.WithStackTrace(err)
}
Expand Down Expand Up @@ -663,7 +660,6 @@ func GetAllResources(targetRegions []string, excludeAfter time.Time, resourceTyp
DynamoDB := DynamoDB{}
if IsNukeable(DynamoDB.ResourceName(), resourceTypes) {
tablenames, err := getAllDynamoTables(session, excludeAfter, configObj, DynamoDB)

if err != nil {
return nil, errors.WithStackTrace(err)
}
Expand Down Expand Up @@ -721,10 +717,26 @@ func GetAllResources(targetRegions []string, excludeAfter time.Time, resourceTyp
}
// End KMS Customer managed keys

// GuardDuty detectors
guardDutyDetectors := GuardDuty{}
if IsNukeable(guardDutyDetectors.ResourceName(), resourceTypes) {
detectors, err := getAllGuardDutyDetectors(session, excludeAfter, configObj, guardDutyDetectors.MaxBatchSize())
if err != nil {
return nil, errors.WithStackTrace(err)
}
if len(detectors) > 0 {
guardDutyDetectors.detectorIds = detectors
resourcesInRegion.Resources = append(resourcesInRegion.Resources, guardDutyDetectors)
}

}
// End GuardDuty detectors

if len(resourcesInRegion.Resources) > 0 {
account.Resources[region] = resourcesInRegion
}
count++

}

// Global Resources - These resources are global and do not belong to a specific region
Expand All @@ -735,7 +747,8 @@ func GetAllResources(targetRegions []string, excludeAfter time.Time, resourceTyp
// As there is no actual region named global we have to pick a valid one just to create the session
sessionRegion := defaultRegion
session, err := session.NewSession(&awsgo.Config{
Region: awsgo.String(sessionRegion)},
Region: awsgo.String(sessionRegion),
},
)
if err != nil {
return nil, errors.WithStackTrace(err)
Expand All @@ -747,7 +760,6 @@ func GetAllResources(targetRegions []string, excludeAfter time.Time, resourceTyp
iamUsers := IAMUsers{}
if IsNukeable(iamUsers.ResourceName(), resourceTypes) {
userNames, err := getAllIamUsers(session, excludeAfter, configObj)

if err != nil {
return nil, errors.WithStackTrace(err)
}
Expand Down Expand Up @@ -816,6 +828,7 @@ func ListResourceTypes() []string {
OIDCProviders{}.ResourceName(),
KmsCustomerKeys{}.ResourceName(),
CloudWatchLogGroups{}.ResourceName(),
GuardDuty{}.ResourceName(),
}
sort.Strings(resourceTypes)
return resourceTypes
Expand Down Expand Up @@ -881,9 +894,9 @@ func NukeAllResources(account *AwsAccountResources, regions []string) error {
}

session, err := session.NewSession(&awsgo.Config{
Region: awsgo.String(sessionRegion)},
Region: awsgo.String(sessionRegion),
},
)

if err != nil {
return errors.WithStackTrace(err)
}
Expand Down
124 changes: 124 additions & 0 deletions aws/guardduty.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package aws

import (
"time"

"github.com/aws/aws-sdk-go/aws"
awsgo "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/guardduty"
"github.com/gruntwork-io/cloud-nuke/config"
"github.com/gruntwork-io/cloud-nuke/logging"
"github.com/gruntwork-io/go-commons/errors"
)

type DetectorOutputWithID struct {
ID *string
Output *guardduty.GetDetectorOutput
}

func getAllGuardDutyDetectors(session *session.Session, excludeAfter time.Time, configObj config.Config, batchSize int) ([]string, error) {
svc := guardduty.New(session)

var result []*string
var annotatedDetectors []*DetectorOutputWithID
var detectorIdsToInclude []string

var next *string = nil
for {
list, err := svc.ListDetectors(&guardduty.ListDetectorsInput{
MaxResults: awsgo.Int64(int64(batchSize)),
NextToken: next,
})
if err != nil {
return nil, errors.WithStackTrace(err)
}

result = append(result, list.DetectorIds...)
if list.NextToken == nil || len(list.DetectorIds) == 0 {
break
}
next = list.NextToken
}

// Due to the ListDetectors method only returning the Ids of found detectors, we need to further enrich our data about
// each detector with a separate call to GetDetector for metadata including when it was created, which we need to make the
// determination about whether or not the given detector should be included
for _, detectorId := range result {

detector, getDetectorErr := svc.GetDetector(&guardduty.GetDetectorInput{
DetectorId: detectorId,
})

if getDetectorErr != nil {
return nil, errors.WithStackTrace(getDetectorErr)
}

detectorOutputWithID := &DetectorOutputWithID{
ID: detectorId,
Output: detector,
}

annotatedDetectors = append(annotatedDetectors, detectorOutputWithID)
}

for _, detector := range annotatedDetectors {
if shouldIncludeDetector(detector, excludeAfter, configObj) {
detectorIdsToInclude = append(detectorIdsToInclude, aws.StringValue(detector.ID))
}
}

return detectorIdsToInclude, nil
}

func shouldIncludeDetector(detector *DetectorOutputWithID, excludeAfter time.Time, configObj config.Config) bool {
if detector == nil {
return false
}

detectorCreatedAt := aws.StringValue(detector.Output.CreatedAt)

createdAtDateTime, err := time.Parse(time.RFC3339, detectorCreatedAt)
if err != nil {
logging.Logger.Warnf("Could not parse createdAt timestamp (%s) of GuardDuty detector %s. Excluding from delete.", detectorCreatedAt, awsgo.StringValue(detector.ID))
}

if excludeAfter.Before(createdAtDateTime) {
return false
}

return true
}

func nukeAllGuardDutyDetectors(session *session.Session, detectorIds []string) error {
svc := guardduty.New(session)

if len(detectorIds) == 0 {
logging.Logger.Infof("No GuardDuty detectors to nuke in region %s", *session.Config.Region)

return nil
}

logging.Logger.Infof("Deleting all GuardDuty detectors in region %s", *session.Config.Region)

deletedIds := []string{}

for _, detectorId := range detectorIds {
params := &guardduty.DeleteDetectorInput{
DetectorId: aws.String(detectorId),
}

_, err := svc.DeleteDetector(params)

if err != nil {
logging.Logger.Errorf("[Failed] %s: %s", detectorId, err)
} else {
deletedIds = append(deletedIds, detectorId)
logging.Logger.Infof("Deleted GuardDuty detector: %s", detectorId)
}
}
Comment on lines +106 to +119
Copy link
Contributor

Choose a reason for hiding this comment

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

This is probably ok since AFAIK there can only be a limited number of detectors per region, but recommend using the same pattern as NAT Gateway (https://github.com/gruntwork-io/cloud-nuke/blob/master/aws/nat_gateway.go#L63) to implement concurrent deletion of the detectors.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, also saw this pattern in the cloudwatch_loggroup that you added! I'll file a ticket to do this in a follow-up PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Filed #324 to track.


logging.Logger.Infof("[OK] %d GuardDuty Detector(s) deleted in %s", len(deletedIds), *session.Config.Region)

return nil
}
Loading