diff --git a/tool/clean/clean_iam_roles/clean_iam_roles.go b/tool/clean/clean_iam_roles/clean_iam_roles.go index 342e43dd21..d87355a1f2 100644 --- a/tool/clean/clean_iam_roles/clean_iam_roles.go +++ b/tool/clean/clean_iam_roles/clean_iam_roles.go @@ -25,6 +25,7 @@ var ( "cwa-integ-assume-role", "cwagent-eks-Worker-Role", "cwagent-integ-test-task-role", + "cwagent-integ-test-task-execution-role", "cwagent-operator-eks-Worker-Role", "cwagent-operator-helm-integ-Worker-Role", } @@ -36,6 +37,9 @@ type iamClient interface { DeleteRole(ctx context.Context, input *iam.DeleteRoleInput, optFns ...func(*iam.Options)) (*iam.DeleteRoleOutput, error) ListAttachedRolePolicies(ctx context.Context, input *iam.ListAttachedRolePoliciesInput, optFns ...func(*iam.Options)) (*iam.ListAttachedRolePoliciesOutput, error) DetachRolePolicy(ctx context.Context, input *iam.DetachRolePolicyInput, optFns ...func(*iam.Options)) (*iam.DetachRolePolicyOutput, error) + ListInstanceProfilesForRole(ctx context.Context, input *iam.ListInstanceProfilesForRoleInput, optFns ...func(*iam.Options)) (*iam.ListInstanceProfilesForRoleOutput, error) + RemoveRoleFromInstanceProfile(ctx context.Context, input *iam.RemoveRoleFromInstanceProfileInput, optFns ...func(*iam.Options)) (*iam.RemoveRoleFromInstanceProfileOutput, error) + DeleteInstanceProfile(ctx context.Context, input *iam.DeleteInstanceProfileInput, optFns ...func(*iam.Options)) (*iam.DeleteInstanceProfileOutput, error) } func main() { @@ -93,7 +97,11 @@ func deleteRole(ctx context.Context, client iamClient, role types.Role) error { if err := detachPolicies(ctx, client, role); err != nil { return err } + if err := deleteProfiles(ctx, client, role); err != nil { + return err + } if _, err := client.DeleteRole(ctx, &iam.DeleteRoleInput{RoleName: role.RoleName}); err != nil { + log.Printf("Failed to delete role (%q): %v", *role.RoleName, err) return err } log.Printf("Deleted role (%q) successfully", *role.RoleName) @@ -108,8 +116,31 @@ func detachPolicies(ctx context.Context, client iamClient, role types.Role) erro return err } for _, policy := range output.AttachedPolicies { + log.Printf("Trying to detach policy (%q) from role (%q)", *policy.PolicyName, *role.RoleName) if _, err = client.DetachRolePolicy(ctx, &iam.DetachRolePolicyInput{PolicyArn: policy.PolicyArn, RoleName: role.RoleName}); err != nil { - return fmt.Errorf("unable to detach policy (%q) from role (%q): %w", *policy.PolicyName, *role.RoleName, err) + log.Printf("Failed to detach policy (%q): %v", *policy.PolicyName, err) + return err + } + log.Printf("Detached policy (%q) from role (%q) successfully", *policy.PolicyName, *role.RoleName) + } + if output.Marker == nil { + break + } + marker = output.Marker + } + return nil +} + +func deleteProfiles(ctx context.Context, client iamClient, role types.Role) error { + var marker *string + for { + output, err := client.ListInstanceProfilesForRole(ctx, &iam.ListInstanceProfilesForRoleInput{RoleName: role.RoleName, Marker: marker}) + if err != nil { + return err + } + for _, profile := range output.InstanceProfiles { + if err = deleteProfile(ctx, client, role, profile); err != nil { + return err } } if output.Marker == nil { @@ -120,6 +151,28 @@ func detachPolicies(ctx context.Context, client iamClient, role types.Role) erro return nil } +func deleteProfile(ctx context.Context, client iamClient, role types.Role, profile types.InstanceProfile) error { + log.Printf("Trying to remove role (%q) from instance profile (%q)", *role.RoleName, *profile.InstanceProfileName) + _, err := client.RemoveRoleFromInstanceProfile(ctx, &iam.RemoveRoleFromInstanceProfileInput{ + InstanceProfileName: profile.InstanceProfileName, + RoleName: role.RoleName, + }) + if err != nil { + return err + } + log.Printf("Removed role (%q) from instance profile (%q) successfully", *role.RoleName, *profile.InstanceProfileName) + // profile's only role is about to be deleted, so delete it too + if len(profile.Roles) == 1 { + log.Printf("Trying to delete instance profile (%q) attached to role (%q)", *profile.InstanceProfileName, *role.RoleName) + if _, err = client.DeleteInstanceProfile(ctx, &iam.DeleteInstanceProfileInput{InstanceProfileName: profile.InstanceProfileName}); err != nil { + log.Printf("Failed to delete instance profile (%q): %v", *profile.InstanceProfileName, err) + return err + } + log.Printf("Deleted instance profile (%q) successfully", *profile.InstanceProfileName) + } + return nil +} + func hasPrefix(roleName string) bool { for _, prefix := range roleNamePrefixes { if strings.HasPrefix(roleName, prefix) { diff --git a/tool/clean/clean_iam_roles/clean_iam_roles_test.go b/tool/clean/clean_iam_roles/clean_iam_roles_test.go index 1b563ee54a..8fb0f6219b 100644 --- a/tool/clean/clean_iam_roles/clean_iam_roles_test.go +++ b/tool/clean/clean_iam_roles/clean_iam_roles_test.go @@ -51,6 +51,21 @@ func (m *mockIamClient) DetachRolePolicy(ctx context.Context, input *iam.DetachR return args.Get(0).(*iam.DetachRolePolicyOutput), args.Error(1) } +func (m *mockIamClient) ListInstanceProfilesForRole(ctx context.Context, input *iam.ListInstanceProfilesForRoleInput, optFns ...func(*iam.Options)) (*iam.ListInstanceProfilesForRoleOutput, error) { + args := m.Called(ctx, input, optFns) + return args.Get(0).(*iam.ListInstanceProfilesForRoleOutput), args.Error(1) +} + +func (m *mockIamClient) RemoveRoleFromInstanceProfile(ctx context.Context, input *iam.RemoveRoleFromInstanceProfileInput, optFns ...func(*iam.Options)) (*iam.RemoveRoleFromInstanceProfileOutput, error) { + args := m.Called(ctx, input, optFns) + return args.Get(0).(*iam.RemoveRoleFromInstanceProfileOutput), args.Error(1) +} + +func (m *mockIamClient) DeleteInstanceProfile(ctx context.Context, input *iam.DeleteInstanceProfileInput, optFns ...func(*iam.Options)) (*iam.DeleteInstanceProfileOutput, error) { + args := m.Called(ctx, input, optFns) + return args.Get(0).(*iam.DeleteInstanceProfileOutput), args.Error(1) +} + func TestDeleteRoles(t *testing.T) { expirationDate := getExpirationDate() @@ -83,16 +98,32 @@ func TestDeleteRoles(t *testing.T) { PolicyName: aws.String("policy-name"), }, } + testProfile := types.InstanceProfile{ + InstanceProfileName: aws.String("instance-profile-name"), + Roles: []types.Role{expiredRole}, + } client := &mockIamClient{} client.On("ListRoles", ctx, &iam.ListRolesInput{}, mock.Anything).Return(&iam.ListRolesOutput{Roles: testRoles}, nil) client.On("GetRole", ctx, &iam.GetRoleInput{RoleName: aws.String(expiredTestRoleName)}, mock.Anything).Return(&iam.GetRoleOutput{Role: &expiredRole}, nil) client.On("GetRole", ctx, &iam.GetRoleInput{RoleName: aws.String(activeTestRoleName)}, mock.Anything).Return(&iam.GetRoleOutput{Role: &activeRole}, nil) client.On("DeleteRole", ctx, mock.Anything, mock.Anything).Return(&iam.DeleteRoleOutput{}, nil) - client.On("ListAttachedRolePolicies", ctx, mock.Anything, mock.Anything).Return(&iam.ListAttachedRolePoliciesOutput{AttachedPolicies: testPolicies}, nil) + client.On("ListAttachedRolePolicies", ctx, mock.Anything, mock.Anything).Return(&iam.ListAttachedRolePoliciesOutput{ + AttachedPolicies: testPolicies, + }, nil) client.On("DetachRolePolicy", ctx, mock.Anything, mock.Anything).Return(&iam.DetachRolePolicyOutput{}, nil) + client.On("ListInstanceProfilesForRole", ctx, &iam.ListInstanceProfilesForRoleInput{ + RoleName: aws.String(expiredTestRoleName), + }, mock.Anything).Return(&iam.ListInstanceProfilesForRoleOutput{InstanceProfiles: []types.InstanceProfile{testProfile}}, nil) + client.On("RemoveRoleFromInstanceProfile", ctx, &iam.RemoveRoleFromInstanceProfileInput{ + RoleName: aws.String(expiredTestRoleName), + InstanceProfileName: testProfile.InstanceProfileName, + }, mock.Anything).Return(&iam.RemoveRoleFromInstanceProfileOutput{}, nil) + client.On("DeleteInstanceProfile", ctx, &iam.DeleteInstanceProfileInput{ + InstanceProfileName: testProfile.InstanceProfileName, + }, mock.Anything).Return(&iam.DeleteInstanceProfileOutput{}, nil) assert.NoError(t, deleteRoles(ctx, client, expirationDate)) - assert.Len(t, client.Calls, 6) + assert.Len(t, client.Calls, 9) for _, call := range client.Calls { switch call.Method { case "DeleteRole":