Skip to content

Commit

Permalink
Implement support for GuardDuty Detectors
Browse files Browse the repository at this point in the history
  • Loading branch information
zackproser committed Jul 6, 2022
1 parent a2f5987 commit 219b80e
Show file tree
Hide file tree
Showing 8 changed files with 359 additions and 12 deletions.
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{}
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)
}
}

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

return nil
}
Loading

0 comments on commit 219b80e

Please sign in to comment.