Add passphrase catalog override option
Adds an option to specify a passphrase catalog to override catalogs discovered in the site repository. This allows the generation of a specified subset of passphrases instead of the entire site's catalog. Change-Id: I797107234292eea8ca788b7a94ed5e2c90566bf5
This commit is contained in:
parent
87d24d530a
commit
9163ef08ca
@ -949,7 +949,6 @@ catalog.
|
||||
|
||||
**-a / --author** (Required)
|
||||
|
||||
|
||||
``Author`` is intended to document the application or the individual, who
|
||||
generates the site passphrase documents, mostly for tracking purposes. It
|
||||
is expected to be leveraged in an operator-specific manner.
|
||||
@ -965,6 +964,15 @@ are placed in the following folder structure under ``save_location``:
|
||||
|
||||
<save_location>/site/<site_name>/secrets/passphrases/<passphrase_name.yaml>
|
||||
|
||||
**-c / --passphrase-catalog** (Optional).
|
||||
|
||||
Specifies a path for a passphrase catalog file to use instead of the catalogs
|
||||
found in the repositories specified by the user. The specified catalog
|
||||
will be used when this option is specified and all other discovered catalogs
|
||||
will be disregarded. This can be used to specify a subset of passphrases to
|
||||
generate instead of the whole catalog or for testing new passphrases before
|
||||
merging them into production.
|
||||
|
||||
**-i / --interactive** (Optional). False by default.
|
||||
|
||||
Enables input prompts for "prompt: true" passphrases. Input prompts are
|
||||
|
@ -707,6 +707,15 @@ def generate_pki(site_name, author, days, regenerate_all, save_location):
|
||||
required=True,
|
||||
help='Identifier for the program or person who is generating the secrets '
|
||||
'documents')
|
||||
@click.option(
|
||||
'-c',
|
||||
'--passphrase-catalog',
|
||||
'passphrase_catalog',
|
||||
required=False,
|
||||
type=click.Path(exists=True, dir_okay=False, readable=True),
|
||||
help='Path to a specific passphrase catalog to generate passphrases from. '
|
||||
'If not specified, defaults to use catalogs discovered in the '
|
||||
'repositories.')
|
||||
@click.option(
|
||||
'-i',
|
||||
'--interactive',
|
||||
@ -722,11 +731,13 @@ def generate_pki(site_name, author, days, regenerate_all, save_location):
|
||||
show_default=True,
|
||||
help='Force cleartext generation of passphrases. This is not recommended.')
|
||||
def generate_passphrases(
|
||||
*, site_name, save_location, author, interactive, force_cleartext):
|
||||
*, site_name, save_location, author, passphrase_catalog, interactive,
|
||||
force_cleartext):
|
||||
engine.repository.process_repositories(site_name)
|
||||
config.set_global_enc_keys(site_name)
|
||||
engine.secrets.generate_passphrases(
|
||||
site_name, save_location, author, interactive, force_cleartext)
|
||||
site_name, save_location, author, passphrase_catalog, interactive,
|
||||
force_cleartext)
|
||||
|
||||
|
||||
@secrets.command(
|
||||
|
@ -40,7 +40,12 @@ class PassphraseGenerator(BaseGenerator):
|
||||
Generates passphrases for a given environment, specified in a
|
||||
passphrase catalog.
|
||||
"""
|
||||
def __init__(self, sitename, save_location, author):
|
||||
def __init__(
|
||||
self,
|
||||
sitename,
|
||||
save_location,
|
||||
author,
|
||||
override_passphrase_catalog=None):
|
||||
"""Constructor for ``PassphraseGenerator``.
|
||||
|
||||
:param str sitename: Site name for which passphrases are generated.
|
||||
@ -49,11 +54,11 @@ class PassphraseGenerator(BaseGenerator):
|
||||
:param str author: Identifying name of the author generating new
|
||||
certificates.
|
||||
"""
|
||||
|
||||
super(PassphraseGenerator,
|
||||
self).__init__(sitename, save_location, author)
|
||||
self._catalog = PassphraseCatalog(
|
||||
self._sitename, documents=self._documents)
|
||||
self._sitename,
|
||||
documents=override_passphrase_catalog or self._documents)
|
||||
|
||||
def generate(self, interactive=False, force_cleartext=False):
|
||||
"""
|
||||
|
@ -138,7 +138,11 @@ def _get_dest_path(repo_base, file_path, save_location):
|
||||
|
||||
|
||||
def generate_passphrases(
|
||||
site_name, save_location, author, interactive=False,
|
||||
site_name,
|
||||
save_location,
|
||||
author,
|
||||
passphrase_catalog=None,
|
||||
interactive=False,
|
||||
force_cleartext=False):
|
||||
"""
|
||||
Look for the site passphrase catalogs, and for every passphrase entry in
|
||||
@ -149,12 +153,19 @@ def generate_passphrases(
|
||||
:param str site_name: The site to read from
|
||||
:param str save_location: Location to write files to
|
||||
:param str author: Author who's generating the files
|
||||
:param path-like passphrase_catalog: Path to file overriding any other
|
||||
discovered passphrase catalogs
|
||||
:param bool interactive: Whether to allow user input for passphrases
|
||||
:param bool force_cleartext: Whether to generate results in clear text
|
||||
"""
|
||||
override_passphrase_catalog = passphrase_catalog
|
||||
if passphrase_catalog:
|
||||
override_passphrase_catalog = files.read(passphrase_catalog)
|
||||
|
||||
PassphraseGenerator(site_name, save_location, author).generate(
|
||||
interactive=interactive, force_cleartext=force_cleartext)
|
||||
PassphraseGenerator(
|
||||
site_name, save_location, author,
|
||||
override_passphrase_catalog).generate(
|
||||
interactive=interactive, force_cleartext=force_cleartext)
|
||||
|
||||
|
||||
def generate_crypto_string(length):
|
||||
|
@ -68,6 +68,34 @@ data:
|
||||
...
|
||||
""")
|
||||
|
||||
TEST_OVERRIDE_PASSPHRASES_CATALOG = yaml.safe_load(
|
||||
"""
|
||||
---
|
||||
schema: pegleg/PassphraseCatalog/v1
|
||||
metadata:
|
||||
schema: metadata/Document/v1
|
||||
name: cluster-passphrases
|
||||
layeringDefinition:
|
||||
abstract: false
|
||||
layer: site
|
||||
storagePolicy: cleartext
|
||||
data:
|
||||
passphrases:
|
||||
- description: 'short description of the passphrase'
|
||||
document_name: ucp_keystone_admin_password
|
||||
encrypted: true
|
||||
length: 24
|
||||
- description: 'short description of the passphrase'
|
||||
document_name: osh_cinder_password
|
||||
encrypted: true
|
||||
length: 25
|
||||
- description: 'short description of the passphrase'
|
||||
document_name: osh_placement_password
|
||||
encrypted: true
|
||||
length: 32
|
||||
...
|
||||
""")
|
||||
|
||||
TEST_GLOBAL_PASSPHRASES_CATALOG = yaml.safe_load(
|
||||
"""
|
||||
---
|
||||
@ -230,6 +258,10 @@ def test_generate_passphrases(*_):
|
||||
os.makedirs(os.path.join(_dir, 'cicd_site_repo'), exist_ok=True)
|
||||
PassphraseGenerator('cicd', _dir, 'test_author').generate()
|
||||
|
||||
passphrase_dir = os.path.join(
|
||||
_dir, 'site', 'cicd', 'secrets', 'passphrases')
|
||||
assert 6 == len(os.listdir(passphrase_dir))
|
||||
|
||||
for passphrase in TEST_PASSPHRASES_CATALOG['data']['passphrases']:
|
||||
passphrase_file_name = '{}.yaml'.format(passphrase['document_name'])
|
||||
passphrase_file_path = os.path.join(
|
||||
@ -281,6 +313,68 @@ def test_generate_passphrases_exception(capture):
|
||||
'try again.')))
|
||||
|
||||
|
||||
@mock.patch.object(
|
||||
util.definition,
|
||||
'documents_for_site',
|
||||
autospec=True,
|
||||
return_value=TEST_SITE_DOCUMENTS)
|
||||
@mock.patch.object(
|
||||
pegleg.config,
|
||||
'get_site_repo',
|
||||
autospec=True,
|
||||
return_value='cicd_site_repo')
|
||||
@mock.patch.object(
|
||||
util.definition,
|
||||
'site_files',
|
||||
autospec=True,
|
||||
return_value=[
|
||||
'cicd_site_repo/site/cicd/passphrases/passphrase-catalog.yaml',
|
||||
])
|
||||
@mock.patch.dict(
|
||||
os.environ, {
|
||||
'PEGLEG_PASSPHRASE': 'ytrr89erARAiPE34692iwUMvWqqBvC',
|
||||
'PEGLEG_SALT': 'MySecretSalt1234567890]['
|
||||
})
|
||||
def test_generate_passphrases_with_overidden_passphrase_catalog(*_):
|
||||
_dir = tempfile.mkdtemp()
|
||||
os.makedirs(os.path.join(_dir, 'cicd_site_repo'), exist_ok=True)
|
||||
PassphraseGenerator(
|
||||
'cicd', _dir, 'test_author',
|
||||
[TEST_OVERRIDE_PASSPHRASES_CATALOG]).generate()
|
||||
|
||||
passphrase_dir = os.path.join(
|
||||
_dir, 'site', 'cicd', 'secrets', 'passphrases')
|
||||
assert 3 == len(os.listdir(passphrase_dir))
|
||||
|
||||
for passphrase in TEST_OVERRIDE_PASSPHRASES_CATALOG['data']['passphrases']:
|
||||
passphrase_file_name = '{}.yaml'.format(passphrase['document_name'])
|
||||
passphrase_file_path = os.path.join(
|
||||
_dir, 'site', 'cicd', 'secrets', 'passphrases',
|
||||
passphrase_file_name)
|
||||
assert os.path.isfile(passphrase_file_path)
|
||||
with open(passphrase_file_path) as stream:
|
||||
doc = yaml.safe_load(stream)
|
||||
assert doc['schema'] == 'pegleg/PeglegManagedDocument/v1'
|
||||
assert doc['metadata']['storagePolicy'] == 'cleartext'
|
||||
assert 'encrypted' in doc['data']
|
||||
assert doc['data']['encrypted']['by'] == 'test_author'
|
||||
assert 'generated' in doc['data']
|
||||
assert doc['data']['generated']['by'] == 'test_author'
|
||||
assert 'managedDocument' in doc['data']
|
||||
assert doc['data']['managedDocument']['metadata'][
|
||||
'storagePolicy'] == 'encrypted'
|
||||
decrypted_passphrase = encryption.decrypt(
|
||||
doc['data']['managedDocument']['data'],
|
||||
os.environ['PEGLEG_PASSPHRASE'].encode(),
|
||||
os.environ['PEGLEG_SALT'].encode())
|
||||
if passphrase_file_name == 'osh_placement_password.yaml':
|
||||
assert len(decrypted_passphrase) == 32
|
||||
elif passphrase_file_name == 'osh_cinder_password.yaml':
|
||||
assert len(decrypted_passphrase) == 25
|
||||
else:
|
||||
assert len(decrypted_passphrase) == 24
|
||||
|
||||
|
||||
@mock.patch.object(
|
||||
util.definition,
|
||||
'documents_for_site',
|
||||
|
Loading…
Reference in New Issue
Block a user