From b1175575259b87756d75000ad03813d5b3b55ddb Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Sat, 10 Feb 2024 15:45:36 +0000 Subject: [PATCH] Add script for batch deletion of AWS IAM users --- aws/delete-iam-users.py | 125 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100755 aws/delete-iam-users.py diff --git a/aws/delete-iam-users.py b/aws/delete-iam-users.py new file mode 100755 index 0000000..714e302 --- /dev/null +++ b/aws/delete-iam-users.py @@ -0,0 +1,125 @@ +#!/usr/bin/python3 +# +# Deleting a user programmatically requires deletion of several +# entities for each user: +# +# Password ( DeleteLoginProfile) +# Access keys ( DeleteAccessKey) +# Signing certificate ( DeleteSigningCertificate) +# SSH public key ( DeleteSSHPublicKey) +# Git credentials ( DeleteServiceSpecificCredential) +# Multi-factor authentication (MFA) device ( DeactivateMFADevice, DeleteVirtualMFADevice) +# Inline policies ( DeleteUserPolicy) +# Attached managed policies ( DetachUserPolicy) +# Group memberships ( RemoveUserFromGroup) +# + +import argparse +import boto3 +import botocore + +iam_client = boto3.client("iam") + +def list_all(paginator_name, collection_name, key_name, init_args): + result = [] + paginator = iam_client.get_paginator(paginator_name) + for page in paginator.paginate(**init_args): + for item in page.get(collection_name, []): + if key_name: + result.append(item[key_name]) + else: + result.append(item) + return result + + +def list_access_keys(user_name): + return list_all("list_access_keys", "AccessKeyMetadata", "AccessKeyId", {"UserName": user_name}) + + +def list_signing_certificates(user_name): + return list_all("list_signing_certificates", "Certificates", "CertificateId", {"UserName": user_name}) + + +def list_ssh_public_keys(user_name): + return list_all("list_ssh_public_keys", "SSHPublicKeys", "SSHPublicKeyId", {"UserName": user_name}) + + +def list_mfa_devices(user_name): + return list_all("list_mfa_devices", "MFADevices", "SerialNumber", {"UserName": user_name}) + + +def list_service_specific_credentials(user_name): + credential_ids = [] + response = iam_client.list_service_specific_credentials(UserName=user_name) + for item in response.get("ServiceSpecificCredentials", []): + credential_ids.append(item["ServiceSpecificCredentialId"]) + return credential_ids + + +def list_inline_policies(user_name): + return list_all("list_user_policies", "PolicyNames", None, {"UserName": user_name}) + + +def list_attached_policies(user_name): + return list_all("list_attached_user_policies", "AttachedPolicies", "PolicyArn", {"UserName": user_name}) + + +def list_groups(user_name): + return list_all("list_groups_for_user", "Groups", "GroupName", {"UserName": user_name}) + + +def delete_user(user_name, dry_run=True): + print(f"Delete user {user_name}:") + user = iam_client.get_user(UserName=user_name) + try: + if not dry_run: + iam_client.delete_login_profile(UserName=user_name) + print(" Delete login profile") + except botocore.exceptions.ClientError as err: + if err.response["Error"]["Code"] != "NoSuchEntity": + raise + for key_id in list_access_keys(user_name): + if not dry_run: + iam_client.delete_access_key(UserName=user_name, AccessKeyId=key_id) + print(f" Delete access key {key_id}") + for cert_id in list_signing_certificates(user_name): + if not dry_run: + iam_client.delete_signing_certificate(UserName=user_name, CertificateId=cert_id) + print(f" Delete signing certificate {cert_id}") + for key_id in list_ssh_public_keys(user_name): + if not dry_run: + iam_client.delete_ssh_public_key(UserName=user_name, SSHPublicKeyId=key_id) + print(f" Delete SSH public key {key_id}") + for cred_id in list_service_specific_credentials(user_name): + if not dry_run: + iam_client.delete_service_specific_credential(UserName=user_name, ServiceSpecificCredentialId=cred_id) + print(f" Delete service-specific credential {cred_id}") + for serial_no in list_mfa_devices(user_name): + if not dry_run: + iam_client.deactivate_mfa_device(UserName=user_name, SerialNumber=serial_no) + iam_client.delete_virtual_mfa_device(SerialNumber=serial_no) + print(f" Delete MFA device {serial_no}") + for policy_name in list_inline_policies(user_name): + if not dry_run: + iam_client.delete_user_policy(UserName=user_name, PolicyName=policy_name) + print(f" Delete inline policy {policy_name}") + for policy_arn in list_attached_policies(user_name): + if not dry_run: + iam_client.detach_user_policy(UserName=user_name, PolicyArn=policy_arn) + print(f" Detach policy {policy_arn}") + for group_name in list_groups(user_name): + if not dry_run: + iam_client.remove_user_from_group(UserName=user_name, GroupName=group_name) + print(f" Remove user from group {group_name}") + if not dry_run: + iam_client.delete_user(UserName=user_name) + print(f" Deleted {user_name}") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Delete IAM user(s)") + parser.add_argument("--dry-run", help="Show the actions that would be performed", action=argparse.BooleanOptionalAction, default=True) + parser.add_argument("user_names", nargs="+", ) + args = parser.parse_args() + for user_name in args.user_names: + delete_user(user_name, args.dry_run) \ No newline at end of file