def test_sanitize():
    non_sanitized_string = 'I s@nitize $tring exc*pt_underscore-hypen.'
    sanitized_string_allow_space = 'I s_nitize _tring exc_pt_underscore-hypen.'
    sanitized_string_no_space_replace_hypen = \
        'I-s-nitize--tring-exc-pt_underscore-hypen.'
    assert sanitize(non_sanitized_string,True) == \
           sanitized_string_allow_space
    assert sanitize(non_sanitized_string, False,'-') == \
           sanitized_string_no_space_replace_hypen
示例#2
0
    def _create_key_pair(self,
                         account,
                         region,
                         param_key_material=None,
                         param_key_fingerprint=None,
                         param_key_name=None):
        """Creates an ec2 key pair if it does not exist already.

        Args:
            account:
            region:
            param_key_material: key material used to encrypt and decrypt data.
                                Default to None
            param_key_fingerprint: key finger print. Default to None
            param_key_name: key name. A key name will be automatically created
                            if there is none. Default to None
        Returns:
            key name
        """
        if param_key_name:
            self.logger.info(
                "Looking up values in SSM parameter:{}".format(param_key_name))
            existing_param = self.ssm.describe_parameters(param_key_name)

            if existing_param:
                return self.ssm.get_parameter(param_key_name)

        key_name = sanitize(
            "%s_%s_%s_%s" %
            ('lz', account, region, time.strftime("%Y-%m-%dT%H-%M-%S")))

        ec2 = self._session(region, account)
        # create EC2 key pair in member account
        self.logger.info("Create key pair in the member account {} in"
                         " region: {}".format(account, region))
        response = ec2.create_key_pair(key_name)

        # add key material and fingerprint in the SSM Parameter Store
        self.logger.info("Adding Key Material and Fingerprint to SSM PS")
        description = "Contains EC2 key pair asset created by " \
                      "Landing Zone Solution: " \
                      "EC2 Key Pair Custom Resource."
        # Get AWS Landing Zone KMS Key ID
        key_id = self._get_kms_key_id()
        if param_key_fingerprint:
            self.ssm.put_parameter_use_cmk(param_key_fingerprint,
                                           response.get('KeyFingerprint'),
                                           key_id, description)
        if param_key_material:
            self.ssm.put_parameter_use_cmk(param_key_material,
                                           response.get('KeyMaterial'), key_id,
                                           description)
        if param_key_name:
            self.ssm.put_parameter(param_key_name, key_name, description)

        return key_name
    def _create_launch_avm_state_machine_input_map(self, accounts):
        """
        Create the input parameters for the state machine
        """
        portfolio = self.avm_portfolio_name
        product = self.avm_product_name.strip()

        request = {}
        request.update({'RequestType': 'Create'})
        request.update({'PortfolioId': self.sc_portfolios.get(portfolio)})

        portfolio_exist = False
        if any(self.sc_portfolios.get(portfolio)):
            portfolio_exist = True
        request.update({'PortfolioExist': portfolio_exist})

        request.update(
            {'ProductId': self.sc_products.get(portfolio).get(product)})
        request.update({
            'ProvisioningArtifactId':
            self._get_provisioning_artifact_id(request.get('ProductId'))
        })

        product_exist = False
        if any(self.sc_products.get(portfolio).get(product)):
            product_exist = True
        request.update({'ProductExist': product_exist})

        input_params = {}
        input_params.update({'PortfolioName': sanitize(portfolio, True)})
        input_params.update({'ProductName': sanitize(product, True)})
        input_params.update({'ProvisioningParametersList': accounts})

        request.update({'ResourceProperties': input_params})
        # Set up the iteration parameters for the state machine
        request.update({'Index': 0})
        request.update({'Step': 1})
        request.update(
            {'Count': len(input_params['ProvisioningParametersList'])})

        return request
示例#4
0
 def _create_service_control_policy_state_machine_input_map(self, policy_name, policy_full_path, policy_desc='', ou_list=[]):
     input_params = {}
     policy_doc = {}
     policy_doc.update({'Name': sanitize(policy_name)})
     policy_doc.update({'Description': policy_desc})
     policy_doc.update({'PolicyURL': policy_full_path})
     input_params.update({'PolicyDocument': policy_doc})
     input_params.update({'AccountId': ''})
     input_params.update({'PolicyList': []})
     input_params.update({'Operation': ''})
     input_params.update({'OUList': ou_list})
     input_params.update({'OUNameDelimiter': self.nested_ou_delimiter})
     return self._create_state_machine_input_map(input_params)
 def trigger_state_machine(self, state_machine_arn, input, name):
     try:
         self.logger.info(
             "Starting execution of state machine: {} with input: {}".
             format(state_machine_arn, input))
         response = self.state_machine_client.start_execution(
             stateMachineArn=state_machine_arn,
             input=json.dumps(input),
             name=sanitize(name))
         self.logger.info("State machine Execution ARN: {}".format(
             response['executionArn']))
         return response.get('executionArn')
     except Exception as e:
         message = {
             'FILE': __file__.split('/')[-1],
             'METHOD': inspect.stack()[0][3],
             'EXCEPTION': str(e)
         }
         self.logger.exception(message)
         raise
示例#6
0
    def _create_stack_set_state_machine_input_map(self, stack_set_name, template_url, parameters, account_list=[], regions_list=[], ssm_map=None, capabilities='CAPABILITY_NAMED_IAM'):
        input_params = {}
        input_params.update({'StackSetName': sanitize(stack_set_name)})
        input_params.update({'TemplateURL': template_url})
        input_params.update({'Parameters': parameters})
        input_params.update({'Capabilities': capabilities})

        if len(account_list) > 0:
            input_params.update({'AccountList': account_list})
            if len(regions_list) > 0:
                input_params.update({'RegionList': regions_list})
            else:
                input_params.update({'RegionList': [self.manifest.region]})
        else:
            input_params.update({'AccountList': ''})
            input_params.update({'RegionList': ''})

        if ssm_map is not None:
            input_params.update({'SSMParameters': ssm_map})

        return self._create_state_machine_input_map(input_params)
示例#7
0
    def _create_service_catalog_state_machine_input_map(self, portfolio, product):
        input_params = {}

        sc_portfolio = {}
        sc_portfolio.update({'PortfolioName': sanitize(portfolio.name, True)})
        sc_portfolio.update({'PortfolioDescription': sanitize(portfolio.description, True)})
        sc_portfolio.update({'PortfolioProvider': sanitize(portfolio.owner, True)})
        ssm_value = self.param_handler.update_params(transform_params({'principal_role': portfolio.principal_role}))
        sc_portfolio.update({'PrincipalArn': ssm_value.get('principal_role')})

        sc_product = {}
        sc_product.update({'ProductName': sanitize(product.name, True)})
        sc_product.update({'ProductDescription': product.description})
        sc_product.update({'ProductOwner': sanitize(portfolio.owner, True)})
        if product.hide_old_versions is True:
            sc_product.update({'HideOldVersions': 'Yes'})
        else:
            sc_product.update({'HideOldVersions': 'No'})
        ssm_value = self.param_handler.update_params(transform_params({'launch_constraint_role': product.launch_constraint_role}))
        sc_product.update({'RoleArn':ssm_value.get('launch_constraint_role')})

        ec2 = EC2(self.logger, environ.get('AWS_REGION'))
        region_list = []
        for region in ec2.describe_regions():
            region_list.append(region.get('RegionName'))

        if os.path.isfile(os.path.join(self.manifest_folder, product.skeleton_file)):
            lambda_arn_param = get_env_var('lambda_arn_param_name')
            lambda_arn = self.ssm.get_parameter(lambda_arn_param)
            portfolio_index = self.manifest.portfolios.index(portfolio)
            product_index = self.manifest.portfolios[portfolio_index].products.index(product)
            product_name = self.manifest.portfolios[portfolio_index].products[product_index].name
            logger.info("Generating the product template for {} from {}".format(product_name, os.path.join(self.manifest_folder,product.skeleton_file)))
            j2loader = jinja2.FileSystemLoader(self.manifest_folder)
            j2env = jinja2.Environment(loader=j2loader)
            j2template = j2env.get_template(product.skeleton_file)
            template_url = None
            if product.product_type.lower() == 'baseline':
                # j2result = j2template.render(manifest=self.manifest, portfolio_index=portfolio_index,
                #                              product_index=product_index, lambda_arn=lambda_arn, uuid=uuid.uuid4(),
                #                              regions=region_list)
                template_url = self._stage_template(product.skeleton_file+".template")
            elif product.product_type.lower() == 'optional':
                if len(product.template_file) > 0:
                    template_url = self._stage_template(product.template_file)
                    j2result = j2template.render(manifest=self.manifest, portfolio_index=portfolio_index,
                                                 product_index=product_index, lambda_arn=lambda_arn, uuid=uuid.uuid4(),
                                                 template_url=template_url)
                    generated_avm_template = os.path.join(self.manifest_folder,
                                                          product.skeleton_file + ".generated.template")
                    logger.info("Writing the generated product template to {}".format(generated_avm_template))
                    with open(generated_avm_template, "w") as fh:
                        fh.write(j2result)
                    template_url = self._stage_template(generated_avm_template)
                else:
                    raise Exception("Missing template_file location for portfolio:{} and product:{} in Manifest file".format(portfolio.name,
                                                                                                                             product.name))

        else:
            raise Exception("Missing skeleton_file for portfolio:{} and product:{} in Manifest file".format(portfolio.name,
                                                                                                                     product.name))

        artifact_params = {}
        artifact_params.update({'Info':{'LoadTemplateFromURL':template_url}})
        artifact_params.update({'Type':'CLOUD_FORMATION_TEMPLATE'})
        artifact_params.update({'Description':product.description})
        sc_product.update({'ProvisioningArtifactParameters':artifact_params})

        try:
            if product.rules_file:
                rules = self._load_template_rules(product.rules_file)
                sc_product.update({'Rules': rules})
        except Exception as e:
            logger.error(e)

        input_params.update({'SCPortfolio':sc_portfolio})
        input_params.update({'SCProduct':sc_product})

        return self._create_state_machine_input_map(input_params)
示例#8
0
    def trigger_state_machine(self):
        try:
            self.logger.info("Executing: " + self.__class__.__name__ + "/" +
                             inspect.stack()[0][3])
            sm = StateMachine(self.logger)
            resource_type = self.event.get('ResourceType')
            request_type = self.event.get('RequestType')

            if resource_type == 'Custom::Organizations' and environ.get(
                    'sm_arn_account'):
                state_machine_arn = environ.get('sm_arn_account')
            elif resource_type == 'Custom::ServiceControlPolicy' and environ.get(
                    'sm_arn_service_control_policy'):
                state_machine_arn = environ.get(
                    'sm_arn_service_control_policy')
            elif resource_type == 'Custom::StackInstance' and environ.get(
                    'sm_arn_stack_set'):
                state_machine_arn = environ.get('sm_arn_stack_set')
            elif resource_type == 'Custom::CheckAVMExistsForAccount' and environ.get(
                    'sm_arn_check_avm_exists'):
                state_machine_arn = environ.get('sm_arn_check_avm_exists')
            elif resource_type == 'Custom::ADConnector' and environ.get(
                    'sm_arn_ad_connector'):
                state_machine_arn = environ.get('sm_arn_ad_connector')
            elif resource_type == 'Custom::HandShakeStateMachine' and environ.get(
                    'sm_arn_handshake_sm'):
                state_machine_arn = environ.get('sm_arn_handshake_sm')
            else:
                self.logger.error(
                    "ResourceType Not Supported {} or Env. Variable not found".
                    format(resource_type))
                raise Exception(
                    "ResourceType Not Supported {} or Env. Variable not found".
                    format(resource_type))

            # Execute State Machine
            if resource_type == 'Custom::StackInstance':
                exec_name = "%s-%s-%s-%s-%s-%s" % (
                    'AVM-CR',
                    request_type,
                    trim_length_from_end(
                        self.event.get('ResourceProperties',
                                       {}).get('StackSetName'), 45),
                    time.strftime("%H-%M-%S"),
                    str(time.time()).split('.')[1],  # append microseconds
                    str(uuid4()).split('-')[1][:2])
            elif resource_type == 'Custom::Organizations':
                exec_name = "%s-%s-%s-%s" % (
                    'AVM-CR', request_type,
                    trim_length_from_end(
                        self.event.get('ResourceProperties', {}).get('OUName')
                        + '-' + self.event.get('ResourceProperties',
                                               {}).get('AccountName'),
                        45), time.strftime("%Y-%m-%dT%H-%M-%S"))
            elif resource_type == 'Custom::ServiceControlPolicy':
                exec_name = "%s-%s-%s-%s" % (
                    'AVM-CR', request_type,
                    trim_length_from_end(
                        self.event.get('ResourceProperties',
                                       {}).get('Operation'),
                        45), time.strftime("%Y-%m-%dT%H-%M-%S"))
            elif resource_type == 'Custom::HandShakeStateMachine':
                exec_name = "%s-%s-%s-%s" % (
                    'AVM-CR', request_type,
                    trim_length_from_end(
                        self.event.get('ResourceProperties',
                                       {}).get('ServiceType') + '-' +
                        self.event.get('ResourceProperties',
                                       {}).get('SpokeRegion') + '-' +
                        self.event.get('ResourceProperties',
                                       {}).get('SpokeAccountId'), 45),
                    time.strftime("%Y-%m-%dT%H-%M-%S"))
            elif resource_type == 'Custom::CheckAVMExistsForAccount':
                exec_name = "%s-%s-%s-%s" % (
                    'AVM-CR', request_type,
                    trim_length_from_end(
                        self.event.get('ResourceProperties', {}).get(
                            'ProdParams', {}).get('OUName') + '-' +
                        self.event.get('ResourceProperties', {}).get(
                            'ProdParams', {}).get('AccountName'),
                        45), time.strftime("%Y-%m-%dT%H-%M-%S"))
            else:
                exec_name = "%s-%s-%s-%s" % (
                    'AVM-CR', request_type,
                    resource_type.replace(
                        "Custom::", ""), time.strftime("%Y-%m-%dT%H-%M-%S-%s"))
            self.event.update({'StateMachineArn': state_machine_arn})
            self.logger.info("Triggering {} State Machine".format(
                state_machine_arn.split(":", 6)[6]))
            response = sm.trigger_state_machine(state_machine_arn, self.event,
                                                sanitize(exec_name))
            self.logger.info(
                "State machine triggered successfully, Execution Arn: {}".
                format(response))
        except Exception as e:
            message = {
                'FILE': __file__.split('/')[-1],
                'CLASS': self.__class__.__name__,
                'METHOD': inspect.stack()[0][3],
                'EXCEPTION': str(e)
            }
            self.logger.exception(message)
            raise
    def _process_accounts_in_batches(self, accounts, organizations, ou_id,
                                     ou_name):
        """
        Each account in an OU is processed into a batch of one or more accounts.
        This function processes one batch.

        For each account:
            get email, name, ou name
            ignore suspended accounts
            build state machine input
            instantiate state machine

        Note: sm_input must not exceed 32K max
        """
        try:
            list_of_accounts = []
            for account in accounts:
                # Process each account
                if account.get('Status').upper() == 'SUSPENDED':
                    # Account is suspended
                    organizations.move_account(account.get('Id'), ou_id,
                                               self.root_id)
                    continue
                else:
                    # Active account
                    params = self.avm_params.copy()
                    for key, value in params.items():
                        if value.lower() == 'accountemail':
                            params.update({key: account.get('Email')})
                        elif value.lower() == 'accountname':
                            params.update({key: account.get('Name')})
                        elif value.lower() == 'orgunitname':
                            params.update({key: ou_name})

                    # Retrieve the provisioned product id
                    ppid = self.provisioned_products_by_account.get(
                        account.get('Email'), None)
                    if ppid:
                        params.update({'ProvisionedProductId': ppid})
                        params.update({'ProvisionedProductExists': True})
                        params.update({
                            'ExistingParameterKeys':
                            self.provisioned_products.get(ppid).get(
                                'ExistingParameterKeys', [])
                        })
                    else:
                        params.update({'ProvisionedProductExists': False})

                    self.logger.info(
                        "Input parameters format for Account: {} are {}".
                        format(account.get('Name'), params))

                    list_of_accounts.append(params)

            if list_of_accounts:
                # list_of_accounts is passed directly through to the input json data
                # This data should be complete from start_launch_avm
                sm_input = self._create_launch_avm_state_machine_input_map(
                    # self.avm_portfolio_name,
                    # self.avm_product_name.strip(),
                    list_of_accounts)
                self.logger.info(
                    "Launch AVM state machine Input: {}".format(sm_input))
                exec_name = "%s-%s-%s-%s-%s" % (
                    "AVM",
                    sanitize(ou_name[:40]),
                    time.strftime("%Y-%m-%dT%H-%M-%S"),
                    str(time.time()).split('.')[1],  # append microsecond
                    str(uuid4()).split('-')[1])
                sm_exec_arn = self.state_machine.trigger_state_machine(
                    self.sm_arn_launch_avm, sm_input, exec_name)
                self.list_sm_exec_arns.append(sm_exec_arn)

                time.sleep(int(self.wait_time))  # Sleeping for sometime

        except Exception as e:
            message = {
                'FILE': __file__.split('/')[-1],
                'METHOD': inspect.stack()[0][3],
                'EXCEPTION': str(e)
            }
            self.logger.exception(message)
            raise