def _create_launch_avm_state_machine_input_map(self, portfolio, product, accounts): input_params = {} input_params.update({'PortfolioName': sanitize(portfolio, True)}) input_params.update({'ProductName': sanitize(product, True)}) input_params.update({'ProvisioningParametersList': accounts}) 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) account_id = self.event.get('account') resource_type = 'stno-console' if self.event.get('detail', {}).get('resource-type') is None \ else account_id + '-' + self.event.get('detail', {}).get('resource-type') + '-tagged' state_machine_arn = environ.get('STATE_MACHINE_ARN') # Execute State Machine exec_name = "%s-%s-%s" % ('event-from', resource_type, 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 _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 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" % ('AVM-CR', request_type, trim_length(self.event.get('ResourceProperties', {}).get('StackSetName'), 45), time.strftime("%Y-%m-%dT%H-%M-%S")) elif resource_type == 'Custom::Organizations': exec_name = "%s-%s-%s-%s" % ('AVM-CR', request_type, trim_length(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(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(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(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 create_key_pair(self, account, region, param_key_material=None, param_key_fingerprint=None, param_key_name=None): 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"))) try: 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 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 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 _create_service_control_policy_state_machine_input_map( self, policy_name, policy_content, policy_desc=''): input_params = {} policy_doc = {} policy_doc.update({'Name': sanitize(policy_name)}) policy_doc.update({'Description': policy_desc}) policy_doc.update({'Content': policy_content}) input_params.update({'PolicyDocument': policy_doc}) input_params.update({'AccountId': ''}) input_params.update({'PolicyList': []}) input_params.update({'Operation': ''}) return self._create_state_machine_input_map(input_params)
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 _process_accounts_in_batches(self, accounts, organizations, ou_id, ou_name): try: list_of_accounts = [] for account in accounts: if account.get('Status').upper() == 'SUSPENDED': organizations.move_account(account.get('Id'), ou_id, self.root_id) continue else: 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}) self.logger.info( "Input parameters format for Account: {} are {}".format(account.get('Name'), params)) list_of_accounts.append(params) if len(list_of_accounts) > 0: 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" % (sm_input.get('RequestType'), sanitize(ou_name), "Launch-AVM", time.strftime("%Y-%m-%dT%H-%M-%S")) 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(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
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 create_key_pair(self, account, region, param_key_material=None, param_key_fingerprint=None, param_key_name=None): 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) sts = STS(self.logger) key_name = sanitize( "%s_%s_%s_%s" % ('lz', account, region, time.strftime("%Y-%m-%dT%H-%M-%S"))) try: role_arn = "arn:aws:iam::" + str( account) + ":role/AWSCloudFormationStackSetExecutionRole" session_name = "create_key_pair_role" # assume role credentials = sts.assume_role(role_arn, session_name) self.logger.info("Assuming IAM role: {}".format(role_arn)) # instantiate EC2 class self.logger.debug( "Creating EC2 Session in {} for account: {}".format( region, account)) ec2 = EC2(self.logger, region, credentials=credentials) if type(credentials) == dict: # 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) self.logger.debug(response) # 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 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 else: self.logger.error("Unable to obtain credentials.") 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