diff --git a/doc/source/cli/cli.rst b/doc/source/cli/cli.rst index 2f4401e6..d950b9b8 100644 --- a/doc/source/cli/cli.rst +++ b/doc/source/cli/cli.rst @@ -662,9 +662,9 @@ Example without optional save location: Decrypt ^^^^^^^ -Unwrap an encrypted secrets document from a `Pegleg Managed Documents`_, -decrypt the encrypted secrets, and dump the cleartext secrets file to -``stdout``. +Unwrap one or more encrypted secrets document from +`Pegleg Managed Documents`_, decrypt the encrypted secrets, and dump the +cleartext to stdout or a specified location. **site_name** (Required). @@ -673,21 +673,25 @@ repository folder structure. This is used to ensure the correct revision of the site and global repositories are used, as specified in the site's :file:`site-definition.yaml`. -**-f / filename** (Required). +**-p / --path** (Required). -The absolute path to the pegleg managed encrypted secrets file. +Path to pegleg managed encrypted secrets file or directory of files. +**-s / --save-location** (Optional). -**-s / save-location** (Optional). +The desired output path for the decrypted file. If not specified, decrypted +data will output to stdout. -The desired output path for the decrypted file. If not specified, it will be -printed to stdout. +**-o / --overwrite** (Optional). False by default. + +When set, encrypted file(s) at the specified path will be overwritten with +the decrypted data. Overrides ``--save-location`` option. Usage: :: - ./pegleg.sh site secrets decrypt -f + ./pegleg.sh site secrets decrypt -p [-s ] Examples diff --git a/pegleg/cli.py b/pegleg/cli.py index 685e8fef..02d11cd1 100644 --- a/pegleg/cli.py +++ b/pegleg/cli.py @@ -679,27 +679,45 @@ def encrypt(*, save_location, author, site_name): help='Command to unwrap and decrypt one site ' 'secrets document and print it to stdout.') @click.option( - '-f', - '--filename', - 'file_name', - help='The file to decrypt') + '-p', + '--path', + 'path', + type=click.Path(exists=True, readable=True), + required=True, + help='The file or directory path to decrypt.') @click.option( '-s', '--save-location', 'save_location', default=None, - help='The destination where the decrypted file should be saved. ' - 'If not specified, it will be printed to stdout.') + help='The destination where the decrypted file(s) should be saved. ' + 'If not specified, decrypted data will output to stdout.') +@click.option( + '-o', + '--overwrite', + 'overwrite', + is_flag=True, + default=False, + help='Overwrites original file(s) at path with decrypted data when set. ' + 'Overrides --save-location option.') @click.argument('site_name') -def decrypt(*, file_name, save_location, site_name): +def decrypt(*, path, save_location, overwrite, site_name): engine.repository.process_repositories(site_name) - decrypted = engine.secrets.decrypt(file_name) - if save_location is None: - click.echo(decrypted) + decrypted = engine.secrets.decrypt(path) + if overwrite: + for key, value in decrypted.items(): + files.write(key, value) + os.chmod(key, 0o600) + elif save_location is None: + for value in decrypted.values(): + click.echo(value) else: - files.write(save_location, decrypted) - os.chmod(save_location, 0o600) + for key, value in decrypted.items(): + file_name = os.path.split(key) + file_save_location = os.path.join(save_location, file_name) + files.write(file_save_location, value) + os.chmod(file_save_location, 0o600) @main.group(help="Miscellaneous generate commands") diff --git a/pegleg/engine/secrets.py b/pegleg/engine/secrets.py index af22e484..349cd7cd 100644 --- a/pegleg/engine/secrets.py +++ b/pegleg/engine/secrets.py @@ -12,11 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +from glob import glob import logging import os -import yaml from prettytable import PrettyTable +import yaml from pegleg.engine.catalog.pki_utility import PKIUtility from pegleg.engine.generators.passphrase_generator import PassphraseGenerator @@ -68,7 +69,7 @@ def encrypt(save_location, author, site_name): 'No secret documents were found for site: {}'.format(site_name)) -def decrypt(file_path): +def decrypt(path): """Decrypt one secrets file, and print the decrypted file to standard out. Search the specified file_path for a file. @@ -77,17 +78,31 @@ def decrypt(file_path): If the file is found, but it is not encrypted, print the contents of the file to standard out. Passphrase and salt for the decryption are read from environment variables. - :param file_path: Path to the file to be unwrapped and decrypted. - :type file_path: string + :param path: Path to the file to be unwrapped and decrypted. + :type path: string :return: The decrypted secrets - :rtype: list + :rtype: dict """ LOG.info('Started decrypting...') - if os.path.isfile(file_path): - return PeglegSecretManagement(file_path).decrypt_secrets() + file_dict = {} + + if not os.path.exists(path): + LOG.error('Path: {} was not found. Check your path and site name, ' + 'and try again.'.format(path)) + return file_dict + + if os.path.isfile(path): + file_dict[path] = PeglegSecretManagement(path).decrypt_secrets() else: - LOG.info('File: {} was not found. Check your file path and name, ' - 'and try again.'.format(file_path)) + match = os.path.join(path, '**', '*.yaml') + file_list = glob(match, recursive=True) + if not file_list: + LOG.warning('No YAML files were discovered in path: {}' + .format(path)) + for file_path in file_list: + file_dict[file_path] = PeglegSecretManagement( + file_path).decrypt_secrets() + return file_dict def _get_dest_path(repo_base, file_path, save_location): diff --git a/tests/unit/engine/test_secrets.py b/tests/unit/engine/test_secrets.py index 4ef38071..520737c3 100644 --- a/tests/unit/engine/test_secrets.py +++ b/tests/unit/engine/test_secrets.py @@ -113,11 +113,10 @@ data: {0}-password encrypted_files = listdir(save_location_str) assert len(encrypted_files) > 0 - # for _file in encrypted_files: - decrypted = secrets.decrypt(str(save_location.join( - "site/cicd/secrets/passphrases/" - "cicd-passphrase-encrypted.yaml"))) - assert yaml.load(decrypted) == yaml.load(passphrase_doc) + encrypted_path = str(save_location.join("site/cicd/secrets/passphrases/" + "cicd-passphrase-encrypted.yaml")) + decrypted = secrets.decrypt(encrypted_path) + assert yaml.load(decrypted[encrypted_path]) == yaml.load(passphrase_doc) def test_pegleg_secret_management_constructor(): diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index 3874fba6..0d3bffdb 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -555,9 +555,7 @@ class TestSiteSecretsActions(BaseCLIActionTest): assert "encrypted" in ceph_fsid["data"] assert "managedDocument" in ceph_fsid["data"] - relative_file_path = os.path.join("secrets", "passphrases", - "ceph_fsid.yaml") - secrets_opts = ['secrets', 'decrypt', '-f', relative_file_path, + secrets_opts = ['secrets', 'decrypt', '-p', file_path, self.site_name] result = self.runner.invoke(cli.site, ['-r', repo_path] + secrets_opts) assert result.exit_code == 0, result.output