Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aws
GitHub Repository: aws/aws-cli
Path: blob/develop/awscli/customizations/emr/createdefaultroles.py
1567 views
1
# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License"). You
4
# may not use this file except in compliance with the License. A copy of
5
# the License is located at
6
#
7
# http://aws.amazon.com/apache2.0/
8
#
9
# or in the "license" file accompanying this file. This file is
10
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
# ANY KIND, either express or implied. See the License for the specific
12
# language governing permissions and limitations under the License.
13
14
import logging
15
import re
16
import botocore.exceptions
17
import botocore.session
18
from botocore import xform_name
19
20
from awscli.customizations.utils import get_policy_arn_suffix
21
from awscli.customizations.emr import configutils
22
from awscli.customizations.emr import emrutils
23
from awscli.customizations.emr import exceptions
24
from awscli.customizations.emr.command import Command
25
from awscli.customizations.emr.constants import EC2
26
from awscli.customizations.emr.constants import EC2_ROLE_NAME
27
from awscli.customizations.emr.constants import EC2_SERVICE_PRINCIPAL
28
from awscli.customizations.emr.constants import ROLE_ARN_PATTERN
29
from awscli.customizations.emr.constants import EMR
30
from awscli.customizations.emr.constants import EMR_ROLE_NAME
31
from awscli.customizations.emr.constants import EMR_AUTOSCALING_ROLE_NAME
32
from awscli.customizations.emr.constants import APPLICATION_AUTOSCALING
33
from awscli.customizations.emr.constants import EC2_ROLE_POLICY_NAME
34
from awscli.customizations.emr.constants import EMR_ROLE_POLICY_NAME
35
from awscli.customizations.emr.constants \
36
import EMR_AUTOSCALING_ROLE_POLICY_NAME
37
from awscli.customizations.emr.constants import EMR_AUTOSCALING_SERVICE_NAME
38
from awscli.customizations.emr.constants \
39
import EMR_AUTOSCALING_SERVICE_PRINCIPAL
40
from awscli.customizations.emr.exceptions import ResolveServicePrincipalError
41
42
43
LOG = logging.getLogger(__name__)
44
45
46
def assume_role_policy(serviceprincipal):
47
return {
48
"Version": "2008-10-17",
49
"Statement": [
50
{
51
"Sid": "",
52
"Effect": "Allow",
53
"Principal": {"Service": serviceprincipal},
54
"Action": "sts:AssumeRole"
55
}
56
]
57
}
58
59
60
def get_role_policy_arn(region, policy_name):
61
region_suffix = get_policy_arn_suffix(region)
62
role_arn = ROLE_ARN_PATTERN.replace("{{region_suffix}}", region_suffix)
63
role_arn = role_arn.replace("{{policy_name}}", policy_name)
64
return role_arn
65
66
67
def get_service_principal(service, endpoint_host, session=None):
68
if service == EC2:
69
return EC2_SERVICE_PRINCIPAL
70
71
suffix, region = _get_suffix_and_region_from_endpoint_host(endpoint_host)
72
if session is None:
73
session = botocore.session.Session()
74
75
if service == EMR_AUTOSCALING_SERVICE_NAME:
76
if region not in session.get_available_regions('emr', 'aws-cn'):
77
return EMR_AUTOSCALING_SERVICE_PRINCIPAL
78
79
return service + '.' + suffix
80
81
82
def _get_suffix_and_region_from_endpoint_host(endpoint_host):
83
suffix_match = _get_regex_match_from_endpoint_host(endpoint_host)
84
85
if suffix_match is not None and suffix_match.lastindex >= 3:
86
suffix = suffix_match.group(3)
87
region = suffix_match.group(2)
88
else:
89
raise ResolveServicePrincipalError
90
91
return suffix, region
92
93
94
def _get_regex_match_from_endpoint_host(endpoint_host):
95
if endpoint_host is None:
96
return None
97
regex_match = re.match("(https?://)([^.]+).elasticmapreduce.([^/]*)",
98
endpoint_host)
99
100
# Supports 'elasticmapreduce.{region}.' and '{region}.elasticmapreduce.'
101
if regex_match is None:
102
regex_match = re.match("(https?://elasticmapreduce).([^.]+).([^/]*)",
103
endpoint_host)
104
return regex_match
105
106
107
class CreateDefaultRoles(Command):
108
NAME = "create-default-roles"
109
DESCRIPTION = ('Creates the default IAM role ' +
110
EC2_ROLE_NAME + ' and ' +
111
EMR_ROLE_NAME + ' which can be used when creating the'
112
' cluster using the create-cluster command. The default'
113
' roles for EMR use managed policies, which are updated'
114
' automatically to support future EMR functionality.\n'
115
'\nIf you do not have a Service Role and Instance Profile '
116
'variable set for your create-cluster command in the AWS '
117
'CLI config file, create-default-roles will automatically '
118
'set the values for these variables with these default '
119
'roles. If you have already set a value for Service Role '
120
'or Instance Profile, create-default-roles will not '
121
'automatically set the defaults for these variables in the '
122
'AWS CLI config file. You can view settings for variables '
123
'in the config file using the "aws configure get" command.'
124
'\n')
125
ARG_TABLE = [
126
{'name': 'iam-endpoint',
127
'no_paramfile': True,
128
'help_text': '<p>The IAM endpoint to call for creating the roles.'
129
' This is optional and should only be specified when a'
130
' custom endpoint should be called for IAM operations'
131
'.</p>'}
132
]
133
134
def _run_main_command(self, parsed_args, parsed_globals):
135
136
self.iam_endpoint_url = parsed_args.iam_endpoint
137
138
self._check_for_iam_endpoint(self.region, self.iam_endpoint_url)
139
self.emr_endpoint_url = \
140
self._session.create_client(
141
'emr',
142
region_name=self.region,
143
endpoint_url=parsed_globals.endpoint_url,
144
verify=parsed_globals.verify_ssl).meta.endpoint_url
145
146
LOG.debug('elasticmapreduce endpoint used for resolving'
147
' service principal: ' + self.emr_endpoint_url)
148
149
# Create default EC2 Role for EMR if it does not exist.
150
ec2_result, ec2_policy = self._create_role_if_not_exists(parsed_globals, EC2_ROLE_NAME,
151
EC2_ROLE_POLICY_NAME, [EC2])
152
153
# Create default EC2 Instance Profile for EMR if it does not exist.
154
instance_profile_name = EC2_ROLE_NAME
155
if self.check_if_instance_profile_exists(instance_profile_name,
156
parsed_globals):
157
LOG.debug('Instance Profile ' + instance_profile_name + ' exists.')
158
else:
159
LOG.debug('Instance Profile ' + instance_profile_name +
160
'does not exist. Creating default Instance Profile ' +
161
instance_profile_name)
162
self._create_instance_profile_with_role(instance_profile_name,
163
instance_profile_name,
164
parsed_globals)
165
166
# Create default EMR Role if it does not exist.
167
emr_result, emr_policy = self._create_role_if_not_exists(parsed_globals, EMR_ROLE_NAME,
168
EMR_ROLE_POLICY_NAME, [EMR])
169
170
# Create default EMR AutoScaling Role if it does not exist.
171
emr_autoscaling_result, emr_autoscaling_policy = \
172
self._create_role_if_not_exists(parsed_globals, EMR_AUTOSCALING_ROLE_NAME,
173
EMR_AUTOSCALING_ROLE_POLICY_NAME, [EMR, APPLICATION_AUTOSCALING])
174
175
configutils.update_roles(self._session)
176
emrutils.display_response(
177
self._session,
178
'create_role',
179
self._construct_result(ec2_result, ec2_policy,
180
emr_result, emr_policy,
181
emr_autoscaling_result, emr_autoscaling_policy),
182
parsed_globals)
183
184
return 0
185
186
def _create_role_if_not_exists(self, parsed_globals, role_name, policy_name, service_names):
187
result = None
188
policy = None
189
190
if self.check_if_role_exists(role_name, parsed_globals):
191
LOG.debug('Role ' + role_name + ' exists.')
192
else:
193
LOG.debug('Role ' + role_name + ' does not exist.'
194
' Creating default role: ' + role_name)
195
role_arn = get_role_policy_arn(self.region, policy_name)
196
result = self._create_role_with_role_policy(
197
role_name, service_names, role_arn, parsed_globals)
198
policy = self._get_role_policy(role_arn, parsed_globals)
199
return result, policy
200
201
def _check_for_iam_endpoint(self, region, iam_endpoint):
202
try:
203
self._session.create_client('emr', region)
204
except botocore.exceptions.UnknownEndpointError:
205
if iam_endpoint is None:
206
raise exceptions.UnknownIamEndpointError(region=region)
207
208
def _construct_result(self, ec2_response, ec2_policy,
209
emr_response, emr_policy,
210
emr_autoscaling_response, emr_autoscaling_policy):
211
result = []
212
self._construct_role_and_role_policy_structure(
213
result, ec2_response, ec2_policy)
214
self._construct_role_and_role_policy_structure(
215
result, emr_response, emr_policy)
216
self._construct_role_and_role_policy_structure(
217
result, emr_autoscaling_response, emr_autoscaling_policy)
218
return result
219
220
def _construct_role_and_role_policy_structure(
221
self, list, response, policy):
222
if response is not None and response['Role'] is not None:
223
list.append({'Role': response['Role'], 'RolePolicy': policy})
224
return list
225
226
def check_if_role_exists(self, role_name, parsed_globals):
227
parameters = {'RoleName': role_name}
228
229
try:
230
self._call_iam_operation('GetRole', parameters, parsed_globals)
231
except botocore.exceptions.ClientError as e:
232
role_not_found_code = "NoSuchEntity"
233
error_code = e.response.get('Error', {}).get('Code', '')
234
if role_not_found_code == error_code:
235
# No role error.
236
return False
237
else:
238
# Some other error. raise.
239
raise e
240
241
return True
242
243
def check_if_instance_profile_exists(self, instance_profile_name,
244
parsed_globals):
245
parameters = {'InstanceProfileName': instance_profile_name}
246
try:
247
self._call_iam_operation('GetInstanceProfile', parameters,
248
parsed_globals)
249
except botocore.exceptions.ClientError as e:
250
profile_not_found_code = 'NoSuchEntity'
251
error_code = e.response.get('Error', {}).get('Code')
252
if profile_not_found_code == error_code:
253
# No instance profile error.
254
return False
255
else:
256
# Some other error. raise.
257
raise e
258
259
return True
260
261
def _get_role_policy(self, arn, parsed_globals):
262
parameters = {}
263
parameters['PolicyArn'] = arn
264
policy_details = self._call_iam_operation('GetPolicy', parameters,
265
parsed_globals)
266
parameters["VersionId"] = policy_details["Policy"]["DefaultVersionId"]
267
policy_version_details = self._call_iam_operation('GetPolicyVersion',
268
parameters,
269
parsed_globals)
270
return policy_version_details["PolicyVersion"]["Document"]
271
272
def _create_role_with_role_policy(
273
self, role_name, service_names, role_arn, parsed_globals):
274
275
if len(service_names) == 1:
276
service_principal = get_service_principal(
277
service_names[0], self.emr_endpoint_url, self._session)
278
else:
279
service_principal = []
280
for service in service_names:
281
service_principal.append(get_service_principal(
282
service, self.emr_endpoint_url, self._session))
283
284
LOG.debug(f'Adding service principal(s) to trust policy: {service_principal}')
285
286
parameters = {'RoleName': role_name}
287
_assume_role_policy = \
288
emrutils.dict_to_string(assume_role_policy(service_principal))
289
parameters['AssumeRolePolicyDocument'] = _assume_role_policy
290
create_role_response = self._call_iam_operation('CreateRole',
291
parameters,
292
parsed_globals)
293
294
parameters = {}
295
parameters['PolicyArn'] = role_arn
296
parameters['RoleName'] = role_name
297
self._call_iam_operation('AttachRolePolicy',
298
parameters, parsed_globals)
299
300
return create_role_response
301
302
def _create_instance_profile_with_role(self, instance_profile_name,
303
role_name, parsed_globals):
304
# Creating an Instance Profile
305
parameters = {'InstanceProfileName': instance_profile_name}
306
self._call_iam_operation('CreateInstanceProfile', parameters,
307
parsed_globals)
308
# Adding the role to the Instance Profile
309
parameters = {}
310
parameters['InstanceProfileName'] = instance_profile_name
311
parameters['RoleName'] = role_name
312
self._call_iam_operation('AddRoleToInstanceProfile', parameters,
313
parsed_globals)
314
315
def _call_iam_operation(self, operation_name, parameters, parsed_globals):
316
client = self._session.create_client(
317
'iam', region_name=self.region, endpoint_url=self.iam_endpoint_url,
318
verify=parsed_globals.verify_ssl)
319
return getattr(client, xform_name(operation_name))(**parameters)
320
321