class SolutionMetrics(object): """This class is used to send anonymous metrics from customer using the solution to the Solutions Builder team when customer choose to have their data sent during the deployment of the solution. """ def __init__(self, logger): self.logger = logger self.ssm = SSM(logger) def _get_parameter_value(self, key): response = self.ssm.describe_parameters(key) # get paramter if key exist if response: value = self.ssm.get_parameter(key) else: value = 'ssm-param-key-not-found' return value def send_metrics(self, data, solution_id=os.environ.get('SOLUTION_ID'), url=os.environ.get('METRICS_URL')): """Sends anonymous customer metrics to s3 via API gateway owned and managed by the Solutions Builder team. Args: data - anonymous customer metrics to be sent solution_id: unique id of the solution url: url for API Gateway via which data is sent Return: status code returned by https post request """ try: send_metrics = self._get_parameter_value('/org/primary/' 'metrics_flag') if send_metrics.lower() == 'yes': uuid = self._get_parameter_value('/org/primary/customer_uuid') time_stamp = {'TimeStamp': str(datetime.utcnow().isoformat())} params = {'Solution': solution_id, 'UUID': uuid, 'Data': data} metrics = dict(time_stamp, **params) json_data = dumps(metrics, indent=4, cls=DecimalEncoder, sort_keys=True) headers = {'content-type': 'application/json'} r = requests.post(url, data=json_data, headers=headers) code = r.status_code return code except: pass
def test_update_params(): logger.info("-- Put new parameter keys in mock environment") ssm = SSM(logger) ssm.put_parameter('/key1', 'value1', 'Test parameter 1', 'String') ssm.put_parameter('/key2', 'value2', 'Test parameter 2', 'String') ssm.put_parameter('/key3', 'value3', 'Test parameter 3', 'String') logger.info("-- Get parameter keys using alfred_ssm") multiple_params = [{ "ParameterKey": "Key1", "ParameterValue": ["$[alfred_ssm_/key1]", "$[alfred_ssm_/key2]", "$[alfred_ssm_/key3]"] }] cph = CFNParamsHandler(logger) values = cph.update_params(multiple_params) assert values == {"Key1": ["value1", "value2", "value3"]} single_param = [{ "ParameterKey": "Key2", "ParameterValue": "$[alfred_ssm_/key1]" }] value = cph.update_params(single_param) assert value == {"Key2": "value1"}
# # # or in the "license" file accompanying this file. This file is # # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # # KIND, express or implied. See the License for the specific language # # governing permissions and limitations under the License. # ############################################################################## from moto import mock_ssm from utils.logger import Logger from manifest.cfn_params_handler import CFNParamsHandler from aws.services.ssm import SSM log_level = 'info' logger = Logger(loglevel=log_level) cph = CFNParamsHandler(logger) ssm = SSM(logger) def test_update_alfred_ssm(): keyword_ssm = 'alfred_ssm_not_exist_alfred_ssm' value_ssm = 'parameter_store_value' value_ssm, param_flag = cph._update_alfred_ssm( keyword_ssm, value_ssm, False) assert param_flag is True @mock_ssm def test_update_alfred_genkeypair(): ssm.put_parameter('testkeyname', 'testvalue', 'A test parameter', 'String') param = { "ssm_parameters": [
def __init__(self, logger): self.logger = logger self.ssm = SSM(self.logger) self.kms = KMS(self.logger) self.assume_role = AssumeRole()
class CFNParamsHandler(object): """This class goes through the cfn parameters passed by users to state machines and SSM parameters to get the correct parameter , create parameter value and update SSM parameters as applicable. For example, if a cfn parameter is passed, save it in SSM parameter store. """ def __init__(self, logger): self.logger = logger self.ssm = SSM(self.logger) self.kms = KMS(self.logger) self.assume_role = AssumeRole() def _session(self, region, account_id=None): # instantiate EC2 session account_id = account_id[0] if \ isinstance(account_id, list) else account_id if account_id is None: return EC2(self.logger, region) else: return EC2(self.logger, region, credentials=self.assume_role(self.logger, account_id)) def _get_ssm_params(self, ssm_parm_name): return self.ssm.get_parameter(ssm_parm_name) def _get_kms_key_id(self): alias_name = environ.get('KMS_KEY_ALIAS_NAME') response = self.kms.describe_key(alias_name) self.logger.debug(response) key_id = response.get('KeyMetadata', {}).get('KeyId') return key_id def get_azs_from_member_account(self, region, qty, account, key_az=None): """gets a predefined quantity of (random) az's from a specified region Args: region (str): region name qty: quantity of az's to return account: account id of the member account key_az (str): ssm parameter store key where existing AZ list is stored Returns: list: availability zone names """ if key_az: self.logger.info( "Looking up values in SSM parameter:{}".format(key_az)) existing_param = self.ssm.describe_parameters(key_az) if existing_param: self.logger.info('Found existing SSM parameter, returning' ' existing AZ list.') return self.ssm.get_parameter(key_az) if account is not None: # fetch account from list for cross account assume role workflow # the account id is arbitrary in this case as we need to get the # AZ list for a given region in any account. acct = account[0] if isinstance(account, list) else account ec2 = self._session(region, acct) self.logger.info("Getting list of AZs in region: {} from" " account: {}".format(region, acct)) return self._get_az(ec2, key_az, qty) else: self.logger.info( "Creating EC2 Session in {} region".format(region)) ec2 = EC2(self.logger, region) return self._get_az(ec2, key_az, qty) def _get_az(self, ec2, key_az, qty): # Get AZs az_list = ec2.describe_availability_zones() self.logger.info("_get_azs output: %s" % az_list) random_az_list = ','.join(random.sample(az_list, qty)) description = "Contains random AZs selected by Custom Control Tower" \ "Solution" if key_az: self.ssm.put_parameter(key_az, random_az_list, description) return random_az_list 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" % ('custom_control_tower', 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 Custom " \ "Control Tower Solution: " \ "EC2 Key Pair Custom Resource." # Get Custom Control Tower 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 random_password(self, length, key_password=None, alphanum=True): """Generates a random string, by default only including letters and numbers Args: length (int): length of string to generate key_password (str): ssm parameter store key where existing password is stored alphanum (bool): [optional] if False it will also include ';:=+!@#%^&*()[]{}' in the character set """ response = '_get_ssm_secure_string_' + key_password param_exists = False if key_password: self.logger.info( "Looking up values in SSM parameter:{}".format(key_password)) existing_param = self.ssm.describe_parameters(key_password) if existing_param: param_exists = True if not param_exists: additional = '' if not alphanum: additional = ';:=+!@#%^&*()[]{}' password = random_pwd_generator(length, additional) self.logger.info("Adding Random password to SSM Parameter Store") description = "Contains random password created by Custom Control"\ " Tower Solution" if key_password: key_id = self._get_kms_key_id() self.ssm.put_parameter_use_cmk(key_password, password, key_id, description) return response def update_params(self, params_in, account=None, region=None, substitute_ssm_values=True): """Updates SSM parameters Args: params_in (list): Python List of dict of input params e.g. [{ "ParameterKey": "LoggingAccountId", "ParameterValue": "$[alfred_ssm_/org/member/logging/account_id]" },{ "ParameterKey": "foo", "ParameterValue": "bar" }] Return: params_out (dict): Python dict of output params e.g. { "LoggingAccountId": "${AWS::AccountId}", "foo": "bar" } """ self.logger.info("params in : {}".format(params_in)) params_out = {} for param in params_in: key = param.get("ParameterKey") value = param.get("ParameterValue") if not isinstance(value, list): if value.startswith('$[') and value.endswith(']'): # Apply transformations keyword = value[2:-1] # Check if supported keyword e.g. alfred_ssm_, # alfred_genaz_, alfred_getaz_, alfred_genuuid, etc. if keyword.startswith('alfred_ssm_'): value, param_flag = self._update_alfred_ssm( keyword, value, substitute_ssm_values) if param_flag is False: raise KeyError( "Missing SSM parameter name for:" " {} in the parameters JSON file.".format(key)) elif keyword.startswith('alfred_genkeypair'): value = self._update_alfred_genkeypair( param, account, region) elif keyword.startswith('alfred_genpass_'): value = self._update_alfred_genpass(keyword, param) elif keyword.startswith('alfred_genaz_'): value = self._update_alfred_genaz( keyword, param, account, region) else: value = keyword params_out.update({key: value}) self.logger.info("params out : {}".format(params_out)) return params_out def _update_alfred_ssm(self, keyword, value, substitute_ssm_values): """Gets the value of the SSM parameter whose name starts with 'alfred_ssm_ ' Args: keyword: string. trimmed parameter value without unwanted leading and trailing characters value: parameter value substitute_ssm_values: boolean. default to true Return: value of the SSM parameter """ ssm_param_name = trim_string_from_front(keyword, 'alfred_ssm_') param_flag = True if ssm_param_name: # If this flag is True, it will replace the SSM parameter name # i.e. /org/member/ss/directory-name with its value i.e. example, # whereas if it is False, it will leave the parameter name as-is. if substitute_ssm_values: value = self._get_ssm_params(ssm_param_name) else: param_flag = False return value, param_flag def _update_alfred_genkeypair(self, param, account, region): """Gets the ec2 key pair name if SSM parameter name starts with 'alfred_genkeypair ' Args: value: string. parameter value param: one parameter in list account: string region: string Return: ec2 key pair name """ keymaterial_param_name = None keyfingerprint_param_name = None keyname_param_name = None ssm_parameters = param.get('ssm_parameters', []) if type(ssm_parameters) is list: for ssm_parameter in ssm_parameters: val = ssm_parameter.get('value')[2:-1] if val.lower() == 'keymaterial': keymaterial_param_name = ssm_parameter.get('name') elif val.lower() == 'keyfingerprint': keyfingerprint_param_name = ssm_parameter.get('name') elif val.lower() == 'keyname': keyname_param_name = ssm_parameter.get('name') value = self._create_key_pair(account, region, keymaterial_param_name, keyfingerprint_param_name, keyname_param_name) return value def _update_alfred_genpass(self, keyword, param): """Creates a random password if SSM parameter name starts with 'alfred_genpass_ ' Args: keyword: string. trimmed parameter value without unwanted leading and trailing characters value: string. parameter value param: one parameter in list Return: generated random password """ sub_string = trim_string_from_front(keyword, 'alfred_genpass_') if sub_string: pw_length = int(sub_string) else: pw_length = 8 password_param_name = None ssm_parameters = param.get('ssm_parameters', []) if type(ssm_parameters) is list: for ssm_parameter in ssm_parameters: val = ssm_parameter.get('value')[2:-1] if val.lower() == 'password': password_param_name = ssm_parameter.get('name') value = self.random_password(pw_length, password_param_name, False) return value def _update_alfred_genaz(self, keyword, param, account, region): """gets a predefined list of (random) az's from a specified region if SSM parameter name starts with 'alfred_genaz ' Args: keyword: string. trimmed parameter value without unwanted leading and trailing characters value: string. parameter value param: one parameter in list account: string region: string Return: list of random az's """ sub_string = trim_string_from_front(keyword, 'alfred_genaz_') if sub_string: no_of_az = int(sub_string) else: no_of_az = 2 az_param_name = None ssm_parameters = param.get('ssm_parameters', []) if type(ssm_parameters) is list: for ssm_parameter in ssm_parameters: val = ssm_parameter.get('value')[2:-1] if val.lower() == 'az': az_param_name = ssm_parameter.get('name') value = self.get_azs_from_member_account(region, no_of_az, account, az_param_name) return value
def __init__(self, logger): self.logger = logger self.ssm = SSM(logger)
def __init__(self, logger): self.logger = logger self.ssm = SSM(self.logger) self.kms = KMS(self.logger)