Merge "Support regenerating PKI"

This commit is contained in:
Zuul 2019-08-06 18:45:24 +00:00 committed by Gerrit Code Review
commit 567a134e01
5 changed files with 78 additions and 30 deletions

View File

@ -473,8 +473,13 @@ Generate PKI
^^^^^^^^^^^^ ^^^^^^^^^^^^
Generate certificates and keys according to all PKICatalog documents in the Generate certificates and keys according to all PKICatalog documents in the
site using the PKI module. Regenerating certificates can be site using the PKI module. The default behavior is to generate all
accomplished by re-running this command. 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 ``<site>/secrets/passphrases``, Pegleg places generated document files in ``<site>/secrets/passphrases``,
``<site>/secrets/certificates``, or ``<site>/secrets/keypairs`` as ``<site>/secrets/certificates``, or ``<site>/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. 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. 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 Examples
"""""""" """"""""
@ -520,7 +529,8 @@ Examples
secrets generate-pki \ secrets generate-pki \
<site_name> \ <site_name> \
-a <author> \ -a <author> \
-d <days> -d <days> \
--regenerate-all
.. _command-line-repository-overrides: .. _command-line-repository-overrides:

View File

@ -421,9 +421,13 @@ def secrets():
@secrets.command( @secrets.command(
'generate-pki', 'generate-pki',
short_help='Generate certs and keys according to the site PKICatalog',
help='Generate certificates and keys according to all PKICatalog ' help='Generate certificates and keys according to all PKICatalog '
'documents in the site. Regenerating certificates can be ' 'documents in the site using the PKI module. The default behavior is '
'accomplished by re-running this command.') '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( @click.option(
'-a', '-a',
'--author', '--author',
@ -439,8 +443,15 @@ def secrets():
default=365, default=365,
show_default=True, show_default=True,
help='Duration in days generated certificates should be valid.') 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') @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 """Generate certificates, certificate authorities and keypairs for a given
site. site.
@ -448,7 +459,7 @@ def generate_pki(site_name, author, days):
engine.repository.process_repositories(site_name, overwrite_existing=True) engine.repository.process_repositories(site_name, overwrite_existing=True)
pkigenerator = catalog.pki_generator.PKIGenerator( 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() output_paths = pkigenerator.generate()
click.echo("Generated PKI files written to:\n%s" % '\n'.join(output_paths)) click.echo("Generated PKI files written to:\n%s" % '\n'.join(output_paths))

View File

@ -21,6 +21,7 @@ from pegleg import config
from pegleg.engine.catalog import pki_utility from pegleg.engine.catalog import pki_utility
from pegleg.engine.common import managed_document as md from pegleg.engine.common import managed_document as md
from pegleg.engine import exceptions from pegleg.engine import exceptions
from pegleg.engine import site
from pegleg.engine import util from pegleg.engine import util
from pegleg.engine.util.pegleg_secret_management import PeglegSecretManagement from pegleg.engine.util.pegleg_secret_management import PeglegSecretManagement
@ -42,7 +43,12 @@ class PKIGenerator(object):
""" """
def __init__( 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``. """Constructor for ``PKIGenerator``.
:param int duration: Duration in days that generated certificates :param int duration: Duration in days that generated certificates
@ -53,11 +59,12 @@ class PKIGenerator(object):
block-style YAML string. Defaults to true. block-style YAML string. Defaults to true.
:param str author: Identifying name of the author generating new :param str author: Identifying name of the author generating new
certificates. certificates.
:param bool regenerate_all: If Pegleg should regenerate all certs.
""" """
self._regenerate_all = regenerate_all
self._sitename = sitename self._sitename = sitename
self._documents = util.definition.documents_for_site(sitename) self._documents = site.get_rendered_docs(sitename)
self._author = author self._author = author
self.keys = pki_utility.PKIUtility( self.keys = pki_utility.PKIUtility(
@ -126,11 +133,10 @@ class PKIGenerator(object):
def _get_or_gen(self, generator, kinds, document_name, *args, **kwargs): def _get_or_gen(self, generator, kinds, document_name, *args, **kwargs):
docs = self._find_docs(kinds, document_name) docs = self._find_docs(kinds, document_name)
if not docs: if not docs or self._regenerate_all:
docs = generator(document_name, *args, **kwargs) docs = generator(document_name, *args, **kwargs)
else: else:
docs = PeglegSecretManagement(docs=docs) docs = PeglegSecretManagement(docs=docs)
# Adding these to output should be idempotent, so we use a dict. # Adding these to output should be idempotent, so we use a dict.
for wrapper_doc in docs: for wrapper_doc in docs:

View File

@ -106,24 +106,7 @@ def collect(site_name, save_location):
def render(site_name, output_stream, validate): def render(site_name, output_stream, validate):
documents = [] rendered_documents = get_rendered_docs(site_name, validate=validate)
# 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)
if output_stream: if output_stream:
files.dump_all( files.dump_all(
@ -142,6 +125,30 @@ def render(site_name, output_stream, validate):
explicit_end=True)) 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): def list_(output_stream):
"""List site names for a given repository.""" """List site names for a given repository."""

View File

@ -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" _CA_KEY_NAME = "kubernetes"
_CERT_KEY_NAME = "kubelet-n3" _CERT_KEY_NAME = "kubelet-n3"
_KEYPAIR_KEY_NAME = "service-account" _KEYPAIR_KEY_NAME = "service-account"
@ -192,6 +204,8 @@ def create_tmp_pki_structure(tmpdir):
test_structure = copy.deepcopy(_SITE_TEST_STRUCTURE) test_structure = copy.deepcopy(_SITE_TEST_STRUCTURE)
test_structure['files']['site-definition.yaml'] = yaml.safe_load( test_structure['files']['site-definition.yaml'] = yaml.safe_load(
site_definition) site_definition)
test_structure['files']['layering-definition.yaml'] = yaml.safe_load(
_LAYERING_DEFINITION)
test_structure['directories']['pki']['files'][ test_structure['directories']['pki']['files'][
'pki-catalog.yaml'] = yaml.safe_load(pki_catalog) 'pki-catalog.yaml'] = yaml.safe_load(pki_catalog)