Fix AWS iam_user remove (#59079)

* Make iam_user not fail on user deletion.

- Use guard clause on already absent user
- Refactor, use variable instead nested dict
- Ensure needed prerequisites for boto3 delete_user successfully
- Use AnsibleAWSModule on iam_user.
- Fix fail_json_aws calls

* Add s-hertel comments to PR
This commit is contained in:
Agustín Herranz 2019-10-21 17:06:05 +02:00 committed by Sloane Hertel
parent 6046386dba
commit f0cadb9843

View file

@ -99,14 +99,14 @@ user:
''' '''
from ansible.module_utils._text import to_native from ansible.module_utils._text import to_native
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.aws.core import AnsibleAWSModule
from ansible.module_utils.ec2 import camel_dict_to_snake_dict, ec2_argument_spec, get_aws_connection_info, boto3_conn from ansible.module_utils.ec2 import camel_dict_to_snake_dict, ec2_argument_spec, get_aws_connection_info, boto3_conn
from ansible.module_utils.ec2 import HAS_BOTO3 from ansible.module_utils.ec2 import HAS_BOTO3
import traceback import traceback
try: try:
from botocore.exceptions import ClientError, ParamValidationError from botocore.exceptions import ClientError, ParamValidationError, BotoCoreError
except ImportError: except ImportError:
pass # caught by imported HAS_BOTO3 pass # caught by imported HAS_BOTO3
@ -227,38 +227,72 @@ def create_or_update_user(connection, module):
def destroy_user(connection, module): def destroy_user(connection, module):
params = dict() user_name = module.params.get('name')
params['UserName'] = module.params.get('name')
user = get_user(connection, module, user_name)
# User is not present
if not user:
module.exit_json(changed=False)
if get_user(connection, module, params['UserName']):
# Check mode means we would remove this user # Check mode means we would remove this user
if module.check_mode: if module.check_mode:
module.exit_json(changed=True) module.exit_json(changed=True)
# Remove any attached policies otherwise deletion fails # Remove any attached policies otherwise deletion fails
try: try:
for policy in get_attached_policy_list(connection, module, params['UserName']): for policy in get_attached_policy_list(connection, module, user_name):
connection.detach_user_policy(UserName=params['UserName'], PolicyArn=policy['PolicyArn']) connection.detach_user_policy(UserName=user_name, PolicyArn=policy['PolicyArn'])
except ClientError as e: except (ClientError, BotoCoreError) as e:
module.fail_json(msg="Unable to detach policy {0} from user {1}: {2}".format( module.fail_json_aws(e, msg="Unable to delete user {0}".format(user_name))
policy['PolicyArn'], params['UserName'], to_native(e)),
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
except ParamValidationError as e:
module.fail_json(msg="Unable to detach policy {0} from user {1}: {2}".format(
policy['PolicyArn'], params['UserName'], to_native(e)),
exception=traceback.format_exc())
try: try:
connection.delete_user(**params) # Remove user's access keys
except ClientError as e: access_keys = connection.list_access_keys(UserName=user_name)["AccessKeyMetadata"]
module.fail_json(msg="Unable to delete user {0}: {1}".format(params['UserName'], to_native(e)), for access_key in access_keys:
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) connection.delete_access_key(UserName=user_name, AccessKeyId=access_key["AccessKeyId"])
except ParamValidationError as e:
module.fail_json(msg="Unable to delete user {0}: {1}".format(params['UserName'], to_native(e)),
exception=traceback.format_exc())
else: # Remove user's login profile (console password)
module.exit_json(changed=False) delete_user_login_profile(connection, module, user_name)
# Remove user's ssh public keys
ssh_public_keys = connection.list_ssh_public_keys(UserName=user_name)["SSHPublicKeys"]
for ssh_public_key in ssh_public_keys:
connection.delete_ssh_public_key(UserName=user_name, SSHPublicKeyId=ssh_public_key["SSHPublicKeyId"])
# Remove user's service specific credentials
service_credentials = connection.list_service_specific_credentials(UserName=user_name)["ServiceSpecificCredentials"]
for service_specific_credential in service_credentials:
connection.delete_service_specific_credential(
UserName=user_name,
ServiceSpecificCredentialId=service_specific_credential["ServiceSpecificCredentialId"]
)
# Remove user's signing certificates
signing_certificates = connection.list_signing_certificates(UserName=user_name)["Certificates"]
for signing_certificate in signing_certificates:
connection.delete_signing_certificate(
UserName=user_name,
CertificateId=signing_certificate["CertificateId"]
)
# Remove user's MFA devices
mfa_devices = connection.list_mfa_devices(UserName=user_name)["MFADevices"]
for mfa_device in mfa_devices:
connection.deactivate_mfa_device(UserName=user_name, SerialNumber=mfa_device["SerialNumber"])
# Remove user's inline policies
inline_policies = connection.list_user_policies(UserName=user_name)["PolicyNames"]
for policy_name in inline_policies:
connection.delete_user_policy(UserName=user_name, PolicyName=policy_name)
# Remove user's group membership
user_groups = connection.list_groups_for_user(UserName=user_name)["Groups"]
for group in user_groups:
connection.remove_user_from_group(UserName=user_name, GroupName=group["GroupName"])
connection.delete_user(UserName=user_name)
except (ClientError, BotoCoreError) as e:
module.fail_json_aws(e, msg="Unable to delete user {0}".format(user_name))
module.exit_json(changed=True) module.exit_json(changed=True)
@ -286,8 +320,18 @@ def get_attached_policy_list(connection, module, name):
if e.response['Error']['Code'] == 'NoSuchEntity': if e.response['Error']['Code'] == 'NoSuchEntity':
return None return None
else: else:
module.fail_json(msg="Unable to get policies for user {0}: {1}".format(name, to_native(e)), module.fail_json_aws(e, msg="Unable to get policies for user {0}".format(name))
**camel_dict_to_snake_dict(e.response))
def delete_user_login_profile(connection, module, user_name):
try:
return connection.delete_login_profile(UserName=user_name)
except ClientError as e:
if e.response["Error"]["Code"] == "NoSuchEntity":
return None
else:
module.fail_json_aws(e, msg="Unable to delete login profile for user {0}".format(user_name))
def main(): def main():
@ -302,7 +346,7 @@ def main():
) )
) )
module = AnsibleModule( module = AnsibleAWSModule(
argument_spec=argument_spec, argument_spec=argument_spec,
supports_check_mode=True supports_check_mode=True
) )