Skip to content

Commit

Permalink
Implement SES resources types
Browse files Browse the repository at this point in the history
  • Loading branch information
james03160927 committed Feb 15, 2024
1 parent d35d088 commit 342d131
Show file tree
Hide file tree
Showing 16 changed files with 1,161 additions and 65 deletions.
140 changes: 75 additions & 65 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ Cloud-nuke suppports 🔎 inspecting and 🔥💀 deleting the following AWS res
| Security Hub | Hubs |
| Security Hub | Members |
| Security Hub | Administrators |
| SES | SES configuration set |
| SES | SES email template |
| SES | SES Identity |
| SES | SES receipt rule set |
| SES | SES receipt filter |
| AWS Certificate Manager | Certificates |
| CodeDeploy | Applications |

Expand Down Expand Up @@ -307,74 +312,74 @@ resources.
package main

import (
"context"
"fmt"
"time"

"github.com/aws/aws-sdk-go/aws"
nuke_aws "github.com/gruntwork-io/cloud-nuke/aws"
nuke_config "github.com/gruntwork-io/cloud-nuke/config"
"github.com/gruntwork-io/cloud-nuke/externalcreds"
"context"
"fmt"
"time"

"github.com/aws/aws-sdk-go/aws"
nuke_aws "github.com/gruntwork-io/cloud-nuke/aws"
nuke_config "github.com/gruntwork-io/cloud-nuke/config"
"github.com/gruntwork-io/cloud-nuke/externalcreds"
)

func main() {
// You can scan multiple regions at once, or just pass a single region for speed
targetRegions := []string{"us-east-1", "us-west-1", "us-west-2"}
excludeRegions := []string{}
// You can simultaneously target multiple resource types as well
resourceTypes := []string{"ec2", "vpc"}
excludeResourceTypes := []string{}
// excludeAfter is parsed identically to the --older-than flag
excludeAfter := time.Now()
// an optional start time- can pass null if the filter is not required
includeAfter := time.Now().AddDate(-1, 0, 0)

// Any custom settings you want
myCustomConfig := &aws.Config{}
myCustomConfig.WithMaxRetries(3)
myCustomConfig.WithLogLevel(aws.LogDebugWithRequestErrors)
// Optionally, set custom credentials
// myCustomConfig.WithCredentials()

// Be sure to set your config prior to calling any library methods such as NewQuery
externalcreds.Set(myCustomConfig)
// this config can be configured to add include/exclude rule to filter the resources- for all resources pass an empty struct
nukeConfig := nuke_config.Config{}

// NewQuery is a convenience method for configuring parameters you want to pass to your resource search
query, err := nuke_aws.NewQuery(
targetRegions,
excludeRegions,
resourceTypes,
excludeResourceTypes,
&excludeAfter,
&includeAfter,
false,
)
if err != nil {
fmt.Println(err)
}

// GetAllResources still returns *AwsAccountResources, but this struct has been extended with several
// convenience methods for quickly determining if resources exist in a given region
accountResources, err := nuke_aws.GetAllResources(context.Background(), query, nukeConfig)
if err != nil {
fmt.Println(err)
}
// You can call GetRegion to examine a single region's resources
usWest1Resources := accountResources.GetRegion("us-west-1")
// Then interrogate them with the new methods:
// Count the number of any resource type within the region
countOfEc2InUsWest1 := usWest1Resources.CountOfResourceType("ec2")
fmt.Printf("countOfEc2InUsWest1: %d\n", countOfEc2InUsWest1)
// countOfEc2InUsWest1: 2
fmt.Printf("usWest1Resources.ResourceTypePresent(\"ec2\"):%b\n", usWest1Resources.ResourceTypePresent("ec2"))
// usWest1Resources.ResourceTypePresent("ec2"): true
// Get all the resource identifiers for a given resource type
// In this example, we're only looking for ec2 instances
resourceIds := usWest1Resources.IdentifiersForResourceType("ec2")
fmt.Printf("resourceIds: %s", resourceIds)
// resourceIds: [i-0c5d16c3ef28dda24 i-09d9739e1f4d27814]
// You can scan multiple regions at once, or just pass a single region for speed
targetRegions := []string{"us-east-1", "us-west-1", "us-west-2"}
excludeRegions := []string{}
// You can simultaneously target multiple resource types as well
resourceTypes := []string{"ec2", "vpc"}
excludeResourceTypes := []string{}
// excludeAfter is parsed identically to the --older-than flag
excludeAfter := time.Now()
// an optional start time- can pass null if the filter is not required
includeAfter := time.Now().AddDate(-1, 0, 0)

// Any custom settings you want
myCustomConfig := &aws.Config{}
myCustomConfig.WithMaxRetries(3)
myCustomConfig.WithLogLevel(aws.LogDebugWithRequestErrors)
// Optionally, set custom credentials
// myCustomConfig.WithCredentials()

// Be sure to set your config prior to calling any library methods such as NewQuery
externalcreds.Set(myCustomConfig)
// this config can be configured to add include/exclude rule to filter the resources- for all resources pass an empty struct
nukeConfig := nuke_config.Config{}

// NewQuery is a convenience method for configuring parameters you want to pass to your resource search
query, err := nuke_aws.NewQuery(
targetRegions,
excludeRegions,
resourceTypes,
excludeResourceTypes,
&excludeAfter,
&includeAfter,
false,
)
if err != nil {
fmt.Println(err)
}

// GetAllResources still returns *AwsAccountResources, but this struct has been extended with several
// convenience methods for quickly determining if resources exist in a given region
accountResources, err := nuke_aws.GetAllResources(context.Background(), query, nukeConfig)
if err != nil {
fmt.Println(err)
}
// You can call GetRegion to examine a single region's resources
usWest1Resources := accountResources.GetRegion("us-west-1")
// Then interrogate them with the new methods:
// Count the number of any resource type within the region
countOfEc2InUsWest1 := usWest1Resources.CountOfResourceType("ec2")
fmt.Printf("countOfEc2InUsWest1: %d\n", countOfEc2InUsWest1)
// countOfEc2InUsWest1: 2
fmt.Printf("usWest1Resources.ResourceTypePresent(\"ec2\"):%b\n", usWest1Resources.ResourceTypePresent("ec2"))
// usWest1Resources.ResourceTypePresent("ec2"): true
// Get all the resource identifiers for a given resource type
// In this example, we're only looking for ec2 instances
resourceIds := usWest1Resources.IdentifiersForResourceType("ec2")
fmt.Printf("resourceIds: %s", resourceIds)
// resourceIds: [i-0c5d16c3ef28dda24 i-09d9739e1f4d27814]
}

```
Expand Down Expand Up @@ -541,6 +546,11 @@ of the file that are supported are listed here.
| opensearchdomain | OpenSearchDomain | ✅ (Domain Name) | ✅ (First Seen Tag Time) | ❌ |
| redshift | Redshift | ✅ (Cluster Identifier) | ✅ (Creation Time) | ❌ |
| s3 | s3 | ✅ (Bucket Name) | ✅ (Creation Time) | ✅ |
| ses-configuration-set | SesConfigurationset | ✅ (Configuration set name) | ❌ | ❌ |
| ses-email-template | SesEmailTemplates | ✅ (Template Name) | ✅ (Creation Time) | ❌ |
| ses-identity | SesIdentity | ✅ (Identity -Mail/Domain) | ❌ | ❌ |
| ses-receipt-rule-set | SesReceiptRuleSet | ✅ (Receipt Rule Set Name) | ✅ (Creation Time) | ❌ |
| ses-receipt-filter | SesReceiptFilter | ✅ (Receipt Filter Name) | ❌ | ❌ |
| snstopic | SNS | ✅ (Topic Name) | ✅ (First Seen Tag Time) | ❌ |
| sqs | SQS | ✅ (Queue Name) | ✅ (Creation Time) | ❌ |
| sagemaker-notebook-smni | SageMakerNotebook | ✅ (Notebook Instnace Name) | ✅ (Creation Time) | ❌ |
Expand Down
5 changes: 5 additions & 0 deletions aws/resource_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ func getRegisteredRegionalResources() []AwsResource {
&resources.SageMakerNotebookInstances{},
&resources.SecretsManagerSecrets{},
&resources.SecurityHub{},
&resources.SesConfigurationSet{},
&resources.SesEmailTemplates{},
&resources.SesIdentities{},
&resources.SesReceiptRule{},
&resources.SesReceiptFilter{},
&resources.Snapshots{},
&resources.SNSTopic{},
&resources.SqsQueue{},
Expand Down
68 changes: 68 additions & 0 deletions aws/resources/ses_configuration_set.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package resources

import (
"context"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ses"
"github.com/gruntwork-io/cloud-nuke/config"
"github.com/gruntwork-io/cloud-nuke/logging"
"github.com/gruntwork-io/cloud-nuke/report"
"github.com/gruntwork-io/go-commons/errors"
)

// Returns a formatted string of ses-configuartion set names
func (s *SesConfigurationSet) getAll(c context.Context, configObj config.Config) ([]*string, error) {
// Remove defalt route table, that will be deleted along with its TransitGateway
param := &ses.ListConfigurationSetsInput{}

result, err := s.Client.ListConfigurationSets(param)
if err != nil {
return nil, errors.WithStackTrace(err)
}

var setnames []*string
for _, set := range result.ConfigurationSets {
if configObj.SESConfigurationSet.ShouldInclude(config.ResourceValue{Name: set.Name}) {
setnames = append(setnames, set.Name)
}
}

return setnames, nil
}

// Deletes all sets
func (s *SesConfigurationSet) nukeAll(sets []*string) error {
if len(sets) == 0 {
logging.Debugf("No SES configuartion sets to nuke in region %s", s.Region)
return nil
}

logging.Debugf("Deleting all SES configuartion sets in region %s", s.Region)
var deletedSets []*string

for _, set := range sets {
_, err := s.Client.DeleteConfigurationSet(&ses.DeleteConfigurationSetInput{
ConfigurationSetName: set,
})

// Record status of this resource
e := report.Entry{
Identifier: aws.StringValue(set),
ResourceType: "SES configuartion set",
Error: err,
}
report.Record(e)

if err != nil {
logging.Debugf("[Failed] %s", err)
} else {
deletedSets = append(deletedSets, set)
logging.Debugf("Deleted SES configuartion set: %s", *set)
}
}

logging.Debugf("[OK] %d SES configuartion set(s) deleted in %s", len(deletedSets), s.Region)

return nil
}
93 changes: 93 additions & 0 deletions aws/resources/ses_configuration_set_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package resources

import (
"context"
"regexp"
"testing"

"github.com/aws/aws-sdk-go-v2/aws"
awsgo "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ses"
"github.com/aws/aws-sdk-go/service/ses/sesiface"
"github.com/gruntwork-io/cloud-nuke/config"
"github.com/stretchr/testify/require"
)

type mockedSesConfigurationSet struct {
sesiface.SESAPI
DeleteConfigurationSetOutput ses.DeleteConfigurationSetOutput
ListConfigurationSetsOutput ses.ListConfigurationSetsOutput
}

func (m mockedSesConfigurationSet) ListConfigurationSets(input *ses.ListConfigurationSetsInput) (*ses.ListConfigurationSetsOutput, error) {
return &m.ListConfigurationSetsOutput, nil
}

func (m mockedSesConfigurationSet) DeleteConfigurationSet(*ses.DeleteConfigurationSetInput) (*ses.DeleteConfigurationSetOutput, error) {
return &m.DeleteConfigurationSetOutput, nil
}

var (
id1 = "test-id-1"
id2 = "test-id-2"
configurationsSet1 = ses.ConfigurationSet{
Name: awsgo.String(id1),
}
configurationsSet2 = ses.ConfigurationSet{
Name: awsgo.String(id2),
}
)

func TestSesConfigurationSet_GetAll(t *testing.T) {
t.Parallel()

identity := SesConfigurationSet{
Client: mockedSesConfigurationSet{
ListConfigurationSetsOutput: ses.ListConfigurationSetsOutput{
ConfigurationSets: []*ses.ConfigurationSet{
&configurationsSet1,
&configurationsSet2,
},
},
},
}

tests := map[string]struct {
configObj config.ResourceType
expected []string
}{
"emptyFilter": {
configObj: config.ResourceType{},
expected: []string{id1, id2},
},
"nameExclusionFilter": {
configObj: config.ResourceType{
ExcludeRule: config.FilterRule{
NamesRegExp: []config.Expression{{
RE: *regexp.MustCompile(id2),
}}},
},
expected: []string{id1},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
names, err := identity.getAll(context.Background(), config.Config{
SESConfigurationSet: tc.configObj,
})
require.NoError(t, err)
require.Equal(t, tc.expected, awsgo.StringValueSlice(names))
})
}
}

func TestSesConfigurationSet_NukeAll(t *testing.T) {
t.Parallel()

identity := SesConfigurationSet{
Client: mockedSesConfigurationSet{},
}

err := identity.nukeAll([]*string{aws.String("test")})
require.NoError(t, err)
}
58 changes: 58 additions & 0 deletions aws/resources/ses_configuration_set_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package resources

import (
"context"

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

// SesConfigurationSet - represents all SES configuartion set
type SesConfigurationSet struct {
BaseAwsResource
Client sesiface.SESAPI
Region string
Ids []string
}

func (scs *SesConfigurationSet) Init(session *session.Session) {
scs.Client = ses.New(session)
}

// ResourceName - the simple name of the aws resource
func (scs *SesConfigurationSet) ResourceName() string {
return "ses-configuration-set"
}

// MaxBatchSize - Tentative batch size to ensure AWS doesn't throttle
func (scs *SesConfigurationSet) MaxBatchSize() int {
return maxBatchSize
}

// ResourceIdentifiers - The Ids of the configuration set
func (scs *SesConfigurationSet) ResourceIdentifiers() []string {
return scs.Ids
}

func (scs *SesConfigurationSet) GetAndSetIdentifiers(c context.Context, configObj config.Config) ([]string, error) {
identifiers, err := scs.getAll(c, configObj)
if err != nil {
return nil, err
}

scs.Ids = awsgo.StringValueSlice(identifiers)
return scs.Ids, nil
}

// Nuke - nuke 'em all!!!
func (scs *SesConfigurationSet) Nuke(identifiers []string) error {
if err := scs.nukeAll(awsgo.StringSlice(identifiers)); err != nil {
return errors.WithStackTrace(err)
}

return nil
}
Loading

0 comments on commit 342d131

Please sign in to comment.