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
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
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
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)
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)
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