From 52b61b8cfd9449a4f40e131b372cbd3a856af629 Mon Sep 17 00:00:00 2001 From: Lev Morgan Date: Wed, 20 Mar 2019 16:45:45 -0500 Subject: [PATCH] Added cleartext option to passphrase generation Added a force-cleartext option (false by default) which forces passphrases to be generated in cleartext rather than encrypted. Change-Id: I157a40103f67f85a24976b4f59aa46f2d4b92334 --- doc/source/cli/cli.rst | 9 ++++++ pegleg/cli.py | 32 ++++++++++++------- .../engine/generators/passphrase_generator.py | 13 ++++++-- pegleg/engine/secrets.py | 21 ++++++------ pegleg/engine/util/definition.py | 2 +- pegleg/engine/util/files.py | 2 +- 6 files changed, 54 insertions(+), 25 deletions(-) diff --git a/doc/source/cli/cli.rst b/doc/source/cli/cli.rst index 592cfa5a..45185a5a 100644 --- a/doc/source/cli/cli.rst +++ b/doc/source/cli/cli.rst @@ -874,6 +874,15 @@ are placed in the following folder structure under ``save_location``: /site//secrets/passphrases/ +**-i / --interactive** (Optional). False by default. + +Generate passphrases interactively, not automatically. + +**--force-cleartext** (Optional). False by default. + +Force cleartext generation of passphrases. This is not +recommended. + Usage: :: diff --git a/pegleg/cli.py b/pegleg/cli.py index 557654e3..a4b86ab8 100644 --- a/pegleg/cli.py +++ b/pegleg/cli.py @@ -106,7 +106,7 @@ ALLOW_MISSING_SUBSTITUTIONS_OPTION = click.option( type=click.BOOL, default=True, show_default=True, - help="Raise Deckhand exception on missing substition sources.") + help='Raise Deckhand exception on missing substition sources.') EXCLUDE_LINT_OPTION = click.option( '-x', @@ -131,7 +131,7 @@ SITE_REPOSITORY_ARGUMENT = click.argument( @click.option( '-v', '--verbose', - is_flag=bool, + is_flag=True, default=False, help='Enable debug logging') def main(*, verbose): @@ -235,7 +235,7 @@ def site(*, site_repository, clone_path, extra_repositories, repo_key, help='Directory to output the complete site definition. Created ' 'automatically if it does not already exist.') @click.option( - '--validate', + '--validate/--no-validate', 'validate', is_flag=True, # TODO(felipemonteiro): Potentially set this to True in the future. This @@ -479,7 +479,7 @@ def generate_pki(site_name, author, days): @click.option( '-f', '--filename', - 'file_name', + 'filename', help='The relative file path for the file to be wrapped.') @click.option( '-o', @@ -512,7 +512,7 @@ def generate_pki(site_name, author, days): show_default=True, help='Whether to encrypt the wrapped file.') @click.argument('site_name') -def wrap_secret_cli(*, site_name, author, file_name, output_path, schema, +def wrap_secret_cli(*, site_name, author, filename, output_path, schema, name, layer, encrypt): """Wrap a bare secrets file in a YAML and ManagedDocument. @@ -520,7 +520,7 @@ def wrap_secret_cli(*, site_name, author, file_name, output_path, schema, engine.repository.process_repositories(site_name, overwrite_existing=True) - wrap_secret(author, file_name, output_path, schema, + wrap_secret(author, filename, output_path, schema, name, layer, encrypt) @@ -652,13 +652,21 @@ def generate(): '-i', '--interactive', 'interactive', - is_flag=bool, + is_flag=True, default=False, help='Generate passphrases interactively, not automatically') -def generate_passphrases(*, site_name, save_location, author, interactive): +@click.option( + '--force-cleartext', + 'force_cleartext', + is_flag=True, + default=False, + show_default=True, + help='Force cleartext generation of passphrases. This is not recommended.') +def generate_passphrases(*, site_name, save_location, author, interactive, + force_cleartext): engine.repository.process_repositories(site_name) - engine.secrets.generate_passphrases(site_name, save_location, author, - interactive) + engine.secrets.generate_passphrases( + site_name, save_location, author, interactive, force_cleartext) @secrets.command( @@ -736,7 +744,7 @@ def decrypt(*, path, save_location, overwrite, site_name): os.chmod(file_save_location, 0o600) -@main.group(help="Miscellaneous generate commands") +@main.group(help='Miscellaneous generate commands') def generate(): pass @@ -753,7 +761,7 @@ def generate(): help='Generate a passphrase of the given length. ' 'Length is >= 24, no maximum length.') def generate_passphrase(length): - click.echo("Generated Passhprase: {}".format( + click.echo('Generated Passhprase: {}'.format( engine.secrets.generate_crypto_string(length))) diff --git a/pegleg/engine/generators/passphrase_generator.py b/pegleg/engine/generators/passphrase_generator.py index 21fbe12d..1687663c 100644 --- a/pegleg/engine/generators/passphrase_generator.py +++ b/pegleg/engine/generators/passphrase_generator.py @@ -52,7 +52,7 @@ class PassphraseGenerator(BaseGenerator): self._sitename, documents=self._documents) self._pass_util = CryptoString() - def generate(self, interactive=False): + def generate(self, interactive=False, force_cleartext=False): """ For each passphrase entry in the passphrase catalog, generate a random passphrase string, based on a passphrase specification in the @@ -60,6 +60,9 @@ class PassphraseGenerator(BaseGenerator): passphrase document in the pegleg managed document, and encrypt the passphrase. Write the wrapped and encrypted document in a file at /site//secrets/passphrases/passphrase_name.yaml. + + :param bool interactive: If true, run interactively + :param bool force_cleartext: If true, don't encrypt """ for p_name in self._catalog.get_passphrase_names: passphrase = None @@ -76,7 +79,13 @@ class PassphraseGenerator(BaseGenerator): passphrase = passphrase.encode() passphrase = base64.b64encode(passphrase) docs = list() - storage_policy = self._catalog.get_storage_policy(p_name) + if force_cleartext: + storage_policy = passphrase_catalog.P_CLEARTEXT + LOG.warning("Passphrases for {} will be " + "generated in clear text.".format(p_name)) + else: + storage_policy = self._catalog.get_storage_policy(p_name) + docs.append(self.generate_doc( KIND, p_name, diff --git a/pegleg/engine/secrets.py b/pegleg/engine/secrets.py index 349cd7cd..c27de499 100644 --- a/pegleg/engine/secrets.py +++ b/pegleg/engine/secrets.py @@ -131,21 +131,24 @@ def _get_dest_path(repo_base, file_path, save_location): return file_path -def generate_passphrases(site_name, save_location, author, interactive=False): +def generate_passphrases(site_name, save_location, author, interactive=False, + force_cleartext=False): """ Look for the site passphrase catalogs, and for every passphrase entry in the passphrase catalog generate a passphrase document, wrap the passphrase document in a pegleg managed document, and encrypt the passphrase data. - :param interactive: Whether to generate the results interactively :param str site_name: The site to read from :param str save_location: Location to write files to - :param str author: + :param str author: Author who's generating the files + :param bool interactive: Whether to generate the results interactively + :param bool force_cleartext: Whether to generate results in clear text """ - PassphraseGenerator(site_name, save_location, author).generate( - interactive=interactive) + PassphraseGenerator( + site_name, save_location, author).generate( + interactive=interactive, force_cleartext=force_cleartext) def generate_crypto_string(length): @@ -159,12 +162,12 @@ def generate_crypto_string(length): return CryptoString().get_crypto_string(length) -def wrap_secret(author, file_name, output_path, schema, +def wrap_secret(author, filename, output_path, schema, name, layer, encrypt): """Wrap a bare secrets file in a YAML and ManagedDocument. :param author: author for ManagedDocument - :param file_name: file path for input file + :param filename: file path for input file :param output_path: file path for output file :param schema: schema for wrapped document :param name: name for wrapped document @@ -173,9 +176,9 @@ def wrap_secret(author, file_name, output_path, schema, """ if not output_path: - output_path = os.path.splitext(file_name)[0] + ".yaml" + output_path = os.path.splitext(filename)[0] + ".yaml" - with open(file_name, "r") as in_fi: + with open(filename, "r") as in_fi: data = in_fi.read() inner_doc = { diff --git a/pegleg/engine/util/definition.py b/pegleg/engine/util/definition.py index 07e25d26..c4baf83f 100644 --- a/pegleg/engine/util/definition.py +++ b/pegleg/engine/util/definition.py @@ -75,7 +75,7 @@ def site_files(site_name): def site_files_by_repo(site_name): - """Yield tuples of repo_base, file_name.""" + """Yield tuples of repo_base, filename.""" params = load_as_params(site_name) dir_map = files.directories_for_each_repo(**params) for repo, dl in dir_map.items(): diff --git a/pegleg/engine/util/files.py b/pegleg/engine/util/files.py index fdef14b9..c726daee 100644 --- a/pegleg/engine/util/files.py +++ b/pegleg/engine/util/files.py @@ -411,7 +411,7 @@ def file_in_subdir(filename, _dir): :return: Whether _dir is a parent of the file :rtype: bool """ - file_path, file_name = os.path.split( + file_path, filename = os.path.split( os.path.realpath(filename)) return _dir in file_path.split(os.path.sep)