Browse Source

Support regenerating PKI

This patch adds functionality Pegleg currently lacks: the ability to
regenerate expired certificates.

This patch adds:
1. CLI toggle --regenerate-all to generate_pki.  Default is False,
   which means if no certificates are present, generate what is in
   the pki catalogue. If new certs have been added to the catalogue
   generate just those.  If the --regenerate-all flag is True, then
   Pegleg will ignore any existing certs and regenerate (or generate
   for the first time) all certificates defined in the PKI catalogue.
2. Documentation updates for CLI change.
3. Updates to pki_utility to accomodate the new flag.
4. Updates pki_generator methods to use rendered documents to
   accommodate documents that have to be layered.
5. Updates pki_generator unit tests to include a layering definition
   which is now required to run the commands.

Change-Id: I2d8086770e9226e44598ef40eca790981279f626
changes/37/671337/24
Alexander Hughes 7 months ago
parent
commit
7018d5941c
5 changed files with 76 additions and 28 deletions
  1. +13
    -3
      doc/source/cli/cli.rst
  2. +15
    -4
      pegleg/cli.py
  3. +11
    -5
      pegleg/engine/catalog/pki_generator.py
  4. +23
    -16
      pegleg/engine/site.py
  5. +14
    -0
      tests/unit/engine/catalog/test_pki_generator.py

+ 13
- 3
doc/source/cli/cli.rst View File

@@ -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 ``<site>/secrets/passphrases``,
``<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.
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 \
<site_name> \
-a <author> \
-d <days>
-d <days> \
--regenerate-all

.. _command-line-repository-overrides:


+ 15
- 4
pegleg/cli.py View File

@@ -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))

+ 11
- 5
pegleg/engine/catalog/pki_generator.py View File

@@ -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:

+ 23
- 16
pegleg/engine/site.py View File

@@ -106,6 +106,26 @@ def collect(site_name, save_location):


def render(site_name, output_stream, validate):
rendered_documents = get_rendered_docs(site_name, validate=validate)

if output_stream:
files.dump_all(
rendered_documents,
output_stream,
default_flow_style=False,
explicit_start=True,
explicit_end=True)
else:
add_representer_ordered_dict()
click.echo(
yaml.dump_all(
rendered_documents,
default_flow_style=False,
explicit_start=True,
explicit_end=True))


def get_rendered_docs(site_name, validate=True):
documents = []
# Ignore YAML tags, only construct dicts
SafeConstructor.add_multi_constructor(
@@ -116,8 +136,9 @@ def render(site_name, output_stream, validate):

rendered_documents, errors = util.deckhand.deckhand_render(
documents=documents, validate=validate)
err_msg = ''
if errors:
err_msg = ''
for err in errors:
if isinstance(err, tuple) and len(err) > 1:
err_msg += ': '.join(err) + '\n'
@@ -125,21 +146,7 @@ def render(site_name, output_stream, validate):
err_msg += str(err) + '\n'
raise click.ClickException(err_msg)

if output_stream:
files.dump_all(
rendered_documents,
output_stream,
default_flow_style=False,
explicit_start=True,
explicit_end=True)
else:
add_representer_ordered_dict()
click.echo(
yaml.dump_all(
rendered_documents,
default_flow_style=False,
explicit_start=True,
explicit_end=True))
return rendered_documents


def list_(output_stream):

+ 14
- 0
tests/unit/engine/catalog/test_pki_generator.py 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"
_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)


Loading…
Cancel
Save