Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated iam_user force delete to include public ssh keys. fixes #4176 #6337

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 126 additions & 59 deletions aws/resource_aws_iam_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,73 +205,25 @@ func resourceAwsIamUserDelete(d *schema.ResourceData, meta interface{}) error {

// All access keys, MFA devices and login profile for the user must be removed
if d.Get("force_destroy").(bool) {
var accessKeys []string
listAccessKeys := &iam.ListAccessKeysInput{
UserName: aws.String(d.Id()),
}
pageOfAccessKeys := func(page *iam.ListAccessKeysOutput, lastPage bool) (shouldContinue bool) {
for _, k := range page.AccessKeyMetadata {
accessKeys = append(accessKeys, *k.AccessKeyId)
}
return !lastPage
}
err = iamconn.ListAccessKeysPages(listAccessKeys, pageOfAccessKeys)

err = deleteAwsIamUserAccessKeys(iamconn, d.Id())
if err != nil {
return fmt.Errorf("Error removing access keys of user %s: %s", d.Id(), err)
}
for _, k := range accessKeys {
_, err := iamconn.DeleteAccessKey(&iam.DeleteAccessKeyInput{
UserName: aws.String(d.Id()),
AccessKeyId: aws.String(k),
})
if err != nil {
return fmt.Errorf("Error deleting access key %s: %s", k, err)
}
return err
}

var MFADevices []string
listMFADevices := &iam.ListMFADevicesInput{
UserName: aws.String(d.Id()),
}
pageOfMFADevices := func(page *iam.ListMFADevicesOutput, lastPage bool) (shouldContinue bool) {
for _, m := range page.MFADevices {
MFADevices = append(MFADevices, *m.SerialNumber)
}
return !lastPage
}
err = iamconn.ListMFADevicesPages(listMFADevices, pageOfMFADevices)
err = deleteAwsIamUserSSHKeys(iamconn, d.Id())
if err != nil {
return fmt.Errorf("Error removing MFA devices of user %s: %s", d.Id(), err)
}
for _, m := range MFADevices {
_, err := iamconn.DeactivateMFADevice(&iam.DeactivateMFADeviceInput{
UserName: aws.String(d.Id()),
SerialNumber: aws.String(m),
})
if err != nil {
return fmt.Errorf("Error deactivating MFA device %s: %s", m, err)
}
return err
}

err = resource.Retry(1*time.Minute, func() *resource.RetryError {
_, err = iamconn.DeleteLoginProfile(&iam.DeleteLoginProfileInput{
UserName: aws.String(d.Id()),
})
if err != nil {
if isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") {
return nil
}
// EntityTemporarilyUnmodifiable: Login Profile for User XXX cannot be modified while login profile is being created.
if isAWSErr(err, iam.ErrCodeEntityTemporarilyUnmodifiableException, "") {
return resource.RetryableError(err)
}
return resource.NonRetryableError(err)
}
return nil
})
err = deleteAwsIamUserMFADevices(iamconn, d.Id())
if err != nil {
return err
}

err = deleteAwsIamUserLoginProfile(iamconn, d.Id())
if err != nil {
return fmt.Errorf("Error deleting Account Login Profile: %s", err)
return err
}
}

Expand Down Expand Up @@ -300,3 +252,118 @@ func validateAwsIamUserName(v interface{}, k string) (ws []string, errors []erro
}
return
}

func deleteAwsIamUserSSHKeys(svc *iam.IAM, username string) error {
var publicKeys []string
var err error

listSSHPublicKeys := &iam.ListSSHPublicKeysInput{
UserName: aws.String(username),
}
pageOfListSSHPublicKeys := func(page *iam.ListSSHPublicKeysOutput, lastPage bool) (shouldContinue bool) {
for _, k := range page.SSHPublicKeys {
publicKeys = append(publicKeys, *k.SSHPublicKeyId)
}
return !lastPage
}
err = svc.ListSSHPublicKeysPages(listSSHPublicKeys, pageOfListSSHPublicKeys)
if err != nil {
return fmt.Errorf("Error removing public SSH keys of user %s: %s", username, err)
}
for _, k := range publicKeys {
_, err := svc.DeleteSSHPublicKey(&iam.DeleteSSHPublicKeyInput{
UserName: aws.String(username),
SSHPublicKeyId: aws.String(k),
})
if err != nil {
return fmt.Errorf("Error deleting public SSH key %s: %s", k, err)
}
}

return nil
}

func deleteAwsIamUserMFADevices(svc *iam.IAM, username string) error {
var MFADevices []string
var err error

listMFADevices := &iam.ListMFADevicesInput{
UserName: aws.String(username),
}
pageOfMFADevices := func(page *iam.ListMFADevicesOutput, lastPage bool) (shouldContinue bool) {
for _, m := range page.MFADevices {
MFADevices = append(MFADevices, *m.SerialNumber)
}
return !lastPage
}
err = svc.ListMFADevicesPages(listMFADevices, pageOfMFADevices)
if err != nil {
return fmt.Errorf("Error removing MFA devices of user %s: %s", username, err)
}
for _, m := range MFADevices {
_, err := svc.DeactivateMFADevice(&iam.DeactivateMFADeviceInput{
UserName: aws.String(username),
SerialNumber: aws.String(m),
})
if err != nil {
return fmt.Errorf("Error deactivating MFA device %s: %s", m, err)
}
}

return nil
}

func deleteAwsIamUserLoginProfile(svc *iam.IAM, username string) error {
var err error
err = resource.Retry(1*time.Minute, func() *resource.RetryError {
_, err = svc.DeleteLoginProfile(&iam.DeleteLoginProfileInput{
UserName: aws.String(username),
})
if err != nil {
if isAWSErr(err, iam.ErrCodeNoSuchEntityException, "") {
return nil
}
// EntityTemporarilyUnmodifiable: Login Profile for User XXX cannot be modified while login profile is being created.
if isAWSErr(err, iam.ErrCodeEntityTemporarilyUnmodifiableException, "") {
return resource.RetryableError(err)
}
return resource.NonRetryableError(err)
}
return nil
})

if err != nil {
return fmt.Errorf("Error deleting Account Login Profile: %s", err)
}

return nil
}

func deleteAwsIamUserAccessKeys(svc *iam.IAM, username string) error {
var accessKeys []string
var err error
listAccessKeys := &iam.ListAccessKeysInput{
UserName: aws.String(username),
}
pageOfAccessKeys := func(page *iam.ListAccessKeysOutput, lastPage bool) (shouldContinue bool) {
for _, k := range page.AccessKeyMetadata {
accessKeys = append(accessKeys, *k.AccessKeyId)
}
return !lastPage
}
err = svc.ListAccessKeysPages(listAccessKeys, pageOfAccessKeys)
if err != nil {
return fmt.Errorf("Error removing access keys of user %s: %s", username, err)
}
for _, k := range accessKeys {
_, err := svc.DeleteAccessKey(&iam.DeleteAccessKeyInput{
UserName: aws.String(username),
AccessKeyId: aws.String(k),
})
if err != nil {
return fmt.Errorf("Error deleting access key %s: %s", k, err)
}
}

return nil
}
58 changes: 58 additions & 0 deletions aws/resource_aws_iam_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"testing"

"io/ioutil"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/terraform/helper/acctest"
Expand Down Expand Up @@ -368,3 +370,59 @@ resource "aws_iam_user" "user" {
}
`, rName, permissionsBoundary)
}

func TestAccAWSUser_ForceDestroy_SSHKey(t *testing.T) {
var user iam.GetUserOutput

rName := acctest.RandomWithPrefix("tf-acc-test")
resourceName := "aws_iam_user.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSUserDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSUserConfigForceDestroy(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSUserExists(resourceName, &user),
testAccCheckAWSUserUploadsSSHKey(&user),
),
},
},
})
}

func testAccCheckAWSUserUploadsSSHKey(getUserOutput *iam.GetUserOutput) resource.TestCheckFunc {

return func(s *terraform.State) error {

sshKey, err := ioutil.ReadFile("./test-fixtures/public-ssh-key.pub")
if err != nil {
return fmt.Errorf("error reading SSH fixture: %s", err)
}

iamconn := testAccProvider.Meta().(*AWSClient).iamconn

input := &iam.UploadSSHPublicKeyInput{
UserName: getUserOutput.User.UserName,
SSHPublicKeyBody: aws.String(string(sshKey)),
}

_, err = iamconn.UploadSSHPublicKey(input)
if err != nil {
return fmt.Errorf("error uploading IAM User (%s) SSH key: %s", *getUserOutput.User.UserName, err)
}

return nil
}
}

func testAccAWSUserConfigForceDestroy(rName string) string {
return fmt.Sprintf(`
resource "aws_iam_user" "test" {
force_destroy = true
name = %q
}
`, rName)
}
1 change: 1 addition & 0 deletions aws/test-fixtures/public-ssh-key.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCtCk0lzMj1gPEOjdfQ37AIxCyETqJBubaMWuB4bgvGHp8LvEghr2YDl2bml1JrE1EOcZhPnIwgyucryXKA959sTUlgbvaFN7vmpVze56Q9tVU6BJQxOdaRoy5FcQMET9LB6SdbXk+V4CkDMsQNaFXezpg98HgCj+V7+bBWsfI6U63IESlWKK7kraCom8EWxkQk4mk9fizE2I+KrtiqN4xcah02LFG6IMnS+Xy3CDhcpZeYzWOV6zhcf675UJOdg/pLgQbUhhiwTOJFgRo8IcvE3iBrRMz508ppx6vLLr8J+3B8ujykc+/3ZSGfQfx6rO+OuSskhG5FLI6icbQBtBzf terraform-provider-aws@hashicorp.com