File size: 7,481 Bytes
105b369
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
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