-
-
Notifications
You must be signed in to change notification settings - Fork 356
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6322be8
commit b6cdebe
Showing
13 changed files
with
468 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package aws | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/aws/session" | ||
"github.com/aws/aws-sdk-go/service/cloudtrail" | ||
"github.com/gruntwork-io/cloud-nuke/config" | ||
"github.com/gruntwork-io/cloud-nuke/logging" | ||
"github.com/gruntwork-io/go-commons/errors" | ||
) | ||
|
||
func getAllCloudtrailTrails(session *session.Session, excludeAfter time.Time, configObj config.Config) ([]*string, error) { | ||
svc := cloudtrail.New(session) | ||
|
||
param := &cloudtrail.ListTrailsInput{} | ||
|
||
trailIds := []*string{} | ||
|
||
paginator := func(output *cloudtrail.ListTrailsOutput, lastPage bool) bool { | ||
for _, trailInfo := range output.Trails { | ||
if shouldIncludeCloudtrailTrail(trailInfo, configObj) { | ||
trailIds = append(trailIds, trailInfo.TrailARN) | ||
} | ||
} | ||
return !lastPage | ||
} | ||
|
||
err := svc.ListTrailsPages(param, paginator) | ||
if err != nil { | ||
return trailIds, errors.WithStackTrace(err) | ||
} | ||
|
||
return trailIds, nil | ||
} | ||
|
||
func shouldIncludeCloudtrailTrail(trail *cloudtrail.TrailInfo, configObj config.Config) bool { | ||
if trail == nil { | ||
return false | ||
} | ||
|
||
return config.ShouldInclude( | ||
aws.StringValue(trail.Name), | ||
configObj.CloudtrailTrail.IncludeRule.NamesRegExp, | ||
configObj.CloudtrailTrail.ExcludeRule.NamesRegExp, | ||
) | ||
} | ||
|
||
func nukeAllCloudTrailTrails(session *session.Session, arns []*string) error { | ||
svc := cloudtrail.New(session) | ||
|
||
if len(arns) == 0 { | ||
logging.Logger.Infof("No Cloudtrail Trails to nuke in region %s", *session.Config.Region) | ||
return nil | ||
} | ||
|
||
logging.Logger.Infof("Deleting all Cloudtrail Trails in region %s", *session.Config.Region) | ||
var deletedArns []*string | ||
|
||
for _, arn := range arns { | ||
params := &cloudtrail.DeleteTrailInput{ | ||
Name: arn, | ||
} | ||
|
||
_, err := svc.DeleteTrail(params) | ||
if err != nil { | ||
logging.Logger.Errorf("[Failed] %s", err) | ||
} else { | ||
deletedArns = append(deletedArns, arn) | ||
logging.Logger.Infof("Deleted Cloudtrail Trail: %s", aws.StringValue(arn)) | ||
} | ||
} | ||
|
||
logging.Logger.Infof("[OK] %d Cloudtrail Trail deleted in %s", len(deletedArns), *session.Config.Region) | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
package aws | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
"testing" | ||
"time" | ||
|
||
"github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/aws/session" | ||
"github.com/aws/aws-sdk-go/service/cloudtrail" | ||
"github.com/aws/aws-sdk-go/service/s3" | ||
"github.com/aws/aws-sdk-go/service/sts" | ||
"github.com/gruntwork-io/cloud-nuke/config" | ||
"github.com/gruntwork-io/cloud-nuke/logging" | ||
"github.com/gruntwork-io/cloud-nuke/util" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestListCloudTrailTrails(t *testing.T) { | ||
t.Parallel() | ||
|
||
region, err := getRandomRegion() | ||
require.NoError(t, err) | ||
|
||
session, err := session.NewSession(&aws.Config{Region: aws.String(region)}) | ||
require.NoError(t, err) | ||
|
||
trailArn := createCloudTrailTrail(t, region) | ||
defer deleteCloudTrailTrail(t, region, trailArn, false) | ||
|
||
trailArns, err := getAllCloudtrailTrails(session, time.Now(), config.Config{}) | ||
require.NoError(t, err) | ||
assert.Contains(t, aws.StringValueSlice(trailArns), aws.StringValue(trailArn)) | ||
} | ||
|
||
func deleteCloudTrailTrail(t *testing.T, region string, trailARN *string, checkErr bool) { | ||
session, err := session.NewSession(&aws.Config{Region: aws.String(region)}) | ||
require.NoError(t, err) | ||
|
||
cloudtrailSvc := cloudtrail.New(session) | ||
|
||
param := &cloudtrail.DeleteTrailInput{ | ||
Name: trailARN, | ||
} | ||
|
||
_, deleteErr := cloudtrailSvc.DeleteTrail(param) | ||
if checkErr { | ||
require.NoError(t, deleteErr) | ||
} | ||
} | ||
|
||
func createCloudTrailTrail(t *testing.T, region string) *string { | ||
session, err := session.NewSession(&aws.Config{Region: aws.String(region)}) | ||
require.NoError(t, err) | ||
|
||
cloudtrailSvc := cloudtrail.New(session) | ||
s3Svc := s3.New(session) | ||
stsSvc := sts.New(session) | ||
|
||
name := strings.ToLower(fmt.Sprintf("cloud-nuke-test-%s-%s", util.UniqueID(), util.UniqueID())) | ||
|
||
logging.Logger.Debugf("Bucket: %s - creating", name) | ||
|
||
_, bucketCreateErr := s3Svc.CreateBucket(&s3.CreateBucketInput{ | ||
Bucket: aws.String(name), | ||
}) | ||
|
||
require.NoError(t, bucketCreateErr) | ||
|
||
waitErr := s3Svc.WaitUntilBucketExists( | ||
&s3.HeadBucketInput{ | ||
Bucket: aws.String(name), | ||
}, | ||
) | ||
|
||
require.NoError(t, waitErr) | ||
|
||
// Create and attach the expected S3 bucket policy that CloudTrail requires | ||
policyJson := ` | ||
{ | ||
"Version": "2012-10-17", | ||
"Statement": [ | ||
{ | ||
"Sid": "AWSCloudTrailAclCheck20150319", | ||
"Effect": "Allow", | ||
"Principal": {"Service": "cloudtrail.amazonaws.com"}, | ||
"Action": "s3:GetBucketAcl", | ||
"Resource": "arn:aws:s3:::%s", | ||
"Condition": { | ||
"StringEquals": { | ||
"aws:SourceArn": "arn:aws:cloudtrail:%s:%s:trail/%s" | ||
} | ||
} | ||
}, | ||
{ | ||
"Sid": "AWSCloudTrailWrite20150319", | ||
"Effect": "Allow", | ||
"Principal": {"Service": "cloudtrail.amazonaws.com"}, | ||
"Action": "s3:PutObject", | ||
"Resource": "arn:aws:s3:::%s/AWSLogs/%s/*", | ||
"Condition": { | ||
"StringEquals": { | ||
"s3:x-amz-acl": "bucket-owner-full-control", | ||
"aws:SourceArn": "arn:aws:cloudtrail:%s:%s:trail/%s" | ||
} | ||
} | ||
} | ||
] | ||
} | ||
` | ||
|
||
// Look up the current account ID so that we can interpolate it in the S3 bucket policy | ||
callerIdInput := &sts.GetCallerIdentityInput{} | ||
|
||
result, err := stsSvc.GetCallerIdentity(callerIdInput) | ||
|
||
require.NoError(t, err) | ||
|
||
renderedJson := fmt.Sprintf( | ||
policyJson, | ||
name, | ||
region, | ||
aws.StringValue(result.Account), | ||
name, | ||
name, | ||
aws.StringValue(result.Account), | ||
region, | ||
aws.StringValue(result.Account), | ||
name, | ||
) | ||
|
||
_, err = s3Svc.PutBucketPolicy(&s3.PutBucketPolicyInput{ | ||
Bucket: aws.String(name), | ||
Policy: aws.String(strings.TrimSpace(renderedJson)), | ||
}) | ||
|
||
require.NoError(t, err) | ||
|
||
// Add an arbitrary sleep to account for eventual consistency | ||
time.Sleep(15 * time.Second) | ||
|
||
param := &cloudtrail.CreateTrailInput{ | ||
Name: aws.String(name), | ||
S3BucketName: aws.String(name), | ||
} | ||
|
||
output, createTrailErr := cloudtrailSvc.CreateTrail(param) | ||
require.NoError(t, createTrailErr) | ||
|
||
return output.TrailARN | ||
} | ||
|
||
func TestNukeCloudTrailOne(t *testing.T) { | ||
t.Parallel() | ||
|
||
region, err := getRandomRegion() | ||
require.NoError(t, err) | ||
|
||
session, err := session.NewSession(&aws.Config{Region: aws.String(region)}) | ||
require.NoError(t, err) | ||
|
||
trailArn := createCloudTrailTrail(t, region) | ||
defer deleteCloudTrailTrail(t, region, trailArn, false) | ||
|
||
identifiers := []*string{trailArn} | ||
|
||
require.NoError( | ||
t, | ||
nukeAllCloudTrailTrails(session, identifiers), | ||
) | ||
|
||
assertCloudTrailTrailsDeleted(t, region, identifiers) | ||
} | ||
|
||
func TestNukeCloudTrailTrailMoreThanOne(t *testing.T) { | ||
t.Parallel() | ||
|
||
region, err := getRandomRegion() | ||
require.NoError(t, err) | ||
|
||
session, err := session.NewSession(&aws.Config{Region: aws.String(region)}) | ||
require.NoError(t, err) | ||
|
||
trailArns := []*string{} | ||
for i := 0; i < 3; i++ { | ||
// We ignore errors in the delete call here, because it is intended to be a stop gap in case there is a bug in nuke. | ||
trailArn := createCloudTrailTrail(t, region) | ||
defer deleteCloudTrailTrail(t, region, trailArn, false) | ||
trailArns = append(trailArns, trailArn) | ||
} | ||
|
||
require.NoError( | ||
t, | ||
nukeAllCloudTrailTrails(session, trailArns), | ||
) | ||
|
||
assertCloudTrailTrailsDeleted(t, region, trailArns) | ||
} | ||
|
||
func assertCloudTrailTrailsDeleted(t *testing.T, region string, identifiers []*string) { | ||
session, err := session.NewSession(&aws.Config{Region: aws.String(region)}) | ||
require.NoError(t, err) | ||
svc := cloudtrail.New(session) | ||
|
||
resp, err := svc.DescribeTrails(&cloudtrail.DescribeTrailsInput{ | ||
TrailNameList: identifiers, | ||
}) | ||
require.NoError(t, err) | ||
if len(resp.TrailList) > 0 { | ||
t.Fatalf("At least one of the following CloudTrail Trails was not deleted: %+v\n", aws.StringValueSlice(identifiers)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package aws | ||
|
||
import ( | ||
awsgo "github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/aws/session" | ||
"github.com/gruntwork-io/go-commons/errors" | ||
) | ||
|
||
// CloudWatchLogGroup - represents all ec2 instances | ||
type CloudtrailTrail struct { | ||
Arns []string | ||
} | ||
|
||
// ResourceName - the simple name of the aws resource | ||
func (ct CloudtrailTrail) ResourceName() string { | ||
return "cloudtrail" | ||
} | ||
|
||
// ResourceIdentifiers - The instance ids of the ec2 instances | ||
func (ct CloudtrailTrail) ResourceIdentifiers() []string { | ||
return ct.Arns | ||
} | ||
|
||
func (ct CloudtrailTrail) MaxBatchSize() int { | ||
return 50 | ||
} | ||
|
||
// Nuke - nuke 'em all!!! | ||
func (ct CloudtrailTrail) Nuke(session *session.Session, identifiers []string) error { | ||
if err := nukeAllCloudTrailTrails(session, awsgo.StringSlice(identifiers)); err != nil { | ||
return errors.WithStackTrace(err) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.