File size: 9,714 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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
from typing import Optional, Any, List, Dict

from phi.aws.api_client import AwsApiClient
from phi.aws.resource.base import AwsResource
from phi.aws.resource.iam.policy import IamPolicy
from phi.cli.console import print_info
from phi.utils.log import logger


class IamRole(AwsResource):
    """
    Reference:
    - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/iam.html#service-resource
    """

    resource_type: Optional[str] = "IamRole"
    service_name: str = "iam"

    # RoleName: The name of the role to create.
    name: str
    # The trust relationship policy document that grants an entity permission to assume the role.
    assume_role_policy_document: str
    # The path to the role. This parameter is optional. If it is not included, it defaults to a slash (/).
    path: Optional[str] = None
    # A description of the role.
    description: Optional[str] = None
    # The maximum session duration (in seconds) that you want to set for the specified role.
    # If you do not specify a value for this setting, the default maximum of one hour is applied.
    # This setting can have a value from 1 hour to 12 hours.
    max_session_duration: Optional[int] = None
    # The ARN of the policy that is used to set the permissions boundary for the role.
    permissions_boundary: Optional[str] = None
    # A list of tags that you want to attach to the new role. Each tag consists of a key name and an associated value.
    tags: Optional[List[Dict[str, str]]] = None

    # List of IAM policies to
    # attach to the role after it is created
    policies: Optional[List[IamPolicy]] = None
    # List of IAM policy ARNs (Amazon Resource Name) to
    # attach to the role after it is created
    policy_arns: Optional[List[str]] = None

    # The Amazon Resource Name (ARN) specifying the role.
    # To get the arn, use get_arn() function
    arn: Optional[str] = None

    def _create(self, aws_client: AwsApiClient) -> bool:
        """Creates the IamRole

        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.max_session_duration:
                not_null_args["MaxSessionDuration"] = self.max_session_duration
            if self.permissions_boundary:
                not_null_args["PermissionsBoundary"] = self.permissions_boundary
            if self.tags:
                not_null_args["Tags"] = self.tags

            # Create Role
            service_resource = self.get_service_resource(aws_client)
            role = service_resource.create_role(
                RoleName=self.name,
                AssumeRolePolicyDocument=self.assume_role_policy_document,
                **not_null_args,
            )
            # logger.debug(f"Role: {role}")

            # Validate Role creation
            create_date = role.create_date
            self.arn = role.arn
            logger.debug(f"create_date: {create_date}")
            logger.debug(f"arn: {self.arn}")
            if create_date is not None:
                print_info(f"Role created: {self.name}")
                self.active_resource = role
                return True
            logger.error("Role 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 Role to be created
        if self.wait_for_create:
            try:
                print_info(f"Waiting for {self.get_resource_type()} to be created.")
                waiter = self.get_service_client(aws_client).get_waiter("role_exists")
                waiter.wait(
                    RoleName=self.name,
                    WaiterConfig={
                        "Delay": self.waiter_delay,
                        "MaxAttempts": self.waiter_max_attempts,
                    },
                )
            except Exception as e:
                logger.error("Waiter failed.")
                logger.error(e)
        # Attach policy arns to role
        attach_policy_success = True
        if self.active_resource is not None and self.policy_arns is not None:
            _success = self.attach_policy_arns(aws_client)
            if not _success:
                attach_policy_success = False
        # Attach policies to role
        if self.active_resource is not None and self.policies is not None:
            _success = self.attach_policies(aws_client)
            if not _success:
                attach_policy_success = False
        # logger.info(f"attach_policy_success: {attach_policy_success}")
        return attach_policy_success

    def _read(self, aws_client: AwsApiClient) -> Optional[Any]:
        """Returns the IamRole

        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)
            role = service_resource.Role(name=self.name)
            role.load()
            create_date = role.create_date
            self.arn = role.arn
            logger.debug(f"create_date: {create_date}")
            logger.debug(f"arn: {self.arn}")
            if create_date is not None:
                logger.debug(f"Role found: {role.role_name}")
                self.active_resource = role
        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 IamRole

        Args:
            aws_client: The AwsApiClient for the current cluster
        """

        print_info(f"Deleting {self.get_resource_type()}: {self.get_resource_name()}")
        try:
            role = self._read(aws_client)
            # logger.debug(f"Role: {role}")
            # logger.debug(f"Role type: {type(role)}")
            self.active_resource = None

            if role is None:
                logger.warning(f"No {self.get_resource_type()} to delete")
                return True

            # detach all policies
            policies = role.attached_policies.all()
            for policy in policies:
                print_info(f"Detaching policy: {policy}")
                role.detach_policy(PolicyArn=policy.arn)

            # detach all instance profiles
            profiles = role.instance_profiles.all()
            for profile in profiles:
                print_info(f"Removing role from profile: {profile}")
                profile.remove_role(RoleName=role.name)

            # delete role
            role.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

    def attach_policy_arns(self, aws_client: AwsApiClient) -> bool:
        """
        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.

        Returns:
            True if operation was successful
        """
        if self.policy_arns is None:
            return True

        role = self._read(aws_client)
        if role is None:
            logger.warning(f"No {self.get_resource_type()} to attach")
            return True
        try:
            # logger.debug("Attaching managed policies to role")
            for arn in self.policy_arns:
                if isinstance(arn, str):
                    role.attach_policy(PolicyArn=arn)
                    print_info(f"Attaching policy to {role.role_name}: {arn}")
            return True
        except Exception as e:
            logger.error(e)
        return False

    def attach_policies(self, aws_client: AwsApiClient) -> bool:
        """
        Returns:
            True if operation was successful
        """
        if self.policies is None:
            return True

        role = self._read(aws_client)
        if role is None:
            logger.warning(f"No {self.get_resource_type()} to attach")
            return True
        try:
            logger.debug("Attaching managed policies to role")
            for policy in self.policies:
                if policy.arn is None:
                    create_success = policy.create(aws_client)
                    if not create_success:
                        return False
                if policy.arn is not None:
                    role.attach_policy(PolicyArn=policy.arn)
                    print_info(f"Attaching policy to {role.role_name}: {policy.arn}")
            return True
        except Exception as e:
            logger.error(e)
        return False

    def get_arn(self, aws_client: AwsApiClient) -> Optional[str]:
        role = self._read(aws_client)
        if role is None:
            return None

        self.arn = role.arn
        return self.arn