From 4b00a4340c65280177b4efd7d2430e06258e964e Mon Sep 17 00:00:00 2001 From: Alexander Hughes Date: Wed, 2 Jan 2019 12:10:35 -0600 Subject: [PATCH] Add CLI passphrase generation 1. Add support to pegleg to generate a passphrase from CLI 2. Update unit test to ensure encryption/decryption supports passphrase rotation 3. Update order of import statements to satisfy pep8 4. Add unit test for CLI passphrase generation 5. Resolve merge conflicts via rebase Change-Id: I5cb9e41b2f0fac2451bd2b74f33c48cda417c22d --- doc/source/cli/cli.rst | 37 +++++++++++++++++++ pegleg/cli.py | 28 +++++++++++--- ...e_generator.py => passphrase_generator.py} | 0 pegleg/engine/secrets.py | 14 ++++++- .../unit/engine/test_generate_passphrases.py | 2 +- tests/unit/engine/test_secrets.py | 6 +++ tests/unit/test_cli.py | 7 ++++ 7 files changed, 86 insertions(+), 8 deletions(-) rename pegleg/engine/generators/{passpharase_generator.py => passphrase_generator.py} (100%) diff --git a/doc/source/cli/cli.rst b/doc/source/cli/cli.rst index 16fea950..7279bb0e 100644 --- a/doc/source/cli/cli.rst +++ b/doc/source/cli/cli.rst @@ -809,3 +809,40 @@ P003 - All repos contain expected directories. .. _Shipyard: https://github.com/openstack/airship-shipyard .. _CLI documentation: https://airship-shipyard.readthedocs.io/en/latest/CLI.html#openstack-keystone-authorization-environment-variables .. _Pegleg Passphrase Catalog: https://airship-specs.readthedocs.io/en/latest/specs/approved/pegleg-secrets.html#document-generation + + +Generate +======== + +Allows you to perform generate operations. + +Passphrase +---------- + +Generate a passphrase and print to ``stdout``. + +**-l / --length** (Optional). + +Length of passphrase to generate. By default length is 24. +Minimum length is 24. No maximum length. + +Usage: + +:: + + ./pegleg.sh generate passphrase -l + +Examples +^^^^^^^^ + +Example without length specified: + +:: + + ./pegleg.sh generate passphrase + +Example with length specified: + +:: + + ./pegleg.sh generate passphrase -l diff --git a/pegleg/cli.py b/pegleg/cli.py index 603e10f2..ca388644 100644 --- a/pegleg/cli.py +++ b/pegleg/cli.py @@ -528,9 +528,25 @@ def encrypt(*, save_location, author, site_name): @click.argument('site_name') def decrypt(*, file_name, site_name): engine.repository.process_repositories(site_name) - try: - click.echo(engine.secrets.decrypt(file_name, site_name)) - except FileNotFoundError: - raise click.exceptions.FileError("Couldn't find file %s, " - "check your arguments and try " - "again." % file_name) + + engine.secrets.decrypt(file_name, site_name) + + +@main.group(help="Miscellaneous generate commands") +def generate(): + pass + + +@generate.command( + 'passphrase', + help='Command to generate a passphrase and print out to stdout') +@click.option( + '-l', + '--length', + 'length', + default=24, + help='Generate a passphrase of the given length. ' + 'Length is >= 24, default length is 24, no maximum length') +def generate_passphrase(length): + click.echo("Generated Passhprase: {}".format( + engine.secrets.generate_passphrase(length))) diff --git a/pegleg/engine/generators/passpharase_generator.py b/pegleg/engine/generators/passphrase_generator.py similarity index 100% rename from pegleg/engine/generators/passpharase_generator.py rename to pegleg/engine/generators/passphrase_generator.py diff --git a/pegleg/engine/secrets.py b/pegleg/engine/secrets.py index 743e86f4..04026905 100644 --- a/pegleg/engine/secrets.py +++ b/pegleg/engine/secrets.py @@ -15,9 +15,10 @@ import logging import os -from pegleg.engine.generators.passpharase_generator import PassphraseGenerator +from pegleg.engine.generators.passphrase_generator import PassphraseGenerator from pegleg.engine.util import definition from pegleg.engine.util import files +from pegleg.engine.util.passphrase import Passphrase from pegleg.engine.util.pegleg_secret_management import PeglegSecretManagement __all__ = ('encrypt', 'decrypt', 'generate_passphrases') @@ -129,3 +130,14 @@ def generate_passphrases(site_name, save_location, author, interactive=False): PassphraseGenerator(site_name, save_location, author).generate( interactive=interactive) + + +def generate_passphrase(length): + """ + Create a passphrase. + + :param int length: Length of passphrase. + :rtype: string + """ + + return Passphrase().get_pass(length) diff --git a/tests/unit/engine/test_generate_passphrases.py b/tests/unit/engine/test_generate_passphrases.py index d14ed691..74f0af72 100644 --- a/tests/unit/engine/test_generate_passphrases.py +++ b/tests/unit/engine/test_generate_passphrases.py @@ -20,7 +20,7 @@ import string import yaml from pegleg.engine.util.passphrase import Passphrase -from pegleg.engine.generators.passpharase_generator import PassphraseGenerator +from pegleg.engine.generators.passphrase_generator import PassphraseGenerator from pegleg.engine.util import encryption from pegleg.engine import util import pegleg diff --git a/tests/unit/engine/test_secrets.py b/tests/unit/engine/test_secrets.py index 39403250..cfe147c9 100644 --- a/tests/unit/engine/test_secrets.py +++ b/tests/unit/engine/test_secrets.py @@ -64,6 +64,12 @@ def test_encrypt_and_decrypt(): enc2 = crypt.encrypt(dec1, passphrase, salt) dec2 = crypt.decrypt(enc2, passphrase, salt) assert data == dec2 + passphrase2 = test_utils.rand_name("passphrase2", "pegleg").encode() + salt2 = test_utils.rand_name("salt2", "pegleg").encode() + enc3 = crypt.encrypt(dec2, passphrase2, salt2) + dec3 = crypt.decrypt(enc3, passphrase2, salt2) + assert data == dec3 + assert data != enc3 @mock.patch.dict(os.environ, { diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index ea7dd324..170d81b3 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -383,6 +383,13 @@ class TestSiteCliActions(BaseCLIActionTest): mock_obj.assert_called_once() +class TestGenerateActions(BaseCLIActionTest): + def test_generate_passphrase(self): + result = self.runner.invoke(cli.generate, ['passphrase']) + + assert result.exit_code == 0, result.output + + class TestRepoCliActions(BaseCLIActionTest): """Tests repo-level CLI actions."""