from typing import Optional, Any, List, Dict from phi.aws.api_client import AwsApiClient from phi.aws.resource.base import AwsResource from phi.cli.console import print_info from phi.utils.log import logger class IamPolicy(AwsResource): """ Reference: - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/iam.html#policy """ resource_type: Optional[str] = "IamPolicy" service_name: str = "iam" # PolicyName # The friendly name of the policy. name: str # The JSON policy document that you want to use as the content for the new policy. # You must provide policies in JSON format in IAM. # However, for CloudFormation templates formatted in YAML, you can provide the policy in JSON or YAML format. # CloudFormation always converts a YAML policy to JSON format before submitting it to IAM. policy_document: str # The path for the policy. This parameter is optional. If it is not included, it defaults to a slash (/). path: Optional[str] = None # A friendly description of the policy. description: Optional[str] = None # A list of tags that you want to attach to the new policy. Each tag consists of a key name and an associated value. tags: Optional[List[Dict[str, str]]] = None arn: Optional[str] = None def _create(self, aws_client: AwsApiClient) -> bool: """Creates the IamPolicy Args: aws_client: The AwsApiClient for the current cluster """ print_info(f"Creating {self.get_resource_type()}: {self.get_resource_name()}") try: # create a dict of args which are not null, otherwise aws type validation fails not_null_args: Dict[str, Any] = {} if self.path: not_null_args["Path"] = self.path if self.description: not_null_args["Description"] = self.description if self.tags: not_null_args["Tags"] = self.tags # Create Policy service_resource = self.get_service_resource(aws_client) policy = service_resource.create_policy( PolicyName=self.name, PolicyDocument=self.policy_document, **not_null_args, ) # logger.debug(f"Policy: {policy}") # Validate Policy creation create_date = policy.create_date self.arn = policy.arn logger.debug(f"create_date: {create_date}") logger.debug(f"arn: {self.arn}") if create_date is not None: print_info(f"Policy created: {self.name}") self.active_resource = policy return True logger.error("Policy could not be created") except Exception as e: logger.error(f"{self.get_resource_type()} could not be created.") logger.error(e) return False def post_create(self, aws_client: AwsApiClient) -> bool: # Wait for Policy to be created if self.wait_for_create: try: print_info(f"Waiting for {self.get_resource_type()} to be created.") if self.arn is not None: waiter = self.get_service_client(aws_client).get_waiter("policy_exists") waiter.wait( PolicyArn=self.arn, WaiterConfig={ "Delay": self.waiter_delay, "MaxAttempts": self.waiter_max_attempts, }, ) else: logger.warning("Skipping waiter, No Policy ARN found") except Exception as e: logger.error("Waiter failed.") logger.error(e) return True def _read(self, aws_client: AwsApiClient) -> Optional[Any]: """Returns the IamPolicy Args: aws_client: The AwsApiClient for the current cluster """ from botocore.exceptions import ClientError logger.debug(f"Reading {self.get_resource_type()}: {self.get_resource_name()}") try: service_resource = self.get_service_resource(aws_client) policy = None for _policy in service_resource.policies.all(): if _policy.policy_name == self.name: policy = _policy break if policy is None: logger.debug("No Policy found") return None policy.load() create_date = policy.create_date self.arn = policy.arn logger.debug(f"create_date: {create_date}") logger.debug(f"arn: {self.arn}") if create_date is not None: logger.debug(f"Policy found: {policy.policy_name}") self.active_resource = policy except ClientError as ce: logger.debug(f"ClientError: {ce}") except Exception as e: logger.error(f"Error reading {self.get_resource_type()}.") logger.error(e) return self.active_resource def _delete(self, aws_client: AwsApiClient) -> bool: """Deletes the IamPolicy Args: aws_client: The AwsApiClient for the current cluster """ print_info(f"Deleting {self.get_resource_type()}: {self.get_resource_name()}") try: policy = self._read(aws_client) # logger.debug(f"Policy: {policy}") # logger.debug(f"Policy type: {type(policy)}") self.active_resource = None if policy is None: logger.warning(f"No {self.get_resource_type()} to delete") return True # Before you can delete a managed policy, # you must first detach the policy from all users, groups, and roles # that it is attached to. In addition, you must delete all # the policy's versions. # detach all roles roles = policy.attached_roles.all() for role in roles: print_info(f"Detaching policy from role: {role}") policy.detach_role(RoleName=role.name) # detach all users users = policy.attached_users.all() for user in users: print_info(f"Detaching policy from user: {user}") policy.detach_user(UserName=user.name) # detach all groups groups = policy.attached_groups.all() for group in groups: print_info(f"Detaching policy from group: {group}") policy.detach_group(GroupName=group.name) # delete all versions default_version = policy.default_version versions = policy.versions.all() for version in versions: if version.version_id == default_version.version_id: print_info(f"Skipping deleting default PolicyVersion: {version}") continue print_info(f"Deleting PolicyVersion: {version}") version.delete() # delete policy policy.delete() return True except Exception as e: logger.error(f"{self.get_resource_type()} could not be deleted.") logger.error("Please try again or delete resources manually.") logger.error(e) return False