Allow decryption of directories

This change allows users to specify a directory or file to be decrypted.

Allows directory decryption.

Adds flag to overwrite encrypted file with decrypted data.

Intelligently recognizes paths vs files in CLI input and outputs data 
accordingly.

Change-Id: I0d5e77f0eb1adb42165aa9b214aa90a0db0a3131
This commit is contained in:
Ian H. Pittwood 2019-05-06 11:30:59 -05:00 committed by Ian Pittwood
parent 5b8bfd9211
commit 2fa6a1a7bd
5 changed files with 72 additions and 38 deletions

View File

@ -662,9 +662,9 @@ Example without optional save location:
Decrypt Decrypt
^^^^^^^ ^^^^^^^
Unwrap an encrypted secrets document from a `Pegleg Managed Documents`_, Unwrap one or more encrypted secrets document from
decrypt the encrypted secrets, and dump the cleartext secrets file to `Pegleg Managed Documents`_, decrypt the encrypted secrets, and dump the
``stdout``. cleartext to stdout or a specified location.
**site_name** (Required). **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 the site and global repositories are used, as specified in the site's
:file:`site-definition.yaml`. :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 **-o / --overwrite** (Optional). False by default.
printed to stdout.
When set, encrypted file(s) at the specified path will be overwritten with
the decrypted data. Overrides ``--save-location`` option.
Usage: Usage:
:: ::
./pegleg.sh site <options> secrets decrypt <site_name> -f <file_path> ./pegleg.sh site <options> secrets decrypt <site_name> -p <path>
[-s <output_path>] [-s <output_path>]
Examples Examples

View File

@ -679,27 +679,45 @@ def encrypt(*, save_location, author, site_name):
help='Command to unwrap and decrypt one site ' help='Command to unwrap and decrypt one site '
'secrets document and print it to stdout.') 'secrets document and print it to stdout.')
@click.option( @click.option(
'-f', '-p',
'--filename', '--path',
'file_name', 'path',
help='The file to decrypt') type=click.Path(exists=True, readable=True),
required=True,
help='The file or directory path to decrypt.')
@click.option( @click.option(
'-s', '-s',
'--save-location', '--save-location',
'save_location', 'save_location',
default=None, default=None,
help='The destination where the decrypted file should be saved. ' help='The destination where the decrypted file(s) should be saved. '
'If not specified, it will be printed to stdout.') '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') @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) engine.repository.process_repositories(site_name)
decrypted = engine.secrets.decrypt(file_name) decrypted = engine.secrets.decrypt(path)
if save_location is None: if overwrite:
click.echo(decrypted) 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: else:
files.write(save_location, decrypted) for key, value in decrypted.items():
os.chmod(save_location, 0o600) 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") @main.group(help="Miscellaneous generate commands")

View File

@ -12,11 +12,12 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from glob import glob
import logging import logging
import os import os
import yaml
from prettytable import PrettyTable from prettytable import PrettyTable
import yaml
from pegleg.engine.catalog.pki_utility import PKIUtility from pegleg.engine.catalog.pki_utility import PKIUtility
from pegleg.engine.generators.passphrase_generator import PassphraseGenerator 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)) '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. """Decrypt one secrets file, and print the decrypted file to standard out.
Search the specified file_path for a file. 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 If the file is found, but it is not encrypted, print the contents of the
file to standard out. file to standard out.
Passphrase and salt for the decryption are read from environment variables. Passphrase and salt for the decryption are read from environment variables.
:param file_path: Path to the file to be unwrapped and decrypted. :param path: Path to the file to be unwrapped and decrypted.
:type file_path: string :type path: string
:return: The decrypted secrets :return: The decrypted secrets
:rtype: list :rtype: dict
""" """
LOG.info('Started decrypting...') LOG.info('Started decrypting...')
if os.path.isfile(file_path): file_dict = {}
return PeglegSecretManagement(file_path).decrypt_secrets()
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: else:
LOG.info('File: {} was not found. Check your file path and name, ' match = os.path.join(path, '**', '*.yaml')
'and try again.'.format(file_path)) 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): def _get_dest_path(repo_base, file_path, save_location):

View File

@ -113,11 +113,10 @@ data: {0}-password
encrypted_files = listdir(save_location_str) encrypted_files = listdir(save_location_str)
assert len(encrypted_files) > 0 assert len(encrypted_files) > 0
# for _file in encrypted_files: encrypted_path = str(save_location.join("site/cicd/secrets/passphrases/"
decrypted = secrets.decrypt(str(save_location.join( "cicd-passphrase-encrypted.yaml"))
"site/cicd/secrets/passphrases/" decrypted = secrets.decrypt(encrypted_path)
"cicd-passphrase-encrypted.yaml"))) assert yaml.load(decrypted[encrypted_path]) == yaml.load(passphrase_doc)
assert yaml.load(decrypted) == yaml.load(passphrase_doc)
def test_pegleg_secret_management_constructor(): def test_pegleg_secret_management_constructor():

View File

@ -555,9 +555,7 @@ class TestSiteSecretsActions(BaseCLIActionTest):
assert "encrypted" in ceph_fsid["data"] assert "encrypted" in ceph_fsid["data"]
assert "managedDocument" in ceph_fsid["data"] assert "managedDocument" in ceph_fsid["data"]
relative_file_path = os.path.join("secrets", "passphrases", secrets_opts = ['secrets', 'decrypt', '-p', file_path,
"ceph_fsid.yaml")
secrets_opts = ['secrets', 'decrypt', '-f', relative_file_path,
self.site_name] self.site_name]
result = self.runner.invoke(cli.site, ['-r', repo_path] + secrets_opts) result = self.runner.invoke(cli.site, ['-r', repo_path] + secrets_opts)
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output