diff --git a/doc/source/cli/cli.rst b/doc/source/cli/cli.rst index 9cb80acc..63477d0e 100644 --- a/doc/source/cli/cli.rst +++ b/doc/source/cli/cli.rst @@ -473,8 +473,13 @@ Generate PKI ^^^^^^^^^^^^ Generate certificates and keys according to all PKICatalog documents in the -site using the PKI module. Regenerating certificates can be -accomplished by re-running this command. +site using the PKI module. The default behavior is to generate all +certificates that are not yet present. For example, the first time generate PKI +is run or when new entries are added to the PKICatalogue, only those new +entries will be generated on subsequent runs. + +Pegleg also supports a full regeneration of all certificates at any time, by +using the --regenerate-all flag. Pegleg places generated document files in ``/secrets/passphrases``, ``/secrets/certificates``, or ``/secrets/keypairs`` as @@ -511,6 +516,10 @@ Minimum=0, no maximum. Values less than 0 will raise an exception. NOTE: A generated certificate where days = 0 should only be used for testing. A certificate generated in such a way will be valid for 0 seconds. +**--regenerate-all** (Optional, Default=False). + +Force Pegleg to regenerate all PKI items. + Examples """""""" @@ -520,7 +529,8 @@ Examples secrets generate-pki \ \ -a \ - -d + -d \ + --regenerate-all .. _command-line-repository-overrides: diff --git a/pegleg/cli.py b/pegleg/cli.py index 633c7c91..de04446b 100644 --- a/pegleg/cli.py +++ b/pegleg/cli.py @@ -413,9 +413,13 @@ def secrets(): @secrets.command( 'generate-pki', + short_help='Generate certs and keys according to the site PKICatalog', help='Generate certificates and keys according to all PKICatalog ' - 'documents in the site. Regenerating certificates can be ' - 'accomplished by re-running this command.') + 'documents in the site using the PKI module. The default behavior is ' + 'to generate all certificates that are not yet present. For example, ' + 'the first time generate PKI is run or when new entries are added ' + 'to the PKICatalogue, only those new entries will be generated on ' + 'subsequent runs.') @click.option( '-a', '--author', @@ -431,8 +435,15 @@ def secrets(): default=365, show_default=True, help='Duration in days generated certificates should be valid.') +@click.option( + '--regenerate-all', + 'regenerate_all', + is_flag=True, + default=False, + show_default=True, + help='Force Pegleg to regenerate all PKI items.') @click.argument('site_name') -def generate_pki(site_name, author, days): +def generate_pki(site_name, author, days, regenerate_all): """Generate certificates, certificate authorities and keypairs for a given site. @@ -440,7 +451,7 @@ def generate_pki(site_name, author, days): engine.repository.process_repositories(site_name, overwrite_existing=True) pkigenerator = catalog.pki_generator.PKIGenerator( - site_name, author=author, duration=days) + site_name, author=author, duration=days, regenerate_all=regenerate_all) output_paths = pkigenerator.generate() click.echo("Generated PKI files written to:\n%s" % '\n'.join(output_paths)) diff --git a/pegleg/engine/catalog/pki_generator.py b/pegleg/engine/catalog/pki_generator.py index 1c796780..d4990089 100644 --- a/pegleg/engine/catalog/pki_generator.py +++ b/pegleg/engine/catalog/pki_generator.py @@ -21,6 +21,7 @@ from pegleg import config from pegleg.engine.catalog import pki_utility from pegleg.engine.common import managed_document as md from pegleg.engine import exceptions +from pegleg.engine import site from pegleg.engine import util from pegleg.engine.util.pegleg_secret_management import PeglegSecretManagement @@ -42,7 +43,12 @@ class PKIGenerator(object): """ def __init__( - self, sitename, block_strings=True, author=None, duration=365): + self, + sitename, + block_strings=True, + author=None, + duration=365, + regenerate_all=False): """Constructor for ``PKIGenerator``. :param int duration: Duration in days that generated certificates @@ -53,11 +59,12 @@ class PKIGenerator(object): block-style YAML string. Defaults to true. :param str author: Identifying name of the author generating new certificates. - + :param bool regenerate_all: If Pegleg should regenerate all certs. """ + self._regenerate_all = regenerate_all self._sitename = sitename - self._documents = util.definition.documents_for_site(sitename) + self._documents = site.get_rendered_docs(sitename) self._author = author self.keys = pki_utility.PKIUtility( @@ -126,11 +133,10 @@ class PKIGenerator(object): def _get_or_gen(self, generator, kinds, document_name, *args, **kwargs): docs = self._find_docs(kinds, document_name) - if not docs: + if not docs or self._regenerate_all: docs = generator(document_name, *args, **kwargs) else: docs = PeglegSecretManagement(docs=docs) - # Adding these to output should be idempotent, so we use a dict. for wrapper_doc in docs: diff --git a/pegleg/engine/site.py b/pegleg/engine/site.py index be52d062..66fe2c7f 100644 --- a/pegleg/engine/site.py +++ b/pegleg/engine/site.py @@ -106,24 +106,7 @@ def collect(site_name, save_location): def render(site_name, output_stream, validate): - documents = [] - # Ignore YAML tags, only construct dicts - SafeConstructor.add_multi_constructor( - '', lambda loader, suffix, node: None) - for filename in util.definition.site_files(site_name): - with open(filename, 'r') as f: - documents.extend(list(yaml.safe_load_all(f))) - - rendered_documents, errors = util.deckhand.deckhand_render( - documents=documents, validate=validate) - err_msg = '' - if errors: - for err in errors: - if isinstance(err, tuple) and len(err) > 1: - err_msg += ': '.join(err) + '\n' - else: - err_msg += str(err) + '\n' - raise click.ClickException(err_msg) + rendered_documents = get_rendered_docs(site_name, validate=validate) if output_stream: files.dump_all( @@ -142,6 +125,30 @@ def render(site_name, output_stream, validate): explicit_end=True)) +def get_rendered_docs(site_name, validate=True): + documents = [] + # Ignore YAML tags, only construct dicts + SafeConstructor.add_multi_constructor( + '', lambda loader, suffix, node: None) + for filename in util.definition.site_files(site_name): + with open(filename, 'r') as f: + documents.extend(list(yaml.safe_load_all(f))) + + rendered_documents, errors = util.deckhand.deckhand_render( + documents=documents, validate=validate) + + if errors: + err_msg = '' + for err in errors: + if isinstance(err, tuple) and len(err) > 1: + err_msg += ': '.join(err) + '\n' + else: + err_msg += str(err) + '\n' + raise click.ClickException(err_msg) + + return rendered_documents + + def list_(output_stream): """List site names for a given repository.""" diff --git a/tests/unit/engine/catalog/test_pki_generator.py b/tests/unit/engine/catalog/test_pki_generator.py index 74398cfb..047ec7cb 100644 --- a/tests/unit/engine/catalog/test_pki_generator.py +++ b/tests/unit/engine/catalog/test_pki_generator.py @@ -63,6 +63,18 @@ _SITE_DEFINITION = textwrap.dedent( ... """) +_LAYERING_DEFINITION = textwrap.dedent( + """ + --- + schema: deckhand/LayeringPolicy/v1 + metadata: + schema: metadata/Control/v1 + name: layering-policy + data: + layerOrder: + - site + """) + _CA_KEY_NAME = "kubernetes" _CERT_KEY_NAME = "kubelet-n3" _KEYPAIR_KEY_NAME = "service-account" @@ -192,6 +204,8 @@ def create_tmp_pki_structure(tmpdir): test_structure = copy.deepcopy(_SITE_TEST_STRUCTURE) test_structure['files']['site-definition.yaml'] = yaml.safe_load( site_definition) + test_structure['files']['layering-definition.yaml'] = yaml.safe_load( + _LAYERING_DEFINITION) test_structure['directories']['pki']['files'][ 'pki-catalog.yaml'] = yaml.safe_load(pki_catalog)