Source code for idem_aws.states.aws.iam.user

"""State module for managing IAM User."""
import copy
from dataclasses import field
from dataclasses import make_dataclass
from typing import Any
from typing import Dict
from typing import List

SERVICE = "iam"
RESOURCE = "User"

__contracts__ = ["resource"]
TREQ = {
    "absent": {
        "require": [
            "aws.iam.user_policy_attachment.absent",
        ],
    },
}


[docs]async def present( hub, ctx, name: str, user_name: str, resource_id: str = None, path: str = None, permissions_boundary: str = None, tags: Dict[str, Any] or List[ make_dataclass( "Tag", [("Key", str, field(default=None)), ("Value", str, field(default=None))], ) ] = None, ) -> Dict[str, Any]: """Creates a new IAM user for your Amazon Web Services account. For information about quotas for the number of IAM users you can create, see IAM and STS quotas in the IAM User Guide. Args: name(str): An Idem name of the resource. resource_id(str, Optional): AWS IAM username user_name(str): The name of the user to create. IAM user, group, role, and policy names must be unique within the account. Names are not distinguished by case. For example, you cannot create resources named both "MyResource" and "myresource". path(str, Optional): The path for the user name. For more information about paths, see IAM identifiers in the IAM User Guide. This parameter is Optional. If it is not included, it defaults to a slash (/). This parameter allows (through its regex pattern) a string of characters consisting of either a forward slash (/) by itself or a string that must begin and end with forward slashes. In addition, it can contain any ASCII character from the ! (\u0021) through the DEL character (\u007F), including most punctuation characters, digits, and upper and lowercased letters. Defaults to None. permissions_boundary(str, Optional): The ARN of the policy that is used to set the permissions boundary for the user. Defaults to None. tags(dict or list, Optional): Dict in the format of ``{tag-key: tag-value}`` or List of tags in the format of ``[{"Key": tag-key, "Value": tag-value}]`` to associate with the user. Each tag consists of a key name and an associated value. Defaults to None. * Key (str): The key name that can be used to look up or retrieve the associated value. For example, Department or Cost Center are common choices. * Value (str): The value associated with this tag. For example, tags with a key name of Department could have values such as Human Resources, Accounting, and Support. Tags with a key name of Cost Center might have values that consist of the number associated with the different cost centers in your company. Typically, many resources have tags with the same key name but with different values. Amazon Web Services always interprets the tag Value as a single string. If you need to store an array, you can store comma-separated values in the string. However, you must interpret the value in your code. Request Syntax: .. code-block:: sls user-name: aws.iam.user.present: - user_name: 'string' - resource_id: 'string' - path: 'string' - permissions_boundary: 'string' - tags: - Key: 'string' Value: 'string' Returns: Dict[str, Any] Examples: .. code-block:: sls user1: aws.iam.user.present: - user_name: iam-user-1 - resource_id: iam-user-1 - path: / - permissions_boundary: arn:aew12k3r4kr9efg9 - tags: - Key: test-key Value: test-value """ result = dict(comment=[], old_state=None, new_state=None, name=name, result=True) before = None is_user_updated = False if resource_id: resource = await hub.tool.boto3.resource.create( ctx, SERVICE, RESOURCE, name=resource_id ) before = await hub.tool.boto3.resource.describe(resource) if isinstance(tags, List): tags = hub.tool.aws.tag_utils.convert_tag_list_to_dict(tags) if before: result[ "old_state" ] = hub.tool.aws.iam.conversion_utils.convert_raw_user_to_present( raw_resource=before, idem_resource_name=name ) plan_state = copy.deepcopy(result["old_state"]) update_ret = await hub.tool.aws.iam.user.update_user( ctx, before=result["old_state"], user_name=user_name, path=path ) result["comment"] += update_ret["comment"] result["result"] = update_ret["result"] if update_ret["ret"] and "user_name" in update_ret["ret"]: resource_id = user_name is_user_updated = bool(update_ret["ret"]) if update_ret["ret"] and ctx.get("test", False): result["comment"] += [f"Would update aws.iam.user '{name}'"] if "user_name" in update_ret["ret"]: plan_state["user_name"] = update_ret["ret"]["user_name"] plan_state["resource_id"] = update_ret["ret"]["user_name"] if "path" in update_ret["ret"]: plan_state["path"] = update_ret["ret"]["path"] if tags is not None and tags != before.get("Tags", None): # Update tags update_ret = await hub.tool.aws.iam.user.update_user_tags( ctx, user_name=resource_id, old_tags=result["old_state"].get("tags", {}), new_tags=tags, ) result["result"] = result["result"] and update_ret["result"] result["comment"] += update_ret["comment"] is_user_updated = is_user_updated or bool(update_ret["result"]) if ctx.get("test", False) and update_ret["ret"] is not None: plan_state["tags"] = update_ret["ret"] if not is_user_updated: result["comment"] += [f"{name} already exists"] else: try: if ctx.get("test", False): result["new_state"] = hub.tool.aws.test_state_utils.generate_test_state( enforced_state={}, desired_state={ "name": name, "user_name": user_name, "arn": "arn_known_after_present", "permissions_boundary": permissions_boundary, "path": path, "tags": tags, }, ) result["comment"] += [f"Would create aws.iam.user '{name}'"] return result # Attempt to create the User ret = await hub.exec.boto3.client.iam.create_user( ctx, Path=path, UserName=user_name, PermissionsBoundary=permissions_boundary, Tags=hub.tool.aws.tag_utils.convert_tag_dict_to_list(tags) if tags else None, ) result["result"] = ret["result"] if not result["result"]: result["comment"] = ret["comment"] return result result["comment"] += [f"Created aws.iam.user '{name}'"] resource_id = user_name except hub.tool.boto3.exception.ClientError as e: result["comment"] += [f"{e.__class__.__name__}: {e}"] result["result"] = False try: if ctx.get("test", False): result["new_state"] = plan_state elif (not before) or is_user_updated: resource = await hub.tool.boto3.resource.create( ctx, SERVICE, RESOURCE, name=resource_id ) after = await hub.tool.boto3.resource.describe(resource) result[ "new_state" ] = hub.tool.aws.iam.conversion_utils.convert_raw_user_to_present( raw_resource=after, idem_resource_name=name ) else: result["new_state"] = copy.deepcopy(result["old_state"]) except Exception as e: result["comment"] += [str(e)] result["result"] = False return result
[docs]async def absent(hub, ctx, name: str, resource_id: str = None) -> Dict[str, Any]: """Deletes the specified IAM user. Unlike the Amazon Web Services Management Console, when you delete a user programmatically, you must delete the items attached to the user manually, or the deletion fails. For more information, see Deleting an IAM user. Before attempting to delete a user, remove the following items: 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) Args: name(str): A name, ID to identify the resource. resource_id(str, Optional): The name of the user to delete. This parameter allows (through its regex pattern) a string of characters consisting of upper and lowercase alphanumeric characters with no spaces. You can also include any of the following characters: _+=,.@-. Request syntax: .. code-block:: sls user-1: aws.iam.user.absent: - name: 'string' - resource_id: 'string' Returns: Dict[str, Any] Examples: .. code-block:: sls user-1: aws.iam.user.absent: - name: user-1 - resource_id: user-1 """ result = dict(comment=[], old_state=None, new_state=None, name=name, result=True) if not resource_id: result["comment"] = hub.tool.aws.comment_utils.already_absent_comment( resource_type="aws.iam.user", name=name ) return result resource = await hub.tool.boto3.resource.create(ctx, SERVICE, RESOURCE, resource_id) before = await hub.tool.boto3.resource.describe(resource) if not before: result["comment"] += [f"'{name}' already absent"] elif ctx.get("test", False): result[ "old_state" ] = hub.tool.aws.iam.conversion_utils.convert_raw_user_to_present( raw_resource=before, idem_resource_name=name ) result["comment"] += [f"Would delete aws.iam.user '{name}'"] return result else: result[ "old_state" ] = hub.tool.aws.iam.conversion_utils.convert_raw_user_to_present( raw_resource=before, idem_resource_name=name ) try: # Attempt to delete user ssh key user_ssh_public_keys_list = ( await hub.exec.boto3.client.iam.list_ssh_public_keys(ctx, UserName=name) ) if user_ssh_public_keys_list["result"]: attached_user_ssh_public_keys_list = user_ssh_public_keys_list[ "ret" ].get("SSHPublicKeys") for ssh_key in attached_user_ssh_public_keys_list: ret_delete = await hub.exec.boto3.client.iam.delete_ssh_public_key( ctx, UserName=name, SSHPublicKeyId=ssh_key.get("SSHPublicKeyId") ) if not ret_delete["result"]: result["result"] = False result["comment"] = ret_delete["comment"] hub.log.debug( f"Could not delete user ssh key {ret_delete['comment']}" ) return result else: if ( user_ssh_public_keys_list["comment"] and "NoSuchEntityException" in user_ssh_public_keys_list["comment"][0] ): result["comment"] += [ f"No user_ssh_key associated with aws.iam.user '{name}'" ] else: result["result"] = False result["comment"] = user_ssh_public_keys_list["comment"] return result # Attempt to delete the User ret = await hub.exec.boto3.client.iam.delete_user( ctx, UserName=resource_id, ) result["result"] = ret["result"] if not result["result"]: result["comment"] = ret["comment"] return result result["comment"] = f"Deleted aws.iam.user '{name}'" except hub.tool.boto3.exception.ClientError as e: result["comment"] += [f"{e.__class__.__name__}: {e}"] result["result"] = False return result
[docs]async def describe(hub, ctx) -> Dict[str, Dict[str, Any]]: """Describe the resource in a way that can be recreated/managed with the corresponding "present" function Lists the IAM users that have the specified path prefix. If no path prefix is specified, the operation returns all users in the Amazon Web Services account. If there are none, the operation returns an empty list. IAM resource-listing operations return a subset of the available attributes for the resource. For example, this operation does not return tags, even though they are an attribute of the returned object. To view all of the information for a user, see GetUser. You can paginate the results using the MaxItems and Marker parameters. Returns: Dict[str, Any] Examples: .. code-block:: bash $ idem describe aws.iam.user """ result = {} ret = await hub.exec.boto3.client.iam.list_users(ctx) if not ret["result"]: hub.log.warning(f"Could not describe user {ret['comment']}") return {} for user in ret["ret"]["Users"]: resource_name = user.get("UserName") # This is required to get tags for each user boto2_resource = await hub.tool.boto3.resource.create( ctx, "iam", "User", resource_name ) resource = await hub.tool.boto3.resource.describe(boto2_resource) resource_id = resource.get("UserName") translated_resource = ( hub.tool.aws.iam.conversion_utils.convert_raw_user_to_present( raw_resource=resource, idem_resource_name=resource_id ) ) resource_key = f"iam-user-{resource_id}" result[resource_key] = { "aws.iam.user.present": [ {parameter_key: parameter_value} for parameter_key, parameter_value in translated_resource.items() ] } return result