"""State module for managing IAM Role Policy Attachments."""
import copy
from typing import Any
from typing import Dict
__contracts__ = ["resource"]
[docs]async def present(
hub, ctx, name: str, role_name: str, policy_arn: str, resource_id: str = None
) -> Dict[str, Any]:
"""Attaches the specified managed policy to the specified IAM role.
When you attach a managed policy to a role, the managed policy becomes
part of the role's permission (access) policy.
Args:
name(str):
A name to represent the operation. This name is only for logging purpose. It is not used to attach a policy
to a role.
role_name(str):
The name (friendly name, not ARN) of the role to attach a policy. 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: _+=,.@-
policy_arn(str):
The Amazon Resource Name (ARN) of the IAM policy you want to attach.
resource_id(str, Optional):
The identifier for this object
Request Syntax:
.. code-block:: sls
[iam-attach-role-policy-name]:
aws.iam.role_policy_attachment.present:
- resource_id: 'string'
- role_name: 'string'
- policy_arn: 'string'
Returns:
Dict[str, Any]
Examples:
.. code-block:: sls
idem-test-policy-temp-name:
aws.iam.role_policy_attachment.present:
- role_name: idem-test-role-name
- policy_arn: arn:aws:iam::aws:policy/ReadOnlyAccess
"""
result = dict(comment=[], old_state=None, new_state=None, name=name, result=True)
try:
before_ret = (
await hub.tool.aws.iam.role_policy_attachment.is_role_policy_attached(
ctx, role_name=role_name, policy_arn=policy_arn
)
)
except hub.tool.boto3.exception.ClientError as e:
result["comment"] += [f"{e.__class__.__name__}: {e}"]
result["result"] = False
return result
# If before_ret has False result and has an error message in comment, then immediately return with the error,
# If role does not exists - NoSuchEntityException is in the comment, do not fail in 'test' mode.
if before_ret["result"] is False and before_ret["comment"]:
if "NoSuchEntityException" in before_ret["comment"][0] and ctx.get(
"test", False
):
result["new_state"] = hub.tool.aws.test_state_utils.generate_test_state(
enforced_state={},
desired_state={
"role_name": role_name,
"policy_arn": policy_arn,
},
)
result["comment"] = (
f"Would create aws.iam.role_policy_attachment. \
Role {role_name}, policy {policy_arn}.",
)
return result
result["result"] = False
result["comment"] = before_ret["comment"]
return result
if before_ret["result"]:
result[
"old_state"
] = hub.tool.aws.iam.conversion_utils.convert_raw_role_policy_attachment_to_present(
role_name, policy_arn
)
result["new_state"] = copy.deepcopy(result["old_state"])
result["comment"] += [f"aws.iam.role_policy_attachment '{name}' already exists"]
else:
if ctx.get("test", False):
result["new_state"] = hub.tool.aws.test_state_utils.generate_test_state(
enforced_state={},
desired_state={
"role_name": role_name,
"policy_arn": policy_arn,
},
)
result["comment"] = (
f"Would create aws.iam.role_policy_attachment. \
Role {role_name}, policy {policy_arn}.",
)
return result
try:
ret = await hub.exec.boto3.client.iam.attach_role_policy(
ctx,
RoleName=role_name,
PolicyArn=policy_arn,
)
result["result"] = ret["result"]
if not result["result"]:
result["comment"] = ret["comment"]
return result
result["comment"] += [f"Created aws.iam.role_policy_attachment '{name}'"]
result[
"new_state"
] = hub.tool.aws.iam.conversion_utils.convert_raw_role_policy_attachment_to_present(
role_name, policy_arn
)
except hub.tool.boto3.exception.ClientError as e:
result["comment"] += [f"{e.__class__.__name__}: {e}"]
result["result"] = False
return result
[docs]async def absent(
hub, ctx, name: str, role_name: str = None, policy_arn: str = None
) -> Dict[str, Any]:
"""Removes the specified managed policy from the specified role.
Args:
name(str):
The name of the AWS IAM role policy.
role_name(str, Optional):
The name (friendly name, not ARN) of the role to attach a policy.
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: _+=,.@-
policy_arn(str, Optional):
The Amazon Resource Name (ARN) of the IAM policy you want to attach.
Request Syntax:
.. code-block:: sls
[rpa-resource-id]:
aws.iam.role_policy_attachment.absent:
- name: "string"
- role_name: "string"
- policy_arn: "string"
Returns:
Dict[str, Any]
Examples:
.. code-block:: sls
resource_is_absent:
aws.iam.role_policy_attachment.absent:
- name: value
- role_name: value
- policy_arn: value
"""
result = dict(comment=[], old_state=None, new_state=None, name=name, result=True)
if not role_name and not policy_arn:
result["comment"] = hub.tool.aws.comment_utils.already_absent_comment(
resource_type="aws.iam.role_policy_attachment", name=name
)
return result
try:
before_ret = (
await hub.tool.aws.iam.role_policy_attachment.is_role_policy_attached(
ctx, role_name=role_name, policy_arn=policy_arn
)
)
except hub.tool.boto3.exception.ClientError as e:
result["comment"] += [f"{e.__class__.__name__}: {e}"]
result["result"] = False
return result
if before_ret["result"] is False and before_ret["comment"]:
if "NoSuchEntityException" in before_ret["comment"][0]:
result["comment"] += [
f"aws.iam.role_policy_attachment '{name}' already absent"
]
else:
result["result"] = False
result["comment"] = before_ret["comment"]
return result
if not before_ret["result"]:
result["comment"] += [f"aws.iam.role_policy_attachment '{name}' already absent"]
else:
try:
result[
"old_state"
] = hub.tool.aws.iam.conversion_utils.convert_raw_role_policy_attachment_to_present(
role_name, policy_arn
)
if ctx.get("test", False):
result["comment"] = (
f"Would detach aws.iam.role_policy_attachment. Role '{role_name}', policy '{policy_arn}'",
)
return result
ret = await hub.exec.boto3.client.iam.detach_role_policy(
ctx, RoleName=role_name, PolicyArn=policy_arn
)
result["result"] = ret["result"]
if not result["result"]:
result["comment"] = ret["comment"]
result["result"] = False
return result
result["comment"] = (
f"Detached aws.iam.role_policy_attachment. Role '{role_name}', policy '{policy_arn}'",
)
except hub.tool.boto3.exception.ClientError as e:
result["comment"] += [f"{e.__class__.__name__}: {e}"]
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 names of the attached managed policies of all IAM roles. If there are no managed policies attached
to the specified role, the operation returns an empty dict.
Returns:
Dict[str, Any]
Examples:
.. code-block:: bash
$ idem describe aws.iam.role_policy_attachment
"""
result = {}
# To describe all the attached role policies of all the roles, we first need to list all the roles, then get all the
# attached policies
ret_roles = await hub.exec.boto3.client.iam.list_roles(ctx)
if not ret_roles["result"]:
hub.log.warning(f"Could not describe role {ret_roles['comment']}")
return {}
role_name_list = [role.get("RoleName") for role in ret_roles["ret"]["Roles"]]
for role_name in role_name_list:
ret_attached_policies = (
await hub.exec.boto3.client.iam.list_attached_role_policies(
ctx=ctx, RoleName=role_name
)
)
if not ret_attached_policies["result"]:
hub.log.warning(
f"Could not get attached policy list with role {role_name} with error"
f" {ret_attached_policies['comment']} . Describe will skip this role and continue."
)
else:
resources = ret_attached_policies["ret"].get("AttachedPolicies")
for resource in resources:
translated_resource = hub.tool.aws.iam.conversion_utils.convert_raw_role_policy_attachment_to_present(
role_name, resource.get("PolicyArn")
)
resource_id = f"{role_name}-{translated_resource['policy_arn']}"
result[resource_id] = {
"aws.iam.role_policy_attachment.present": [
{parameter_key: parameter_value}
for parameter_key, parameter_value in translated_resource.items()
]
}
return result