diff --git a/docs/resources/iam-role.md b/docs/resources/iam-role.md new file mode 100644 index 00000000..efbabdd2 --- /dev/null +++ b/docs/resources/iam-role.md @@ -0,0 +1,14 @@ +# IAM Role + +This will remove all IAM Roles an AWS account. + +## Settings + +- `IncludeServiceLinkedRoles` + +### IncludeServiceLinkedRoles + +By default, service linked roles are excluded from the deletion process. This setting allows you to include them in the +deletion process now that AWS allows for them to be removed. + +Default is `false`. diff --git a/mkdocs.yml b/mkdocs.yml index 751996cd..10dd4ad7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -92,6 +92,7 @@ nav: - Migration Guide: config-migration.md - Resources: - Cognito User Pool: resources/cognito-user-pool.md + - IAM Role: resources/iam-role.md - S3 Bucket: resources/s3-bucket.md - Development: - Overview: development.md diff --git a/resources/apigateway-apikeys.go b/resources/apigateway-api-key.go similarity index 66% rename from resources/apigateway-apikeys.go rename to resources/apigateway-api-key.go index b4233e2e..cc53b1f1 100644 --- a/resources/apigateway-apikeys.go +++ b/resources/apigateway-api-key.go @@ -2,12 +2,14 @@ package resources import ( "context" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/apigateway" "github.com/ekristen/libnuke/pkg/registry" "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" "github.com/ekristen/aws-nuke/v3/pkg/nuke" ) @@ -43,8 +45,11 @@ func (l *APIGatewayAPIKeyLister) List(_ context.Context, o interface{}) ([]resou for _, item := range output.Items { resources = append(resources, &APIGatewayAPIKey{ - svc: svc, - APIKey: item.Id, + svc: svc, + apiKey: item.Id, + Name: item.Name, + Tags: item.Tags, + CreatedDate: item.CreatedDate, }) } @@ -59,18 +64,25 @@ func (l *APIGatewayAPIKeyLister) List(_ context.Context, o interface{}) ([]resou } type APIGatewayAPIKey struct { - svc *apigateway.APIGateway - APIKey *string + svc *apigateway.APIGateway + apiKey *string + Name *string + Tags map[string]*string + CreatedDate *time.Time } -func (f *APIGatewayAPIKey) Remove(_ context.Context) error { - _, err := f.svc.DeleteApiKey(&apigateway.DeleteApiKeyInput{ - ApiKey: f.APIKey, +func (r *APIGatewayAPIKey) Remove(_ context.Context) error { + _, err := r.svc.DeleteApiKey(&apigateway.DeleteApiKeyInput{ + ApiKey: r.apiKey, }) return err } -func (f *APIGatewayAPIKey) String() string { - return *f.APIKey +func (r *APIGatewayAPIKey) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) +} + +func (r *APIGatewayAPIKey) String() string { + return *r.apiKey } diff --git a/resources/apigateway-usageplans.go b/resources/apigateway-usage-plan.go similarity index 68% rename from resources/apigateway-usageplans.go rename to resources/apigateway-usage-plan.go index c66c847b..3e0d0e30 100644 --- a/resources/apigateway-usageplans.go +++ b/resources/apigateway-usage-plan.go @@ -26,13 +26,6 @@ func init() { type APIGatewayUsagePlanLister struct{} -type APIGatewayUsagePlan struct { - svc *apigateway.APIGateway - usagePlanID *string - name *string - tags map[string]*string -} - func (l *APIGatewayUsagePlanLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { opts := o.(*nuke.ListerOpts) svc := apigateway.New(opts.Session) @@ -51,9 +44,9 @@ func (l *APIGatewayUsagePlanLister) List(_ context.Context, o interface{}) ([]re for _, item := range output.Items { resources = append(resources, &APIGatewayUsagePlan{ svc: svc, - usagePlanID: item.Id, - name: item.Name, - tags: item.Tags, + UsagePlanID: item.Id, + Name: item.Name, + Tags: item.Tags, }) } @@ -67,27 +60,25 @@ func (l *APIGatewayUsagePlanLister) List(_ context.Context, o interface{}) ([]re return resources, nil } -func (f *APIGatewayUsagePlan) Remove(_ context.Context) error { - _, err := f.svc.DeleteUsagePlan(&apigateway.DeleteUsagePlanInput{ - UsagePlanId: f.usagePlanID, +type APIGatewayUsagePlan struct { + svc *apigateway.APIGateway + UsagePlanID *string + Name *string + Tags map[string]*string +} + +func (r *APIGatewayUsagePlan) Remove(_ context.Context) error { + _, err := r.svc.DeleteUsagePlan(&apigateway.DeleteUsagePlanInput{ + UsagePlanId: r.UsagePlanID, }) return err } -func (f *APIGatewayUsagePlan) String() string { - return *f.usagePlanID +func (r *APIGatewayUsagePlan) String() string { + return *r.UsagePlanID } -func (f *APIGatewayUsagePlan) Properties() types.Properties { - properties := types.NewProperties() - - for key, tag := range f.tags { - properties.SetTag(&key, tag) - } - - properties. - Set("UsagePlanID", f.usagePlanID). - Set("Name", f.name) - return properties +func (r *APIGatewayUsagePlan) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) } diff --git a/resources/cloudwatchevents-rule.go b/resources/cloudwatchevents-rule.go index 8ff7fceb..1fb58345 100644 --- a/resources/cloudwatchevents-rule.go +++ b/resources/cloudwatchevents-rule.go @@ -28,33 +28,44 @@ type CloudWatchEventsRuleLister struct{} func (l *CloudWatchEventsRuleLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { opts := o.(*nuke.ListerOpts) + var resources []resource.Resource svc := cloudwatchevents.New(opts.Session) - resp, err := svc.ListEventBuses(nil) - if err != nil { - return nil, err - } + params := &cloudwatchevents.ListEventBusesInput{} - resources := make([]resource.Resource, 0) - for _, bus := range resp.EventBuses { - resp, err := svc.ListRules(&cloudwatchevents.ListRulesInput{ - EventBusName: bus.Name, - }) + for { + resp, err := svc.ListEventBuses(params) if err != nil { return nil, err } - for _, rule := range resp.Rules { - resources = append(resources, &CloudWatchEventsRule{ - svc: svc, - Name: rule.Name, - ARN: rule.Arn, - State: rule.State, + for _, bus := range resp.EventBuses { + resp, err := svc.ListRules(&cloudwatchevents.ListRulesInput{ EventBusName: bus.Name, }) + if err != nil { + return nil, err + } + + for _, rule := range resp.Rules { + resources = append(resources, &CloudWatchEventsRule{ + svc: svc, + Name: rule.Name, + ARN: rule.Arn, + State: rule.State, + EventBusName: bus.Name, + }) + } + } + + if resp.NextToken == nil { + break } + + params.NextToken = resp.NextToken } + return resources, nil } diff --git a/resources/databasemigrationservice-certificates.go b/resources/databasemigrationservice-certificate.go similarity index 78% rename from resources/databasemigrationservice-certificates.go rename to resources/databasemigrationservice-certificate.go index 4e6d5995..ef2d546a 100644 --- a/resources/databasemigrationservice-certificates.go +++ b/resources/databasemigrationservice-certificate.go @@ -8,6 +8,7 @@ import ( "github.com/ekristen/libnuke/pkg/registry" "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" "github.com/ekristen/aws-nuke/v3/pkg/nuke" ) @@ -62,14 +63,18 @@ type DatabaseMigrationServiceCertificate struct { ARN *string } -func (f *DatabaseMigrationServiceCertificate) Remove(_ context.Context) error { - _, err := f.svc.DeleteEndpoint(&databasemigrationservice.DeleteEndpointInput{ - EndpointArn: f.ARN, +func (r *DatabaseMigrationServiceCertificate) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) +} + +func (r *DatabaseMigrationServiceCertificate) Remove(_ context.Context) error { + _, err := r.svc.DeleteCertificate(&databasemigrationservice.DeleteCertificateInput{ + CertificateArn: r.ARN, }) return err } -func (f *DatabaseMigrationServiceCertificate) String() string { - return *f.ARN +func (r *DatabaseMigrationServiceCertificate) String() string { + return *r.ARN } diff --git a/resources/eks-fargate.go b/resources/eks-fargate-profile.go similarity index 65% rename from resources/eks-fargate.go rename to resources/eks-fargate-profile.go index 489397a4..44d5f625 100644 --- a/resources/eks-fargate.go +++ b/resources/eks-fargate-profile.go @@ -2,8 +2,10 @@ package resources import ( "context" - "fmt" + "time" + + "github.com/sirupsen/logrus" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/eks" @@ -71,10 +73,21 @@ func (l *EKSFargateProfileLister) List(_ context.Context, o interface{}) ([]reso } for _, name := range resp.FargateProfileNames { + profResp, err := svc.DescribeFargateProfile(&eks.DescribeFargateProfileInput{ + ClusterName: clusterName, + FargateProfileName: name, + }) + if err != nil { + logrus.WithError(err).Error("unable to describe fargate profile") + continue + } + resources = append(resources, &EKSFargateProfile{ - svc: svc, - name: name, - cluster: clusterName, + svc: svc, + Name: name, + Cluster: clusterName, + CreatedAt: profResp.FargateProfile.CreatedAt, + Tags: profResp.FargateProfile.Tags, }) } @@ -91,25 +104,25 @@ func (l *EKSFargateProfileLister) List(_ context.Context, o interface{}) ([]reso } type EKSFargateProfile struct { - svc *eks.EKS - cluster *string - name *string + svc *eks.EKS + Cluster *string + Name *string + CreatedAt *time.Time + Tags map[string]*string } -func (fp *EKSFargateProfile) Remove(_ context.Context) error { - _, err := fp.svc.DeleteFargateProfile(&eks.DeleteFargateProfileInput{ - ClusterName: fp.cluster, - FargateProfileName: fp.name, +func (r *EKSFargateProfile) Remove(_ context.Context) error { + _, err := r.svc.DeleteFargateProfile(&eks.DeleteFargateProfileInput{ + ClusterName: r.Cluster, + FargateProfileName: r.Name, }) return err } -func (fp *EKSFargateProfile) Properties() types.Properties { - return types.NewProperties(). - Set("Cluster", *fp.cluster). - Set("Profile", *fp.name) +func (r *EKSFargateProfile) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) } -func (fp *EKSFargateProfile) String() string { - return fmt.Sprintf("%s:%s", *fp.cluster, *fp.name) +func (r *EKSFargateProfile) String() string { + return fmt.Sprintf("%s:%s", *r.Cluster, *r.Name) } diff --git a/resources/eks-fargate-profile_mock_test.go b/resources/eks-fargate-profile_mock_test.go new file mode 100644 index 00000000..b7b3717c --- /dev/null +++ b/resources/eks-fargate-profile_mock_test.go @@ -0,0 +1,21 @@ +package resources + +import ( + "testing" + + "github.com/gotidy/ptr" + "github.com/stretchr/testify/assert" +) + +func TestEKSFargateProperties(t *testing.T) { + resource := &EKSFargateProfile{ + Cluster: ptr.String("test-id"), + Name: ptr.String("test-name"), + } + + properties := resource.Properties() + + assert.Equal(t, "test-id", properties.Get("Cluster")) + assert.Equal(t, "test-name", properties.Get("Name")) + assert.Equal(t, "test-id:test-name", resource.String()) +} diff --git a/resources/iam-role-policy.go b/resources/iam-role-policy.go index 0d80e7e3..9a2345b4 100644 --- a/resources/iam-role-policy.go +++ b/resources/iam-role-policy.go @@ -2,7 +2,6 @@ package resources import ( "context" - "fmt" "strings" @@ -41,6 +40,9 @@ func (e *IAMRolePolicy) Filter() error { if strings.HasPrefix(e.rolePath, "/aws-service-role/") { return fmt.Errorf("cannot alter service roles") } + if strings.HasPrefix(e.rolePath, "/aws-reserved/sso.amazonaws.com/") { + return fmt.Errorf("cannot alter sso roles") + } return nil } diff --git a/resources/iam-roles.go b/resources/iam-role.go similarity index 89% rename from resources/iam-roles.go rename to resources/iam-role.go index 56498dc7..e28df742 100644 --- a/resources/iam-roles.go +++ b/resources/iam-role.go @@ -14,6 +14,7 @@ import ( "github.com/ekristen/libnuke/pkg/registry" "github.com/ekristen/libnuke/pkg/resource" + libsettings "github.com/ekristen/libnuke/pkg/settings" "github.com/ekristen/libnuke/pkg/types" "github.com/ekristen/aws-nuke/v3/pkg/nuke" @@ -32,11 +33,15 @@ func init() { DeprecatedAliases: []string{ "IamRole", }, + Settings: []string{ + "IncludeServiceLinkedRoles", + }, }) } type IAMRole struct { svc iamiface.IAMAPI + settings *libsettings.Setting Name *string Path *string CreateDate *time.Time @@ -44,8 +49,12 @@ type IAMRole struct { Tags []*iam.Tag } +func (r *IAMRole) Settings(settings *libsettings.Setting) { + r.settings = settings +} + func (r *IAMRole) Filter() error { - if strings.HasPrefix(*r.Path, "/aws-service-role/") { + if strings.HasPrefix(*r.Path, "/aws-service-role/") && !r.settings.GetBool("IncludeServiceLinkedRoles") { return fmt.Errorf("cannot delete service roles") } if strings.HasPrefix(*r.Path, "/aws-reserved/sso.amazonaws.com/") { diff --git a/resources/iam-roles_mock_test.go b/resources/iam-role_mock_test.go similarity index 75% rename from resources/iam-roles_mock_test.go rename to resources/iam-role_mock_test.go index afe08013..28660c51 100644 --- a/resources/iam-roles_mock_test.go +++ b/resources/iam-role_mock_test.go @@ -13,6 +13,8 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/iam" + libsettings "github.com/ekristen/libnuke/pkg/settings" + "github.com/ekristen/aws-nuke/v3/mocks/mock_iamiface" "github.com/ekristen/aws-nuke/v3/pkg/nuke" ) @@ -67,6 +69,9 @@ func Test_Mock_IAMRole_List(t *testing.T) { a.Equal("/", *iamRole.Path) a.Equal(createDate.Format(time.RFC3339), iamRole.Properties().Get("CreateDate")) a.Equal(lastUsedDate.Format(time.RFC3339), iamRole.Properties().Get("LastUsedDate")) + + err = iamRole.Filter() + a.Nil(err) } func Test_Mock_IAMRole_Remove(t *testing.T) { @@ -91,6 +96,37 @@ func Test_Mock_IAMRole_Remove(t *testing.T) { a.Nil(err) } +func Test_Mock_IAMRole_Filter_ServiceLinked(t *testing.T) { + a := assert.New(t) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockIAM := mock_iamiface.NewMockIAMAPI(ctrl) + + settings := &libsettings.Setting{} + + iamRole := IAMRole{ + svc: mockIAM, + settings: settings, + Name: ptr.String("test"), + Path: ptr.String("/aws-service-role/"), + Tags: []*iam.Tag{}, + } + + err := iamRole.Filter() + a.NotNil(err, "should not be able to delete service linked roles") + + iamRole.settings.Set("IncludeServiceLinkedRoles", false) + + err = iamRole.Filter() + a.NotNil(err, "should not be able to delete service linked roles") + + iamRole.settings.Set("IncludeServiceLinkedRoles", true) + + err = iamRole.Filter() + a.Nil(err, "should be able to delete service linked roles") +} + func Test_Mock_IAMRole_Properties(t *testing.T) { a := assert.New(t) ctrl := gomock.NewController(t) diff --git a/resources/mgn-source-servers.go b/resources/mgn-source-server.go similarity index 91% rename from resources/mgn-source-servers.go rename to resources/mgn-source-server.go index 936846db..02cef99e 100644 --- a/resources/mgn-source-servers.go +++ b/resources/mgn-source-server.go @@ -76,6 +76,13 @@ type MGNSourceServer struct { } func (f *MGNSourceServer) Remove(_ context.Context) error { + // Disconnect source server from service first before delete + if _, err := f.svc.DisconnectFromService(&mgn.DisconnectFromServiceInput{ + SourceServerID: f.sourceServerID, + }); err != nil { + return err + } + _, err := f.svc.DeleteSourceServer(&mgn.DeleteSourceServerInput{ SourceServerID: f.sourceServerID, })