awscli/customizations/emr/createdefaultroles.py (245 lines of code) (raw):
# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
import logging
import re
import botocore.exceptions
import botocore.session
from botocore import xform_name
from awscli.customizations.utils import get_policy_arn_suffix
from awscli.customizations.emr import configutils
from awscli.customizations.emr import emrutils
from awscli.customizations.emr import exceptions
from awscli.customizations.emr.command import Command
from awscli.customizations.emr.constants import EC2
from awscli.customizations.emr.constants import EC2_ROLE_NAME
from awscli.customizations.emr.constants import EC2_SERVICE_PRINCIPAL
from awscli.customizations.emr.constants import ROLE_ARN_PATTERN
from awscli.customizations.emr.constants import EMR
from awscli.customizations.emr.constants import EMR_ROLE_NAME
from awscli.customizations.emr.constants import EMR_AUTOSCALING_ROLE_NAME
from awscli.customizations.emr.constants import APPLICATION_AUTOSCALING
from awscli.customizations.emr.constants import EC2_ROLE_POLICY_NAME
from awscli.customizations.emr.constants import EMR_ROLE_POLICY_NAME
from awscli.customizations.emr.constants \
import EMR_AUTOSCALING_ROLE_POLICY_NAME
from awscli.customizations.emr.constants import EMR_AUTOSCALING_SERVICE_NAME
from awscli.customizations.emr.constants \
import EMR_AUTOSCALING_SERVICE_PRINCIPAL
from awscli.customizations.emr.exceptions import ResolveServicePrincipalError
LOG = logging.getLogger(__name__)
def assume_role_policy(serviceprincipal):
return {
"Version": "2008-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {"Service": serviceprincipal},
"Action": "sts:AssumeRole"
}
]
}
def get_role_policy_arn(region, policy_name):
region_suffix = get_policy_arn_suffix(region)
role_arn = ROLE_ARN_PATTERN.replace("{{region_suffix}}", region_suffix)
role_arn = role_arn.replace("{{policy_name}}", policy_name)
return role_arn
def get_service_principal(service, endpoint_host, session=None):
if service == EC2:
return EC2_SERVICE_PRINCIPAL
suffix, region = _get_suffix_and_region_from_endpoint_host(endpoint_host)
if session is None:
session = botocore.session.Session()
if service == EMR_AUTOSCALING_SERVICE_NAME:
if region not in session.get_available_regions('emr', 'aws-cn'):
return EMR_AUTOSCALING_SERVICE_PRINCIPAL
return service + '.' + suffix
def _get_suffix_and_region_from_endpoint_host(endpoint_host):
suffix_match = _get_regex_match_from_endpoint_host(endpoint_host)
if suffix_match is not None and suffix_match.lastindex >= 3:
suffix = suffix_match.group(3)
region = suffix_match.group(2)
else:
raise ResolveServicePrincipalError
return suffix, region
def _get_regex_match_from_endpoint_host(endpoint_host):
if endpoint_host is None:
return None
regex_match = re.match("(https?://)([^.]+).elasticmapreduce.([^/]*)",
endpoint_host)
# Supports 'elasticmapreduce.{region}.' and '{region}.elasticmapreduce.'
if regex_match is None:
regex_match = re.match("(https?://elasticmapreduce).([^.]+).([^/]*)",
endpoint_host)
return regex_match
class CreateDefaultRoles(Command):
NAME = "create-default-roles"
DESCRIPTION = ('Creates the default IAM role ' +
EC2_ROLE_NAME + ' and ' +
EMR_ROLE_NAME + ' which can be used when creating the'
' cluster using the create-cluster command. The default'
' roles for EMR use managed policies, which are updated'
' automatically to support future EMR functionality.\n'
'\nIf you do not have a Service Role and Instance Profile '
'variable set for your create-cluster command in the AWS '
'CLI config file, create-default-roles will automatically '
'set the values for these variables with these default '
'roles. If you have already set a value for Service Role '
'or Instance Profile, create-default-roles will not '
'automatically set the defaults for these variables in the '
'AWS CLI config file. You can view settings for variables '
'in the config file using the "aws configure get" command.'
'\n')
ARG_TABLE = [
{'name': 'iam-endpoint',
'no_paramfile': True,
'help_text': '<p>The IAM endpoint to call for creating the roles.'
' This is optional and should only be specified when a'
' custom endpoint should be called for IAM operations'
'.</p>'}
]
def _run_main_command(self, parsed_args, parsed_globals):
self.iam_endpoint_url = parsed_args.iam_endpoint
self._check_for_iam_endpoint(self.region, self.iam_endpoint_url)
self.emr_endpoint_url = \
self._session.create_client(
'emr',
region_name=self.region,
endpoint_url=parsed_globals.endpoint_url,
verify=parsed_globals.verify_ssl).meta.endpoint_url
LOG.debug('elasticmapreduce endpoint used for resolving'
' service principal: ' + self.emr_endpoint_url)
# Create default EC2 Role for EMR if it does not exist.
ec2_result, ec2_policy = self._create_role_if_not_exists(parsed_globals, EC2_ROLE_NAME,
EC2_ROLE_POLICY_NAME, [EC2])
# Create default EC2 Instance Profile for EMR if it does not exist.
instance_profile_name = EC2_ROLE_NAME
if self.check_if_instance_profile_exists(instance_profile_name,
parsed_globals):
LOG.debug('Instance Profile ' + instance_profile_name + ' exists.')
else:
LOG.debug('Instance Profile ' + instance_profile_name +
'does not exist. Creating default Instance Profile ' +
instance_profile_name)
self._create_instance_profile_with_role(instance_profile_name,
instance_profile_name,
parsed_globals)
# Create default EMR Role if it does not exist.
emr_result, emr_policy = self._create_role_if_not_exists(parsed_globals, EMR_ROLE_NAME,
EMR_ROLE_POLICY_NAME, [EMR])
# Create default EMR AutoScaling Role if it does not exist.
emr_autoscaling_result, emr_autoscaling_policy = \
self._create_role_if_not_exists(parsed_globals, EMR_AUTOSCALING_ROLE_NAME,
EMR_AUTOSCALING_ROLE_POLICY_NAME, [EMR, APPLICATION_AUTOSCALING])
configutils.update_roles(self._session)
emrutils.display_response(
self._session,
'create_role',
self._construct_result(ec2_result, ec2_policy,
emr_result, emr_policy,
emr_autoscaling_result, emr_autoscaling_policy),
parsed_globals)
return 0
def _create_role_if_not_exists(self, parsed_globals, role_name, policy_name, service_names):
result = None
policy = None
if self.check_if_role_exists(role_name, parsed_globals):
LOG.debug('Role ' + role_name + ' exists.')
else:
LOG.debug('Role ' + role_name + ' does not exist.'
' Creating default role: ' + role_name)
role_arn = get_role_policy_arn(self.region, policy_name)
result = self._create_role_with_role_policy(
role_name, service_names, role_arn, parsed_globals)
policy = self._get_role_policy(role_arn, parsed_globals)
return result, policy
def _check_for_iam_endpoint(self, region, iam_endpoint):
try:
self._session.create_client('emr', region)
except botocore.exceptions.UnknownEndpointError:
if iam_endpoint is None:
raise exceptions.UnknownIamEndpointError(region=region)
def _construct_result(self, ec2_response, ec2_policy,
emr_response, emr_policy,
emr_autoscaling_response, emr_autoscaling_policy):
result = []
self._construct_role_and_role_policy_structure(
result, ec2_response, ec2_policy)
self._construct_role_and_role_policy_structure(
result, emr_response, emr_policy)
self._construct_role_and_role_policy_structure(
result, emr_autoscaling_response, emr_autoscaling_policy)
return result
def _construct_role_and_role_policy_structure(
self, list, response, policy):
if response is not None and response['Role'] is not None:
list.append({'Role': response['Role'], 'RolePolicy': policy})
return list
def check_if_role_exists(self, role_name, parsed_globals):
parameters = {'RoleName': role_name}
try:
self._call_iam_operation('GetRole', parameters, parsed_globals)
except botocore.exceptions.ClientError as e:
role_not_found_code = "NoSuchEntity"
error_code = e.response.get('Error', {}).get('Code', '')
if role_not_found_code == error_code:
# No role error.
return False
else:
# Some other error. raise.
raise e
return True
def check_if_instance_profile_exists(self, instance_profile_name,
parsed_globals):
parameters = {'InstanceProfileName': instance_profile_name}
try:
self._call_iam_operation('GetInstanceProfile', parameters,
parsed_globals)
except botocore.exceptions.ClientError as e:
profile_not_found_code = 'NoSuchEntity'
error_code = e.response.get('Error', {}).get('Code')
if profile_not_found_code == error_code:
# No instance profile error.
return False
else:
# Some other error. raise.
raise e
return True
def _get_role_policy(self, arn, parsed_globals):
parameters = {}
parameters['PolicyArn'] = arn
policy_details = self._call_iam_operation('GetPolicy', parameters,
parsed_globals)
parameters["VersionId"] = policy_details["Policy"]["DefaultVersionId"]
policy_version_details = self._call_iam_operation('GetPolicyVersion',
parameters,
parsed_globals)
return policy_version_details["PolicyVersion"]["Document"]
def _create_role_with_role_policy(
self, role_name, service_names, role_arn, parsed_globals):
if len(service_names) == 1:
service_principal = get_service_principal(
service_names[0], self.emr_endpoint_url, self._session)
else:
service_principal = []
for service in service_names:
service_principal.append(get_service_principal(
service, self.emr_endpoint_url, self._session))
LOG.debug(f'Adding service principal(s) to trust policy: {service_principal}')
parameters = {'RoleName': role_name}
_assume_role_policy = \
emrutils.dict_to_string(assume_role_policy(service_principal))
parameters['AssumeRolePolicyDocument'] = _assume_role_policy
create_role_response = self._call_iam_operation('CreateRole',
parameters,
parsed_globals)
parameters = {}
parameters['PolicyArn'] = role_arn
parameters['RoleName'] = role_name
self._call_iam_operation('AttachRolePolicy',
parameters, parsed_globals)
return create_role_response
def _create_instance_profile_with_role(self, instance_profile_name,
role_name, parsed_globals):
# Creating an Instance Profile
parameters = {'InstanceProfileName': instance_profile_name}
self._call_iam_operation('CreateInstanceProfile', parameters,
parsed_globals)
# Adding the role to the Instance Profile
parameters = {}
parameters['InstanceProfileName'] = instance_profile_name
parameters['RoleName'] = role_name
self._call_iam_operation('AddRoleToInstanceProfile', parameters,
parsed_globals)
def _call_iam_operation(self, operation_name, parameters, parsed_globals):
client = self._session.create_client(
'iam', region_name=self.region, endpoint_url=self.iam_endpoint_url,
verify=parsed_globals.verify_ssl)
return getattr(client, xform_name(operation_name))(**parameters)