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
This commit is contained in:
Lev Morgan 2019-03-20 16:45:45 -05:00 committed by Alexander Hughes
parent 820df6d625
commit 52b61b8cfd
6 changed files with 54 additions and 25 deletions

View File

@ -874,6 +874,15 @@ are placed in the following folder structure under ``save_location``:
<save_location>/site/<site_name>/secrets/passphrases/<passphrase_name.yaml> <save_location>/site/<site_name>/secrets/passphrases/<passphrase_name.yaml>
**-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: Usage:
:: ::

View File

@ -106,7 +106,7 @@ ALLOW_MISSING_SUBSTITUTIONS_OPTION = click.option(
type=click.BOOL, type=click.BOOL,
default=True, default=True,
show_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( EXCLUDE_LINT_OPTION = click.option(
'-x', '-x',
@ -131,7 +131,7 @@ SITE_REPOSITORY_ARGUMENT = click.argument(
@click.option( @click.option(
'-v', '-v',
'--verbose', '--verbose',
is_flag=bool, is_flag=True,
default=False, default=False,
help='Enable debug logging') help='Enable debug logging')
def main(*, verbose): 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 ' help='Directory to output the complete site definition. Created '
'automatically if it does not already exist.') 'automatically if it does not already exist.')
@click.option( @click.option(
'--validate', '--validate/--no-validate',
'validate', 'validate',
is_flag=True, is_flag=True,
# TODO(felipemonteiro): Potentially set this to True in the future. This # TODO(felipemonteiro): Potentially set this to True in the future. This
@ -479,7 +479,7 @@ def generate_pki(site_name, author, days):
@click.option( @click.option(
'-f', '-f',
'--filename', '--filename',
'file_name', 'filename',
help='The relative file path for the file to be wrapped.') help='The relative file path for the file to be wrapped.')
@click.option( @click.option(
'-o', '-o',
@ -512,7 +512,7 @@ def generate_pki(site_name, author, days):
show_default=True, show_default=True,
help='Whether to encrypt the wrapped file.') help='Whether to encrypt the wrapped file.')
@click.argument('site_name') @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): name, layer, encrypt):
"""Wrap a bare secrets file in a YAML and ManagedDocument. """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, engine.repository.process_repositories(site_name,
overwrite_existing=True) overwrite_existing=True)
wrap_secret(author, file_name, output_path, schema, wrap_secret(author, filename, output_path, schema,
name, layer, encrypt) name, layer, encrypt)
@ -652,13 +652,21 @@ def generate():
'-i', '-i',
'--interactive', '--interactive',
'interactive', 'interactive',
is_flag=bool, is_flag=True,
default=False, default=False,
help='Generate passphrases interactively, not automatically') 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.repository.process_repositories(site_name)
engine.secrets.generate_passphrases(site_name, save_location, author, engine.secrets.generate_passphrases(
interactive) site_name, save_location, author, interactive, force_cleartext)
@secrets.command( @secrets.command(
@ -736,7 +744,7 @@ def decrypt(*, path, save_location, overwrite, site_name):
os.chmod(file_save_location, 0o600) os.chmod(file_save_location, 0o600)
@main.group(help="Miscellaneous generate commands") @main.group(help='Miscellaneous generate commands')
def generate(): def generate():
pass pass
@ -753,7 +761,7 @@ def generate():
help='Generate a passphrase of the given length. ' help='Generate a passphrase of the given length. '
'Length is >= 24, no maximum length.') 'Length is >= 24, no maximum length.')
def generate_passphrase(length): def generate_passphrase(length):
click.echo("Generated Passhprase: {}".format( click.echo('Generated Passhprase: {}'.format(
engine.secrets.generate_crypto_string(length))) engine.secrets.generate_crypto_string(length)))

View File

@ -52,7 +52,7 @@ class PassphraseGenerator(BaseGenerator):
self._sitename, documents=self._documents) self._sitename, documents=self._documents)
self._pass_util = CryptoString() 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 For each passphrase entry in the passphrase catalog, generate a
random passphrase string, based on a passphrase specification in the 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 document in the pegleg managed document, and encrypt the
passphrase. Write the wrapped and encrypted document in a file at passphrase. Write the wrapped and encrypted document in a file at
<repo_name>/site/<site_name>/secrets/passphrases/passphrase_name.yaml. <repo_name>/site/<site_name>/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: for p_name in self._catalog.get_passphrase_names:
passphrase = None passphrase = None
@ -76,7 +79,13 @@ class PassphraseGenerator(BaseGenerator):
passphrase = passphrase.encode() passphrase = passphrase.encode()
passphrase = base64.b64encode(passphrase) passphrase = base64.b64encode(passphrase)
docs = list() 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( docs.append(self.generate_doc(
KIND, KIND,
p_name, p_name,

View File

@ -131,21 +131,24 @@ def _get_dest_path(repo_base, file_path, save_location):
return file_path 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 Look for the site passphrase catalogs, and for every passphrase entry in
the passphrase catalog generate a passphrase document, wrap the the passphrase catalog generate a passphrase document, wrap the
passphrase document in a pegleg managed document, and encrypt the passphrase document in a pegleg managed document, and encrypt the
passphrase data. passphrase data.
:param interactive: Whether to generate the results interactively
:param str site_name: The site to read from :param str site_name: The site to read from
:param str save_location: Location to write files to :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( PassphraseGenerator(
interactive=interactive) site_name, save_location, author).generate(
interactive=interactive, force_cleartext=force_cleartext)
def generate_crypto_string(length): def generate_crypto_string(length):
@ -159,12 +162,12 @@ def generate_crypto_string(length):
return CryptoString().get_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): name, layer, encrypt):
"""Wrap a bare secrets file in a YAML and ManagedDocument. """Wrap a bare secrets file in a YAML and ManagedDocument.
:param author: author for 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 output_path: file path for output file
:param schema: schema for wrapped document :param schema: schema for wrapped document
:param name: name 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: 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() data = in_fi.read()
inner_doc = { inner_doc = {

View File

@ -75,7 +75,7 @@ def site_files(site_name):
def site_files_by_repo(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) params = load_as_params(site_name)
dir_map = files.directories_for_each_repo(**params) dir_map = files.directories_for_each_repo(**params)
for repo, dl in dir_map.items(): for repo, dl in dir_map.items():

View File

@ -411,7 +411,7 @@ def file_in_subdir(filename, _dir):
:return: Whether _dir is a parent of the file :return: Whether _dir is a parent of the file
:rtype: bool :rtype: bool
""" """
file_path, file_name = os.path.split( file_path, filename = os.path.split(
os.path.realpath(filename)) os.path.realpath(filename))
return _dir in file_path.split(os.path.sep) return _dir in file_path.split(os.path.sep)