← Back to team overview

cloud-init-dev team mailing list archive

[Merge] ~matthiasbaur/cloud-init:feature/puppet-csr-attributes into cloud-init:master

 

Matthias Baur has proposed merging ~matthiasbaur/cloud-init:feature/puppet-csr-attributes into cloud-init:master.

Requested reviews:
  cloud-init commiters (cloud-init-dev)

For more details, see:
https://code.launchpad.net/~matthiasbaur/cloud-init/+git/cloud-init/+merge/371997
-- 
Your team cloud-init commiters is requested to review the proposed merge of ~matthiasbaur/cloud-init:feature/puppet-csr-attributes into cloud-init:master.
diff --git a/cloudinit/config/cc_puppet.py b/cloudinit/config/cc_puppet.py
index 4190a20..edffdb6 100644
--- a/cloudinit/config/cc_puppet.py
+++ b/cloudinit/config/cc_puppet.py
@@ -24,9 +24,10 @@ module will attempt to start puppet even if no installation was performed.
 The module also provides keys for configuring the new puppet 4 paths and
 installing the puppet package from the puppetlabs repositories:
 https://docs.puppet.com/puppet/4.2/reference/whered_it_go.html
-The keys are ``package_name``, ``conf_file`` and ``ssl_dir``. If unset, their
-values will default to ones that work with puppet 3.x and with distributions
-that ship modified puppet 4.x that uses the old paths.
+The keys are ``package_name``, ``conf_file``, ``ssl_dir`` and
+``csr_attributes_path``. If unset, their values will default to
+ones that work with puppet 3.x and with distributions that ship modified
+puppet 4.x that uses the old paths.
 
 Puppet configuration can be specified under the ``conf`` key. The
 configuration is specified as a dictionary containing high-level ``<section>``
@@ -40,6 +41,10 @@ If ``ca_cert`` is present, it will not be written to ``puppet.conf``, but
 instead will be used as the puppermaster certificate. It should be specified
 in pem format as a multi-line string (using the ``|`` yaml notation).
 
+Additionally it's possible to create a csr_attributes.yaml for
+CSR attributes and certificate extension requests.
+See https://puppet.com/docs/puppet/latest/config_file_csr_attributes.html
+
 **Internal name:** ``cc_puppet``
 
 **Module frequency:** per instance
@@ -53,6 +58,7 @@ in pem format as a multi-line string (using the ``|`` yaml notation).
         version: <version>
         conf_file: '/etc/puppet/puppet.conf'
         ssl_dir: '/var/lib/puppet/ssl'
+        csr_attributes_path: '/etc/puppet/csr_attributes.yaml'
         package_name: 'puppet'
         conf:
             agent:
@@ -62,28 +68,39 @@ in pem format as a multi-line string (using the ``|`` yaml notation).
                     -------BEGIN CERTIFICATE-------
                     <cert data>
                     -------END CERTIFICATE-------
+        csr_attributes:
+            custom_attributes:
+                1.2.840.113549.1.9.7: 342thbjkt82094y0uthhor289jnqthpc2290
+            extension_requests:
+                pp_uuid: ED803750-E3C7-44F5-BB08-41A04433FE2E
+                pp_image_name: my_ami_image
+                pp_preshared_key: 342thbjkt82094y0uthhor289jnqthpc2290
 """
 
 from six import StringIO
 
 import os
 import socket
+import yaml
 
 from cloudinit import helpers
 from cloudinit import util
 
 PUPPET_CONF_PATH = '/etc/puppet/puppet.conf'
 PUPPET_SSL_DIR = '/var/lib/puppet/ssl'
+PUPPET_CSR_ATTRIBUTES_PATH = '/etc/puppet/csr_attributes.yaml'
 PUPPET_PACKAGE_NAME = 'puppet'
 
 
 class PuppetConstants(object):
 
-    def __init__(self, puppet_conf_file, puppet_ssl_dir, log):
+    def __init__(self, puppet_conf_file, puppet_ssl_dir,
+                 csr_attributes_path, log):
         self.conf_path = puppet_conf_file
         self.ssl_dir = puppet_ssl_dir
         self.ssl_cert_dir = os.path.join(puppet_ssl_dir, "certs")
         self.ssl_cert_path = os.path.join(self.ssl_cert_dir, "ca.pem")
+        self.csr_attributes_path = csr_attributes_path
 
 
 def _autostart_puppet(log):
@@ -118,8 +135,10 @@ def handle(name, cfg, cloud, log, _args):
     conf_file = util.get_cfg_option_str(
         puppet_cfg, 'conf_file', PUPPET_CONF_PATH)
     ssl_dir = util.get_cfg_option_str(puppet_cfg, 'ssl_dir', PUPPET_SSL_DIR)
+    csr_attributes_path = util.get_cfg_option_str(
+        puppet_cfg, 'csr_attributes_path', PUPPET_CSR_ATTRIBUTES_PATH)
 
-    p_constants = PuppetConstants(conf_file, ssl_dir, log)
+    p_constants = PuppetConstants(conf_file, ssl_dir, csr_attributes_path, log)
     if not install and version:
         log.warn(("Puppet install set false but version supplied,"
                   " doing nothing."))
@@ -176,6 +195,10 @@ def handle(name, cfg, cloud, log, _args):
                         % (p_constants.conf_path))
             util.write_file(p_constants.conf_path, puppet_config.stringify())
 
+    if 'csr_attributes' in puppet_cfg:
+        util.write_file(p_constants.csr_attributes_path,
+                        yaml.dump(puppet_cfg['csr_attributes']))
+
     # Set it up so it autostarts
     _autostart_puppet(log)
 
diff --git a/tests/unittests/test_handler/test_handler_puppet.py b/tests/unittests/test_handler/test_handler_puppet.py
index 0b6e3b5..1494177 100644
--- a/tests/unittests/test_handler/test_handler_puppet.py
+++ b/tests/unittests/test_handler/test_handler_puppet.py
@@ -6,6 +6,7 @@ from cloudinit import (distros, helpers, cloud, util)
 from cloudinit.tests.helpers import CiTestCase, mock
 
 import logging
+import textwrap
 
 
 LOG = logging.getLogger(__name__)
@@ -64,6 +65,7 @@ class TestPuppetHandle(CiTestCase):
         super(TestPuppetHandle, self).setUp()
         self.new_root = self.tmp_dir()
         self.conf = self.tmp_path('puppet.conf')
+        self.csr_attributes_path = self.tmp_path('csr_attributes.yaml')
 
     def _get_cloud(self, distro):
         paths = helpers.Paths({'templates_dir': self.new_root})
@@ -140,3 +142,35 @@ class TestPuppetHandle(CiTestCase):
         content = util.load_file(self.conf)
         expected = '[agent]\nserver = puppetmaster.example.org\nother = 3\n\n'
         self.assertEqual(expected, content)
+
+    @mock.patch('cloudinit.config.cc_puppet.util.subp')
+    def test_handler_puppet_writes_csr_attributes_file(self, m_subp, m_auto):
+        """When csr_attributes is provided
+            creates file in PUPPET_CSR_ATTRIBUTES_PATH."""
+        mycloud = self._get_cloud('ubuntu')
+        mycloud.distro = mock.MagicMock()
+        cfg = {
+            'puppet': {
+              'csr_attributes': {
+                'custom_attributes': {
+                  '1.2.840.113549.1.9.7': '342thbjkt82094y0ut'
+                                          'hhor289jnqthpc2290'},
+                'extension_requests': {
+                  'pp_uuid': 'ED803750-E3C7-44F5-BB08-41A04433FE2E',
+                  'pp_image_name': 'my_ami_image',
+                  'pp_preshared_key': '342thbjkt82094y0uthhor289jnqthpc2290'}
+                }}}
+        csr_attributes = 'cloudinit.config.cc_puppet.' \
+                         'PUPPET_CSR_ATTRIBUTES_PATH'
+        with mock.patch(csr_attributes, self.csr_attributes_path):
+            cc_puppet.handle('notimportant', cfg, mycloud, LOG, None)
+        content = util.load_file(self.csr_attributes_path)
+        expected = textwrap.dedent("""\
+            custom_attributes:
+              1.2.840.113549.1.9.7: 342thbjkt82094y0uthhor289jnqthpc2290
+            extension_requests:
+              pp_image_name: my_ami_image
+              pp_preshared_key: 342thbjkt82094y0uthhor289jnqthpc2290
+              pp_uuid: ED803750-E3C7-44F5-BB08-41A04433FE2E
+            """)
+        self.assertEqual(expected, content)

Follow ups