pegleg/pegleg/engine/secrets.py
Felipe Monteiro 2a8d2638b3 pki: Port Promenade's PKI catalog into Pegleg
This patch set implements the PKICatalog [0] requirements
as well as PeglegManagedDocument [1] generation requirements
outlined in the spec [2].

Included in this patch set:

* New CLI entry point called "pegleg site secrets generate-pki"
* PeglegManagedDocument generation logic in
  engine.cache.managed_document
* Refactored PKICatalog logic in engine.cache.pki_catalog derived
  from the Promenade PKI implementation [3], responsible for
  generating certificates, CAs, and keypairs
* Refactored PKIGenerator logic in engine.cache.pki_generator
  derived from Promenade Generator implementation [4],
  responsible for reading in pegleg/PKICatalog/v1 documents (as
  well as promenade/PKICatalog/v1 documents for backwards
  compatibility) and generating required secrets and storing
  them into the paths specified under [0]
* Unit tests for all of the above [5]
* Example pki-catalog.yaml document under pegleg/site_yamls
* Validation schema for pki-catalog.yaml (TODO: implement
  validation logic here: [6])
* Updates to CLI documentation and inclusion of PKICatalog
  and PeglegManagedDocument documentation
* Documentation updates with PKI information [7]

TODO (in follow-up patch sets):

* Expand on overview documentation to include new Pegleg
  responsibilities
* Allow the original repository (not the copied one) to
  be the destination where the secrets are written to
* Finish up cert expiry/revocation logic

[0] https://airship-specs.readthedocs.io/en/latest/specs/approved/pegleg-secrets.html#document-generation
[1] https://airship-specs.readthedocs.io/en/latest/specs/approved/pegleg-secrets.html#peglegmanageddocument
[2] https://airship-specs.readthedocs.io/en/latest/specs/approved/pegleg-secrets.html
[3] https://github.com/openstack/airship-promenade/blob/master/promenade/pki.py
[4] https://github.com/openstack/airship-promenade/blob/master/promenade/generator.py
[5] https://review.openstack.org/#/c/611739/
[6] https://review.openstack.org/#/c/608159/
[7] https://review.openstack.org/#/c/611738/

Change-Id: I3010d04cac6d22c656d144f0dafeaa5e19a13068
2019-01-15 13:29:21 -06:00

114 lines
4.5 KiB
Python

# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import os
from pegleg.engine.util import definition
from pegleg.engine.util import files
from pegleg.engine.util.pegleg_secret_management import PeglegSecretManagement
__all__ = ('encrypt', 'decrypt')
LOG = logging.getLogger(__name__)
def encrypt(save_location, author, site_name):
"""
Encrypt all secrets documents for a site identifies by site_name.
Parse through all documents related to site_name and encrypt all
site documents which have metadata.storagePolicy: encrypted, and which are
not already encrypted and wrapped in a PeglegManagedDocument.
Passphrase and salt for the encryption are read from environment
variables ($PEGLEG_PASSPHRASE and $PEGLEG_SALT respectively).
By default, the resulting output files will overwrite the original
unencrypted secrets documents.
:param save_location: if provided, identifies the base directory to store
the encrypted secrets files. If not provided the encrypted secrets files
will overwrite the original unencrypted files (default behavior).
:type save_location: string
:param author: The identifier provided by the application or
the person who requests encrypt the site secrets documents.
:type author: string
:param site_name: The name of the site to encrypt its secrets files.
:type site_name: string
"""
files.check_file_save_location(save_location)
LOG.info('Started encrypting...')
secrets_found = False
for repo_base, file_path in definition.site_files_by_repo(site_name):
secrets_found = True
PeglegSecretManagement(file_path).encrypt_secrets(
_get_dest_path(repo_base, file_path, save_location), author)
if secrets_found:
LOG.info('Encryption of all secret files was completed.')
else:
LOG.warn(
'No secret documents were found for site: {}'.format(site_name))
def decrypt(file_path, site_name):
"""
Decrypt one secrets file and print the decrypted data to standard out.
Search in in secrets file of a site, identified by site_name, for a file
named file_name.
If the file is found and encrypted, unwrap and decrypt it and print the
result to standard out.
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 site_name: The name of the site to search for the file.
:type site_name: string
:return: The decrypted secrets
:rtype: list
"""
LOG.info('Started decrypting...')
if (os.path.isfile(file_path) and
[s for s in file_path.split(os.path.sep) if s == site_name]):
return PeglegSecretManagement(file_path).decrypt_secrets()
else:
LOG.info('File: {} was not found. Check your file path and name, '
'and try again.'.format(file_path))
def _get_dest_path(repo_base, file_path, save_location):
"""
Calculate and return the destination base directory path for the
encrypted or decrypted secrets files.
:param repo_base: Base repo of the source secrets file.
:type repo_base: string
:param file_path: File path to the source secrets file.
:type file_path: string
:param save_location: Base location of destination secrets file
:type save_location: string
:return: The file path of the destination secrets file.
:rtype: string
"""
if (save_location and save_location != os.path.sep and
save_location.endswith(os.path.sep)):
save_location = save_location.rstrip(os.path.sep)
if repo_base and repo_base.endswith(os.path.sep):
repo_base = repo_base.rstrip(os.path.sep)
if save_location:
return file_path.replace(repo_base, save_location)
else:
return file_path