Example #1
0
    def test_encrypt_decrypt_with_context(self):
        kms_client = KMS()

        context = {'system': 'cfn-sphere', 'env': 'test'}

        before = u"abcd$%&öäüß"
        ciphertext = kms_client.encrypt(self.key_alias, before, context)
        after = kms_client.decrypt(ciphertext, context)
        self.assertEqual(before, after)
Example #2
0
    def test_decrypt_value(self, kms_mock):
        kms_mock.return_value.decrypt.return_value = {
            'Plaintext': 'decryptedValue'
        }

        self.assertEqual('decryptedValue',
                         KMS().decrypt("ZW5jcnlwdGVkVmFsdWU="))
        kms_mock.return_value.decrypt.assert_called_once_with(
            base64.b64decode("ZW5jcnlwdGVkVmFsdWU=".encode()))
Example #3
0
    def test_decrypt_value_with_unicode_char(self, boto_mock):
        boto_mock.return_value.decrypt.return_value = {
            'Plaintext': b'(\xe2\x95\xaf\xc2\xb0\xe2\x96\xa1\xc2\xb0\xef\xbc\x89\xe2\x95\xaf\xef\xb8\xb5 \xe2\x94\xbb\xe2\x94\x81\xe2\x94\xbb'}

        self.assertEqual(u'(\u256f\xb0\u25a1\xb0\uff09\u256f\ufe35 \u253b\u2501\u253b',
                         KMS().decrypt("KOKVr8Kw4pahwrDvvInila/vuLUg4pS74pSB4pS7"))

        boto_mock.return_value.decrypt.assert_called_once_with(
            CiphertextBlob=base64.b64decode("KOKVr8Kw4pahwrDvvInila/vuLUg4pS74pSB4pS7".encode()), EncryptionContext={})
Example #4
0
    def test_decrypt_value(self, boto_mock):
        boto_mock.return_value.decrypt.return_value = {
            'Plaintext': b'decryptedValue'
        }

        self.assertEqual('decryptedValue',
                         KMS().decrypt("ZW5jcnlwdGVkVmFsdWU="))
        boto_mock.return_value.decrypt.assert_called_once_with(
            CiphertextBlob=b'encryptedValue')
Example #5
0
def decrypt(region, ciphertext, context, confirm, yes):
    confirm = confirm or yes
    if not confirm:
        check_update_available()

    try:
        cleartext = KMS(region).decrypt(ciphertext, kv_list_to_dict(context))
        click.echo("Cleartext: {0}".format(cleartext))
    except CfnSphereException as e:
        LOGGER.error(e)
        sys.exit(1)
    except Exception as e:
        LOGGER.error("Failed with unexpected error")
        LOGGER.exception(e)
        LOGGER.info("Please report at https://github.com/cfn-sphere/cfn-sphere/issues!")
        sys.exit(1)
Example #6
0
def encrypt(region, keyid, cleartext, confirm, yes):
    confirm = confirm or yes
    if not confirm:
        check_update_available()

    try:
        cipertext = KMS(region).encrypt(keyid, cleartext)
        click.echo("Ciphertext: {0}".format(cipertext))
    except CfnSphereException as e:
        LOGGER.error(e)
        sys.exit(1)
    except Exception as e:
        LOGGER.error("Failed with unexpected error")
        LOGGER.exception(e)
        LOGGER.info(
            "Please report at https://github.com/KCOM-Enterprise/cfn-square/issues!"
        )
        sys.exit(1)
Example #7
0
 def test_encrypt_with_invalid_key_id(self):
     kms_client = KMS()
     self.assertRaises(CfnSphereBotoError, kms_client.encrypt,
                       "invalid or nonexsting ID", "x")
Example #8
0
 def __init__(self, region="eu-west-1"):
     self.logger = get_logger()
     self.cfn = CloudFormation(region)
     self.ec2 = Ec2Api(region)
     self.kms = KMS(region)
Example #9
0
class ParameterResolver(object):
    """
    Resolves a given artifact identifier to the value of a stacks output.
    """

    def __init__(self, region="eu-west-1"):
        self.logger = get_logger()
        self.cfn = CloudFormation(region)
        self.ec2 = Ec2Api(region)
        self.kms = KMS(region)

    @staticmethod
    def convert_list_to_string(value):
        if not value:
            return ""

        value_string = ""
        for item in value:
            if value_string:
                value_string += ','
            value_string += str(item)
        return value_string

    def get_stack_outputs(self):
        """
        Get a list of all available stack outputs in format <stack-name>.<output>.
        :return: dict
        """
        artifacts = {}
        stacks = self.cfn.get_stacks()
        for stack in stacks:
            for output in stack.outputs:
                key = stack.stack_name + '.' + output.key
                artifacts[key] = output.value
        return artifacts

    def get_output_value(self, key):
        """
        Get value for a specific output key in format <stack-name>.<output>.
        :param key: str <stack-name>.<output>
        :return: str
        """
        artifacts = self.get_stack_outputs()
        self.logger.debug("Looking up key: {0}".format(key))
        self.logger.debug("Found artifacts: {0}".format(artifacts))
        try:
            artifact = artifacts[key]
            return artifact
        except KeyError:
            raise CfnSphereException("Could not get a valid value for {0}.".format(key))

    @staticmethod
    def is_keep_value(value):
        return value.lower().startswith('|keeporuse|')

    @staticmethod
    def is_taupage_ami_reference(value):
        return value.lower() == '|latesttaupageami|'

    @staticmethod
    def is_kms(value):
        return value.lower().startswith('|kms|')

    @staticmethod
    def get_default_from_keep_value(value):
        return value.split('|', 2)[2]

    def get_latest_value(self, key, value, stack_name):
        try:
            if self.cfn.stack_exists(stack_name):
                latest_stack_parameters = self.cfn.get_stack_parameters_dict(stack_name)
                latest_value = latest_stack_parameters.get(key, None)
                if latest_value:
                    self.logger.info("Will keep '{0}' as latest value for {1}".format(latest_value, key))
                    return latest_value
                else:
                    return self.get_default_from_keep_value(value)
            else:
                return self.get_default_from_keep_value(value)
        except CfnSphereBotoError as e:
            raise CfnSphereException("Could not get latest value for {0}: {1}".format(key, e))

    def resolve_parameter_values(self, parameters_dict, stack_name):
        parameters = {}

        for key, value in parameters_dict.items():

            if isinstance(value, list):

                self.logger.debug("List parameter found for {0}".format(key))
                value_string = self.convert_list_to_string(value)
                parameters[key] = value_string

            elif isinstance(value, str):

                if DependencyResolver.is_parameter_reference(value):
                    referenced_stack, output_name = DependencyResolver.parse_stack_reference_value(value)
                    parameters[key] = str(self.get_output_value(referenced_stack + '.' + output_name))

                elif self.is_keep_value(value):
                    parameters[key] = str(self.get_latest_value(key, value, stack_name))

                elif self.is_taupage_ami_reference(value):
                    parameters[key] = str(self.ec2.get_latest_taupage_image_id())

                elif self.is_kms(value):
                    parameters[key] = str(self.kms.decrypt(value.split('|', 2)[2]))

                else:
                    parameters[key] = value

            elif isinstance(value, bool):
                parameters[key] = str(value).lower()
            elif isinstance(value, int):
                parameters[key] = str(value)
            elif isinstance(value, float):
                parameters[key] = str(value)
            else:
                raise NotImplementedError("Cannot handle {0} value for key: {1}".format(type(value), key))

        return parameters
Example #10
0
 def test_encrypt_decrypt_with_unicode_data(self):
     before = u"abcd$%&öäüß"
     kms_client = KMS()
     ciphertext = kms_client.encrypt(self.key_alias, before)
     after = kms_client.decrypt(ciphertext)
     self.assertEqual(before, after)
Example #11
0
 def __init__(self, cfn, region=DEFAULT_REGION):
     self.logger = get_logger()
     self.cfn = cfn
     self.ec2 = Ec2Api(region)
     self.kms = KMS(region)
     self.ssm = SSM(region)
Example #12
0
    def test_decrypt_value_with_execution_context(self, boto_mock):
        boto_mock.return_value.decrypt.return_value = {'Plaintext': b'decryptedValue'}

        self.assertEqual('decryptedValue', KMS().decrypt("ZW5jcnlwdGVkVmFsdWU=", {"k1": "v1", "k2": "v2"}))
        boto_mock.return_value.decrypt.assert_called_once_with(CiphertextBlob=b'encryptedValue',
                                                               EncryptionContext={'k2': 'v2', 'k1': 'v1'})
class ParameterResolver(object):
    """
    Resolves a given artifact identifier to the value of a stacks output.
    """

    def __init__(self, region="eu-west-1"):
        self.logger = get_logger()
        self.cfn = CloudFormation(region)
        self.ec2 = Ec2Api(region)
        self.kms = KMS(region)

    @staticmethod
    def convert_list_to_string(value):
        if not value:
            return ""

        value_string = ""
        for item in value:
            if value_string:
                value_string += ','
            value_string += str(item)
        return value_string

    def get_output_value(self, stack_outputs, stack, output_key):
        """
        Get value for a specific output key in format <stack-name>.<output>.
        :param output_key: str <stack-name>.<output>
        :return: str
        """
        self.logger.debug("Looking up output {0} from stack {1}".format(output_key, stack))

        try:
            artifact = stack_outputs[stack][output_key]
            return artifact
        except KeyError:
            raise CfnSphereException("Could not get a valid value for {0}.".format(output_key))

    @staticmethod
    def is_keep_value(value):
        return value.lower().startswith('|keeporuse|')

    @staticmethod
    def is_taupage_ami_reference(value):
        return value.lower() == '|latesttaupageami|'

    @staticmethod
    def is_kms(value):
        return value.lower().startswith('|kms|')

    @staticmethod
    def get_default_from_keep_value(value):
        return value.split('|', 2)[2]

    @staticmethod
    def is_file(value):
        return value.lower().startswith('|file|')

    def get_latest_value(self, key, value, stack_name):
        try:
            if self.cfn.stack_exists(stack_name):
                latest_stack_parameters = self.cfn.get_stack_parameters_dict(stack_name)
                latest_value = latest_stack_parameters.get(key, None)
                if latest_value:
                    self.logger.info("Will keep '{0}' as latest value for {1}".format(latest_value, key))
                    return latest_value
                else:
                    return self.get_default_from_keep_value(value)
            else:
                return self.get_default_from_keep_value(value)
        except Exception as e:
            raise CfnSphereException("Could not get latest value for {0}: {1}".format(key, e))

    def resolve_parameter_values(self, stack_name, stack_config):
        resolved_parameters = {}
        stack_outputs = self.cfn.get_stacks_outputs()

        for key, value in stack_config.parameters.items():

            if isinstance(value, list):
                self.logger.debug("List parameter found for {0}".format(key))
                for i, item in enumerate(value):
                    if DependencyResolver.is_parameter_reference(item):
                        referenced_stack, output_name = DependencyResolver.parse_stack_reference_value(item)
                        value[i] = str(self.get_output_value(stack_outputs, referenced_stack, output_name))

                value_string = self.convert_list_to_string(value)
                resolved_parameters[key] = value_string

            elif isinstance(value, str):

                if DependencyResolver.is_parameter_reference(value):
                    referenced_stack, output_name = DependencyResolver.parse_stack_reference_value(value)
                    resolved_parameters[key] = str(self.get_output_value(stack_outputs, referenced_stack, output_name))

                elif self.is_keep_value(value):
                    resolved_parameters[key] = str(self.get_latest_value(key, value, stack_name))

                elif self.is_taupage_ami_reference(value):
                    resolved_parameters[key] = str(self.ec2.get_latest_taupage_image_id())

                elif self.is_kms(value):
                    resolved_parameters[key] = str(self.kms.decrypt(value.split('|', 2)[2]))

                elif self.is_file(value):
                    url = value.split('|', 2)[2]
                    resolved_parameters[key] = FileLoader.get_file(url, stack_config.working_dir)

                else:
                    resolved_parameters[key] = value

            elif isinstance(value, bool):
                resolved_parameters[key] = str(value).lower()
            elif isinstance(value, (int, float)):
                resolved_parameters[key] = str(value)
            else:
                raise NotImplementedError("Cannot handle {0} type for key: {1}".format(type(value), key))

        return resolved_parameters

    @staticmethod
    def update_parameters_with_cli_parameters(parameters, cli_parameters, stack_name):
        """
        Overwrite parameters from stack_config with those provided by user by cli
        :param parameters: dict
        :param cli_parameters: dict
        :param stack_name: string
        :return: dict

        """
        if stack_name in cli_parameters.keys():
            for new_key, new_value in cli_parameters[stack_name].items():
                parameters[new_key] = new_value

        return parameters
 def __init__(self, region="eu-west-1"):
     self.logger = get_logger()
     self.cfn = CloudFormation(region)
     self.ec2 = Ec2Api(region)
     self.kms = KMS(region)
Example #15
0
 def test_encrypt_decrypt_unicode_data(self):
     before = u"abcd$%&öäüß"
     kms_client = KMS()
     ciphertext = kms_client.encrypt(self.key_alias, before)
     after = kms_client.decrypt(ciphertext)
     self.assertEqual(before, after)
class ParameterResolver(object):
    """
    Resolves a given artifact identifier to the value of a stacks output.
    """

    def __init__(self, region="eu-west-1"):
        self.logger = get_logger()
        self.cfn = CloudFormation(region)
        self.ec2 = Ec2Api(region)
        self.kms = KMS(region)

    @staticmethod
    def convert_list_to_string(value):
        if not value:
            return ""

        value_string = ""
        for item in value:
            if value_string:
                value_string += ","
            value_string += str(item)
        return value_string

    def get_stack_outputs(self):
        """
        Get a list of all available stack outputs in format <stack-name>.<output>.
        :return: dict
        """
        artifacts = {}
        stacks = self.cfn.get_stacks()
        for stack in stacks:
            for output in stack.outputs:
                key = stack.stack_name + "." + output.key
                artifacts[key] = output.value
        return artifacts

    def get_output_value(self, key):
        """
        Get value for a specific output key in format <stack-name>.<output>.
        :param key: str <stack-name>.<output>
        :return: str
        """
        artifacts = self.get_stack_outputs()
        self.logger.debug("Looking up key: {0}".format(key))
        self.logger.debug("Found artifacts: {0}".format(artifacts))
        try:
            artifact = artifacts[key]
            return artifact
        except KeyError:
            raise CfnSphereException("Could not get a valid value for {0}.".format(key))

    @staticmethod
    def is_keep_value(value):
        return value.lower().startswith("|keeporuse|")

    @staticmethod
    def is_taupage_ami_reference(value):
        return value.lower() == "|latesttaupageami|"

    @staticmethod
    def is_kms(value):
        return value.lower().startswith("|kms|")

    @staticmethod
    def get_default_from_keep_value(value):
        return value.split("|", 2)[2]

    def get_latest_value(self, key, value, stack_name):
        try:
            if self.cfn.stack_exists(stack_name):
                latest_stack_parameters = self.cfn.get_stack_parameters_dict(stack_name)
                latest_value = latest_stack_parameters.get(key, None)
                if latest_value:
                    self.logger.info("Will keep '{0}' as latest value for {1}".format(latest_value, key))
                    return latest_value
                else:
                    return self.get_default_from_keep_value(value)
            else:
                return self.get_default_from_keep_value(value)
        except CfnSphereBotoError as e:
            raise CfnSphereException("Could not get latest value for {0}: {1}".format(key, e))

    def resolve_parameter_values(self, parameters_dict, stack_name):
        parameters = {}

        for key, value in parameters_dict.items():

            if isinstance(value, list):

                self.logger.debug("List parameter found for {0}".format(key))
                value_string = self.convert_list_to_string(value)
                parameters[key] = value_string

            elif isinstance(value, str):

                if DependencyResolver.is_parameter_reference(value):
                    referenced_stack, output_name = DependencyResolver.parse_stack_reference_value(value)
                    parameters[key] = str(self.get_output_value(referenced_stack + "." + output_name))

                elif self.is_keep_value(value):
                    parameters[key] = str(self.get_latest_value(key, value, stack_name))

                elif self.is_taupage_ami_reference(value):
                    parameters[key] = str(self.ec2.get_latest_taupage_image_id())

                elif self.is_kms(value):
                    parameters[key] = str(self.kms.decrypt(value.split("|", 2)[2]))

                else:
                    parameters[key] = value

            elif isinstance(value, bool):
                parameters[key] = str(value).lower()
            elif isinstance(value, int):
                parameters[key] = str(value)
            elif isinstance(value, float):
                parameters[key] = str(value)
            else:
                raise NotImplementedError("Cannot handle {0} value for key: {1}".format(type(value), key))

        return parameters
Example #17
0
class ParameterResolver(object):
    """
    Resolves a given artifact identifier to the value of a stacks output.
    """
    def __init__(self, region="eu-west-1"):
        self.logger = get_logger()
        self.cfn = CloudFormation(region)
        self.ec2 = Ec2Api(region)
        self.kms = KMS(region)

    @staticmethod
    def convert_list_to_string(value):
        if not value:
            return ""

        value_string = ""
        for item in value:
            if value_string:
                value_string += ','
            value_string += str(item)
        return value_string

    def get_output_value(self, stack_outputs, stack, output_key):
        """
        Get value for a specific output key in format <stack-name>.<output>.
        :param output_key: str <stack-name>.<output>
        :return: str
        """
        self.logger.debug("Looking up output {0} from stack {1}".format(
            output_key, stack))

        try:
            artifact = stack_outputs[stack][output_key]
            return artifact
        except KeyError:
            raise CfnSphereException(
                "Could not get a valid value for {0}.".format(output_key))

    @staticmethod
    def is_keep_value(value):
        return value.lower().startswith('|keeporuse|')

    @staticmethod
    def is_taupage_ami_reference(value):
        return value.lower() == '|latesttaupageami|'

    @staticmethod
    def is_kms(value):
        return value.lower().startswith('|kms|')

    @staticmethod
    def get_default_from_keep_value(value):
        return value.split('|', 2)[2]

    @staticmethod
    def is_file(value):
        return value.lower().startswith('|file|')

    def get_latest_value(self, key, value, stack_name):
        try:
            if self.cfn.stack_exists(stack_name):
                latest_stack_parameters = self.cfn.get_stack_parameters_dict(
                    stack_name)
                latest_value = latest_stack_parameters.get(key, None)
                if latest_value:
                    self.logger.info(
                        "Will keep '{0}' as latest value for {1}".format(
                            latest_value, key))
                    return latest_value
                else:
                    return self.get_default_from_keep_value(value)
            else:
                return self.get_default_from_keep_value(value)
        except Exception as e:
            raise CfnSphereException(
                "Could not get latest value for {0}: {1}".format(key, e))

    def resolve_parameter_values(self,
                                 stack_name,
                                 stack_config,
                                 cli_parameters=None):
        resolved_parameters = {}
        stack_outputs = self.cfn.get_stacks_outputs()

        for key, value in stack_config.parameters.items():

            if isinstance(value, list):
                self.logger.debug("List parameter found for {0}".format(key))
                for i, item in enumerate(value):
                    if DependencyResolver.is_parameter_reference(item):
                        referenced_stack, output_name = DependencyResolver.parse_stack_reference_value(
                            item)
                        value[i] = str(
                            self.get_output_value(stack_outputs,
                                                  referenced_stack,
                                                  output_name))

                value_string = self.convert_list_to_string(value)
                resolved_parameters[key] = value_string

            elif isinstance(value, str):

                if DependencyResolver.is_parameter_reference(value):
                    referenced_stack, output_name = DependencyResolver.parse_stack_reference_value(
                        value)
                    resolved_parameters[key] = str(
                        self.get_output_value(stack_outputs, referenced_stack,
                                              output_name))

                elif self.is_keep_value(value):
                    resolved_parameters[key] = str(
                        self.get_latest_value(key, value, stack_name))

                elif self.is_taupage_ami_reference(value):
                    resolved_parameters[key] = str(
                        self.ec2.get_latest_taupage_image_id())

                elif self.is_kms(value):
                    resolved_parameters[key] = str(
                        self.kms.decrypt(value.split('|', 2)[2]))

                elif self.is_file(value):
                    resolved_parameters[key] = self.handle_file_value(
                        value, stack_config.working_dir)

                else:
                    resolved_parameters[key] = value

            elif isinstance(value, bool):
                resolved_parameters[key] = str(value).lower()
            elif isinstance(value, (int, float)):
                resolved_parameters[key] = str(value)
            else:
                raise NotImplementedError(
                    "Cannot handle {0} type for key: {1}".format(
                        type(value), key))

        if cli_parameters:
            return self.update_parameters_with_cli_parameters(
                resolved_parameters, cli_parameters, stack_name)
        else:
            return resolved_parameters

    @staticmethod
    def handle_file_value(value, working_dir):
        components = value.split('|', 3)

        if len(components) == 3:
            url = components[2]
            return FileLoader.get_file(url, working_dir)
        elif len(components) == 4:
            url = components[2]
            pattern = components[3]
            file_content = FileLoader.get_yaml_or_json_file(url, working_dir)
            try:
                return jmespath.search(pattern, file_content)
            except JMESPathError as e:
                raise CfnSphereException(e)
        else:
            raise CfnSphereException(
                "Invalid format for |File| macro, it must be |File|<path>[|<pattern>]"
            )

    @staticmethod
    def update_parameters_with_cli_parameters(parameters, cli_parameters,
                                              stack_name):
        """
        Overwrite parameters from stack_config with those provided by user by cli
        :param parameters: dict
        :param cli_parameters: dict
        :param stack_name: string
        :return: dict

        """
        if stack_name in cli_parameters.keys():
            for new_key, new_value in cli_parameters[stack_name].items():
                parameters[new_key] = new_value

        return parameters
Example #18
0
 def test_invalid_base64(self, kms_mock):
     with self.assertRaises(InvalidEncryptedValueException):
         KMS().decrypt("asdqwda")
Example #19
0
    def test_invalid_kms_key(self, kms_mock):
        kms_mock.return_value.decrypt.side_effect = InvalidCiphertextException(
            "400", "Bad Request")

        with self.assertRaises(InvalidEncryptedValueException):
            KMS().decrypt("ZW5jcnlwdGVkVmFsdWU=")