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 2 months ago
parent
commit
7018d5941c

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

@@ -473,8 +473,13 @@ Generate PKI
473 473
 ^^^^^^^^^^^^
474 474
 
475 475
 Generate certificates and keys according to all PKICatalog documents in the
476
-site using the PKI module. Regenerating certificates can be
477
-accomplished by re-running this command.
476
+site using the PKI module. The default behavior is to generate all
477
+certificates that are not yet present. For example, the first time generate PKI
478
+is run or when new entries are added to the PKICatalogue, only those new
479
+entries will be generated on subsequent runs.
480
+
481
+Pegleg also supports a full regeneration of all certificates at any time, by
482
+using the --regenerate-all flag.
478 483
 
479 484
 Pegleg places generated document files in ``<site>/secrets/passphrases``,
480 485
 ``<site>/secrets/certificates``, or ``<site>/secrets/keypairs`` as
@@ -511,6 +516,10 @@ Minimum=0, no maximum.  Values less than 0 will raise an exception.
511 516
 NOTE: A generated certificate where days = 0 should only be used for testing.
512 517
 A certificate generated in such a way will be valid for 0 seconds.
513 518
 
519
+**--regenerate-all** (Optional, Default=False).
520
+
521
+Force Pegleg to regenerate all PKI items.
522
+
514 523
 Examples
515 524
 """"""""
516 525
 
@@ -520,7 +529,8 @@ Examples
520 529
     secrets generate-pki \
521 530
     <site_name> \
522 531
     -a <author> \
523
-    -d <days>
532
+    -d <days> \
533
+    --regenerate-all
524 534
 
525 535
 .. _command-line-repository-overrides:
526 536
 

+ 15
- 4
pegleg/cli.py View File

@@ -413,9 +413,13 @@ def secrets():
413 413
 
414 414
 @secrets.command(
415 415
     'generate-pki',
416
+    short_help='Generate certs and keys according to the site PKICatalog',
416 417
     help='Generate certificates and keys according to all PKICatalog '
417
-    'documents in the site. Regenerating certificates can be '
418
-    'accomplished by re-running this command.')
418
+    'documents in the site using the PKI module. The default behavior is '
419
+    'to generate all certificates that are not yet present. For example, '
420
+    'the first time generate PKI is run or when new entries are added '
421
+    'to the PKICatalogue, only those new entries will be generated on '
422
+    'subsequent runs.')
419 423
 @click.option(
420 424
     '-a',
421 425
     '--author',
@@ -431,8 +435,15 @@ def secrets():
431 435
     default=365,
432 436
     show_default=True,
433 437
     help='Duration in days generated certificates should be valid.')
438
+@click.option(
439
+    '--regenerate-all',
440
+    'regenerate_all',
441
+    is_flag=True,
442
+    default=False,
443
+    show_default=True,
444
+    help='Force Pegleg to regenerate all PKI items.')
434 445
 @click.argument('site_name')
435
-def generate_pki(site_name, author, days):
446
+def generate_pki(site_name, author, days, regenerate_all):
436 447
     """Generate certificates, certificate authorities and keypairs for a given
437 448
     site.
438 449
 
@@ -440,7 +451,7 @@ def generate_pki(site_name, author, days):
440 451
 
441 452
     engine.repository.process_repositories(site_name, overwrite_existing=True)
442 453
     pkigenerator = catalog.pki_generator.PKIGenerator(
443
-        site_name, author=author, duration=days)
454
+        site_name, author=author, duration=days, regenerate_all=regenerate_all)
444 455
     output_paths = pkigenerator.generate()
445 456
 
446 457
     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
21 21
 from pegleg.engine.catalog import pki_utility
22 22
 from pegleg.engine.common import managed_document as md
23 23
 from pegleg.engine import exceptions
24
+from pegleg.engine import site
24 25
 from pegleg.engine import util
25 26
 from pegleg.engine.util.pegleg_secret_management import PeglegSecretManagement
26 27
 
@@ -42,7 +43,12 @@ class PKIGenerator(object):
42 43
 
43 44
     """
44 45
     def __init__(
45
-            self, sitename, block_strings=True, author=None, duration=365):
46
+            self,
47
+            sitename,
48
+            block_strings=True,
49
+            author=None,
50
+            duration=365,
51
+            regenerate_all=False):
46 52
         """Constructor for ``PKIGenerator``.
47 53
 
48 54
         :param int duration: Duration in days that generated certificates
@@ -53,11 +59,12 @@ class PKIGenerator(object):
53 59
             block-style YAML string. Defaults to true.
54 60
         :param str author: Identifying name of the author generating new
55 61
             certificates.
56
-
62
+        :param bool regenerate_all: If Pegleg should regenerate all certs.
57 63
         """
58 64
 
65
+        self._regenerate_all = regenerate_all
59 66
         self._sitename = sitename
60
-        self._documents = util.definition.documents_for_site(sitename)
67
+        self._documents = site.get_rendered_docs(sitename)
61 68
         self._author = author
62 69
 
63 70
         self.keys = pki_utility.PKIUtility(
@@ -126,11 +133,10 @@ class PKIGenerator(object):
126 133
 
127 134
     def _get_or_gen(self, generator, kinds, document_name, *args, **kwargs):
128 135
         docs = self._find_docs(kinds, document_name)
129
-        if not docs:
136
+        if not docs or self._regenerate_all:
130 137
             docs = generator(document_name, *args, **kwargs)
131 138
         else:
132 139
             docs = PeglegSecretManagement(docs=docs)
133
-
134 140
         # Adding these to output should be idempotent, so we use a dict.
135 141
 
136 142
         for wrapper_doc in docs:

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

@@ -106,6 +106,26 @@ def collect(site_name, save_location):
106 106
 
107 107
 
108 108
 def render(site_name, output_stream, validate):
109
+    rendered_documents = get_rendered_docs(site_name, validate=validate)
110
+
111
+    if output_stream:
112
+        files.dump_all(
113
+            rendered_documents,
114
+            output_stream,
115
+            default_flow_style=False,
116
+            explicit_start=True,
117
+            explicit_end=True)
118
+    else:
119
+        add_representer_ordered_dict()
120
+        click.echo(
121
+            yaml.dump_all(
122
+                rendered_documents,
123
+                default_flow_style=False,
124
+                explicit_start=True,
125
+                explicit_end=True))
126
+
127
+
128
+def get_rendered_docs(site_name, validate=True):
109 129
     documents = []
110 130
     # Ignore YAML tags, only construct dicts
111 131
     SafeConstructor.add_multi_constructor(
@@ -116,8 +136,9 @@ def render(site_name, output_stream, validate):
116 136
 
117 137
     rendered_documents, errors = util.deckhand.deckhand_render(
118 138
         documents=documents, validate=validate)
119
-    err_msg = ''
139
+
120 140
     if errors:
141
+        err_msg = ''
121 142
         for err in errors:
122 143
             if isinstance(err, tuple) and len(err) > 1:
123 144
                 err_msg += ': '.join(err) + '\n'
@@ -125,21 +146,7 @@ def render(site_name, output_stream, validate):
125 146
                 err_msg += str(err) + '\n'
126 147
         raise click.ClickException(err_msg)
127 148
 
128
-    if output_stream:
129
-        files.dump_all(
130
-            rendered_documents,
131
-            output_stream,
132
-            default_flow_style=False,
133
-            explicit_start=True,
134
-            explicit_end=True)
135
-    else:
136
-        add_representer_ordered_dict()
137
-        click.echo(
138
-            yaml.dump_all(
139
-                rendered_documents,
140
-                default_flow_style=False,
141
-                explicit_start=True,
142
-                explicit_end=True))
149
+    return rendered_documents
143 150
 
144 151
 
145 152
 def list_(output_stream):

+ 14
- 0
tests/unit/engine/catalog/test_pki_generator.py View File

@@ -63,6 +63,18 @@ _SITE_DEFINITION = textwrap.dedent(
63 63
     ...
64 64
     """)
65 65
 
66
+_LAYERING_DEFINITION = textwrap.dedent(
67
+    """
68
+    ---
69
+    schema: deckhand/LayeringPolicy/v1
70
+    metadata:
71
+      schema: metadata/Control/v1
72
+      name: layering-policy
73
+    data:
74
+      layerOrder:
75
+        - site
76
+    """)
77
+
66 78
 _CA_KEY_NAME = "kubernetes"
67 79
 _CERT_KEY_NAME = "kubelet-n3"
68 80
 _KEYPAIR_KEY_NAME = "service-account"
@@ -192,6 +204,8 @@ def create_tmp_pki_structure(tmpdir):
192 204
         test_structure = copy.deepcopy(_SITE_TEST_STRUCTURE)
193 205
         test_structure['files']['site-definition.yaml'] = yaml.safe_load(
194 206
             site_definition)
207
+        test_structure['files']['layering-definition.yaml'] = yaml.safe_load(
208
+            _LAYERING_DEFINITION)
195 209
         test_structure['directories']['pki']['files'][
196 210
             'pki-catalog.yaml'] = yaml.safe_load(pki_catalog)
197 211
 

Loading…
Cancel
Save