def test_value_decyrpt(self, sops_mock):
        encrypted_config_file_path = os.path.join(
            os.path.dirname(__file__),
            'decryptors',
            'files',
            'step-runner-config-secret-stuff.yml'
        )

        config_value = ConfigValue(
            value='ENC[AES256_GCM,data:UGKfnzsSrciR7GXZJhOCMmFrz3Y6V3pZsd3P,iv:yuReqA+n+rRXVHMc+2US5t7yPx54sooZSXWV4KLjDIs=,tag:jueP7/ZWLfYrEuhh+4eS8g==,type:str]',
            parent_source=encrypted_config_file_path,
            path_parts=['step-runner-config', 'global-environment-defaults', 'DEV', 'kube-api-token']
        )

        DecryptionUtils.register_config_value_decryptor(SOPS())

        sops_mock.side_effect=create_sops_side_effect('mock decrypted value')
        decrypted_value = config_value.value
        sops_mock.assert_called_once_with(
            '--decrypt',
            '--extract=["step-runner-config"]["global-environment-defaults"]["DEV"]["kube-api-token"]',
            None,
            encrypted_config_file_path,
            _in=None,
            _out=Any(StringIO),
            _err=Any(StringIO)
        )
        self.assertEqual(
            decrypted_value,
            'mock decrypted value'
        )
Exemplo n.º 2
0
 def test__get_decryption_class_does_not_exist_with_module_name(self):
     with self.assertRaisesRegex(
         StepRunnerException,
         r"Could not dynamically load decryptor implementer \(foo.bar.hello.DoesNotExist\) " \
         r"from module \(foo.bar.hello\) with class name \(DoesNotExist\)"
     ):
         DecryptionUtils._DecryptionUtils__get_decryption_class(
             'foo.bar.hello.DoesNotExist')
Exemplo n.º 3
0
    def test_decrypt_sample_decryptor_does_not_match(self):
        config_value = ConfigValue('attempt to decrypt me')

        DecryptionUtils.register_config_value_decryptor(
            SampleConfigValueDecryptor())

        decrypted_value = DecryptionUtils.decrypt(config_value)
        self.assertIsNone(decrypted_value)
Exemplo n.º 4
0
 def test__get_decryption_class_does_not_exist_short_name(self):
     with self.assertRaisesRegex(
         StepRunnerException,
         r"Could not dynamically load decryptor implementer \(DoesNotExist\) " \
         r"from module \(ploigos_step_runner.config.decryptors\) with class name \(DoesNotExist\)"
     ):
         DecryptionUtils._DecryptionUtils__get_decryption_class(
             'DoesNotExist')
Exemplo n.º 5
0
    def test_decrypt_sample_decryptor(self):
        secret_value = "decrypt me"
        config_value = ConfigValue(f'TEST_ENC[{secret_value}]')

        DecryptionUtils.register_config_value_decryptor(
            SampleConfigValueDecryptor())

        decrypted_value = DecryptionUtils.decrypt(config_value)
        self.assertEqual(decrypted_value, secret_value)
Exemplo n.º 6
0
    def test_create_and_register_config_value_decryptor_no_constructor_args(
            self):
        DecryptionUtils.create_and_register_config_value_decryptor(
            'tests.test_decryption_utils.SampleConfigValueDecryptor')

        secret_value = "decrypt me"
        config_value = ConfigValue(f'TEST_ENC[{secret_value}]')
        decrypted_value = DecryptionUtils.decrypt(config_value)
        self.assertEqual(decrypted_value, secret_value)
Exemplo n.º 7
0
    def test_create_and_register_config_value_decryptor_required_constructor_args(
            self):
        DecryptionUtils.create_and_register_config_value_decryptor(
            'tests.test_decryption_utils.RequiredConstructorParamsConfigValueDecryptor',
            {'required_arg': 'hello world'})

        secret_value = "decrypt me"
        config_value = ConfigValue(f'TEST_ENC[{secret_value}]')
        decrypted_value = DecryptionUtils.decrypt(config_value)
        self.assertEqual(decrypted_value, secret_value)
Exemplo n.º 8
0
 def test__get_decryption_class_not_correct_type(self):
     with self.assertRaisesRegex(
         StepRunnerException,
         r"For decryptor implementer \(tests.test_decryption_utils.BadConfigValueDecryptor\) " \
         r"dynamically loaded class \(<class 'tests.test_decryption_utils." \
         r"BadConfigValueDecryptor'>\) which is not sub class of " \
         r"\(<class 'ploigos_step_runner.config.config_value_decryptor.ConfigValueDecryptor'>\) and should be."
     ):
         DecryptionUtils._DecryptionUtils__get_decryption_class(
             'tests.test_decryption_utils.BadConfigValueDecryptor')
Exemplo n.º 9
0
 def test_create_and_register_config_value_decryptor_required_constructor_args_missing_arg(
         self):
     with self.assertRaisesRegex(
             ValueError,
             r"Loaded decryptor class \(<class 'tests.test_decryption_utils."
             +
             r"RequiredConstructorParamsConfigValueDecryptor'>\) failed to construct with "
             +
             r"given constructor arguments \({}\): __init__\(\) missing 1 "
             + r"required positional argument: 'required_arg'"):
         DecryptionUtils.create_and_register_config_value_decryptor(
             'tests.test_decryption_utils.RequiredConstructorParamsConfigValueDecryptor'
         )
Exemplo n.º 10
0
    def test_register_obfuscation_stream(self):
        secret_value = "decrypt me"
        config_value = ConfigValue(f'TEST_ENC[{secret_value}]')

        DecryptionUtils.register_config_value_decryptor(
            SampleConfigValueDecryptor())

        out = io.StringIO()
        with redirect_stdout(out):
            old_stdout = sys.stdout
            new_stdout = TextIOSelectiveObfuscator(old_stdout)
            DecryptionUtils.register_obfuscation_stream(new_stdout)

            try:
                sys.stdout = new_stdout
                DecryptionUtils.decrypt(config_value)

                print(
                    f"ensure that I can't actually leak secret value ({secret_value})"
                )
                self.assertRegex(
                    out.getvalue(),
                    r"ensure that I can't actually leak secret value \(\*+\)")
            finally:
                new_stdout.close()
                sys.stdout = old_stdout
Exemplo n.º 11
0
    def parse_and_register_decryptors_definitions(decryptors_definitions):
        """Parse decryptor definitions from a list and then register them with the DecryptionUtils.

        Parameters
        ----------
        decryptors_definitions : list of dicts
            List of decryptor definitions. Each element should be a dict with at least an
            'implementer' key with a string value and optionally a 'config' key with a dict value.

        Raises
        ------
        AssertionError
            If decryptors_definitions is not a list.
            If a decryptor definition does not have a
                Config.CONFIG_KEY_DECRYPTOR_IMPLEMENTER key.
        """
        assert isinstance(decryptors_definitions, list), \
            f"Decryptors configuration ({decryptors_definitions}) must be of type " + \
            f"(list) got: {type(decryptors_definitions)}"

        for decryptor_definition in decryptors_definitions:
            assert Config.CONFIG_KEY_DECRYPTOR_IMPLEMENTER in decryptor_definition, \
                "Decryptor configuration is missing key " + \
                f"({Config.CONFIG_KEY_DECRYPTOR_IMPLEMENTER}): {decryptor_definition}"

            decryptor_implementer_name = \
                decryptor_definition[Config.CONFIG_KEY_DECRYPTOR_IMPLEMENTER]

            if Config.CONFIG_KEY_DECRYPTOR_CONFIG in decryptor_definition:
                decryptor_config = decryptor_definition[Config.CONFIG_KEY_DECRYPTOR_CONFIG]
            else:
                decryptor_config = {}

            DecryptionUtils.create_and_register_config_value_decryptor(
                decryptor_implementer_name,
                decryptor_config
            )
Exemplo n.º 12
0
def main(argv=None):
    """Main entry point for Ploigos step runner.
    """
    parser = argparse.ArgumentParser(description='Ploigos Step Runner (psr)')
    parser.add_argument(
        '-s',
        '--step',
        required=True,
        help='Workflow step to run'
    )
    parser.add_argument(
        '-e',
        '--environment',
        required=False,
        help='The environment to run this step against.'
    )
    parser.add_argument(
        '-c',
        '--config',
        required=True,
        nargs='+',
        help='Workflow configuration files, or directories containing files, in yml or json'
    )
    parser.add_argument(
        '--step-config',
        metavar='STEP_CONFIG_KEY=STEP_CONFIG_VALUE',
        nargs='+',
        help='Override step config provided by the given config-file with these arguments.',
        action=ParseKeyValueArge
    )
    args = parser.parse_args(argv)

    obfuscated_stdout = TextIOSelectiveObfuscator(sys.stdout)
    obfuscated_stderr = TextIOSelectiveObfuscator(sys.stderr)
    DecryptionUtils.register_obfuscation_stream(obfuscated_stdout)
    DecryptionUtils.register_obfuscation_stream(obfuscated_stderr)

    with redirect_stdout(obfuscated_stdout), redirect_stderr(obfuscated_stderr):
        # validate args
        for config_file in args.config:
            if not os.path.exists(config_file) or os.stat(config_file).st_size == 0:
                print_error('specified -c/--config must exist and not be empty')
                sys.exit(101)

        try:
            config = Config(args.config)
        except (ValueError, AssertionError) as error:
            print_error(f"specified -c/--config is invalid configuration: {error}")
            sys.exit(102)

        config.set_step_config_overrides(args.step, args.step_config)
        # it is VERY important that the working dir be an absolute path because some
        # commands (looking at you maven) will change the context of relative paths on you
        step_runner = StepRunner(
            config=config,
            work_dir_path=os.path.abspath('step-runner-working')
        )

        try:
            if not step_runner.run_step(args.step, args.environment):
                print_error(f"Step {args.step} not successful")
                sys.exit(200)

        except Exception as error:  # pylint: disable=broad-except
            print_error(f"Fatal error calling step ({args.step}): {str(error)}")
            track = traceback.format_exc()
            print(track)
            sys.exit(300)
Exemplo n.º 13
0
 def test__get_decryption_class_sops_short_name(self):
     decryptor_class = DecryptionUtils._DecryptionUtils__get_decryption_class(
         'SOPS')
     self.assertEqual(decryptor_class, SOPS)
Exemplo n.º 14
0
 def test_decrypt_no_decryptors(self):
     config_value = ConfigValue('attempt to decrypt me')
     decrypted_value = DecryptionUtils.decrypt(config_value)
     self.assertIsNone(decrypted_value)
Exemplo n.º 15
0
 def test__get_decryption_class_sops_full_name(self):
     decryptor_class = DecryptionUtils._DecryptionUtils__get_decryption_class(
         'ploigos_step_runner.config.decryptors.sops.SOPS')
     self.assertEqual(decryptor_class, SOPS)