Skip to content
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
11 changes: 6 additions & 5 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ ignored = [

[[constraint]]
name = "github.com/aws/aws-sdk-go"
version = "1.15.39"
version = "1.16.14"

[[constraint]]
name = "github.com/coreos/ignition"
Expand Down
126 changes: 126 additions & 0 deletions pkg/destroy/aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ func populateDeleteFuncs(funcs map[string]deleteFunc) {
funcs["deleteS3Buckets"] = deleteS3Buckets
funcs["deleteRoute53"] = deleteRoute53
funcs["deletePVs"] = deletePVs
funcs["deleteUsers"] = deleteUsers
}

// Run is the entrypoint to start the uninstall process
Expand Down Expand Up @@ -195,6 +196,10 @@ func tagsToMap(tags interface{}) (map[string]string, error) {
for _, tag := range v {
x[*tag.Key] = *tag.Value
}
case []*iam.Tag:
for _, tag := range v {
x[*tag.Key] = *tag.Value
}
default:
return x, errors.Errorf("unable to convert type: %v", v)
}
Expand Down Expand Up @@ -499,6 +504,11 @@ func getEC2Client(awsSession *session.Session) *ec2.EC2 {
return ec2.New(awsSession)
}

// getIAMClient is a wrapper for creating an AWS IAM client.
func getIAMClient(awsSession *session.Session) *iam.IAM {
return iam.New(awsSession)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why bother with these wrapper functions? getIAMClient is more characters than the code it wraps ;). But we also have some existing precedent for these in getEC2Client, so I'm fine either way and can clean these up later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just following the convention that was there but you make a good point. :)

}

// deleteNATGateways will attempt to delete all NAT Gateways that match the provided filters
func deleteNATGateways(awsSession *session.Session, filters Filter, clusterName string, logger logrus.FieldLogger) (bool, error) {

Expand Down Expand Up @@ -1458,3 +1468,119 @@ func deletePVs(session *session.Session, filters Filter, clusterName string, log

return false, nil
}

// deleteUsers will find users created by the cloud credential operator and delete them.
func deleteUsers(session *session.Session, filters Filter, clusterName string, logger logrus.FieldLogger) (bool, error) {

logger.Debugf("Deleting users (%s)", filters)
defer logger.Debugf("Exiting deleting users (%s)", filters)

iamClient := getIAMClient(session)

listUsersInput := iam.ListUsersInput{}

for {
allUsers, err := iamClient.ListUsers(&listUsersInput)
if err != nil {
logger.WithError(err).Debug("error listing all users")
return false, nil
}
logger.Debugf("Found %d users", len(allUsers.Users))

awsUsers, err := usersToAWSObjects(allUsers.Users, iamClient, logger)
if err != nil {
logger.Debugf("error converting users to native AWS objects: %v", err)
return false, nil
}

filteredUsers := filterObjects(awsUsers, filters)
logger.Debugf("from %d total users, %d match filters", len(awsUsers), len(filteredUsers))
if len(filteredUsers) == 0 {
break
}

for _, user := range filteredUsers {
uLog := logger.WithField("user", user.Name)

// list user policies:
policiesOut, err := iamClient.ListUserPolicies(&iam.ListUserPoliciesInput{UserName: aws.String(user.Name)})
if err != nil {
uLog.WithError(err).Debug("error listing user policies")
return false, nil
}

// delete any user policies:
for _, policy := range policiesOut.PolicyNames {
_, err = iamClient.DeleteUserPolicy(&iam.DeleteUserPolicyInput{
UserName: aws.String(user.Name),
PolicyName: policy,
})
if err != nil {
uLog.WithError(err).WithField("policy", *policy).Debug("error deleting user policy")
return false, nil
}
uLog.WithField("policy", *policy).Debug("deleted user policy")
}

// list access keys:
allUserKeys, err := iamClient.ListAccessKeys(&iam.ListAccessKeysInput{UserName: aws.String(user.Name)})
if err != nil {
uLog.WithError(err).Error("error listing all access keys for user")
return false, err
}

// delete access keys:
for _, kmd := range allUserKeys.AccessKeyMetadata {
akLog := uLog.WithFields(logrus.Fields{
"accessKeyID": *kmd.AccessKeyId,
})
akLog.Info("deleting access key")
_, err := iamClient.DeleteAccessKey(&iam.DeleteAccessKeyInput{AccessKeyId: kmd.AccessKeyId, UserName: aws.String(user.Name)})
if err != nil {
akLog.WithError(err).Error("error deleting access key")
return false, err
}
}

// delete user:
_, err = iamClient.DeleteUser(&iam.DeleteUserInput{
UserName: aws.String(user.Name),
})
if err != nil {
uLog.WithError(err).Debug("error deleting user")
}
uLog.Info("user deleted")
}

return false, nil
}

return true, nil
}

// usersToAWSObjects will create a list of awsObjectsWithTags for the provided list of users.
func usersToAWSObjects(users []*iam.User, iamClient *iam.IAM, logger logrus.FieldLogger) ([]awsObjectWithTags, error) {
usersAsAWSObjects := []awsObjectWithTags{}

for _, user := range users {

// Unfortunately the ListUsers Users do not have tags populated, so we need to query each:
userOut, err := iamClient.GetUser(&iam.GetUserInput{UserName: user.UserName})
if err != nil {
return usersAsAWSObjects, err
}

tagsToMap, err := tagsToMap(userOut.User.Tags)
if err != nil {
return usersAsAWSObjects, err
}

usersAsAWSObjects = append(usersAsAWSObjects, awsObjectWithTags{
Name: *user.UserName,
Tags: tagsToMap,
})

}

return usersAsAWSObjects, nil
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading