From 5287cde7a480460ce8df22cb2aa0e42423e7fc7a Mon Sep 17 00:00:00 2001 From: Jan Weitz Date: Tue, 13 Jul 2021 21:29:32 +0200 Subject: [PATCH 01/12] Add acmpa :sparkles: This adds support to delete AWS ACM Private CAs. Fixes #200 --- README.md | 5 ++ aws/acmpca.go | 73 ++++++++++++++++++ aws/acmpca_test.go | 103 +++++++++++++++++++++++++ aws/acmpca_types.go | 36 +++++++++ aws/aws.go | 15 ++++ config/config.go | 1 + config/config_test.go | 93 ++++++++++++++++++++++ config/mocks/acmpca_all.yaml | 4 + config/mocks/acmpca_cleanup.yaml | 5 ++ config/mocks/acmpca_empty.yaml | 1 + config/mocks/acmpca_empty_filters.yaml | 5 ++ config/mocks/acmpca_empty_rules.yaml | 3 + config/mocks/acmpca_exclude_names.yaml | 5 ++ config/mocks/acmpca_filter_names.yaml | 9 +++ config/mocks/acmpca_include_names.yaml | 5 ++ 15 files changed, 363 insertions(+) create mode 100644 aws/acmpca.go create mode 100644 aws/acmpca_test.go create mode 100644 aws/acmpca_types.go create mode 100644 config/mocks/acmpca_all.yaml create mode 100644 config/mocks/acmpca_cleanup.yaml create mode 100644 config/mocks/acmpca_empty.yaml create mode 100644 config/mocks/acmpca_empty_filters.yaml create mode 100644 config/mocks/acmpca_empty_rules.yaml create mode 100644 config/mocks/acmpca_exclude_names.yaml create mode 100644 config/mocks/acmpca_filter_names.yaml create mode 100644 config/mocks/acmpca_include_names.yaml diff --git a/README.md b/README.md index 67e7cf67..d5ee7e6f 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ The currently supported functionality includes: ## AWS +- Deleting all ACM Private CA in an AWS account - Deleting all Auto scaling groups in an AWS account - Deleting all Elastic Load Balancers (Classic and V2) in an AWS account - Deleting all Transit Gateways in an AWS account @@ -156,6 +157,9 @@ The following resources support the Config file: - IAM Access Analyzers - Resource type: `accessanalyzer` - Config key: `AccessAnalyzer` +- ACM Private CAs + - Resource type: `acmpca` + - Config key: `ACMPCA` #### Example @@ -248,6 +252,7 @@ To find out what we options are supported in the config file today, consult this | secretsmanager | none | ✅ | none | none | | nat-gateway | none | ✅ | none | none | | accessanalyzer | none | ✅ | none | none | +| acmpca | none | ✅ | none | none | | ec2 instance | none | none | none | none | | iam role | none | none | none | none | | ... (more to come) | none | none | none | none | diff --git a/aws/acmpca.go b/aws/acmpca.go new file mode 100644 index 00000000..3ac4d9a9 --- /dev/null +++ b/aws/acmpca.go @@ -0,0 +1,73 @@ +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/acmpca" + "github.com/gruntwork-io/cloud-nuke/logging" + "github.com/gruntwork-io/go-commons/errors" +) + +// getAllACMPCA returns a list of all arns of ACMPCA, which can be deleted. +func getAllACMPCA(session *session.Session, region string, excludeAfter time.Time) ([]*string, error) { + svc := acmpca.New(session) + + result, err := svc.ListCertificateAuthorities(&acmpca.ListCertificateAuthoritiesInput{}) + if err != nil { + return nil, errors.WithStackTrace(err) + } + + var arns []*string + for _, ca := range result.CertificateAuthorities { + // one can only delete CAs if they are 'ACTIVE' or 'DISABLED' + isCandidateForDeletion := *ca.Status == acmpca.CertificateAuthorityStatusActive || *ca.Status == acmpca.CertificateAuthorityStatusDisabled + if !isCandidateForDeletion { + continue + } + if excludeAfter.After(*ca.CreatedAt) { + arns = append(arns, ca.Arn) + } + } + return arns, nil +} + +// nukeAllACMPCA will delete all ACMPCA, which are given by a list of arns. +func nukeAllACMPCA(session *session.Session, arns []*string) error { + if len(arns) == 0 { + logging.Logger.Infof("No ACMPCA to nuke in region %s", *session.Config.Region) + return nil + } + svc := acmpca.New(session) + + logging.Logger.Infof("Deleting all ACMPCA in region %s", *session.Config.Region) + var deletedARNs []*string + + for _, arn := range arns { + logging.Logger.Infof("Setting status to 'DISABLED' for ACMPCA %s in region %s", *arn, *session.Config.Region) + if _, updateStatusErr := svc.UpdateCertificateAuthority(&acmpca.UpdateCertificateAuthorityInput{ + CertificateAuthorityArn: arn, + Status: aws.String(acmpca.CertificateAuthorityStatusDisabled), + }); updateStatusErr != nil { + logging.Logger.Errorf("[Failed] %s", updateStatusErr) + continue + } + logging.Logger.Infof("Did set status to 'DISABLED' for ACMPCA: %s in region %s", *arn, *session.Config.Region) + + if _, deleteErr := svc.DeleteCertificateAuthority(&acmpca.DeleteCertificateAuthorityInput{ + CertificateAuthorityArn: arn, + // the range is 7 to 30 days. + // since cloud-nuke should not be used in production, + // we assume that the minimum (7 days) is fine. + PermanentDeletionTimeInDays: aws.Int64(7), + }); deleteErr != nil { + logging.Logger.Errorf("[Failed] %s", deleteErr) + continue + } + deletedARNs = append(deletedARNs, arn) + logging.Logger.Infof("Deleted ACMPCA: %s", *arn) + } + logging.Logger.Infof("[OK] %d ACMPCA(s) deleted in %s", len(arns), *session.Config.Region) + return nil +} diff --git a/aws/acmpca_test.go b/aws/acmpca_test.go new file mode 100644 index 00000000..2a3527c7 --- /dev/null +++ b/aws/acmpca_test.go @@ -0,0 +1,103 @@ +package aws + +import ( + "testing" + "time" + + awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/acmpca" + "github.com/gruntwork-io/cloud-nuke/util" + "github.com/gruntwork-io/go-commons/errors" + "github.com/stretchr/testify/assert" +) + +// createTestACMPCA will create am ACMPCA and return its ARN. +func createTestACMPCA(t *testing.T, session *session.Session, name string) *string { + svc := acmpca.New(session) + ca, err := svc.CreateCertificateAuthority(&acmpca.CreateCertificateAuthorityInput{ + CertificateAuthorityConfiguration: &acmpca.CertificateAuthorityConfiguration{ + KeyAlgorithm: awsgo.String(acmpca.KeyAlgorithmRsa2048), + SigningAlgorithm: awsgo.String(acmpca.SigningAlgorithmSha256withrsa), + Subject: &acmpca.ASN1Subject{ + CommonName: awsgo.String(name), + }, + }, + CertificateAuthorityType: awsgo.String("ROOT"), + Tags: []*acmpca.Tag{ + { + Key: awsgo.String("Name"), + Value: awsgo.String(name), + }, + }, + }) + if err != nil { + assert.Failf(t, "Could not create ACMPCA", errors.WithStackTrace(err).Error()) + } + return ca.CertificateAuthorityArn +} + +func TestListACMPCA(t *testing.T) { + t.Parallel() + + region, err := getRandomRegion() + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + session, err := session.NewSession(&awsgo.Config{ + Region: awsgo.String(region)}, + ) + + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + uniqueTestID := "cloud-nuke-test-" + util.UniqueID() + arn := createTestACMPCA(t, session, uniqueTestID) + // clean up after this test + defer nukeAllACMPCA(session, []*string{arn}) + + newARNs, err := getAllACMPCA(session, region, time.Now().Add(1*time.Hour*-1)) + if err != nil { + assert.Fail(t, "Unable to fetch list of ACMPCA arns") + } + assert.NotContains(t, awsgo.StringValueSlice(newARNs), awsgo.StringValue(arn)) + + allARNs, err := getAllACMPCA(session, region, time.Now().Add(1*time.Hour)) + if err != nil { + assert.Fail(t, "Unable to fetch list of ACMPCA arns") + } + + assert.Contains(t, awsgo.StringValueSlice(allARNs), awsgo.StringValue(arn)) +} + +func TestNukeACMPCA(t *testing.T) { + t.Parallel() + + region, err := getRandomRegion() + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + session, err := session.NewSession(&awsgo.Config{ + Region: awsgo.String(region)}, + ) + + if err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + uniqueTestID := "cloud-nuke-test-" + util.UniqueID() + arn := createTestACMPCA(t, session, uniqueTestID) + + if err := nukeAllACMPCA(session, []*string{arn}); err != nil { + assert.Fail(t, errors.WithStackTrace(err).Error()) + } + + arns, err := getAllACMPCA(session, region, time.Now().Add(1*time.Hour)) + if err != nil { + assert.Fail(t, "Unable to fetch list of ACMPCA arns") + } + + assert.NotContains(t, awsgo.StringValueSlice(arns), awsgo.StringValue(arn)) +} diff --git a/aws/acmpca_types.go b/aws/acmpca_types.go new file mode 100644 index 00000000..35c68157 --- /dev/null +++ b/aws/acmpca_types.go @@ -0,0 +1,36 @@ +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" +) + +// ACMPA - represents all ACMPA +type ACMPCA struct { + ARNs []string +} + +// ResourceName - the simple name of the aws resource +func (ca ACMPCA) ResourceName() string { + return "acmpca" +} + +// ResourceIdentifiers - The volume ids of the ebs volumes +func (ca ACMPCA) ResourceIdentifiers() []string { + return ca.ARNs +} + +func (ca ACMPCA) MaxBatchSize() int { + // Tentative batch size to ensure AWS doesn't throttle + return 10 +} + +// Nuke - nuke 'em all!!! +func (ca ACMPCA) Nuke(session *session.Session, arns []string) error { + if err := nukeAllACMPCA(session, awsgo.StringSlice(arns)); err != nil { + return errors.WithStackTrace(err) + } + + return nil +} diff --git a/aws/aws.go b/aws/aws.go index c8164e63..a0ad3149 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -220,6 +220,20 @@ func GetAllResources(targetRegions []string, excludeAfter time.Time, resourceTyp // The order in which resources are nuked is important // because of dependencies between resources + // ACMPCA arns + acmpca := ACMPCA{} + if IsNukeable(acmpca.ResourceName(), resourceTypes) { + arns, err := getAllACMPCA(session, region, excludeAfter) + if err != nil { + return nil, errors.WithStackTrace(err) + } + if len(arns) > 0 { + acmpca.ARNs = awsgo.StringValueSlice(arns) + resourcesInRegion.Resources = append(resourcesInRegion.Resources, acmpca) + } + } + // End ACMPCA arns + // ASG Names asGroups := ASGroups{} if IsNukeable(asGroups.ResourceName(), resourceTypes) { @@ -634,6 +648,7 @@ func GetAllResources(targetRegions []string, excludeAfter time.Time, resourceTyp // ListResourceTypes - Returns list of resources which can be passed to --resource-type func ListResourceTypes() []string { resourceTypes := []string{ + ACMPCA{}.ResourceName(), ASGroups{}.ResourceName(), LaunchConfigs{}.ResourceName(), LoadBalancers{}.ResourceName(), diff --git a/config/config.go b/config/config.go index 949c7460..92f0e649 100644 --- a/config/config.go +++ b/config/config.go @@ -15,6 +15,7 @@ type Config struct { SecretsManagerSecrets ResourceType `yaml:"SecretsManager"` NatGateway ResourceType `yaml:"NatGateway"` AccessAnalyzer ResourceType `yaml:"AccessAnalyzer"` + ACMPCA ResourceType `yaml:"ACMPCA"` } type ResourceType struct { diff --git a/config/config_test.go b/config/config_test.go index 13d3c26c..e33ff016 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -53,6 +53,99 @@ func TestConfig_Empty(t *testing.T) { return } +// ACMPCA Tests + +func TestConfigACMPCA_Empty(t *testing.T) { + configFilePath := "./mocks/acmpa_empty.yaml" + configObj, err := GetConfig(configFilePath) + + require.NoError(t, err) + + if !reflect.DeepEqual(configObj, emptyConfig()) { + assert.Fail(t, "Config should be empty, %+v\n", configObj.ACMPCA) + } + + return +} + +func TestConfigACMPCA_EmptyFilters(t *testing.T) { + configFilePath := "./mocks/acmpca_empty_filters.yaml" + configObj, err := GetConfig(configFilePath) + + require.NoError(t, err) + + if !reflect.DeepEqual(configObj, emptyConfig()) { + assert.Fail(t, "Config should be empty, %+v\n", configObj) + } + + return +} + +func TestConfigACMPCA_EmptyRules(t *testing.T) { + configFilePath := "./mocks/acmpca_empty_rules.yaml" + configObj, err := GetConfig(configFilePath) + + require.NoError(t, err) + + if !reflect.DeepEqual(configObj, emptyConfig()) { + assert.Fail(t, "Config should be empty, %+v\n", configObj) + } + + return +} + +func TestConfigACMPCA_IncludeNames(t *testing.T) { + configFilePath := "./mocks/acmpca_include_names.yaml" + configObj, err := GetConfig(configFilePath) + + require.NoError(t, err) + + if reflect.DeepEqual(configObj, emptyConfig()) { + assert.Fail(t, "Config should not be empty, %+v\n", configObj) + } + + if len(configObj.ACMPCA.IncludeRule.NamesRegExp) == 0 { + assert.Fail(t, "ConfigObj should contain ACMPCA arn regexes, %+v\n", configObj) + } + + return +} + +func TestConfigACMPCA_ExcludeNames(t *testing.T) { + configFilePath := "./mocks/acmpca_exclude_names.yaml" + configObj, err := GetConfig(configFilePath) + + require.NoError(t, err) + + if reflect.DeepEqual(configObj, emptyConfig()) { + assert.Fail(t, "Config should not be empty, %+v\n", configObj) + } + + if len(configObj.ACMPCA.ExcludeRule.NamesRegExp) == 0 { + assert.Fail(t, "ConfigObj should contain ACMPCA arn regexes, %+v\n", configObj) + } + + return +} + +func TestConfigACMPCA_FilterNames(t *testing.T) { + configFilePath := "./mocks/acmpca_filter_names.yaml" + configObj, err := GetConfig(configFilePath) + + require.NoError(t, err) + + if reflect.DeepEqual(configObj, emptyConfig()) { + assert.Fail(t, "Config should not be empty, %+v\n", configObj) + } + + if len(configObj.ACMPCA.IncludeRule.NamesRegExp) == 0 || + len(configObj.ACMPCA.ExcludeRule.NamesRegExp) == 0 { + assert.Fail(t, "ConfigObj should contain ACMPCA arn regexes, %+v\n", configObj) + } + + return +} + // S3 Tests func TestConfigS3_Empty(t *testing.T) { diff --git a/config/mocks/acmpca_all.yaml b/config/mocks/acmpca_all.yaml new file mode 100644 index 00000000..825fd8a2 --- /dev/null +++ b/config/mocks/acmpca_all.yaml @@ -0,0 +1,4 @@ +acmpca: + include: + names_regex: + - .* diff --git a/config/mocks/acmpca_cleanup.yaml b/config/mocks/acmpca_cleanup.yaml new file mode 100644 index 00000000..a13c4c08 --- /dev/null +++ b/config/mocks/acmpca_cleanup.yaml @@ -0,0 +1,5 @@ +acmpca: + include: + names_regex: + - ^cloud-nuke-test- + - -cloud-nuke-test- diff --git a/config/mocks/acmpca_empty.yaml b/config/mocks/acmpca_empty.yaml new file mode 100644 index 00000000..75232ae6 --- /dev/null +++ b/config/mocks/acmpca_empty.yaml @@ -0,0 +1 @@ +acmpca: diff --git a/config/mocks/acmpca_empty_filters.yaml b/config/mocks/acmpca_empty_filters.yaml new file mode 100644 index 00000000..b4afa920 --- /dev/null +++ b/config/mocks/acmpca_empty_filters.yaml @@ -0,0 +1,5 @@ +acmpca: + include: + names_regex: + exclude: + names_regex: diff --git a/config/mocks/acmpca_empty_rules.yaml b/config/mocks/acmpca_empty_rules.yaml new file mode 100644 index 00000000..a0f984c2 --- /dev/null +++ b/config/mocks/acmpca_empty_rules.yaml @@ -0,0 +1,3 @@ +acmpca: + include: + exclude: diff --git a/config/mocks/acmpca_exclude_names.yaml b/config/mocks/acmpca_exclude_names.yaml new file mode 100644 index 00000000..b998c0c5 --- /dev/null +++ b/config/mocks/acmpca_exclude_names.yaml @@ -0,0 +1,5 @@ +acmpca: + exclude: + names_regex: + - donotdelete-0 + - donotdelete-1 diff --git a/config/mocks/acmpca_filter_names.yaml b/config/mocks/acmpca_filter_names.yaml new file mode 100644 index 00000000..ed54bb58 --- /dev/null +++ b/config/mocks/acmpca_filter_names.yaml @@ -0,0 +1,9 @@ +acmpca: + include: + names_regex: + - ^cloud-nuke-* + - test + exclude: + names_regex: + - donotdelete-0 + - donotdelete-1 diff --git a/config/mocks/acmpca_include_names.yaml b/config/mocks/acmpca_include_names.yaml new file mode 100644 index 00000000..639ee0c4 --- /dev/null +++ b/config/mocks/acmpca_include_names.yaml @@ -0,0 +1,5 @@ +acmpca: + include: + names_regex: + - ^cloud-nuke-* + - test From 1ee809e0c85335a86dc02ff89895802c44e55d95 Mon Sep 17 00:00:00 2001 From: Jan Weitz Date: Wed, 14 Jul 2021 23:19:15 +0200 Subject: [PATCH 02/12] Safely dereference the aws ca status :wrench: --- aws/acmpca.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aws/acmpca.go b/aws/acmpca.go index 3ac4d9a9..7df543c0 100644 --- a/aws/acmpca.go +++ b/aws/acmpca.go @@ -22,7 +22,8 @@ func getAllACMPCA(session *session.Session, region string, excludeAfter time.Tim var arns []*string for _, ca := range result.CertificateAuthorities { // one can only delete CAs if they are 'ACTIVE' or 'DISABLED' - isCandidateForDeletion := *ca.Status == acmpca.CertificateAuthorityStatusActive || *ca.Status == acmpca.CertificateAuthorityStatusDisabled + statusSafe := aws.StringValue(ca.Status) + isCandidateForDeletion := statusSafe == acmpca.CertificateAuthorityStatusActive || statusSafe == acmpca.CertificateAuthorityStatusDisabled if !isCandidateForDeletion { continue } From 92e624514d40772f9e0f2d81ba86ba4c1b435801 Mon Sep 17 00:00:00 2001 From: Jan Weitz Date: Wed, 14 Jul 2021 23:21:49 +0200 Subject: [PATCH 03/12] Safely dereference the aws time :wrench: --- aws/acmpca.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/acmpca.go b/aws/acmpca.go index 7df543c0..9fa75cdd 100644 --- a/aws/acmpca.go +++ b/aws/acmpca.go @@ -27,7 +27,7 @@ func getAllACMPCA(session *session.Session, region string, excludeAfter time.Tim if !isCandidateForDeletion { continue } - if excludeAfter.After(*ca.CreatedAt) { + if excludeAfter.After(aws.TimeValue(ca.CreatedAt)) { arns = append(arns, ca.Arn) } } From 4209721130bb4a8531292961d93271d3289a0b81 Mon Sep 17 00:00:00 2001 From: Jan Weitz Date: Wed, 14 Jul 2021 23:30:50 +0200 Subject: [PATCH 04/12] Add pagination :wrench: --- aws/acmpca.go | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/aws/acmpca.go b/aws/acmpca.go index 9fa75cdd..ab99171d 100644 --- a/aws/acmpca.go +++ b/aws/acmpca.go @@ -13,23 +13,22 @@ import ( // getAllACMPCA returns a list of all arns of ACMPCA, which can be deleted. func getAllACMPCA(session *session.Session, region string, excludeAfter time.Time) ([]*string, error) { svc := acmpca.New(session) - - result, err := svc.ListCertificateAuthorities(&acmpca.ListCertificateAuthoritiesInput{}) - if err != nil { - return nil, errors.WithStackTrace(err) - } - var arns []*string - for _, ca := range result.CertificateAuthorities { - // one can only delete CAs if they are 'ACTIVE' or 'DISABLED' - statusSafe := aws.StringValue(ca.Status) - isCandidateForDeletion := statusSafe == acmpca.CertificateAuthorityStatusActive || statusSafe == acmpca.CertificateAuthorityStatusDisabled - if !isCandidateForDeletion { - continue - } - if excludeAfter.After(aws.TimeValue(ca.CreatedAt)) { - arns = append(arns, ca.Arn) + if paginationErr := svc.ListCertificateAuthoritiesPages(&acmpca.ListCertificateAuthoritiesInput{}, func(p *acmpca.ListCertificateAuthoritiesOutput, lastPage bool) bool { + for _, ca := range p.CertificateAuthorities { + // one can only delete CAs if they are 'ACTIVE' or 'DISABLED' + statusSafe := aws.StringValue(ca.Status) + isCandidateForDeletion := statusSafe == acmpca.CertificateAuthorityStatusActive || statusSafe == acmpca.CertificateAuthorityStatusDisabled + if !isCandidateForDeletion { + continue + } + if excludeAfter.After(aws.TimeValue(ca.CreatedAt)) { + arns = append(arns, ca.Arn) + } } + return true + }); paginationErr != nil { + return nil, errors.WithStackTrace(paginationErr) } return arns, nil } From 8f9b7df9369f69fbd8182dffeca72b04460ee96e Mon Sep 17 00:00:00 2001 From: Jan Weitz Date: Wed, 14 Jul 2021 23:43:30 +0200 Subject: [PATCH 05/12] Add support to concurrently delete ACMPCA :wrench: --- aws/acmpca.go | 69 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/aws/acmpca.go b/aws/acmpca.go index ab99171d..503e9509 100644 --- a/aws/acmpca.go +++ b/aws/acmpca.go @@ -1,6 +1,7 @@ package aws import ( + "sync" "time" "github.com/aws/aws-sdk-go/aws" @@ -8,6 +9,7 @@ import ( "github.com/aws/aws-sdk-go/service/acmpca" "github.com/gruntwork-io/cloud-nuke/logging" "github.com/gruntwork-io/go-commons/errors" + "github.com/hashicorp/go-multierror" ) // getAllACMPCA returns a list of all arns of ACMPCA, which can be deleted. @@ -42,32 +44,51 @@ func nukeAllACMPCA(session *session.Session, arns []*string) error { svc := acmpca.New(session) logging.Logger.Infof("Deleting all ACMPCA in region %s", *session.Config.Region) - var deletedARNs []*string + // There is no bulk delete acmpca API, so we delete the batch of ARNs concurrently using go routines. + wg := new(sync.WaitGroup) + wg.Add(len(arns)) + errChans := make([]chan error, len(arns)) + for i, arn := range arns { + errChans[i] = make(chan error, 1) + go deleteACMPCAASync(wg, errChans[i], svc, arn, aws.StringValue(session.Config.Region)) + } + wg.Wait() - for _, arn := range arns { - logging.Logger.Infof("Setting status to 'DISABLED' for ACMPCA %s in region %s", *arn, *session.Config.Region) - if _, updateStatusErr := svc.UpdateCertificateAuthority(&acmpca.UpdateCertificateAuthorityInput{ - CertificateAuthorityArn: arn, - Status: aws.String(acmpca.CertificateAuthorityStatusDisabled), - }); updateStatusErr != nil { - logging.Logger.Errorf("[Failed] %s", updateStatusErr) - continue + // Collect all the errors from the async delete calls into a single error struct. + var allErrs *multierror.Error + for _, errChan := range errChans { + if err := <-errChan; err != nil { + allErrs = multierror.Append(allErrs, err) + logging.Logger.Errorf("[Failed] %s", err) } - logging.Logger.Infof("Did set status to 'DISABLED' for ACMPCA: %s in region %s", *arn, *session.Config.Region) + } + return errors.WithStackTrace(allErrs.ErrorOrNil()) +} - if _, deleteErr := svc.DeleteCertificateAuthority(&acmpca.DeleteCertificateAuthorityInput{ - CertificateAuthorityArn: arn, - // the range is 7 to 30 days. - // since cloud-nuke should not be used in production, - // we assume that the minimum (7 days) is fine. - PermanentDeletionTimeInDays: aws.Int64(7), - }); deleteErr != nil { - logging.Logger.Errorf("[Failed] %s", deleteErr) - continue - } - deletedARNs = append(deletedARNs, arn) - logging.Logger.Infof("Deleted ACMPCA: %s", *arn) +// deleteACMPCAASync deletes the provided ACMPCA arn. Intended to be run in a goroutine, using wait groups +// and a return channel for errors. +func deleteACMPCAASync(wg *sync.WaitGroup, errChan chan error, svc *acmpca.ACMPCA, arn *string, region string) { + defer wg.Done() + + logging.Logger.Infof("Setting status to 'DISABLED' for ACMPCA %s in region %s", *arn, region) + if _, updateStatusErr := svc.UpdateCertificateAuthority(&acmpca.UpdateCertificateAuthorityInput{ + CertificateAuthorityArn: arn, + Status: aws.String(acmpca.CertificateAuthorityStatusDisabled), + }); updateStatusErr != nil { + errChan <- updateStatusErr + return + } + logging.Logger.Infof("Did set status to 'DISABLED' for ACMPCA: %s in region %s", *arn, region) + + if _, deleteErr := svc.DeleteCertificateAuthority(&acmpca.DeleteCertificateAuthorityInput{ + CertificateAuthorityArn: arn, + // the range is 7 to 30 days. + // since cloud-nuke should not be used in production, + // we assume that the minimum (7 days) is fine. + PermanentDeletionTimeInDays: aws.Int64(7), + }); deleteErr != nil { + errChan <- deleteErr + return } - logging.Logger.Infof("[OK] %d ACMPCA(s) deleted in %s", len(arns), *session.Config.Region) - return nil + logging.Logger.Infof("Deleted ACMPCA: %s", *arn) } From d678810355f7c33cb74c7722311149be5a87b2a0 Mon Sep 17 00:00:00 2001 From: Jan Weitz Date: Thu, 15 Jul 2021 00:05:53 +0200 Subject: [PATCH 06/12] Implement configObj for acmpca :wrench: This also refactors the logic for calculating excludeTime. --- aws/acmpca.go | 44 +++++++++++++++++++++++++++++++++++--------- aws/acmpca_test.go | 7 ++++--- aws/aws.go | 2 +- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/aws/acmpca.go b/aws/acmpca.go index 503e9509..169ab433 100644 --- a/aws/acmpca.go +++ b/aws/acmpca.go @@ -7,34 +7,60 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/acmpca" + "github.com/gruntwork-io/cloud-nuke/config" "github.com/gruntwork-io/cloud-nuke/logging" "github.com/gruntwork-io/go-commons/errors" "github.com/hashicorp/go-multierror" ) // getAllACMPCA returns a list of all arns of ACMPCA, which can be deleted. -func getAllACMPCA(session *session.Session, region string, excludeAfter time.Time) ([]*string, error) { +func getAllACMPCA(session *session.Session, region string, excludeAfter time.Time, configObj config.Config) ([]*string, error) { svc := acmpca.New(session) var arns []*string if paginationErr := svc.ListCertificateAuthoritiesPages(&acmpca.ListCertificateAuthoritiesInput{}, func(p *acmpca.ListCertificateAuthoritiesOutput, lastPage bool) bool { for _, ca := range p.CertificateAuthorities { - // one can only delete CAs if they are 'ACTIVE' or 'DISABLED' - statusSafe := aws.StringValue(ca.Status) - isCandidateForDeletion := statusSafe == acmpca.CertificateAuthorityStatusActive || statusSafe == acmpca.CertificateAuthorityStatusDisabled - if !isCandidateForDeletion { - continue - } - if excludeAfter.After(aws.TimeValue(ca.CreatedAt)) { + if shouldIncludeACMPCA(ca, excludeAfter, configObj) { arns = append(arns, ca.Arn) } } - return true + return !lastPage }); paginationErr != nil { return nil, errors.WithStackTrace(paginationErr) } return arns, nil } +func shouldIncludeACMPCA(ca *acmpca.CertificateAuthority, excludeAfter time.Time, configObj config.Config) bool { + if ca == nil { + return false + } + + // one can only delete CAs if they are 'ACTIVE' or 'DISABLED' + statusSafe := aws.StringValue(ca.Status) + isCandidateForDeletion := statusSafe == acmpca.CertificateAuthorityStatusActive || statusSafe == acmpca.CertificateAuthorityStatusDisabled + if !isCandidateForDeletion { + return false + } + + // reference time for excludeAfter is lastStateChangeAt time, + // unless it was never changed and createAt time is used. + var referenceTime time.Time + if ca.LastStateChangeAt == nil { + referenceTime = aws.TimeValue(ca.CreatedAt) + } else { + referenceTime = aws.TimeValue(ca.LastStateChangeAt) + } + if excludeAfter.Before(referenceTime) { + return false + } + + return config.ShouldInclude( + aws.StringValue(ca.Arn), + configObj.ACMPCA.IncludeRule.NamesRegExp, + configObj.ACMPCA.ExcludeRule.NamesRegExp, + ) +} + // nukeAllACMPCA will delete all ACMPCA, which are given by a list of arns. func nukeAllACMPCA(session *session.Session, arns []*string) error { if len(arns) == 0 { diff --git a/aws/acmpca_test.go b/aws/acmpca_test.go index 2a3527c7..ce18ac5c 100644 --- a/aws/acmpca_test.go +++ b/aws/acmpca_test.go @@ -7,6 +7,7 @@ import ( awsgo "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/acmpca" + "github.com/gruntwork-io/cloud-nuke/config" "github.com/gruntwork-io/cloud-nuke/util" "github.com/gruntwork-io/go-commons/errors" "github.com/stretchr/testify/assert" @@ -57,13 +58,13 @@ func TestListACMPCA(t *testing.T) { // clean up after this test defer nukeAllACMPCA(session, []*string{arn}) - newARNs, err := getAllACMPCA(session, region, time.Now().Add(1*time.Hour*-1)) + newARNs, err := getAllACMPCA(session, region, time.Now().Add(1*time.Hour*-1), config.Config{}) if err != nil { assert.Fail(t, "Unable to fetch list of ACMPCA arns") } assert.NotContains(t, awsgo.StringValueSlice(newARNs), awsgo.StringValue(arn)) - allARNs, err := getAllACMPCA(session, region, time.Now().Add(1*time.Hour)) + allARNs, err := getAllACMPCA(session, region, time.Now().Add(1*time.Hour), config.Config{}) if err != nil { assert.Fail(t, "Unable to fetch list of ACMPCA arns") } @@ -94,7 +95,7 @@ func TestNukeACMPCA(t *testing.T) { assert.Fail(t, errors.WithStackTrace(err).Error()) } - arns, err := getAllACMPCA(session, region, time.Now().Add(1*time.Hour)) + arns, err := getAllACMPCA(session, region, time.Now().Add(1*time.Hour), config.Config{}) if err != nil { assert.Fail(t, "Unable to fetch list of ACMPCA arns") } diff --git a/aws/aws.go b/aws/aws.go index a0ad3149..37bde167 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -223,7 +223,7 @@ func GetAllResources(targetRegions []string, excludeAfter time.Time, resourceTyp // ACMPCA arns acmpca := ACMPCA{} if IsNukeable(acmpca.ResourceName(), resourceTypes) { - arns, err := getAllACMPCA(session, region, excludeAfter) + arns, err := getAllACMPCA(session, region, excludeAfter, configObj) if err != nil { return nil, errors.WithStackTrace(err) } From 542ee79b524522e86ee84dfb90f12c06ea5b36fd Mon Sep 17 00:00:00 2001 From: Jan Weitz Date: Thu, 15 Jul 2021 00:25:29 +0200 Subject: [PATCH 07/12] Add opt-in feature for acmpca tests :wrench: --- README.md | 7 +++++++ aws/acmpca_test.go | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/README.md b/README.md index d5ee7e6f..03558791 100644 --- a/README.md +++ b/README.md @@ -340,6 +340,13 @@ cd aws go test -v -run TestListAMIs ``` +Use env-vars to opt-in to special tests, which are expensive to run: + +```bash +# Run acmpca tests +TEST_ACMPCA_EXPENSIVE_ENABLE=1 go test -v ./... +``` + ### Formatting Every source file in this project should be formatted with `go fmt`. diff --git a/aws/acmpca_test.go b/aws/acmpca_test.go index ce18ac5c..92e7e746 100644 --- a/aws/acmpca_test.go +++ b/aws/acmpca_test.go @@ -1,6 +1,7 @@ package aws import ( + "os" "testing" "time" @@ -13,8 +14,27 @@ import ( "github.com/stretchr/testify/assert" ) +// enableACMPCAExpensiveEnv is used to control whether to run +// the following test or not. The idea is that the test are disabled +// per default and one has to opt-in to enable the test as creating +// and destroying a ACM PCA is expensive. +// Upper bound, worst case: $400 / month per single CA create/delete. +const enableACMPCAExpensiveEnv = "TEST_ACMPCA_EXPENSIVE_ENABLE" + +// runOrSkip decides whether to run or skip the test depending +// whether the env-var `TEST_ACMPCA_EXPENSIVE_ENABLE` is set or not. +func runOrSkip(t *testing.T) { + if _, isSet := os.LookupEnv(enableACMPCAExpensiveEnv); !isSet { + t.Skipf("Skipping the integration test for acmpca. Set the env-var '%s' to enable this expensive test.", enableACMPCAExpensiveEnv) + } +} + // createTestACMPCA will create am ACMPCA and return its ARN. func createTestACMPCA(t *testing.T, session *session.Session, name string) *string { + // As an additional safety guard, we are adding another check here + // to decide whether to run the test or not. + runOrSkip(t) + svc := acmpca.New(session) ca, err := svc.CreateCertificateAuthority(&acmpca.CreateCertificateAuthorityInput{ CertificateAuthorityConfiguration: &acmpca.CertificateAuthorityConfiguration{ @@ -39,6 +59,7 @@ func createTestACMPCA(t *testing.T, session *session.Session, name string) *stri } func TestListACMPCA(t *testing.T) { + runOrSkip(t) t.Parallel() region, err := getRandomRegion() @@ -73,6 +94,7 @@ func TestListACMPCA(t *testing.T) { } func TestNukeACMPCA(t *testing.T) { + runOrSkip(t) t.Parallel() region, err := getRandomRegion() From 0a6016aba266cb3e06248164b273010d04f8fece Mon Sep 17 00:00:00 2001 From: Jan Weitz Date: Wed, 4 Aug 2021 19:27:32 +0200 Subject: [PATCH 08/12] Fix race-condition for CA to be ready in test --- aws/acmpca_test.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/aws/acmpca_test.go b/aws/acmpca_test.go index 92e7e746..e13b615c 100644 --- a/aws/acmpca_test.go +++ b/aws/acmpca_test.go @@ -1,10 +1,14 @@ package aws import ( + "fmt" "os" "testing" "time" + "github.com/gruntwork-io/cloud-nuke/logging" + "github.com/gruntwork-io/go-commons/retry" + awsgo "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/acmpca" @@ -55,6 +59,31 @@ func createTestACMPCA(t *testing.T, session *session.Session, name string) *stri if err != nil { assert.Failf(t, "Could not create ACMPCA", errors.WithStackTrace(err).Error()) } + + // Wait for the ACMPCA to be ready (i.e. not CREATING). + // Ready does not mean "ACTIVE". + if err := retry.DoWithRetry( + logging.Logger, + fmt.Sprintf("Waiting for ACMPCA %s to be stable", awsgo.StringValue(ca.CertificateAuthorityArn)), + 10, + 1*time.Second, + func() error { + details, detailsErr := svc.DescribeCertificateAuthority(&acmpca.DescribeCertificateAuthorityInput{CertificateAuthorityArn: ca.CertificateAuthorityArn}) + if detailsErr != nil { + return detailsErr + } + if details.CertificateAuthority == nil { + return fmt.Errorf("no CA instance found") + } + if awsgo.StringValue(details.CertificateAuthority.Status) != acmpca.CertificateAuthorityStatusPendingCertificate { + return fmt.Errorf("CA not ready, status %s", awsgo.StringValue(details.CertificateAuthority.Status)) + } + return nil + }, + ); err != nil { + assert.Failf(t, "WARNING: ACMPCA is in some unfinished state. Delete manually inside the test-runner.", errors.WithStackTrace(err).Error()) + } + return ca.CertificateAuthorityArn } From 2f018d9a2c05a5ad05da4c73ab368e13f4f370b9 Mon Sep 17 00:00:00 2001 From: Jan Weitz Date: Wed, 4 Aug 2021 19:28:06 +0200 Subject: [PATCH 09/12] Fix deletion behaviour selector --- aws/acmpca.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/aws/acmpca.go b/aws/acmpca.go index 169ab433..9f8e7cdd 100644 --- a/aws/acmpca.go +++ b/aws/acmpca.go @@ -35,10 +35,9 @@ func shouldIncludeACMPCA(ca *acmpca.CertificateAuthority, excludeAfter time.Time return false } - // one can only delete CAs if they are 'ACTIVE' or 'DISABLED' statusSafe := aws.StringValue(ca.Status) - isCandidateForDeletion := statusSafe == acmpca.CertificateAuthorityStatusActive || statusSafe == acmpca.CertificateAuthorityStatusDisabled - if !isCandidateForDeletion { + isAlreadyDeleted := statusSafe == acmpca.CertificateAuthorityStatusDeleted + if isAlreadyDeleted { return false } From f0b78f629f979fc9938eb3ccd0cc0f345e55197b Mon Sep 17 00:00:00 2001 From: Jan Weitz Date: Wed, 4 Aug 2021 19:28:34 +0200 Subject: [PATCH 10/12] Fix marking for deletion, fix waitGroup :bug: --- aws/acmpca.go | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/aws/acmpca.go b/aws/acmpca.go index 9f8e7cdd..bfe6b152 100644 --- a/aws/acmpca.go +++ b/aws/acmpca.go @@ -1,6 +1,7 @@ package aws import ( + "fmt" "sync" "time" @@ -95,15 +96,39 @@ func nukeAllACMPCA(session *session.Session, arns []*string) error { func deleteACMPCAASync(wg *sync.WaitGroup, errChan chan error, svc *acmpca.ACMPCA, arn *string, region string) { defer wg.Done() - logging.Logger.Infof("Setting status to 'DISABLED' for ACMPCA %s in region %s", *arn, region) - if _, updateStatusErr := svc.UpdateCertificateAuthority(&acmpca.UpdateCertificateAuthorityInput{ - CertificateAuthorityArn: arn, - Status: aws.String(acmpca.CertificateAuthorityStatusDisabled), - }); updateStatusErr != nil { - errChan <- updateStatusErr + logging.Logger.Infof("Fetching details of CA to be deleted for ACMPCA %s in region %s", *arn, region) + details, detailsErr := svc.DescribeCertificateAuthority(&acmpca.DescribeCertificateAuthorityInput{CertificateAuthorityArn: arn}) + if detailsErr != nil { + errChan <- detailsErr + return + } + if details.CertificateAuthority == nil { + errChan <- fmt.Errorf("could not find CA %s", aws.StringValue(arn)) + return + } + if details.CertificateAuthority.Status == nil { + errChan <- fmt.Errorf("could not fetch status for CA %s", aws.StringValue(arn)) return } - logging.Logger.Infof("Did set status to 'DISABLED' for ACMPCA: %s in region %s", *arn, region) + + // find out, whether we have to disable the CA first, prior to deletion. + statusSafe := aws.StringValue(details.CertificateAuthority.Status) + shouldUpdateStatus := statusSafe != acmpca.CertificateAuthorityStatusCreating && + statusSafe != acmpca.CertificateAuthorityStatusPendingCertificate && + statusSafe != acmpca.CertificateAuthorityStatusDisabled && + statusSafe != acmpca.CertificateAuthorityStatusDeleted + + if shouldUpdateStatus { + logging.Logger.Infof("Setting status to 'DISABLED' for ACMPCA %s in region %s", *arn, region) + if _, updateStatusErr := svc.UpdateCertificateAuthority(&acmpca.UpdateCertificateAuthorityInput{ + CertificateAuthorityArn: arn, + Status: aws.String(acmpca.CertificateAuthorityStatusDisabled), + }); updateStatusErr != nil { + errChan <- updateStatusErr + return + } + logging.Logger.Infof("Did set status to 'DISABLED' for ACMPCA: %s in region %s", *arn, region) + } if _, deleteErr := svc.DeleteCertificateAuthority(&acmpca.DeleteCertificateAuthorityInput{ CertificateAuthorityArn: arn, @@ -115,5 +140,6 @@ func deleteACMPCAASync(wg *sync.WaitGroup, errChan chan error, svc *acmpca.ACMPC errChan <- deleteErr return } - logging.Logger.Infof("Deleted ACMPCA: %s", *arn) + logging.Logger.Infof("Deleted ACMPCA: %s successfully", *arn) + errChan <- nil } From 97d6cac532b26122de310a485087f0c3f2ba6125 Mon Sep 17 00:00:00 2001 From: Jan Weitz Date: Fri, 6 Aug 2021 10:53:41 +0200 Subject: [PATCH 11/12] Remove configObj support --- README.md | 5 +-- aws/acmpca.go | 10 ++--- aws/acmpca_test.go | 7 ++-- aws/aws.go | 2 +- config/config.go | 1 - config/config_test.go | 93 ------------------------------------------- 6 files changed, 10 insertions(+), 108 deletions(-) diff --git a/README.md b/README.md index 03558791..855fb47e 100644 --- a/README.md +++ b/README.md @@ -157,9 +157,6 @@ The following resources support the Config file: - IAM Access Analyzers - Resource type: `accessanalyzer` - Config key: `AccessAnalyzer` -- ACM Private CAs - - Resource type: `acmpca` - - Config key: `ACMPCA` #### Example @@ -252,7 +249,7 @@ To find out what we options are supported in the config file today, consult this | secretsmanager | none | ✅ | none | none | | nat-gateway | none | ✅ | none | none | | accessanalyzer | none | ✅ | none | none | -| acmpca | none | ✅ | none | none | +| acmpca | none | none | none | none | | ec2 instance | none | none | none | none | | iam role | none | none | none | none | | ... (more to come) | none | none | none | none | diff --git a/aws/acmpca.go b/aws/acmpca.go index bfe6b152..8f991f42 100644 --- a/aws/acmpca.go +++ b/aws/acmpca.go @@ -15,12 +15,12 @@ import ( ) // getAllACMPCA returns a list of all arns of ACMPCA, which can be deleted. -func getAllACMPCA(session *session.Session, region string, excludeAfter time.Time, configObj config.Config) ([]*string, error) { +func getAllACMPCA(session *session.Session, region string, excludeAfter time.Time) ([]*string, error) { svc := acmpca.New(session) var arns []*string if paginationErr := svc.ListCertificateAuthoritiesPages(&acmpca.ListCertificateAuthoritiesInput{}, func(p *acmpca.ListCertificateAuthoritiesOutput, lastPage bool) bool { for _, ca := range p.CertificateAuthorities { - if shouldIncludeACMPCA(ca, excludeAfter, configObj) { + if shouldIncludeACMPCA(ca, excludeAfter) { arns = append(arns, ca.Arn) } } @@ -31,7 +31,7 @@ func getAllACMPCA(session *session.Session, region string, excludeAfter time.Tim return arns, nil } -func shouldIncludeACMPCA(ca *acmpca.CertificateAuthority, excludeAfter time.Time, configObj config.Config) bool { +func shouldIncludeACMPCA(ca *acmpca.CertificateAuthority, excludeAfter time.Time) bool { if ca == nil { return false } @@ -56,8 +56,8 @@ func shouldIncludeACMPCA(ca *acmpca.CertificateAuthority, excludeAfter time.Time return config.ShouldInclude( aws.StringValue(ca.Arn), - configObj.ACMPCA.IncludeRule.NamesRegExp, - configObj.ACMPCA.ExcludeRule.NamesRegExp, + nil, + nil, ) } diff --git a/aws/acmpca_test.go b/aws/acmpca_test.go index e13b615c..00bca51b 100644 --- a/aws/acmpca_test.go +++ b/aws/acmpca_test.go @@ -12,7 +12,6 @@ import ( awsgo "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/acmpca" - "github.com/gruntwork-io/cloud-nuke/config" "github.com/gruntwork-io/cloud-nuke/util" "github.com/gruntwork-io/go-commons/errors" "github.com/stretchr/testify/assert" @@ -108,13 +107,13 @@ func TestListACMPCA(t *testing.T) { // clean up after this test defer nukeAllACMPCA(session, []*string{arn}) - newARNs, err := getAllACMPCA(session, region, time.Now().Add(1*time.Hour*-1), config.Config{}) + newARNs, err := getAllACMPCA(session, region, time.Now().Add(1*time.Hour*-1)) if err != nil { assert.Fail(t, "Unable to fetch list of ACMPCA arns") } assert.NotContains(t, awsgo.StringValueSlice(newARNs), awsgo.StringValue(arn)) - allARNs, err := getAllACMPCA(session, region, time.Now().Add(1*time.Hour), config.Config{}) + allARNs, err := getAllACMPCA(session, region, time.Now().Add(1*time.Hour)) if err != nil { assert.Fail(t, "Unable to fetch list of ACMPCA arns") } @@ -146,7 +145,7 @@ func TestNukeACMPCA(t *testing.T) { assert.Fail(t, errors.WithStackTrace(err).Error()) } - arns, err := getAllACMPCA(session, region, time.Now().Add(1*time.Hour), config.Config{}) + arns, err := getAllACMPCA(session, region, time.Now().Add(1*time.Hour)) if err != nil { assert.Fail(t, "Unable to fetch list of ACMPCA arns") } diff --git a/aws/aws.go b/aws/aws.go index 37bde167..a0ad3149 100644 --- a/aws/aws.go +++ b/aws/aws.go @@ -223,7 +223,7 @@ func GetAllResources(targetRegions []string, excludeAfter time.Time, resourceTyp // ACMPCA arns acmpca := ACMPCA{} if IsNukeable(acmpca.ResourceName(), resourceTypes) { - arns, err := getAllACMPCA(session, region, excludeAfter, configObj) + arns, err := getAllACMPCA(session, region, excludeAfter) if err != nil { return nil, errors.WithStackTrace(err) } diff --git a/config/config.go b/config/config.go index 92f0e649..949c7460 100644 --- a/config/config.go +++ b/config/config.go @@ -15,7 +15,6 @@ type Config struct { SecretsManagerSecrets ResourceType `yaml:"SecretsManager"` NatGateway ResourceType `yaml:"NatGateway"` AccessAnalyzer ResourceType `yaml:"AccessAnalyzer"` - ACMPCA ResourceType `yaml:"ACMPCA"` } type ResourceType struct { diff --git a/config/config_test.go b/config/config_test.go index e33ff016..13d3c26c 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -53,99 +53,6 @@ func TestConfig_Empty(t *testing.T) { return } -// ACMPCA Tests - -func TestConfigACMPCA_Empty(t *testing.T) { - configFilePath := "./mocks/acmpa_empty.yaml" - configObj, err := GetConfig(configFilePath) - - require.NoError(t, err) - - if !reflect.DeepEqual(configObj, emptyConfig()) { - assert.Fail(t, "Config should be empty, %+v\n", configObj.ACMPCA) - } - - return -} - -func TestConfigACMPCA_EmptyFilters(t *testing.T) { - configFilePath := "./mocks/acmpca_empty_filters.yaml" - configObj, err := GetConfig(configFilePath) - - require.NoError(t, err) - - if !reflect.DeepEqual(configObj, emptyConfig()) { - assert.Fail(t, "Config should be empty, %+v\n", configObj) - } - - return -} - -func TestConfigACMPCA_EmptyRules(t *testing.T) { - configFilePath := "./mocks/acmpca_empty_rules.yaml" - configObj, err := GetConfig(configFilePath) - - require.NoError(t, err) - - if !reflect.DeepEqual(configObj, emptyConfig()) { - assert.Fail(t, "Config should be empty, %+v\n", configObj) - } - - return -} - -func TestConfigACMPCA_IncludeNames(t *testing.T) { - configFilePath := "./mocks/acmpca_include_names.yaml" - configObj, err := GetConfig(configFilePath) - - require.NoError(t, err) - - if reflect.DeepEqual(configObj, emptyConfig()) { - assert.Fail(t, "Config should not be empty, %+v\n", configObj) - } - - if len(configObj.ACMPCA.IncludeRule.NamesRegExp) == 0 { - assert.Fail(t, "ConfigObj should contain ACMPCA arn regexes, %+v\n", configObj) - } - - return -} - -func TestConfigACMPCA_ExcludeNames(t *testing.T) { - configFilePath := "./mocks/acmpca_exclude_names.yaml" - configObj, err := GetConfig(configFilePath) - - require.NoError(t, err) - - if reflect.DeepEqual(configObj, emptyConfig()) { - assert.Fail(t, "Config should not be empty, %+v\n", configObj) - } - - if len(configObj.ACMPCA.ExcludeRule.NamesRegExp) == 0 { - assert.Fail(t, "ConfigObj should contain ACMPCA arn regexes, %+v\n", configObj) - } - - return -} - -func TestConfigACMPCA_FilterNames(t *testing.T) { - configFilePath := "./mocks/acmpca_filter_names.yaml" - configObj, err := GetConfig(configFilePath) - - require.NoError(t, err) - - if reflect.DeepEqual(configObj, emptyConfig()) { - assert.Fail(t, "Config should not be empty, %+v\n", configObj) - } - - if len(configObj.ACMPCA.IncludeRule.NamesRegExp) == 0 || - len(configObj.ACMPCA.ExcludeRule.NamesRegExp) == 0 { - assert.Fail(t, "ConfigObj should contain ACMPCA arn regexes, %+v\n", configObj) - } - - return -} - // S3 Tests func TestConfigS3_Empty(t *testing.T) { From f028ccd56e70e7f2acea2c5d096a71cd1cd1c806 Mon Sep 17 00:00:00 2001 From: Jan Weitz Date: Fri, 6 Aug 2021 10:54:24 +0200 Subject: [PATCH 12/12] `gimports -w .` --- aws/iam.go | 2 +- aws/rds_cluster.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aws/iam.go b/aws/iam.go index 45eb4494..9ed41758 100644 --- a/aws/iam.go +++ b/aws/iam.go @@ -10,8 +10,8 @@ import ( "github.com/aws/aws-sdk-go/service/iam" "github.com/gruntwork-io/cloud-nuke/config" "github.com/gruntwork-io/cloud-nuke/logging" - "github.com/gruntwork-io/go-commons/retry" "github.com/gruntwork-io/go-commons/errors" + "github.com/gruntwork-io/go-commons/retry" "github.com/hashicorp/go-multierror" ) diff --git a/aws/rds_cluster.go b/aws/rds_cluster.go index 41ccc295..e443ebfa 100644 --- a/aws/rds_cluster.go +++ b/aws/rds_cluster.go @@ -1,10 +1,10 @@ package aws import ( - "github.com/aws/aws-sdk-go/aws/awserr" "time" awsgo "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/rds" "github.com/gruntwork-io/cloud-nuke/logging"