summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/appendix/guides/sslca_howto.txt183
-rw-r--r--doc/man/bcfg2.conf.txt16
-rw-r--r--doc/server/info.txt3
-rw-r--r--doc/server/plugins/generators/cfg.txt223
-rw-r--r--doc/server/plugins/generators/sslca.txt361
-rw-r--r--doc/server/plugins/structures/bundler/bcfg2.txt3
-rw-r--r--doc/server/xml-common.txt108
-rw-r--r--man/bcfg2.conf.51
-rw-r--r--schemas/sslca-cert.xsd4
-rw-r--r--schemas/sslca-key.xsd23
-rw-r--r--src/lib/Bcfg2/Server/Lint/Validate.py5
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/CfgAuthorizedKeysGenerator.py4
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/CfgPrivateKeyCreator.py77
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/CfgPublicKeyCreator.py6
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/CfgSSLCACertCreator.py255
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/CfgSSLCAKeyCreator.py36
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py162
-rw-r--r--src/lib/Bcfg2/Server/Plugins/SSLCA.py387
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py6
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgPrivateKeyCreator.py154
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py100
-rw-r--r--tools/upgrade/1.4/README6
-rwxr-xr-xtools/upgrade/1.4/migrate_sslca.py44
23 files changed, 1082 insertions, 1085 deletions
diff --git a/doc/appendix/guides/sslca_howto.txt b/doc/appendix/guides/sslca_howto.txt
new file mode 100644
index 000000000..9c939dcd3
--- /dev/null
+++ b/doc/appendix/guides/sslca_howto.txt
@@ -0,0 +1,183 @@
+.. -*- mode: rst -*-
+
+.. _appendix-guides-sslca_howto:
+
+====================================
+ Automated Bcfg2 SSL Authentication
+====================================
+
+This how-to describes one possible scenario for automating SSL
+certificate generation and distribution for bcfg2 client/server
+communication using the :ref:`SSL CA feature
+<server-plugins-generators-cfg-ssl-certificates>` of
+:ref:`server-plugins-generators-cfg`. The process involves configuring
+a certificate authority (CA), generating the CA cert and key pair,
+configuring the Cfg SSL CA feature and a Bundle to use the generated
+certs to authenticate the Bcfg2 client and server.
+
+OpenSSL CA
+==========
+
+If you already have a SSL CA available you can skip this section,
+otherwise you can easily build one on the server using openssl. The
+paths should be adjusted to suite your preferences.
+
+#. Prepare the directories and files::
+
+ mkdir -p /etc/pki/CA/newcerts
+ mkdir /etc/pki/CA/crl
+ echo '01' > /etc/pki/CA/serial
+ touch /etc/pki/CA/index.txt
+ touch /etc/pki/CA/crlnumber
+
+#. Edit the ``openssl.cnf`` config file, and in the **[ CA_default ]**
+ section adjust the following parameters::
+
+ dir = /etc/pki # Where everything is kept
+ certs = /etc/pki/CA/certs # Where the issued certs are kept
+ database = /etc/pki/CA/index.txt # database index file.
+ new_certs_dir = /etc/pki/CA/newcerts # default place for new certs.
+ certificate = /etc/pki/CA/certs/bcfg2ca.crt # The CA certificate
+ serial = /etc/pki/CA/serial # The current serial number
+ crl_dir = /etc/pki/CA/crl # Where the issued crl are kept
+ crlnumber = /etc/pki/CA/crlnumber # the current crl number
+ crl = /etc/pki/CA/crl.pem # The current CRL
+ private_key = /etc/pki/CA/private/bcfg2ca.key # The private key
+
+#. Create the CA root certificate and key pair. You'll be asked to
+ supply a passphrase, and some organizational info. The most
+ important bit is **Common Name** which you should set to be the
+ hostname of your bcfg2 server that your clients will see when doing
+ a reverse DNS query on it's ip address.::
+
+ openssl req -new -x509 -extensions v3_ca -keyout bcfg2ca.key \
+ -out bcfg2ca.crt -days 3650
+
+#. Move the generated cert and key to the locations specified in
+ ``openssl.cnf``::
+
+ mv bcfg2ca.key /etc/pki/CA/private/
+ mv bcfg2ca.crt /etc/pki/CA/certs/
+
+Your self-signing CA is now ready to use.
+
+Bcfg2
+=====
+
+SSL CA Feature
+--------------
+
+The SSL CA feature of Cfg was not designed specifically to manage
+Bcfg2 client/server communication, though it is certainly able to
+provide certificate generation and management services for that
+purpose. You'll need to configure Cfg as described in
+:ref:`server-plugins-generators-cfg-ssl-certificates`, including:
+
+* Configuring a ``[sslca_default]`` section in ``bcfg2.conf`` that
+ describes the CA you created above;
+* Creating ``Cfg/etc/pki/tls/certs/bcfg2client.crt/sslcert.xml`` and
+ ``Cfg/etc/pki/tls/private/bcfg2client.key/sslkey.xml`` to describe
+ the key and cert you want generated.
+
+In general, the defaults in ``sslcert.xml`` and ``sslkey.xml`` should
+be fine, so those files can look like this:
+
+``Cfg/etc/pki/tls/certs/bcfg2client.crt/sslcert.xml``:
+
+.. code-block:: xml
+
+ <CertInfo>
+ <Cert key="/etc/pki/tls/private/bcfg2client.key"/>
+ </CertInfo>
+
+``Cfg/etc/pki/tls/private/bcfg2client.key/sslkey.xml``:
+
+.. code-block:: xml
+
+ <KeyInfo/>
+
+Client Bundle
+-------------
+
+To automate the process of generating and distributing certs to the
+clients we need define at least the cert and key paths created by Cfg,
+as well as the CA certificate path in a Bundle. For example:
+
+.. code-block:: xml
+
+ <Path name='/etc/pki/tls/certs/bcfg2ca.crt'/>
+ <Path name='/etc/pki/tls/bcfg2client.crt'/>
+ <Path name='/etc/pki/tls/private/bcfg2client.key'/>
+
+Here's a more complete example bcfg2-client bundle:
+
+.. code-block:: xml
+
+ <Bundle>
+ <Path name='/etc/bcfg2.conf'/>
+ <Path name='/etc/cron.d/bcfg2-client'/>
+ <Package name='bcfg2'/>
+ <Service name='bcfg2'/>
+ <Group name='rpm'>
+ <Path name='/etc/sysconfig/bcfg2'/>
+ <Path name='/etc/pki/tls/certs/bcfg2ca.crt'/>
+ <Path name='/etc/pki/tls/certs/bcfg2client.crt'/>
+ <Path name='/etc/pki/tls/private/bcfg2client.key'/>
+ </Group>
+ <Group name='deb'>
+ <Path name='/etc/default/bcfg2' altsrc='/etc/sysconfig/bcfg2'/>
+ <Path name='/etc/ssl/certs/bcfg2ca.crt' altsrc='/etc/pki/tls/certs/bcfg2ca.crt'/>
+ <Path name='/etc/ssl/certs/bcfg2client.crt' altsrc='/etc/pki/tls/certs/bcfg2client.crt'/>
+ <Path name='/etc/ssl/private/bcfg2client.key' altsrc='/etc/pki/tls/private/bcfg2client.key'/>
+ </Group>
+ </Bundle>
+
+The ``bcfg2.conf`` client config needs at least 5 parameters set for
+SSL auth.
+
+#. ``key`` : This is the host specific key that Cfg will create.
+#. ``certificate`` : This is the host specific cert that Cfg will
+ create.
+#. ``ca`` : This is a copy of your CA certificate. Not generated by
+ Cfg.
+#. ``password`` : Set to arbitrary string when using certificate
+ auth. This also *shouldn't* be required. See:
+ http://trac.mcs.anl.gov/projects/bcfg2/ticket/1019
+
+Here's what a functional **[communication]** section in a
+``bcfg2.conf`` genshi template for clients might look like.::
+
+ [communication]
+ protocol = xmlrpc/ssl
+ {% if metadata.uuid != None %}\
+ user = ${metadata.uuid}
+ {% end %}\
+ password = DUMMYPASSWORDFORCERTAUTH
+ {% choose %}\
+ {% when 'rpm' in metadata.groups %}\
+ certificate = /etc/pki/tls/certs/bcfg2client.crt
+ key = /etc/pki/tls/private/bcfg2client.key
+ ca = /etc/pki/tls/certs/bcfg2ca.crt
+ {% end %}\
+ {% when 'deb' in metadata.groups %}\
+ certificate = /etc/ssl/certs/bcfg2client.crt
+ key = /etc/ssl/private/bcfg2client.key
+ ca = /etc/ssl/certs/bcfg2ca.crt
+ {% end %}\
+ {% end %}\
+
+As a client will not be able to authenticate with certificates it does
+not yet posses we need to overcome the chicken and egg scenario the
+first time we try to connect such a client to the server. We can do so
+using password based auth to bootstrap the client manually specifying
+all the relevant auth parameters like so::
+
+ bcfg2 -qv -S https://fqdn.of.bcfg2-server:6789 -u fqdn.of.client \
+ -x SUPER_SECRET_PASSWORD
+
+If all goes well the client should recieve a freshly generated key and
+cert and you should be able to run ``bcfg2`` again without specifying
+the connection parameters.
+
+If you do run into problems you may want to review
+:ref:`appendix-guides-authentication`.
diff --git a/doc/man/bcfg2.conf.txt b/doc/man/bcfg2.conf.txt
index 24bcb5142..f5612e08f 100644
--- a/doc/man/bcfg2.conf.txt
+++ b/doc/man/bcfg2.conf.txt
@@ -107,7 +107,6 @@ plugins
SEModules
ServiceCompat
SSHbase
- SSLCA
Svn
TemplateHelper
Trigger
@@ -364,12 +363,6 @@ The SSHbase generator plugin manages ssh host keys (both v1 and v2) for
hosts. It also manages the ssh_known_hosts file. It can integrate host
keys from other management domains and similarly export its keys.
-SSLCA Plugin
-++++++++++++
-
-The SSLCA plugin is designed to handle creation of SSL privatekeys and
-certificates on request.
-
Svn Plugin
++++++++++
@@ -610,11 +603,12 @@ the configuration file.
running in paranoid mode. Only the most recent versions of these
copies will be kept.
-SSLCA options
--------------
+SSL CA options
+--------------
-These options are necessary to configure the SSLCA plugin and can be
-found in the **[sslca_default]** section of the configuration file.
+These options are necessary to configure the SSL CA feature of the Cfg
+plugin and can be found in the **[sslca_default]** section of the
+configuration file.
config
Specifies the location of the openssl configuration file for
diff --git a/doc/server/info.txt b/doc/server/info.txt
index 2c50f0031..8342e1cee 100644
--- a/doc/server/info.txt
+++ b/doc/server/info.txt
@@ -7,8 +7,7 @@ info.xml
========
Various file properties for entries served by most generator plugins,
-including :ref:`server-plugins-generators-cfg`,
-:ref:`server-plugins-generators-sslca`, and
+including :ref:`server-plugins-generators-cfg` and
:ref:`server-plugins-generators-sshbase`, are controlled through the
use of ``info.xml`` files.
diff --git a/doc/server/plugins/generators/cfg.txt b/doc/server/plugins/generators/cfg.txt
index 4d35a5970..56804db99 100644
--- a/doc/server/plugins/generators/cfg.txt
+++ b/doc/server/plugins/generators/cfg.txt
@@ -413,7 +413,7 @@ See :ref:`server-encryption` for more details on encryption in Bcfg2
in general.
``pubkey.xml``
-~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~
``pubkey.xml`` only ever contains a single line:
@@ -560,29 +560,163 @@ Example
Hopefully, the performance concerns can be resolved in a future
release and these features can be added.
+.. _server-plugins-generators-sslca:
+.. _server-plugins-generators-cfg-ssl-certificates:
+
+SSL Keys and Certificates
+=========================
+
+Cfg can also create SSL keys and certs on the fly, and store the
+generated data in the repo so that subsequent requests do not result
+in repeated key/cert recreation. In the event that a new key or cert
+is needed, the old file can simply be removed from the
+repository, and the next time that host checks in, a new file will be
+created. If that file happens to be the key, any dependent
+certificates will also be regenerated.
+
+See also :ref:`appendix-guides-sslca_howto` for a detailed example
+that uses the SSL key management feature to automate Bcfg2 certificate
+authentication.
+
+Getting started
+---------------
+
+In order to use the SSL certificate generation feature, you must first
+have at least one CA configured on your system. For details on
+setting up your own OpenSSL based CA, please see
+http://www.openssl.org/docs/apps/ca.html for details of the suggested
+directory layout and configuration directives.
+
+For SSL cert generation to work, the openssl.cnf (or other
+configuration file) for that CA must contain full (not relative)
+paths.
+
+#. Add a section to your ``/etc/bcfg2.conf`` called ``sslca_foo``,
+ replacing foo with the name you wish to give your CA so you can
+ reference it in certificate definitions. (If you only have one CA,
+ you can name it ``sslca_default``, and it will be the default CA
+ for all other operations.)
+
+#. Under that section, add a ``config`` option that gives the location
+ of the ``openssl.cnf`` file for your CA.
+
+#. If necessary, add a ``passphrase`` option containing the passphrase
+ for the CA's private key. If no passphrase is entry exists, it is
+ assumed that the private key is stored unencrypted.
+
+#. Optionally, add a ``chaincert`` option that points to the location
+ of your ssl chaining certificate. This is used when preexisting
+ certificate hostfiles are found, so that they can be validated and
+ only regenerated if they no longer meet the specification. If
+ you're using a self signing CA this would be the CA cert that you
+ generated. If the chain cert is a root CA cert (e.g., if it is a
+ self-signing CA), also add an entry ``root_ca = true``. If
+ ``chaincert`` is omitted, certificate verification will not be
+ performed.
+
+#. Once all this is done, you should have a section in your
+ ``/etc/bcfg2.conf`` that looks similar to the following::
+
+ [sslca_default]
+ config = /etc/pki/CA/openssl.cnf
+ passphrase = youReallyThinkIdShareThis?
+ chaincert = /etc/pki/CA/chaincert.crt
+ root_ca = true
+
+#. You are now ready to create key and certificate definitions. For
+ this example we'll assume you've added Path entries for the key,
+ ``/etc/pki/tls/private/localhost.key``, and the certificate,
+ ``/etc/pki/tls/certs/localhost.crt`` to a bundle.
+
+#. Within the ``Cfg/etc/pki/tls/private/localhost.key`` directory,
+ create a `sslkey.xml`_ file containing the following:
+
+ .. code-block:: xml
+
+ <KeyInfo/>
+
+#. This will cause the generation of an SSL key when a client requests
+ that Path. (By default, it will be a 2048-bit RSA key; see
+ `sslkey.xml`_ for details on how to change the key type and size.)
+
+#. Similarly, create `sslcert.xml`_ in
+ ``Cfg/etc/pki/tls/certs/localhost.cfg/``, containing the following:
+
+ .. code-block:: xml
+
+ <CertInfo>
+ <Cert key="/etc/pki/tls/private/localhost.key" ca="foo"/>
+ </CertInfo>
+
+#. When a client requests the cert path, a certificate will be
+ generated using the key hostfile at the specified key location,
+ using the CA matching the ``ca`` attribute. ie. ``ca="foo"`` will
+ match ``[sslca_default]`` in your ``/etc/bcfg2.conf``
+
+The :ref:`Bcfg2 bundle example
+<server-plugins-structures-bundler-bcfg2-server>` contains entries to
+automate the process of setting up a CA.
+
Configuration
-------------
-In addition to ``privkey.xml`` and ``authorized_keys.xml``, described
-above, the behavior of the SSH key generation feature can be
-influenced by several options in the ``[sshkeys]`` section of
-``bcfg2.conf``:
+``bcfg2.conf``
+~~~~~~~~~~~~~~
-+----------------+---------------------------------------------------------+-----------------------+------------+
-| Option | Description | Values | Default |
-+================+=========================================================+=======================+============+
-| ``passphrase`` | Use the named passphrase to encrypt private keys on the | String | None |
-| | filesystem. The passphrase must be defined in the | | |
-| | ``[encryption]`` section. See :ref:`server-encryption` | | |
-| | for more details on encryption in Bcfg2 in general. | | |
-+----------------+---------------------------------------------------------+-----------------------+------------+
-| ``category`` | Generate keys specific to groups in the given category. | String | None |
-| | It is best to pick a category that all clients have a | | |
-| | group from. | | |
-+----------------+---------------------------------------------------------+-----------------------+------------+
+In ``bcfg2.conf``, you must declare your CA(s) in ``[sslca_<name>]``
+sections. At least one is required. Valid options are detailed
+below, in `Cfg Configuration`_.
+
+Only the ``config`` option is required; i.e., the simplest possible CA
+section is::
+
+ [sslca_default]
+ config = /etc/pki/CA/openssl.cnf
+
+``sslcert.xml``
+~~~~~~~~~~~~~~~
+
+.. xml:schema:: sslca-cert.xsd
+ :linktotype:
+ :inlinetypes: CertType
+
+Example
+^^^^^^^
+
+.. code-block:: xml
+
+ <CertInfo>
+ <subjectAltName>test.example.com</subjectAltName>
+ <Group name="apache">
+ <Cert key="/etc/pki/tls/private/foo.key" days="730"/>
+ </Group>
+ <Group name="nginx">
+ <Cert key="/etc/pki/tls/private/foo.key" days="730"
+ append_chain="true"/>
+ </Group>
+ </CertInfo>
+
+``sslkey.xml``
+~~~~~~~~~~~~~~
+
+.. xml:schema:: sslca-key.xsd
+ :linktotype:
+ :inlinetypes: KeyType
+
+Example
+^^^^^^^
+
+.. code-block:: xml
+
+ <KeyInfo>
+ <Group name="fast">
+ <Key type="rsa" bits="1024"/>
+ </Group>
+ <Group name="secure">
+ <Key type="rsa" bits="4096"/>
+ </Group>
+ </KeyInfo>
-See :ref:`server-encryption` for more details on encryption in Bcfg2
-in general.
.. _server-plugins-generators-cfg-validation:
@@ -637,3 +771,54 @@ File permissions
File permissions for entries handled by Cfg are controlled via the use
of :ref:`server-info` files. Note that you **cannot** use both a
Permissions entry and a Path entry to handle the same file.
+
+Cfg Configuration
+=================
+
+The behavior of many bits of the Cfg plugin can be configured in
+``bcfg2.conf`` with the following options.
+
+In addition to ``privkey.xml`` and ``authorized_keys.xml``, described
+above, the behavior of the SSH key generation feature can be
+influenced by several options in the ``[sshkeys]`` section of
+``bcfg2.conf``:
+
++-------------+----------------+---------------------------------------------------------+-----------------------+------------+
+| Section | Option | Description | Values | Default |
++================+=========================================================+=======================+============+
+| ``cfg`` | ``passphrase`` | Use the named passphrase to encrypt created data on the | String | None |
+| | | filesystem. (E.g., SSH and SSL keys.) The passphrase | | |
+| | | must be defined in the ``[encryption]`` section. | | |
++-------------+----------------+---------------------------------------------------------+-----------------------+------------+
+| ``cfg`` | ``category`` | Generate data (e.g., SSH keys, SSL keys and certs) | String | None |
+| | | specific to groups in the given category. It is best to | | |
+| | | pick a category that all clients have a group from. | | |
++-------------+----------------+---------------------------------------------------------+-----------------------+------------+
+| ``cfg`` | ``validation`` | Whether or not to perform `Content Validation`_ | Boolean | True |
+| | | specific to groups in the given category. It is best to | | |
+| | | pick a category that all clients have a group from. | | |
++-------------+----------------+---------------------------------------------------------+-----------------------+------------+
+| ``sshkeys`` | ``passphrase`` | Override the global Cfg passphrase with a specific | String | None |
+| | | passphrase for encrypting created SSH private keys. | | |
++-------------+----------------+---------------------------------------------------------+-----------------------+------------+
+| ``sshkeys`` | ``category`` | Override the global Cfg category with a specific | String | None |
+| | | category for created SSH keys. | | |
++-------------+----------------+---------------------------------------------------------+-----------------------+------------+
+| ``sslca`` | ``passphrase`` | Override the global Cfg passphrase with a specific | String | None |
+| | | passphrase for encrypting created SSL keys. | | |
++-------------+----------------+---------------------------------------------------------+-----------------------+------------+
+| ``sslca`` | ``category`` | Override the global Cfg category with a specific | String | None |
+| | | category for created SSL keys and certs. | | |
++-------------+----------------+---------------------------------------------------------+-----------------------+------------+
+| ``sslca_*`` | ``config`` | Path to the openssl config for the CA | String | None |
++-------------+----------------+---------------------------------------------------------+-----------------------+------------+
+| ``sslca_*`` | ``passphrase`` | Passphrase for the CA private key | String | None |
++-------------+----------------+---------------------------------------------------------+-----------------------+------------+
+| ``sslca_*`` | ``chaincert`` | Path to the SSL chaining certificate for verification | String | None |
++-------------+----------------+---------------------------------------------------------+-----------------------+------------+
+| ``sslca_*`` | ``root_ca`` | Whether or not ``<chaincert>`` is a root CA (as | Boolean | False |
+| | | opposed to an intermediate cert) | | |
++-------------+----------------+---------------------------------------------------------+-----------------------+------------+
+
+See :ref:`server-encryption` for more details on encryption in Bcfg2
+in general.
diff --git a/doc/server/plugins/generators/sslca.txt b/doc/server/plugins/generators/sslca.txt
deleted file mode 100644
index 2a7e3ecad..000000000
--- a/doc/server/plugins/generators/sslca.txt
+++ /dev/null
@@ -1,361 +0,0 @@
-.. -*- mode: rst -*-
-
-.. _server-plugins-generators-sslca:
-
-=====
-SSLCA
-=====
-
-SSLCA is a generator plugin designed to handle creation of SSL private
-keys and certificates on request.
-
-Borrowing ideas from :ref:`server-plugins-generators-cfg-genshi` and
-the :ref:`server-plugins-generators-sshbase` plugin, SSLCA automates
-the generation of SSL certificates by allowing you to specify key and
-certificate definitions. Then, when a client requests a Path that
-contains such a definition within the SSLCA repository, the matching
-key/cert is generated, and stored in a hostfile in the repo so that
-subsequent requests do not result in repeated key/cert recreation. In
-the event that a new key or cert is needed, the offending hostfile can
-simply be removed from the repository, and the next time that host
-checks in, a new file will be created. If that file happens to be the
-key, any dependent certificates will also be regenerated.
-
-.. _getting-started:
-
-Getting started
-===============
-
-In order to use SSLCA, you must first have at least one CA configured
-on your system. For details on setting up your own OpenSSL based CA,
-please see http://www.openssl.org/docs/apps/ca.html for details of the
-suggested directory layout and configuration directives.
-
-For SSLCA to work, the openssl.cnf (or other configuration file) for
-that CA must contain full (not relative) paths.
-
-#. Add SSLCA to the **plugins** line in ``/etc/bcfg2.conf`` and
- restart the server -- This enabled the SSLCA plugin on the Bcfg2
- server.
-
-#. Add a section to your ``/etc/bcfg2.conf`` called ``sslca_foo``,
- replacing foo with the name you wish to give your CA so you can
- reference it in certificate definitions.
-
-#. Under that section, add an entry for ``config`` that gives the
- location of the openssl configuration file for your CA.
-
-#. If necessary, add an entry for ``passphrase`` containing the
- passphrase for the CA's private key. We store this in
- ``/etc/bcfg2.conf`` as the permissions on that file should have it
- only readable by the bcfg2 user. If no passphrase is entry exists,
- it is assumed that the private key is stored unencrypted.
-
-#. Optionally, Add an entry ``chaincert`` that points to the location
- of your ssl chaining certificate. This is used when preexisting
- certifcate hostfiles are found, so that they can be validated and
- only regenerated if they no longer meet the specification. If
- you're using a self signing CA this would be the CA cert that you
- generated. If the chain cert is a root CA cert (e.g., if it is a
- self-signing CA), also add an entry ``root_ca = true``. If
- ``chaincert`` is omitted, certificate verification will not be
- performed.
-
-#. Once all this is done, you should have a section in your
- ``/etc/bcfg2.conf`` that looks similar to the following::
-
- [sslca_default]
- config = /etc/pki/CA/openssl.cnf
- passphrase = youReallyThinkIdShareThis?
- chaincert = /etc/pki/CA/chaincert.crt
- root_ca = true
-
-#. You are now ready to create key and certificate definitions. For
- this example we'll assume you've added Path entries for the key,
- ``/etc/pki/tls/private/localhost.key``, and the certificate,
- ``/etc/pki/tls/certs/localhost.crt`` to a bundle or base.
-
-#. Defining a key or certificate is similar to defining a Cfg file.
- Under your Bcfg2's ``SSLCA/`` directory, create the directory
- structure to match the path to your key. In this case this would be
- something like
- ``/var/lib/bcfg2/SSLCA/etc/pki/tls/private/localhost.key``.
-
-#. Within that directory, create a `key.xml`_ file containing the
- following:
-
- .. code-block:: xml
-
- <KeyInfo>
- <Key type="rsa" bits="2048" />
- </KeyInfo>
-
-#. This will cause the generation of an 2048 bit RSA key when a client
- requests that Path. Alternatively you can specify ``dsa`` as the
- keytype, or a different number of bits.
-
-#. Similarly, create the matching directory structure for the
- certificate path, and a `cert.xml`_ containing the following:
-
- .. code-block:: xml
-
- <CertInfo>
- <Cert format="pem" key="/etc/pki/tls/private/localhost.key"
- ca="default" days="365" c="US" l="New York" st="New York"
- o="Your Company Name" />
- </CertInfo>
-
-#. When a client requests the cert path, a certificate will be
- generated using the key hostfile at the specified key location,
- using the CA matching the ca attribute. ie. ca="default" will match
- [sslca_default] in your ``/etc/bcfg2.conf``
-
-.. _sslca-configuration:
-
-Configuration
-=============
-
-bcfg2.conf
-----------
-
-``bcfg2.conf`` contains miscellaneous configuration options for the
-SSLCA plugin. These are described in some detail above in
-`getting-started`, but are also enumerated here as a reference. Any
-booleans in the config file accept the values "1", "yes", "true", and
-"on" for True, and "0", "no", "false", and "off" for False.
-
-Each directive below should appear at most once in each
-``[sslca_<name>]`` section. The following directives are understood:
-
-+--------------+------------------------------------------+---------+---------+
-| Name | Description | Values | Default |
-+==============+==========================================+=========+=========+
-| config | Path to the openssl config for the CA | String | None |
-+--------------+------------------------------------------+---------+---------+
-| passphrase | Passphrase for the CA private key | String | None |
-+--------------+------------------------------------------+---------+---------+
-| chaincert | Path to the SSL chaining certificate for | String | None |
-| | verification | | |
-+--------------+------------------------------------------+---------+---------+
-| root_ca | Whether or not ``<chaincert>`` is a root | Boolean | false |
-| | CA (as opposed to an intermediate cert) | | |
-+--------------+------------------------------------------+---------+---------+
-
-Only ``config`` is required.
-
-cert.xml
---------
-
-.. xml:schema:: sslca-cert.xsd
- :linktotype:
- :inlinetypes: CertType
-
-Example
-^^^^^^^
-
-.. code-block:: xml
-
- <CertInfo>
- <subjectAltName>test.example.com</subjectAltName>
- <Group name="apache">
- <Cert key="/etc/pki/tls/private/foo.key" days="730"/>
- </Group>
- <Group name="nginx">
- <Cert key="/etc/pki/tls/private/foo.key" days="730"
- append_chain="true"/>
- </Group>
- </CertInfo>
-
-key.xml
--------
-
-.. xml:schema:: sslca-key.xsd
- :linktotype:
- :inlinetypes: KeyType
-
-Example
-^^^^^^^
-
-.. code-block:: xml
-
- <KeyInfo>
- <Group name="fast">
- <Key type="rsa" bits="1024"/>
- </Group>
- <Group name="secure">
- <Key type="rsa" bits="4096"/>
- </Group>
- </KeyInfo>
-
-Automated Bcfg2 SSL Authentication
-==================================
-
-This section describes one possible scenario for automating ssl
-certificate generation and distribution for bcfg2 client/server
-communication using SSLCA. The process involves configuring a
-certificate authority (CA), generating the CA cert and key pair,
-configuring the bcfg2 SSLCA plugin and a Bundle to use the SSLCA
-generated certs to authenticate the bcfg2 client and server.
-
-OpenSSL CA
-----------
-
-If you already have a SSL CA available you can skip this section,
-otherwise you can easily build one on the server using openssl. The
-paths should be adjusted to suite your preferences.
-
-#. Prepare the directories and files::
-
- mkdir -p /etc/pki/CA/newcerts
- mkdir /etc/pki/CA/crl
- echo '01' > /etc/pki/CA/serial
- touch /etc/pki/CA/index.txt
- touch /etc/pki/CA/crlnumber
-
-#. Edit the ``openssl.cnf`` config file, and in the **[ CA_default ]**
- section adjust the following parameters::
-
- dir = /etc/pki # Where everything is kept
- certs = /etc/pki/CA/certs # Where the issued certs are kept
- database = /etc/pki/CA/index.txt # database index file.
- new_certs_dir = /etc/pki/CA/newcerts # default place for new certs.
- certificate = /etc/pki/CA/certs/bcfg2ca.crt # The CA certificate
- serial = /etc/pki/CA/serial # The current serial number
- crl_dir = /etc/pki/CA/crl # Where the issued crl are kept
- crlnumber = /etc/pki/CA/crlnumber # the current crl number
- crl = /etc/pki/CA/crl.pem # The current CRL
- private_key = /etc/pki/CA/private/bcfg2ca.key # The private key
-
-#. Create the CA root certificate and key pair. You'll be asked to
- supply a passphrase, and some organizational info. The most
- important bit is **Common Name** which you should set to be the
- hostname of your bcfg2 server that your clients will see when doing
- a reverse DNS query on it's ip address.::
-
- openssl req -new -x509 -extensions v3_ca -keyout bcfg2ca.key \
- -out bcfg2ca.crt -days 3650
-
-#. Move the generated cert and key to the locations specified in
- ``openssl.cnf``::
-
- mv bcfg2ca.key /etc/pki/CA/private/
- mv bcfg2ca.crt /etc/pki/CA/certs/
-
-Your self-signing CA is now ready to use.
-
-Bcfg2
------
-
-SSLCA
-^^^^^
-
-The SSLCA plugin was not designed specifically to manage bcfg2
-client/server communication though it is certainly able to provide
-certificate generation and management services for that
-purpose. You'll need to configure the **SSLCA** plugin to serve the
-key, and certificate paths that we will define later in our client's
-``bcfg2.conf`` file.
-
-The rest of these instructions will assume that you've configured the
-**SSLCA** plugin as described above and that the files
-``SSLCA/etc/pki/tls/certs/bcfg2client.crt/cert.xml`` and
-``SSLCA/etc/pki/tls/private/bcfg2client.key/key.xml`` represent the
-cert and key paths you want generated for SSL auth.
-
-Client Bundle
-^^^^^^^^^^^^^
-
-To automate the process of generating and distributing certs to the
-clients we need define at least the Cert and Key paths served by the
-SSLCA plugin, as well as the ca certificate path in a Bundle. For
-example:
-
-.. code-block:: xml
-
- <Path name='/etc/pki/tls/certs/bcfg2ca.crt'/>
- <Path name='/etc/pki/tls/bcfg2client.crt'/>
- <Path name='/etc/pki/tls/private/bcfg2client.key'/>
-
-Here's a more complete example bcfg2-client bundle:
-
-.. code-block:: xml
-
- <Bundle>
- <Path name='/etc/bcfg2.conf'/>
- <Path name='/etc/cron.d/bcfg2-client'/>
- <Package name='bcfg2'/>
- <Service name='bcfg2'/>
- <Group name='rpm'>
- <Path name='/etc/sysconfig/bcfg2'/>
- <Path name='/etc/pki/tls/certs/bcfg2ca.crt'/>
- <Path name='/etc/pki/tls/certs/bcfg2client.crt'/>
- <Path name='/etc/pki/tls/private/bcfg2client.key'/>
- </Group>
- <Group name='deb'>
- <Path name='/etc/default/bcfg2' altsrc='/etc/sysconfig/bcfg2'/>
- <Path name='/etc/ssl/certs/bcfg2ca.crt' altsrc='/etc/pki/tls/certs/bcfg2ca.crt'/>
- <Path name='/etc/ssl/certs/bcfg2client.crt' altsrc='/etc/pki/tls/certs/bcfg2client.crt'/>
- <Path name='/etc/ssl/private/bcfg2client.key' altsrc='/etc/pki/tls/private/bcfg2client.key'/>
- </Group>
- </Bundle>
-
-In the above example we told Bcfg2 that it also needs to serve
-``/etc/bcfg2.conf``. This is optional but convenient.
-
-The ``bcfg2.conf`` client config needs at least 5 parameters set for
-SSL auth.
-
-#. ``key`` : This is the host specific key that SSLCA will generate.
-#. ``certificate`` : This is the host specific cert that SSLCA will
- generate.
-#. ``ca`` : This is a copy of your CA certificate. Not generated by
- SSLCA.
-#. ``user`` : Usually set to fqdn of client. This *shouldn't* be
- required but is as of 1.3.0. See:
- http://trac.mcs.anl.gov/projects/bcfg2/ticket/1019
-#. ``password`` : Set to arbitrary string when using certificate
- auth. This also *shouldn't* be required. See:
- http://trac.mcs.anl.gov/projects/bcfg2/ticket/1019
-
-Here's what a functional **[communication]** section in a
-``bcfg2.conf`` genshi template for clients might look like.::
-
- [communication]
- protocol = xmlrpc/ssl
- {% if metadata.uuid != None %}\
- user = ${metadata.uuid}
- {% end %}\
- password = DUMMYPASSWORDFORCERTAUTH
- {% choose %}\
- {% when 'rpm' in metadata.groups %}\
- certificate = /etc/pki/tls/certs/bcfg2client.crt
- key = /etc/pki/tls/private/bcfg2client.key
- ca = /etc/pki/tls/certs/bcfg2ca.crt
- {% end %}\
- {% when 'deb' in metadata.groups %}\
- certificate = /etc/ssl/certs/bcfg2client.crt
- key = /etc/ssl/private/bcfg2client.key
- ca = /etc/ssl/certs/bcfg2ca.crt
- {% end %}\
- {% end %}\
-
-As a client will not be able to authenticate with certificates it does
-not yet posses we need to overcome the chicken and egg scenario the
-first time we try to connect such a client to the server. We can do so
-using password based auth to boot strap the client manually specifying
-all the relevant auth parameters like so::
-
- bcfg2 -qv -S https://fqdn.of.bcfg2-server:6789 -u fqdn.of.client \
- -x SUPER_SECRET_PASSWORD
-
-If all goes well the client should recieve a freshly generated key and
-cert and you should be able to run ``bcfg2`` again without specifying
-the connection parameters.
-
-If you do run into problems you may want to review
-:ref:`appendix-guides-authentication`.
-
-TODO
-====
-
-#. Add generation of pkcs12 format certs
diff --git a/doc/server/plugins/structures/bundler/bcfg2.txt b/doc/server/plugins/structures/bundler/bcfg2.txt
index 7465f15cb..0fd0a3fdf 100644
--- a/doc/server/plugins/structures/bundler/bcfg2.txt
+++ b/doc/server/plugins/structures/bundler/bcfg2.txt
@@ -52,7 +52,7 @@ entries between Bundler and Rules.
<BoundPOSIXUser name='bcfg2' shell='/sbin/nologin' gecos='Bcfg2 User'/>
<Path name="/home/bcfg2/.ssh/id_rsa"/>
- <!-- SSLCA setup -->
+ <!-- SSL CA setup -->
<BoundPath name="/etc/pki/CA" type="directory" important="true"
owner="bcfg2" group="bcfg2" mode="755"/>
<BoundPath name="/etc/pki/CA/crl" type="directory" owner="bcfg2"
@@ -85,4 +85,3 @@ entries between Bundler and Rules.
name="create-CA-crlnumber" timing="post" when="always" status="check"
command="[ -e /etc/pki/CA/crlnumber ] || touch /etc/pki/CA/crlnumber"/>
</Bundle>
-
diff --git a/doc/server/xml-common.txt b/doc/server/xml-common.txt
index 073e409b2..fad054213 100644
--- a/doc/server/xml-common.txt
+++ b/doc/server/xml-common.txt
@@ -324,60 +324,60 @@ tag, described above, if a glob may potentially find no files.
Feature Matrix
==============
-+-------------------------------------------------+--------------+--------+------------+------------+
-| File | Group/Client | Genshi | Encryption | XInclude |
-+=================================================+==============+========+============+============+
-| :ref:`ACL ip.xml <server-plugins-misc-acl>` | No | No | No | Yes |
-+-------------------------------------------------+--------------+--------+------------+------------+
-| :ref:`ACL metadata.xml | Yes | Yes | Yes | Yes |
-| <server-plugins-misc-acl>` | | | | |
-+-------------------------------------------------+--------------+--------+------------+------------+
-| :ref:`Bundler | Yes | Yes | Yes | Yes |
-| <server-plugins-structures-bundler-index>` | | | | |
-+-------------------------------------------------+--------------+--------+------------+------------+
-| :ref:`info.xml <server-info>` | Yes [#f1]_ | Yes | Yes | Yes |
-+-------------------------------------------------+--------------+--------+------------+------------+
-| :ref:`privkey.xml and pubkey.xml | Yes | Yes | Yes | Yes [#f2]_ |
-| <server-plugins-generators-cfg-sshkeys>` | | | | |
-+-------------------------------------------------+--------------+--------+------------+------------+
-| :ref:`authorizedkeys.xml | Yes | Yes | Yes | Yes |
-| <server-plugins-generators-cfg-sshkeys>` | | | | |
-+-------------------------------------------------+--------------+--------+------------+------------+
-| :ref:`Decisions | Yes | Yes | Yes | Yes |
-| <server-plugins-generators-decisions>` | | | | |
-+-------------------------------------------------+--------------+--------+------------+------------+
-| :ref:`Defaults | Yes | Yes | Yes | Yes |
-| <server-plugins-structures-defaults>` | | | | |
-+-------------------------------------------------+--------------+--------+------------+------------+
-| :ref:`FileProbes | Yes | Yes | Yes | Yes |
-| <server-plugins-probes-fileprobes>` | | | | |
-+-------------------------------------------------+--------------+--------+------------+------------+
-| :ref:`GroupPatterns | No | No | No | Yes |
-| <server-plugins-grouping-grouppatterns>` | | | | |
-+-------------------------------------------------+--------------+--------+------------+------------+
-| :ref:`Metadata clients.xml | No | No | No | Yes |
-| <server-plugins-grouping-metadata-clients-xml>` | | | | |
-+-------------------------------------------------+--------------+--------+------------+------------+
-| :ref:`Metadata groups.xml | Yes [#f3]_ | No | No | Yes |
-| <server-plugins-grouping-metadata-groups-xml>` | | | | |
-+-------------------------------------------------+--------------+--------+------------+------------+
-| :ref:`NagiosGen | Yes | Yes | Yes | Yes |
-| <server-plugins-generators-nagiosgen>` | | | | |
-+-------------------------------------------------+--------------+--------+------------+------------+
-| :ref:`Packages | Yes | Yes | Yes | Yes |
-| <server-plugins-generators-packages>` | | | | |
-+-------------------------------------------------+--------------+--------+------------+------------+
-| :ref:`Pkgmgr | Yes | No | No | No |
-| <server-plugins-generators-pkgmgr>` | | | | |
-+-------------------------------------------------+--------------+--------+------------+------------+
-| :ref:`Properties | Yes [#f4]_ | Yes | Yes | Yes |
-| <server-plugins-connectors-properties>` | | | | |
-+-------------------------------------------------+--------------+--------+------------+------------+
-| :ref:`Rules <server-plugins-generators-rules>` | Yes | Yes | Yes | Yes |
-+-------------------------------------------------+--------------+--------+------------+------------+
-| :ref:`SSLCA cert.xml and key.xml | Yes | Yes | Yes | Yes |
-| <server-plugins-generators-sslca>` | | | | |
-+-------------------------------------------------+--------------+--------+------------+------------+
++---------------------------------------------------+--------------+--------+------------+------------+
+| File | Group/Client | Genshi | Encryption | XInclude |
++===================================================+==============+========+============+============+
+| :ref:`ACL ip.xml <server-plugins-misc-acl>` | No | No | No | Yes |
++---------------------------------------------------+--------------+--------+------------+------------+
+| :ref:`ACL metadata.xml | Yes | Yes | Yes | Yes |
+| <server-plugins-misc-acl>` | | | | |
++---------------------------------------------------+--------------+--------+------------+------------+
+| :ref:`Bundler | Yes | Yes | Yes | Yes |
+| <server-plugins-structures-bundler-index>` | | | | |
++---------------------------------------------------+--------------+--------+------------+------------+
+| :ref:`info.xml <server-info>` | Yes [#f1]_ | Yes | Yes | Yes |
++---------------------------------------------------+--------------+--------+------------+------------+
+| :ref:`privkey.xml and pubkey.xml | Yes | Yes | Yes | Yes [#f2]_ |
+| <server-plugins-generators-cfg-sshkeys>` | | | | |
++---------------------------------------------------+--------------+--------+------------+------------+
+| :ref:`authorizedkeys.xml | Yes | Yes | Yes | Yes |
+| <server-plugins-generators-cfg-sshkeys>` | | | | |
++---------------------------------------------------+--------------+--------+------------+------------+
+| :ref:`sslcert.xml and sslkey.xml | Yes | Yes | Yes | Yes |
+| <server-plugins-generators-cfg-ssl-certificates>` | | | | |
++---------------------------------------------------+--------------+--------+------------+------------+
+| :ref:`Decisions | Yes | Yes | Yes | Yes |
+| <server-plugins-generators-decisions>` | | | | |
++---------------------------------------------------+--------------+--------+------------+------------+
+| :ref:`Defaults | Yes | Yes | Yes | Yes |
+| <server-plugins-structures-defaults>` | | | | |
++---------------------------------------------------+--------------+--------+------------+------------+
+| :ref:`FileProbes | Yes | Yes | Yes | Yes |
+| <server-plugins-probes-fileprobes>` | | | | |
++---------------------------------------------------+--------------+--------+------------+------------+
+| :ref:`GroupPatterns | No | No | No | Yes |
+| <server-plugins-grouping-grouppatterns>` | | | | |
++---------------------------------------------------+--------------+--------+------------+------------+
+| :ref:`Metadata clients.xml | No | No | No | Yes |
+| <server-plugins-grouping-metadata-clients-xml>` | | | | |
++---------------------------------------------------+--------------+--------+------------+------------+
+| :ref:`Metadata groups.xml | Yes [#f3]_ | No | No | Yes |
+| <server-plugins-grouping-metadata-groups-xml>` | | | | |
++---------------------------------------------------+--------------+--------+------------+------------+
+| :ref:`NagiosGen | Yes | Yes | Yes | Yes |
+| <server-plugins-generators-nagiosgen>` | | | | |
++---------------------------------------------------+--------------+--------+------------+------------+
+| :ref:`Packages | Yes | Yes | Yes | Yes |
+| <server-plugins-generators-packages>` | | | | |
++---------------------------------------------------+--------------+--------+------------+------------+
+| :ref:`Pkgmgr | Yes | No | No | No |
+| <server-plugins-generators-pkgmgr>` | | | | |
++---------------------------------------------------+--------------+--------+------------+------------+
+| :ref:`Properties | Yes [#f4]_ | Yes | Yes | Yes |
+| <server-plugins-connectors-properties>` | | | | |
++---------------------------------------------------+--------------+--------+------------+------------+
+| :ref:`Rules <server-plugins-generators-rules>` | Yes | Yes | Yes | Yes |
++---------------------------------------------------+--------------+--------+------------+------------+
.. rubric:: Footnotes
diff --git a/man/bcfg2.conf.5 b/man/bcfg2.conf.5
index 5e64caae9..91ebc0020 100644
--- a/man/bcfg2.conf.5
+++ b/man/bcfg2.conf.5
@@ -153,7 +153,6 @@ SEModules
ServiceCompat
Snapshots
SSHbase
-SSLCA
Statistics
Svn
TCheetah
diff --git a/schemas/sslca-cert.xsd b/schemas/sslca-cert.xsd
index a3f6db94d..7a9fb5683 100644
--- a/schemas/sslca-cert.xsd
+++ b/schemas/sslca-cert.xsd
@@ -2,7 +2,7 @@
xmlns:py="http://genshi.edgewall.org/" xml:lang="en">
<xsd:annotation>
<xsd:documentation>
- Schema for :ref:`server-plugins-generators-sslca` ``cert.xml``
+ Schema for :ref:`server-plugins-generators-sslca` ``sslcert.xml``
</xsd:documentation>
</xsd:annotation>
@@ -76,7 +76,7 @@
<xsd:documentation>
The full path to the key entry to use for this certificate.
This is the *client* path; e.g., for a key defined at
- ``/var/lib/bcfg2/SSLCA/etc/pki/tls/private/foo.key/key.xml``,
+ ``/var/lib/bcfg2/SSLCA/etc/pki/tls/private/foo.key/sslkey.xml``,
**key** should be ``/etc/pki/tls/private/foo.key``.
</xsd:documentation>
</xsd:annotation>
diff --git a/schemas/sslca-key.xsd b/schemas/sslca-key.xsd
index 261b71e1a..3523a0c60 100644
--- a/schemas/sslca-key.xsd
+++ b/schemas/sslca-key.xsd
@@ -2,7 +2,7 @@
xmlns:py="http://genshi.edgewall.org/" xml:lang="en">
<xsd:annotation>
<xsd:documentation>
- Schema for :ref:`server-plugins-generators-sslca` ``key.xml``
+ Schema for :ref:`server-plugins-generators-sslca` ``sslkey.xml``
</xsd:documentation>
</xsd:annotation>
@@ -91,11 +91,26 @@
<xsd:element name="Client" type="SSLCAKeyGroupType"/>
<xsd:element name="KeyInfo" type="KeyInfoType"/>
</xsd:choice>
- <xsd:attribute name="lax_decryption" type="xsd:boolean">
+ <xsd:attribute name="perhost" type="xsd:boolean">
<xsd:annotation>
<xsd:documentation>
- Override the global lax_decryption setting in
- ``bcfg2.conf``.
+ Create keys on a per-host basis (rather than on a per-group
+ basis).
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="category" type="xsd:string">
+ <xsd:annotation>
+ <xsd:documentation>
+ Create keys specific to the given category, instead of
+ specific to the category given in ``bcfg2.conf``.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name="priority" type="xsd:positiveInteger" default="50">
+ <xsd:annotation>
+ <xsd:documentation>
+ Create group-specific keys with the given priority.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
diff --git a/src/lib/Bcfg2/Server/Lint/Validate.py b/src/lib/Bcfg2/Server/Lint/Validate.py
index 2f245561b..de7ae038a 100644
--- a/src/lib/Bcfg2/Server/Lint/Validate.py
+++ b/src/lib/Bcfg2/Server/Lint/Validate.py
@@ -39,8 +39,9 @@ class Validate(Bcfg2.Server.Lint.ServerlessPlugin):
"Cfg/**/pubkey.xml": "pubkey.xsd",
"Cfg/**/authorizedkeys.xml": "authorizedkeys.xsd",
"Cfg/**/authorized_keys.xml": "authorizedkeys.xsd",
+ "Cfg/**/sslcert.xml": "sslca-cert.xsd",
+ "Cfg/**/sslkey.xml": "sslca-key.xsd",
"SSHbase/**/info.xml": "info.xsd",
- "SSLCA/**/info.xml": "info.xsd",
"TGenshi/**/info.xml": "info.xsd",
"TCheetah/**/info.xml": "info.xsd",
"Bundler/*.xml": "bundle.xsd",
@@ -55,8 +56,6 @@ class Validate(Bcfg2.Server.Lint.ServerlessPlugin):
"GroupPatterns/config.xml": "grouppatterns.xsd",
"NagiosGen/config.xml": "nagiosgen.xsd",
"FileProbes/config.xml": "fileprobes.xsd",
- "SSLCA/**/cert.xml": "sslca-cert.xsd",
- "SSLCA/**/key.xml": "sslca-key.xsd",
"GroupLogic/groups.xml": "grouplogic.xsd"
}
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgAuthorizedKeysGenerator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgAuthorizedKeysGenerator.py
index c08d3ec44..384d1bf12 100644
--- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgAuthorizedKeysGenerator.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgAuthorizedKeysGenerator.py
@@ -5,7 +5,7 @@ access. """
import lxml.etree
import Bcfg2.Options
from Bcfg2.Server.Plugin import StructFile, PluginExecutionError
-from Bcfg2.Server.Plugins.Cfg import CfgGenerator, CFG
+from Bcfg2.Server.Plugins.Cfg import CfgGenerator, get_cfg
from Bcfg2.Server.Plugins.Metadata import ClientMetadata
@@ -25,7 +25,7 @@ class CfgAuthorizedKeysGenerator(CfgGenerator, StructFile):
CfgGenerator.__init__(self, fname, None)
StructFile.__init__(self, fname)
self.cache = dict()
- self.core = CFG.core
+ self.core = get_cfg().core
__init__.__doc__ = CfgGenerator.__init__.__doc__
def handle_event(self, event):
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgPrivateKeyCreator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgPrivateKeyCreator.py
index 7bb5d3cf5..e5611d50b 100644
--- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgPrivateKeyCreator.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgPrivateKeyCreator.py
@@ -5,17 +5,11 @@ import shutil
import tempfile
import Bcfg2.Options
from Bcfg2.Utils import Executor
-from Bcfg2.Server.Plugin import StructFile
-from Bcfg2.Server.Plugins.Cfg import CfgCreator, CfgCreationError
+from Bcfg2.Server.Plugins.Cfg import XMLCfgCreator, CfgCreationError
from Bcfg2.Server.Plugins.Cfg.CfgPublicKeyCreator import CfgPublicKeyCreator
-try:
- import Bcfg2.Server.Encryption
- HAS_CRYPTO = True
-except ImportError:
- HAS_CRYPTO = False
-class CfgPrivateKeyCreator(CfgCreator, StructFile):
+class CfgPrivateKeyCreator(XMLCfgCreator):
"""The CfgPrivateKeyCreator creates SSH keys on the fly. """
#: Different configurations for different clients/groups can be
@@ -25,6 +19,7 @@ class CfgPrivateKeyCreator(CfgCreator, StructFile):
#: Handle XML specifications of private keys
__basenames__ = ['privkey.xml']
+ cfg_section = "sshkeys"
options = [
Bcfg2.Options.Option(
cf=("sshkeys", "category"), dest="sshkeys_category",
@@ -34,27 +29,12 @@ class CfgPrivateKeyCreator(CfgCreator, StructFile):
help="Passphrase used to encrypt generated SSH private keys")]
def __init__(self, fname):
- CfgCreator.__init__(self, fname)
- StructFile.__init__(self, fname)
-
+ XMLCfgCreator.__init__(self, fname)
pubkey_path = os.path.dirname(self.name) + ".pub"
pubkey_name = os.path.join(pubkey_path, os.path.basename(pubkey_path))
self.pubkey_creator = CfgPublicKeyCreator(pubkey_name)
self.cmd = Executor()
- __init__.__doc__ = CfgCreator.__init__.__doc__
-
- @property
- def passphrase(self):
- """ The passphrase used to encrypt private keys """
- if HAS_CRYPTO and Bcfg2.Options.setup.sshkeys_passphrase:
- return Bcfg2.Options.setup.passphrases[
- Bcfg2.Options.setup.sshkeys_passphrase]
- return None
-
- def handle_event(self, event):
- CfgCreator.handle_event(self, event)
- StructFile.HandleEvent(self, event)
- handle_event.__doc__ = CfgCreator.handle_event.__doc__
+ __init__.__doc__ = XMLCfgCreator.__init__.__doc__
def _gen_keypair(self, metadata, spec=None):
""" Generate a keypair according to the given client medata
@@ -117,45 +97,6 @@ class CfgPrivateKeyCreator(CfgCreator, StructFile):
shutil.rmtree(tempdir)
raise
- def get_specificity(self, metadata, spec=None):
- """ Get config settings for key generation specificity
- (per-host or per-group).
-
- :param metadata: The client metadata to create data for
- :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
- :param spec: The key specification to follow when creating the
- keys. This should be an XML document that only
- contains key specification data that applies to
- the given client metadata, and may be obtained by
- doing ``self.XMLMatch(metadata)``
- :type spec: lxml.etree._Element
- :returns: dict - A dict of specificity arguments suitable for
- passing to
- :func:`Bcfg2.Server.Plugins.Cfg.CfgCreator.write_data`
- or
- :func:`Bcfg2.Server.Plugins.Cfg.CfgCreator.get_filename`
- """
- if spec is None:
- spec = self.XMLMatch(metadata)
- category = spec.get("category", Bcfg2.Options.setup.sshkeys_category)
- if category is None:
- per_host_default = "true"
- else:
- per_host_default = "false"
- per_host = spec.get("perhost", per_host_default).lower() == "true"
-
- specificity = dict(host=metadata.hostname)
- if category and not per_host:
- group = metadata.group_in_category(category)
- if group:
- specificity = dict(group=group,
- prio=int(spec.get("priority", 50)))
- else:
- self.logger.info("Cfg: %s has no group in category %s, "
- "creating host-specific key" %
- (metadata.hostname, category))
- return specificity
-
# pylint: disable=W0221
def create_data(self, entry, metadata, return_pair=False):
""" Create data for the given entry on the given client
@@ -176,7 +117,7 @@ class CfgPrivateKeyCreator(CfgCreator, StructFile):
``return_pair`` is set to True
"""
spec = self.XMLMatch(metadata)
- specificity = self.get_specificity(metadata, spec)
+ specificity = self.get_specificity(metadata)
filename = self._gen_keypair(metadata, spec)
try:
@@ -190,12 +131,6 @@ class CfgPrivateKeyCreator(CfgCreator, StructFile):
# encrypt the private key, write to the proper place, and
# return it
privkey = open(filename).read()
- if HAS_CRYPTO and self.passphrase:
- self.debug_log("Cfg: Encrypting key data at %s" % filename)
- privkey = Bcfg2.Server.Encryption.ssl_encrypt(privkey,
- self.passphrase)
- specificity['ext'] = '.crypt'
-
self.write_data(privkey, **specificity)
if return_pair:
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgPublicKeyCreator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgPublicKeyCreator.py
index 4c61e338e..de1848159 100644
--- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgPublicKeyCreator.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgPublicKeyCreator.py
@@ -4,7 +4,7 @@ to create SSH keys on the fly. """
import lxml.etree
from Bcfg2.Server.Plugin import StructFile, PluginExecutionError
-from Bcfg2.Server.Plugins.Cfg import CfgCreator, CfgCreationError, CFG
+from Bcfg2.Server.Plugins.Cfg import CfgCreator, CfgCreationError, get_cfg
class CfgPublicKeyCreator(CfgCreator, StructFile):
@@ -17,7 +17,7 @@ class CfgPublicKeyCreator(CfgCreator, StructFile):
creation of a keypair when a public key is created. """
#: Different configurations for different clients/groups can be
- #: handled with Client and Group tags within privkey.xml
+ #: handled with Client and Group tags within pubkey.xml
__specific__ = False
#: Handle XML specifications of private keys
@@ -29,7 +29,7 @@ class CfgPublicKeyCreator(CfgCreator, StructFile):
def __init__(self, fname):
CfgCreator.__init__(self, fname)
StructFile.__init__(self, fname)
- self.cfg = CFG
+ self.cfg = get_cfg()
__init__.__doc__ = CfgCreator.__init__.__doc__
def create_data(self, entry, metadata):
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgSSLCACertCreator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgSSLCACertCreator.py
new file mode 100644
index 000000000..92fcc4cd8
--- /dev/null
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgSSLCACertCreator.py
@@ -0,0 +1,255 @@
+""" Cfg creator that creates SSL certs """
+
+import os
+import sys
+import tempfile
+import lxml.etree
+import Bcfg2.Options
+from Bcfg2.Utils import Executor
+from Bcfg2.Compat import ConfigParser
+from Bcfg2.Server.FileMonitor import get_fam
+from Bcfg2.Server.Plugin import PluginExecutionError
+from Bcfg2.Server.Plugins.Cfg import CfgCreationError, XMLCfgCreator, \
+ CfgCreator, CfgVerifier, CfgVerificationError, get_cfg
+
+
+class CfgSSLCACertCreator(XMLCfgCreator, CfgVerifier):
+ """ This class acts as both a Cfg creator that creates SSL certs,
+ and as a Cfg verifier that verifies SSL certs. """
+
+ #: Different configurations for different clients/groups can be
+ #: handled with Client and Group tags within pubkey.xml
+ __specific__ = False
+
+ #: Handle XML specifications of private keys
+ __basenames__ = ['sslcert.xml']
+
+ cfg_section = "sslca"
+ options = [
+ Bcfg2.Options.Option(
+ cf=("sslca", "category"), dest="sslca_category",
+ help="Metadata category that generated SSL keys are specific to"),
+ Bcfg2.Options.Option(
+ cf=("sslca", "passphrase"), dest="sslca_passphrase",
+ help="Passphrase used to encrypt generated SSL keys"),
+ Bcfg2.Options.WildcardSectionGroup(
+ Bcfg2.Options.PathOption(
+ cf=("sslca_*", "config"),
+ help="Path to the openssl config for the CA"),
+ Bcfg2.Options.Option(
+ cf=("sslca_*", "passphrase"),
+ help="Passphrase for the CA private key"),
+ Bcfg2.Options.PathOption(
+ cf=("sslca_*", "chaincert"),
+ help="Path to the SSL chaining certificate for verification"),
+ Bcfg2.Options.BooleanOption(
+ cf=("sslca_*", "root_ca"),
+ help="Whether or not <chaincert> is a root CA (as opposed to "
+ "an intermediate cert"),
+ prefix="")]
+
+ def __init__(self, fname):
+ XMLCfgCreator.__init__(self, fname)
+ CfgVerifier.__init__(self, fname, None)
+ self.cmd = Executor()
+ self.cfg = get_cfg()
+
+ def build_req_config(self, metadata):
+ """ Generates a temporary openssl configuration file that is
+ used to generate the required certificate request. """
+ fd, fname = tempfile.mkstemp()
+ cfp = ConfigParser.ConfigParser({})
+ cfp.optionxform = str
+ defaults = dict(
+ req=dict(
+ default_md='sha1',
+ distinguished_name='req_distinguished_name',
+ req_extensions='v3_req',
+ x509_extensions='v3_req',
+ prompt='no'),
+ req_distinguished_name=dict(),
+ v3_req=dict(subjectAltName='@alt_names'),
+ alt_names=dict())
+ for section in list(defaults.keys()):
+ cfp.add_section(section)
+ for key in defaults[section]:
+ cfp.set(section, key, defaults[section][key])
+ spec = self.XMLMatch(metadata)
+ cert = spec.find("Cert")
+ altnamenum = 1
+ altnames = spec.findall('subjectAltName')
+ altnames.extend(list(metadata.aliases))
+ altnames.append(metadata.hostname)
+ for altname in altnames:
+ cfp.set('alt_names', 'DNS.' + str(altnamenum), altname)
+ altnamenum += 1
+ for item in ['C', 'L', 'ST', 'O', 'OU', 'emailAddress']:
+ if cert.get(item):
+ cfp.set('req_distinguished_name', item, cert.get(item))
+ cfp.set('req_distinguished_name', 'CN', metadata.hostname)
+ self.debug_log("Cfg: Writing temporary CSR config to %s" % fname)
+ try:
+ cfp.write(os.fdopen(fd, 'w'))
+ except IOError:
+ raise CfgCreationError("Cfg: Failed to write temporary CSR config "
+ "file: %s" % sys.exc_info()[1])
+ return fname
+
+ def build_request(self, keyfile, metadata):
+ """ Create the certificate request """
+ req_config = self.build_req_config(metadata)
+ try:
+ fd, req = tempfile.mkstemp()
+ os.close(fd)
+ cert = self.XMLMatch(metadata).find("Cert")
+ days = cert.get("days", "365")
+ cmd = ["openssl", "req", "-new", "-config", req_config,
+ "-days", days, "-key", keyfile, "-text", "-out", req]
+ result = self.cmd.run(cmd)
+ if not result.success:
+ raise CfgCreationError("Failed to generate CSR: %s" %
+ result.error)
+ return req
+ finally:
+ try:
+ os.unlink(req_config)
+ except OSError:
+ self.logger.error("Cfg: Failed to unlink temporary CSR "
+ "config: %s" % sys.exc_info()[1])
+
+ def get_ca(self, name):
+ """ get a dict describing a CA from the config file """
+ rv = dict()
+ prefix = "sslca_%s_" % name
+ for attr in dir(Bcfg2.Options.setup):
+ if attr.startswith(prefix):
+ rv[attr[len(prefix):]] = getattr(Bcfg2.Options.setup, attr)
+ return rv
+
+ def create_data(self, entry, metadata):
+ """ generate a new cert """
+ self.logger.info("Cfg: Generating new SSL cert for %s" % self.name)
+ cert = self.XMLMatch(metadata).find("Cert")
+ ca = self.get_ca(cert.get('ca', 'default'))
+ req = self.build_request(self._get_keyfile(cert, metadata), metadata)
+ try:
+ days = cert.get('days', '365')
+ cmd = ["openssl", "ca", "-config", ca['config'], "-in", req,
+ "-days", days, "-batch"]
+ passphrase = ca.get('passphrase')
+ if passphrase:
+ cmd.extend(["-passin", "pass:%s" % passphrase])
+ result = self.cmd.run(cmd)
+ if not result.success:
+ raise CfgCreationError("Failed to generate cert: %s" %
+ result.error)
+ except KeyError:
+ raise CfgCreationError("Cfg: [sslca_%s] section has no 'config' "
+ "option" % cert.get('ca', 'default'))
+ finally:
+ try:
+ os.unlink(req)
+ except OSError:
+ self.logger.error("Cfg: Failed to unlink temporary CSR: %s " %
+ sys.exc_info()[1])
+ data = result.stdout
+ if cert.get('append_chain') and 'chaincert' in ca:
+ data += open(ca['chaincert']).read()
+
+ self.write_data(data, **self.get_specificity(metadata))
+ return data
+
+ def verify_entry(self, entry, metadata, data):
+ fd, fname = tempfile.mkstemp()
+ self.debug_log("Cfg: Writing SSL cert %s to temporary file %s for "
+ "verification" % (entry.get("name"), fname))
+ os.fdopen(fd, 'w').write(data)
+ cert = self.XMLMatch(metadata).find("Cert")
+ ca = self.get_ca(cert.get('ca', 'default'))
+ try:
+ if ca.get('chaincert'):
+ self.verify_cert_against_ca(fname, entry, metadata)
+ self.verify_cert_against_key(fname,
+ self._get_keyfile(cert, metadata))
+ finally:
+ os.unlink(fname)
+
+ def _get_keyfile(self, cert, metadata):
+ """ Given a <Cert/> element and client metadata, return the
+ full path to the file on the filesystem that the key lives in."""
+ keypath = cert.get("key")
+ eset = self.cfg.entries[keypath]
+ try:
+ return eset.best_matching(metadata).name
+ except PluginExecutionError:
+ # SSL key needs to be created
+ try:
+ creator = eset.best_matching(metadata,
+ eset.get_handlers(metadata,
+ CfgCreator))
+ except PluginExecutionError:
+ raise CfgCreationError("Cfg: No SSL key or key creator "
+ "defined for %s" % keypath)
+
+ keyentry = lxml.etree.Element("Path", name=keypath)
+ creator.create_data(keyentry, metadata)
+
+ tries = 0
+ while True:
+ if tries >= 10:
+ raise CfgCreationError("Cfg: Timed out waiting for event "
+ "on SSL key at %s" % keypath)
+ get_fam().handle_events_in_interval(1)
+ try:
+ return eset.best_matching(metadata).name
+ except PluginExecutionError:
+ tries += 1
+ continue
+
+ def verify_cert_against_ca(self, filename, entry, metadata):
+ """
+ check that a certificate validates against the ca cert,
+ and that it has not expired.
+ """
+ cert = self.XMLMatch(metadata).find("Cert")
+ ca = self.get_ca(cert.get("ca", "default"))
+ chaincert = ca.get('chaincert')
+ cmd = ["openssl", "verify"]
+ is_root = ca.get('root_ca', "false").lower() == 'true'
+ if is_root:
+ cmd.append("-CAfile")
+ else:
+ # verifying based on an intermediate cert
+ cmd.extend(["-purpose", "sslserver", "-untrusted"])
+ cmd.extend([chaincert, filename])
+ self.debug_log("Cfg: Verifying %s against CA" % entry.get("name"))
+ result = self.cmd.run(cmd)
+ if result.stdout == cert + ": OK\n":
+ self.debug_log("Cfg: %s verified successfully against CA" %
+ entry.get("name"))
+ else:
+ raise CfgVerificationError("%s failed verification against CA: %s"
+ % (entry.get("name"), result.error))
+
+ def _get_modulus(self, fname, ftype="x509"):
+ """ get the modulus from the given file """
+ cmd = ["openssl", ftype, "-noout", "-modulus", "-in", fname]
+ self.debug_log("Cfg: Getting modulus of %s for verification: %s" %
+ (fname, " ".join(cmd)))
+ result = self.cmd.run(cmd)
+ if not result.success:
+ raise CfgVerificationError("Failed to get modulus of %s: %s" %
+ (fname, result.error))
+ return result.stdout.strip()
+
+ def verify_cert_against_key(self, filename, keyfile):
+ """ check that a certificate validates against its private
+ key. """
+ cert = self._get_modulus(filename)
+ key = self._get_modulus(keyfile, ftype="rsa")
+ if cert == key:
+ self.debug_log("Cfg: %s verified successfully against key %s" %
+ (filename, keyfile))
+ else:
+ raise CfgVerificationError("%s failed verification against key %s"
+ % (filename, keyfile))
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgSSLCAKeyCreator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgSSLCAKeyCreator.py
new file mode 100644
index 000000000..a158302be
--- /dev/null
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgSSLCAKeyCreator.py
@@ -0,0 +1,36 @@
+""" Cfg creator that creates SSL keys """
+
+from Bcfg2.Utils import Executor
+from Bcfg2.Server.Plugins.Cfg import CfgCreationError, XMLCfgCreator
+
+
+class CfgSSLCAKeyCreator(XMLCfgCreator):
+ """ Cfg creator that creates SSL keys """
+
+ #: Different configurations for different clients/groups can be
+ #: handled with Client and Group tags within sslkey.xml
+ __specific__ = False
+
+ __basenames__ = ["sslkey.xml"]
+
+ cfg_section = "sslca"
+
+ def create_data(self, entry, metadata):
+ self.logger.info("Cfg: Generating new SSL key for %s" % self.name)
+ spec = self.XMLMatch(metadata)
+ key = spec.find("Key")
+ if not key:
+ key = dict()
+ ktype = key.get('type', 'rsa')
+ bits = key.get('bits', '2048')
+ if ktype == 'rsa':
+ cmd = ["openssl", "genrsa", bits]
+ elif ktype == 'dsa':
+ cmd = ["openssl", "dsaparam", "-noout", "-genkey", bits]
+ result = Executor().run(cmd)
+ if not result.success:
+ raise CfgCreationError("Failed to generate key %s for %s: %s" %
+ (self.name, metadata.hostname,
+ result.error))
+ self.write_data(result.stdout, **self.get_specificity(metadata))
+ return result.stdout
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py b/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py
index 99afac7eb..21dc35e5a 100644
--- a/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py
@@ -10,16 +10,26 @@ import Bcfg2.Options
import Bcfg2.Server.Plugin
from Bcfg2.Server.Plugin import PluginExecutionError
# pylint: disable=W0622
-from Bcfg2.Compat import u_str, unicode, b64encode, any, oct_mode
+from Bcfg2.Compat import u_str, unicode, b64encode, any
# pylint: enable=W0622
-#: CFG is a reference to the :class:`Bcfg2.Server.Plugins.Cfg.Cfg`
-#: plugin object created by the Bcfg2 core. This is provided so that
-#: the handler objects can access it as necessary, since the existing
-#: :class:`Bcfg2.Server.Plugin.helpers.GroupSpool` and
-#: :class:`Bcfg2.Server.Plugin.helpers.EntrySet` classes have no
-#: facility for passing it otherwise.
-CFG = None
+try:
+ import Bcfg2.Server.Encryption
+ HAS_CRYPTO = True
+except ImportError:
+ HAS_CRYPTO = False
+
+
+_CFG = None
+
+def get_cfg():
+ """ Get the :class:`Bcfg2.Server.Plugins.Cfg.Cfg` plugin object
+ created by the Bcfg2 core. This is provided so that the handler
+ objects can access it as necessary, since the existing
+ :class:`Bcfg2.Server.Plugin.helpers.GroupSpool` and
+ :class:`Bcfg2.Server.Plugin.helpers.EntrySet` classes have no
+ facility for passing it otherwise."""
+ return _CFG
class CfgBaseFileMatcher(Bcfg2.Server.Plugin.SpecificData):
@@ -288,7 +298,7 @@ class CfgCreator(CfgBaseFileMatcher):
:type name: string
.. -----
- .. autoattribute:: Bcfg2.Server.Plugins.Cfg.CfgCreator.__specific__
+ .. autoattribute:: Bcfg2.Server.Plugins.Cfg.CfgInfo.__specific__
"""
CfgBaseFileMatcher.__init__(self, fname, None)
@@ -310,7 +320,9 @@ class CfgCreator(CfgBaseFileMatcher):
``host`` is given, it will be host-specific. It will be
group-specific if ``group`` and ``prio`` are given. If
neither ``host`` nor ``group`` is given, the filename will be
- non-specific.
+ non-specific. In general, this will be called as::
+
+ self.get_filename(**self.get_specificity(metadata))
:param host: The file applies to the given host
:type host: bool
@@ -341,6 +353,9 @@ class CfgCreator(CfgBaseFileMatcher):
written as a host-specific file, or as a group-specific file
if ``group`` and ``prio`` are given. If neither ``host`` nor
``group`` is given, it will be written as a non-specific file.
+ In general, this will be called as::
+
+ self.write_data(data, **self.get_specificity(metadata))
:param data: The data to write
:type data: string
@@ -360,7 +375,7 @@ class CfgCreator(CfgBaseFileMatcher):
:raises: :exc:`Bcfg2.Server.Plugins.Cfg.CfgCreationError`
"""
fileloc = self.get_filename(host=host, group=group, prio=prio, ext=ext)
- self.debug_log("%s: Writing new file %s" % (self.name, fileloc))
+ self.debug_log("Cfg: Writing new file %s" % fileloc)
try:
os.makedirs(os.path.dirname(fileloc))
except OSError:
@@ -376,6 +391,95 @@ class CfgCreator(CfgBaseFileMatcher):
raise CfgCreationError("Could not write %s: %s" % (fileloc, err))
+class XMLCfgCreator(CfgCreator, # pylint: disable=W0223
+ Bcfg2.Server.Plugin.StructFile):
+ """ A CfgCreator that uses XML to describe how data should be
+ generated. """
+
+ #: Whether or not the created data from this class can be
+ #: encrypted
+ encryptable = True
+
+ #: Encryption and creation settings can be stored in bcfg2.conf,
+ #: either under the [cfg] section, or under the named section.
+ cfg_section = None
+
+ def __init__(self, name):
+ CfgCreator.__init__(self, name)
+ Bcfg2.Server.Plugin.StructFile.__init__(self, name)
+
+ def handle_event(self, event):
+ CfgCreator.handle_event(self, event)
+ Bcfg2.Server.Plugin.StructFile.HandleEvent(self, event)
+
+ @property
+ def passphrase(self):
+ """ The passphrase used to encrypt created data """
+ if self.cfg_section:
+ localopt = "%s_passphrase" % self.cfg_section
+ passphrase = getattr(Bcfg2.Options.setup, localopt,
+ Bcfg2.Options.setup.cfg_passphrase)
+ else:
+ passphrase = Bcfg2.Options.setup.cfg_passphrase
+ if passphrase is None:
+ return None
+ try:
+ return Bcfg2.Options.setup.passphrases[passphrase]
+ except KeyError:
+ raise CfgCreationError("%s: No such passphrase: %s" %
+ (self.__class__.__name__, passphrase))
+
+ @property
+ def category(self):
+ """ The category to which created data is specific """
+ if self.cfg_section:
+ localopt = "%s_category" % self.cfg_section
+ return getattr(Bcfg2.Options.setup, localopt,
+ Bcfg2.Options.setup.cfg_category)
+ else:
+ return Bcfg2.Options.setup.cfg_category
+
+ def write_data(self, data, host=None, group=None, prio=0, ext=''):
+ if HAS_CRYPTO and self.encryptable and self.passphrase:
+ self.debug_log("Cfg: Encrypting created data")
+ data = Bcfg2.Server.Encryption.ssl_encrypt(data, self.passphrase)
+ ext = '.crypt'
+ CfgCreator.write_data(self, data, host=host, group=group, prio=prio,
+ ext=ext)
+
+ def get_specificity(self, metadata):
+ """ Get config settings for key generation specificity
+ (per-host or per-group).
+
+ :param metadata: The client metadata to create data for
+ :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata
+ :returns: dict - A dict of specificity arguments suitable for
+ passing to
+ :func:`Bcfg2.Server.Plugins.Cfg.CfgCreator.write_data`
+ or
+ :func:`Bcfg2.Server.Plugins.Cfg.CfgCreator.get_filename`
+ """
+ category = self.xdata.get("category", self.category)
+ if category is None:
+ per_host_default = "true"
+ else:
+ per_host_default = "false"
+ per_host = self.xdata.get("perhost",
+ per_host_default).lower() == "true"
+
+ specificity = dict(host=metadata.hostname)
+ if category and not per_host:
+ group = metadata.group_in_category(category)
+ if group:
+ specificity = dict(group=group,
+ prio=int(self.xdata.get("priority", 50)))
+ else:
+ self.logger.info("Cfg: %s has no group in category %s, "
+ "creating host-specific data" %
+ (metadata.hostname, category))
+ return specificity
+
+
class CfgVerificationError(Exception):
""" Raised by
:func:`Bcfg2.Server.Plugins.Cfg.CfgVerifier.verify_entry` when an
@@ -411,7 +515,6 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
def __init__(self, basename, path, entry_type):
Bcfg2.Server.Plugin.EntrySet.__init__(self, basename, path, entry_type)
self.specific = None
- self._handlers = None
__init__.__doc__ = Bcfg2.Server.Plugin.EntrySet.__doc__
def set_debug(self, debug):
@@ -420,14 +523,6 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
entry.set_debug(debug)
return rv
- @property
- def handlers(self):
- """ A list of Cfg handler classes. """
- if self._handlers is None:
- self._handlers = Bcfg2.Options.setup.cfg_handlers
- self._handlers.sort(key=operator.attrgetter("__priority__"))
- return self._handlers
-
def handle_event(self, event):
""" Dispatch a FAM event to :func:`entry_init` or the
appropriate child handler object.
@@ -444,7 +539,7 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet):
# process a bogus changed event like a created
return
- for hdlr in self.handlers:
+ for hdlr in Bcfg2.Options.setup.cfg_handlers:
if hdlr.handles(event, basename=self.path):
if action == 'changed':
# warn about a bogus 'changed' event, but
@@ -783,6 +878,13 @@ class Cfg(Bcfg2.Server.Plugin.GroupSpool,
'--cfg-validation', cf=('cfg', 'validation'), default=True,
help='Run validation on Cfg files'),
Bcfg2.Options.Option(
+ cf=('cfg', 'category'), dest="cfg_category",
+ help='The default name of the metadata category that created data '
+ 'is specific to'),
+ Bcfg2.Options.Option(
+ cf=('cfg', 'passphrase'), dest="cfg_passphrase",
+ help='The default passphrase name used to encrypt created data'),
+ Bcfg2.Options.Option(
cf=("cfg", "handlers"), dest="cfg_handlers",
help="Cfg handlers to load",
type=Bcfg2.Options.Types.comma_list, action=CfgHandlerAction,
@@ -791,24 +893,18 @@ class Cfg(Bcfg2.Server.Plugin.GroupSpool,
'CfgGenshiGenerator', 'CfgEncryptedGenshiGenerator',
'CfgExternalCommandVerifier', 'CfgInfoXML',
'CfgPlaintextGenerator',
- 'CfgPrivateKeyCreator', 'CfgPublicKeyCreator'])]
+ 'CfgPrivateKeyCreator', 'CfgPublicKeyCreator',
+ 'CfgSSLCACertCreator', 'CfgSSLCAKeyCreator'])]
def __init__(self, core, datastore):
- global CFG # pylint: disable=W0603
+ global _CFG # pylint: disable=W0603
Bcfg2.Server.Plugin.GroupSpool.__init__(self, core, datastore)
Bcfg2.Server.Plugin.PullTarget.__init__(self)
- self._handlers = None
- CFG = self
+ Bcfg2.Options.setup.cfg_handlers.sort(
+ key=operator.attrgetter("__priority__"))
+ _CFG = self
__init__.__doc__ = Bcfg2.Server.Plugin.GroupSpool.__init__.__doc__
- @property
- def handlers(self):
- """ A list of Cfg handler classes. """
- if self._handlers is None:
- self._handlers = Bcfg2.Options.setup.cfg_handlers
- self._handlers.sort(key=operator.attrgetter("__priority__"))
- return self._handlers
-
def has_generator(self, entry, metadata):
""" Return True if the given entry can be generated for the
given metadata; False otherwise
diff --git a/src/lib/Bcfg2/Server/Plugins/SSLCA.py b/src/lib/Bcfg2/Server/Plugins/SSLCA.py
deleted file mode 100644
index 74d8833f4..000000000
--- a/src/lib/Bcfg2/Server/Plugins/SSLCA.py
+++ /dev/null
@@ -1,387 +0,0 @@
-""" The SSLCA generator handles the creation and management of ssl
-certificates and their keys. """
-
-import os
-import sys
-import tempfile
-import lxml.etree
-import Bcfg2.Server.Plugin
-from Bcfg2.Utils import Executor
-from Bcfg2.Compat import ConfigParser
-from Bcfg2.Server.Plugin import PluginExecutionError
-
-
-class SSLCAXMLSpec(Bcfg2.Server.Plugin.StructFile):
- """ Base class to handle key.xml and cert.xml """
- encryption = False
- attrs = dict()
- tag = None
-
- def get_spec(self, metadata):
- """ Get a specification for the type of object described by
- this SSLCA XML file for the given client metadata object """
- entries = [e for e in self.Match(metadata) if e.tag == self.tag]
- if len(entries) == 0:
- raise PluginExecutionError("No matching %s entry found for %s "
- "in %s" % (self.tag,
- metadata.hostname,
- self.name))
- elif len(entries) > 1:
- self.logger.warning(
- "More than one matching %s entry found for %s in %s; "
- "using first match" % (self.tag, metadata.hostname, self.name))
- rv = dict()
- for attr, default in self.attrs.items():
- val = entries[0].get(attr.lower(), default)
- if default in ['true', 'false']:
- rv[attr] = val == 'true'
- else:
- rv[attr] = val
- return rv
-
-
-class SSLCAKeySpec(SSLCAXMLSpec):
- """ Handle key.xml files """
- attrs = dict(bits='2048', type='rsa')
- tag = 'Key'
-
-
-class SSLCACertSpec(SSLCAXMLSpec):
- """ Handle cert.xml files """
- attrs = dict(ca='default',
- format='pem',
- key=None,
- days='365',
- C=None,
- L=None,
- ST=None,
- OU=None,
- O=None,
- emailAddress=None,
- append_chain='false')
- tag = 'Cert'
-
- def get_spec(self, metadata):
- rv = SSLCAXMLSpec.get_spec(self, metadata)
- rv['subjectaltname'] = [e.text for e in self.Match(metadata)
- if e.tag == "subjectAltName"]
- return rv
-
-
-class SSLCADataFile(Bcfg2.Server.Plugin.SpecificData):
- """ Handle key and cert files """
- def bind_entry(self, entry, _):
- """ Bind the data in the file to the given abstract entry """
- entry.text = self.data
- entry.set("type", "file")
- return entry
-
-
-class SSLCAEntrySet(Bcfg2.Server.Plugin.EntrySet):
- """ Entry set to handle SSLCA entries and XML files """
- def __init__(self, _, path, entry_type, parent=None):
- Bcfg2.Server.Plugin.EntrySet.__init__(self, os.path.basename(path),
- path, entry_type)
- self.parent = parent
- self.key = None
- self.cert = None
- self.cmd = Executor(timeout=120)
-
- def handle_event(self, event):
- action = event.code2str()
- fpath = os.path.join(self.path, event.filename)
-
- if event.filename == 'key.xml':
- if action in ['exists', 'created', 'changed']:
- self.key = SSLCAKeySpec(fpath)
- self.key.HandleEvent(event)
- elif event.filename == 'cert.xml':
- if action in ['exists', 'created', 'changed']:
- self.cert = SSLCACertSpec(fpath)
- self.cert.HandleEvent(event)
- else:
- Bcfg2.Server.Plugin.EntrySet.handle_event(self, event)
-
- def build_key(self, entry, metadata):
- """
- either grabs a prexisting key hostfile, or triggers the generation
- of a new key if one doesn't exist.
- """
- # TODO: verify key fits the specs
- filename = "%s.H_%s" % (os.path.basename(entry.get('name')),
- metadata.hostname)
- self.logger.info("SSLCA: Generating new key %s" % filename)
- key_spec = self.key.get_spec(metadata)
- ktype = key_spec['type']
- bits = key_spec['bits']
- if ktype == 'rsa':
- cmd = ["openssl", "genrsa", bits]
- elif ktype == 'dsa':
- cmd = ["openssl", "dsaparam", "-noout", "-genkey", bits]
- self.debug_log("SSLCA: Generating new key: %s" % " ".join(cmd))
- result = self.cmd.run(cmd)
- if not result.success:
- raise PluginExecutionError("SSLCA: Failed to generate key %s for "
- "%s: %s" % (entry.get("name"),
- metadata.hostname,
- result.error))
- open(os.path.join(self.path, filename), 'w').write(result.stdout)
- return result.stdout
-
- def build_cert(self, entry, metadata, keyfile):
- """ generate a new cert """
- filename = "%s.H_%s" % (os.path.basename(entry.get('name')),
- metadata.hostname)
- self.logger.info("SSLCA: Generating new cert %s" % filename)
- cert_spec = self.cert.get_spec(metadata)
- ca = self.parent.get_ca(cert_spec['ca'])
- req_config = None
- req = None
- try:
- req_config = self.build_req_config(metadata)
- req = self.build_request(keyfile, req_config, metadata)
- days = cert_spec['days']
- cmd = ["openssl", "ca", "-config", ca['config'], "-in", req,
- "-days", days, "-batch"]
- passphrase = ca.get('passphrase')
- if passphrase:
- cmd.extend(["-passin", "pass:%s" % passphrase])
-
- def _scrub_pass(arg):
- """ helper to scrub the passphrase from the
- argument list """
- if arg.startswith("pass:"):
- return "pass:******"
- else:
- return arg
- else:
- _scrub_pass = lambda a: a
-
- self.debug_log("SSLCA: Generating new certificate: %s" %
- " ".join(_scrub_pass(a) for a in cmd))
- result = self.cmd.run(cmd)
- if not result.success:
- raise PluginExecutionError("SSLCA: Failed to generate cert: %s"
- % result.error)
- finally:
- try:
- if req_config and os.path.exists(req_config):
- os.unlink(req_config)
- if req and os.path.exists(req):
- os.unlink(req)
- except OSError:
- self.logger.error("SSLCA: Failed to unlink temporary files: %s"
- % sys.exc_info()[1])
- cert = result.stdout
- if cert_spec['append_chain'] and 'chaincert' in ca:
- cert += open(ca['chaincert']).read()
-
- open(os.path.join(self.path, filename), 'w').write(cert)
- return cert
-
- def build_req_config(self, metadata):
- """
- generates a temporary openssl configuration file that is
- used to generate the required certificate request
- """
- # create temp request config file
- fd, fname = tempfile.mkstemp()
- cfp = ConfigParser.ConfigParser({})
- cfp.optionxform = str
- defaults = {
- 'req': {
- 'default_md': 'sha1',
- 'distinguished_name': 'req_distinguished_name',
- 'req_extensions': 'v3_req',
- 'x509_extensions': 'v3_req',
- 'prompt': 'no'
- },
- 'req_distinguished_name': {},
- 'v3_req': {
- 'subjectAltName': '@alt_names'
- },
- 'alt_names': {}
- }
- for section in list(defaults.keys()):
- cfp.add_section(section)
- for key in defaults[section]:
- cfp.set(section, key, defaults[section][key])
- cert_spec = self.cert.get_spec(metadata)
- altnamenum = 1
- altnames = cert_spec['subjectaltname']
- altnames.extend(list(metadata.aliases))
- altnames.append(metadata.hostname)
- for altname in altnames:
- cfp.set('alt_names', 'DNS.' + str(altnamenum), altname)
- altnamenum += 1
- for item in ['C', 'L', 'ST', 'O', 'OU', 'emailAddress']:
- if cert_spec[item]:
- cfp.set('req_distinguished_name', item, cert_spec[item])
- cfp.set('req_distinguished_name', 'CN', metadata.hostname)
- self.debug_log("SSLCA: Writing temporary request config to %s" % fname)
- try:
- cfp.write(os.fdopen(fd, 'w'))
- except IOError:
- raise PluginExecutionError("SSLCA: Failed to write temporary CSR "
- "config file: %s" % sys.exc_info()[1])
- return fname
-
- def build_request(self, keyfile, req_config, metadata):
- """
- creates the certificate request
- """
- fd, req = tempfile.mkstemp()
- os.close(fd)
- days = self.cert.get_spec(metadata)['days']
- cmd = ["openssl", "req", "-new", "-config", req_config,
- "-days", days, "-key", keyfile, "-text", "-out", req]
- self.debug_log("SSLCA: Generating new CSR: %s" % " ".join(cmd))
- result = self.cmd.run(cmd)
- if not result.success:
- raise PluginExecutionError("SSLCA: Failed to generate CSR: %s" %
- result.error)
- return req
-
- def verify_cert(self, filename, keyfile, entry, metadata):
- """ Perform certification verification against the CA and
- against the key """
- ca = self.parent.get_ca(self.cert.get_spec(metadata)['ca'])
- do_verify = ca.get('chaincert')
- if do_verify:
- return (self.verify_cert_against_ca(filename, entry, metadata) and
- self.verify_cert_against_key(filename, keyfile))
- return True
-
- def verify_cert_against_ca(self, filename, entry, metadata):
- """
- check that a certificate validates against the ca cert,
- and that it has not expired.
- """
- ca = self.parent.get_ca(self.cert.get_spec(metadata)['ca'])
- chaincert = ca.get('chaincert')
- cert = os.path.join(self.path, filename)
- cmd = ["openssl", "verify"]
- is_root = ca.get('root_ca', "false").lower() == 'true'
- if is_root:
- cmd.append("-CAfile")
- else:
- # verifying based on an intermediate cert
- cmd.extend(["-purpose", "sslserver", "-untrusted"])
- cmd.extend([chaincert, cert])
- self.debug_log("SSLCA: Verifying %s against CA: %s" %
- (entry.get("name"), " ".join(cmd)))
- result = self.cmd.run(cmd)
- if result.stdout == cert + ": OK\n":
- self.debug_log("SSLCA: %s verified successfully against CA" %
- entry.get("name"))
- return True
- self.logger.warning("SSLCA: %s failed verification against CA: %s" %
- (entry.get("name"), result.error))
- return False
-
- def _get_modulus(self, fname, ftype="x509"):
- """ get the modulus from the given file """
- cmd = ["openssl", ftype, "-noout", "-modulus", "-in", fname]
- self.debug_log("SSLCA: Getting modulus of %s for verification: %s" %
- (fname, " ".join(cmd)))
- result = self.cmd.run(cmd)
- if not result.success:
- self.logger.warning("SSLCA: Failed to get modulus of %s: %s" %
- (fname, result.error))
- return result.stdout.strip()
-
- def verify_cert_against_key(self, filename, keyfile):
- """
- check that a certificate validates against its private key.
- """
-
- certfile = os.path.join(self.path, filename)
- cert = self._get_modulus(certfile)
- key = self._get_modulus(keyfile, ftype="rsa")
- if cert == key:
- self.debug_log("SSLCA: %s verified successfully against key %s" %
- (filename, keyfile))
- return True
- self.logger.warning("SSLCA: %s failed verification against key %s" %
- (filename, keyfile))
- return False
-
- def bind_entry(self, entry, metadata):
- if self.key:
- self.bind_info_to_entry(entry, metadata)
- try:
- return self.best_matching(metadata).bind_entry(entry, metadata)
- except PluginExecutionError:
- entry.text = self.build_key(entry, metadata)
- entry.set("type", "file")
- return entry
- elif self.cert:
- key = self.cert.get_spec(metadata)['key']
- cleanup_keyfile = False
- try:
- keyfile = self.parent.entries[key].best_matching(metadata).name
- except PluginExecutionError:
- cleanup_keyfile = True
- # create a temp file with the key in it
- fd, keyfile = tempfile.mkstemp()
- os.chmod(keyfile, 384) # 0600
- el = lxml.etree.Element('Path', name=key)
- self.parent.core.Bind(el, metadata)
- os.fdopen(fd, 'w').write(el.text)
-
- try:
- self.bind_info_to_entry(entry, metadata)
- try:
- best = self.best_matching(metadata)
- if self.verify_cert(best.name, keyfile, entry, metadata):
- return best.bind_entry(entry, metadata)
- except PluginExecutionError:
- pass
- # if we get here, it's because either a) there was no best
- # matching entry; or b) the existing cert did not verify
- entry.text = self.build_cert(entry, metadata, keyfile)
- entry.set("type", "file")
- return entry
- finally:
- if cleanup_keyfile:
- try:
- os.unlink(keyfile)
- except OSError:
- err = sys.exc_info()[1]
- self.logger.error("SSLCA: Failed to unlink temporary "
- "key %s: %s" % (keyfile, err))
-
-
-class SSLCA(Bcfg2.Server.Plugin.GroupSpool):
- """ The SSLCA generator handles the creation and management of ssl
- certificates and their keys. """
- __author__ = 'g.hagger@gmail.com'
-
- options = Bcfg2.Server.Plugin.GroupSpool.options + [
- Bcfg2.Options.WildcardSectionGroup(
- Bcfg2.Options.PathOption(
- cf=("sslca_*", "config"),
- help="Path to the openssl config for the CA"),
- Bcfg2.Options.Option(
- cf=("sslca_*", "passphrase"),
- help="Passphrase for the CA private key"),
- Bcfg2.Options.PathOption(
- cf=("sslca_*", "chaincert"),
- help="Path to the SSL chaining certificate for verification"),
- Bcfg2.Options.BooleanOption(
- cf=("sslca_*", "root_ca"),
- help="Whether or not <chaincert> is a root CA (as opposed to "
- "an intermediate cert"))]
-
- # python 2.5 doesn't support mixing *magic and keyword arguments
- es_cls = lambda self, *args: SSLCAEntrySet(*args, **dict(parent=self))
- es_child_cls = SSLCADataFile
-
- def get_ca(self, name):
- """ get a dict describing a CA from the config file """
- rv = dict()
- prefix = "sslca_%s_" % name
- for attr in dir(Bcfg2.Options.setup):
- if attr.startswith(prefix):
- rv[attr[len(prefix):]] = getattr(Bcfg2.Options.setup, attr)
- return rv
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py
index 7006e29e3..7515b5e97 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugin/Testhelpers.py
@@ -1375,10 +1375,10 @@ class TestSpecificData(TestDebuggable):
sd = self.get_obj()
sd.handle_event(event)
self.assertFalse(mock_open.called)
- if hasattr(sd, 'data'):
- self.assertIsNone(sd.data)
- else:
+ try:
self.assertFalse(hasattr(sd, 'data'))
+ except AssertionError:
+ self.assertIsNone(sd.data)
event = Mock()
mock_open.return_value.read.return_value = "test"
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgPrivateKeyCreator.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgPrivateKeyCreator.py
index c4961db1c..ea0853a8d 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgPrivateKeyCreator.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgPrivateKeyCreator.py
@@ -33,15 +33,6 @@ class TestCfgPrivateKeyCreator(TestCfgCreator, TestStructFile):
def get_obj(self, name=None, fam=None):
return TestCfgCreator.get_obj(self, name=name)
- @patch("Bcfg2.Server.Plugins.Cfg.CfgCreator.handle_event")
- @patch("Bcfg2.Server.Plugin.helpers.StructFile.HandleEvent")
- def test_handle_event(self, mock_HandleEvent, mock_handle_event):
- pkc = self.get_obj()
- evt = Mock()
- pkc.handle_event(evt)
- mock_HandleEvent.assert_called_with(pkc, evt)
- mock_handle_event.assert_called_with(pkc, evt)
-
@patch("shutil.rmtree")
@patch("tempfile.mkdtemp")
def test__gen_keypair(self, mock_mkdtemp, mock_rmtree):
@@ -90,57 +81,6 @@ class TestCfgPrivateKeyCreator(TestCfgCreator, TestStructFile):
self.assertRaises(CfgCreationError, pkc._gen_keypair, metadata)
mock_rmtree.assert_called_with(datastore)
- def test_get_specificity(self):
- pkc = self.get_obj()
- pkc.XMLMatch = Mock()
-
- metadata = Mock()
-
- def reset():
- pkc.XMLMatch.reset_mock()
- metadata.group_in_category.reset_mock()
-
- Bcfg2.Options.setup.sshkeys_category = None
- pkc.XMLMatch.return_value = lxml.etree.Element("PrivateKey")
- self.assertItemsEqual(pkc.get_specificity(metadata),
- dict(host=metadata.hostname))
-
- Bcfg2.Options.setup.sshkeys_category = "foo"
- pkc.XMLMatch.return_value = lxml.etree.Element("PrivateKey")
- self.assertItemsEqual(pkc.get_specificity(metadata),
- dict(group=metadata.group_in_category.return_value,
- prio=50))
- metadata.group_in_category.assert_called_with("foo")
-
- reset()
- pkc.XMLMatch.return_value = lxml.etree.Element("PrivateKey",
- perhost="true")
- self.assertItemsEqual(pkc.get_specificity(metadata),
- dict(host=metadata.hostname))
-
- reset()
- pkc.XMLMatch.return_value = lxml.etree.Element("PrivateKey",
- category="bar")
- self.assertItemsEqual(pkc.get_specificity(metadata),
- dict(group=metadata.group_in_category.return_value,
- prio=50))
- metadata.group_in_category.assert_called_with("bar")
-
- reset()
- pkc.XMLMatch.return_value = lxml.etree.Element("PrivateKey",
- prio="10")
- self.assertItemsEqual(pkc.get_specificity(metadata),
- dict(group=metadata.group_in_category.return_value,
- prio=10))
- metadata.group_in_category.assert_called_with("foo")
-
- reset()
- pkc.XMLMatch.return_value = lxml.etree.Element("PrivateKey")
- metadata.group_in_category.return_value = ''
- self.assertItemsEqual(pkc.get_specificity(metadata),
- dict(host=metadata.hostname))
- metadata.group_in_category.assert_called_with("foo")
-
@patch("shutil.rmtree")
@patch("%s.open" % builtins)
def test_create_data(self, mock_open, mock_rmtree):
@@ -179,67 +119,33 @@ class TestCfgPrivateKeyCreator(TestCfgCreator, TestStructFile):
mock_open.return_value.read.side_effect = open_read_rv
reset()
- passphrase = "Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.CfgPrivateKeyCreator.passphrase"
-
- @patch(passphrase, None)
- def inner():
- self.assertEqual(pkc.create_data(entry, metadata), "privatekey")
- pkc.XMLMatch.assert_called_with(metadata)
- pkc.get_specificity.assert_called_with(metadata,
- pkc.XMLMatch.return_value)
- pkc._gen_keypair.assert_called_with(metadata,
- pkc.XMLMatch.return_value)
- self.assertItemsEqual(mock_open.call_args_list,
- [call(privkey + ".pub"), call(privkey)])
- pkc.pubkey_creator.get_filename.assert_called_with(group="foo")
- pkc.pubkey_creator.write_data.assert_called_with(
- "ssh-rsa publickey pubkey.filename\n", group="foo")
- pkc.write_data.assert_called_with("privatekey", group="foo")
- mock_rmtree.assert_called_with(datastore)
-
- reset()
- self.assertEqual(pkc.create_data(entry, metadata, return_pair=True),
- ("ssh-rsa publickey pubkey.filename\n",
- "privatekey"))
- pkc.XMLMatch.assert_called_with(metadata)
- pkc.get_specificity.assert_called_with(metadata,
- pkc.XMLMatch.return_value)
- pkc._gen_keypair.assert_called_with(metadata,
- pkc.XMLMatch.return_value)
- self.assertItemsEqual(mock_open.call_args_list,
- [call(privkey + ".pub"), call(privkey)])
- pkc.pubkey_creator.get_filename.assert_called_with(group="foo")
- pkc.pubkey_creator.write_data.assert_called_with(
- "ssh-rsa publickey pubkey.filename\n",
- group="foo")
- pkc.write_data.assert_called_with("privatekey", group="foo")
- mock_rmtree.assert_called_with(datastore)
-
- inner()
-
- if HAS_CRYPTO:
- @patch(passphrase, "foo")
- @patch("Bcfg2.Server.Encryption.ssl_encrypt")
- def inner2(mock_ssl_encrypt):
- reset()
- mock_ssl_encrypt.return_value = "encryptedprivatekey"
- Bcfg2.Server.Plugins.Cfg.CfgPrivateKeyCreator.HAS_CRYPTO = True
- self.assertEqual(pkc.create_data(entry, metadata),
- "encryptedprivatekey")
- pkc.XMLMatch.assert_called_with(metadata)
- pkc.get_specificity.assert_called_with(
- metadata,
- pkc.XMLMatch.return_value)
- pkc._gen_keypair.assert_called_with(metadata,
- pkc.XMLMatch.return_value)
- self.assertItemsEqual(mock_open.call_args_list,
- [call(privkey + ".pub"), call(privkey)])
- pkc.pubkey_creator.get_filename.assert_called_with(group="foo")
- pkc.pubkey_creator.write_data.assert_called_with(
- "ssh-rsa publickey pubkey.filename\n", group="foo")
- pkc.write_data.assert_called_with("encryptedprivatekey",
- group="foo", ext=".crypt")
- mock_ssl_encrypt.assert_called_with("privatekey", "foo")
- mock_rmtree.assert_called_with(datastore)
-
- inner2()
+ self.assertEqual(pkc.create_data(entry, metadata), "privatekey")
+ pkc.XMLMatch.assert_called_with(metadata)
+ pkc.get_specificity.assert_called_with(metadata)
+ pkc._gen_keypair.assert_called_with(metadata,
+ pkc.XMLMatch.return_value)
+ self.assertItemsEqual(mock_open.call_args_list,
+ [call(privkey + ".pub"), call(privkey)])
+ pkc.pubkey_creator.get_filename.assert_called_with(group="foo")
+ pkc.pubkey_creator.write_data.assert_called_with(
+ "ssh-rsa publickey pubkey.filename\n", group="foo")
+ pkc.write_data.assert_called_with("privatekey", group="foo")
+ mock_rmtree.assert_called_with(datastore)
+
+ reset()
+ self.assertEqual(pkc.create_data(entry, metadata, return_pair=True),
+ ("ssh-rsa publickey pubkey.filename\n",
+ "privatekey"))
+ pkc.XMLMatch.assert_called_with(metadata)
+ pkc.get_specificity.assert_called_with(metadata,
+ pkc.XMLMatch.return_value)
+ pkc._gen_keypair.assert_called_with(metadata,
+ pkc.XMLMatch.return_value)
+ self.assertItemsEqual(mock_open.call_args_list,
+ [call(privkey + ".pub"), call(privkey)])
+ pkc.pubkey_creator.get_filename.assert_called_with(group="foo")
+ pkc.pubkey_creator.write_data.assert_called_with(
+ "ssh-rsa publickey pubkey.filename\n",
+ group="foo")
+ pkc.write_data.assert_called_with("privatekey", group="foo")
+ mock_rmtree.assert_called_with(datastore)
diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py
index 72be50299..170a31c3f 100644
--- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py
+++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py
@@ -3,7 +3,7 @@ import sys
import errno
import lxml.etree
import Bcfg2.Options
-from Bcfg2.Compat import walk_packages
+from Bcfg2.Compat import walk_packages, ConfigParser
from mock import Mock, MagicMock, patch
from Bcfg2.Server.Plugins.Cfg import *
from Bcfg2.Server.Plugin import PluginExecutionError, Specificity
@@ -19,7 +19,7 @@ while path != "/":
path = os.path.dirname(path)
from common import *
from TestPlugin import TestSpecificData, TestEntrySet, TestGroupSpool, \
- TestPullTarget
+ TestPullTarget, TestStructFile
class TestCfgBaseFileMatcher(TestSpecificData):
@@ -172,6 +172,7 @@ class TestCfgVerifier(TestCfgBaseFileMatcher):
class TestCfgCreator(TestCfgBaseFileMatcher):
test_obj = CfgCreator
path = "/foo/bar/test.txt"
+ should_monitor = False
def setUp(self):
TestCfgBaseFileMatcher.setUp(self)
@@ -245,6 +246,101 @@ class TestCfgCreator(TestCfgBaseFileMatcher):
self.assertRaises(CfgCreationError, cc.write_data, data)
+class TestXMLCfgCreator(TestCfgCreator, TestStructFile):
+ test_obj = XMLCfgCreator
+
+ @patch("Bcfg2.Server.Plugins.Cfg.CfgCreator.handle_event")
+ @patch("Bcfg2.Server.Plugin.helpers.StructFile.HandleEvent")
+ def test_handle_event(self, mock_HandleEvent, mock_handle_event):
+ cc = self.get_obj()
+ evt = Mock()
+ cc.handle_event(evt)
+ mock_HandleEvent.assert_called_with(cc, evt)
+ mock_handle_event.assert_called_with(cc, evt)
+
+ def test_get_specificity(self):
+ cc = self.get_obj()
+ metadata = Mock()
+
+ def reset():
+ metadata.group_in_category.reset_mock()
+
+ category = "%s.%s.category" % (self.test_obj.__module__,
+ self.test_obj.__name__)
+ @patch(category, None)
+ def inner():
+ cc.xdata = lxml.etree.Element("PrivateKey")
+ self.assertItemsEqual(cc.get_specificity(metadata),
+ dict(host=metadata.hostname))
+ inner()
+
+ @patch(category, "foo")
+ def inner2():
+ cc.xdata = lxml.etree.Element("PrivateKey")
+ self.assertItemsEqual(cc.get_specificity(metadata),
+ dict(group=metadata.group_in_category.return_value,
+ prio=50))
+ metadata.group_in_category.assert_called_with("foo")
+
+ reset()
+ cc.xdata = lxml.etree.Element("PrivateKey", perhost="true")
+ self.assertItemsEqual(cc.get_specificity(metadata),
+ dict(host=metadata.hostname))
+
+ reset()
+ cc.xdata = lxml.etree.Element("PrivateKey", category="bar")
+ self.assertItemsEqual(cc.get_specificity(metadata),
+ dict(group=metadata.group_in_category.return_value,
+ prio=50))
+ metadata.group_in_category.assert_called_with("bar")
+
+ reset()
+ cc.xdata = lxml.etree.Element("PrivateKey", prio="10")
+ self.assertItemsEqual(cc.get_specificity(metadata),
+ dict(group=metadata.group_in_category.return_value,
+ prio=10))
+ metadata.group_in_category.assert_called_with("foo")
+
+ reset()
+ cc.xdata = lxml.etree.Element("PrivateKey")
+ metadata.group_in_category.return_value = ''
+ self.assertItemsEqual(cc.get_specificity(metadata),
+ dict(host=metadata.hostname))
+ metadata.group_in_category.assert_called_with("foo")
+
+ inner2()
+
+ def _test_cfg_property(self, name):
+ """ generic test function to test both category and passphrase
+ properties """
+ cc = self.get_obj()
+ cc.setup = Mock()
+ cc.setup.cfp = ConfigParser.ConfigParser()
+ self.assertIsNone(getattr(cc, name))
+
+ cc.setup.reset_mock()
+ cc.setup.cfp.add_section("cfg")
+ cc.setup.cfp.set("cfg", name, "foo")
+ self.assertEqual(getattr(cc, name), "foo")
+
+ if cc.cfg_section:
+ cc.setup.cfp.add_section(cc.cfg_section)
+ cc.setup.cfp.set(cc.cfg_section, name, "bar")
+ self.assertEqual(getattr(cc, name), "bar")
+
+ def test_category(self):
+ self._test_cfg_property("category")
+
+ @patchIf(HAS_CRYPTO, "Bcfg2.Server.Encryption.get_passphrases")
+ def test_passphrase(self, mock_get_passphrases):
+ cc = self.get_obj()
+ if HAS_CRYPTO and cc.encryptable:
+ mock_get_passphrases.return_value = dict(foo="foo", bar="bar")
+ self._test_cfg_property("passphrase")
+ else:
+ self.assertIsNone(getattr(cc, name))
+
+
class TestCfgDefaultInfo(TestCfgInfo):
test_obj = CfgDefaultInfo
diff --git a/tools/upgrade/1.4/README b/tools/upgrade/1.4/README
index 8dde8b8b5..b03cb9b74 100644
--- a/tools/upgrade/1.4/README
+++ b/tools/upgrade/1.4/README
@@ -6,5 +6,9 @@ migrate_decisions.py
files into structured XML
convert_bundles.py
- - Remove deprecated explicit bundle names, renames .genshi bundles
+ - Remove deprecated explicit bundle names, rename .genshi bundles
to .xml
+
+migrate_sslca.py
+ - Migrate from the standalone SSLCA plugin to the built-in SSL
+ certificate generation abilities of the Cfg plugin \ No newline at end of file
diff --git a/tools/upgrade/1.4/migrate_sslca.py b/tools/upgrade/1.4/migrate_sslca.py
new file mode 100755
index 000000000..958228c86
--- /dev/null
+++ b/tools/upgrade/1.4/migrate_sslca.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import shutil
+import Bcfg2.Options
+
+
+def main():
+ parser = Bcfg2.Options.get_parser(
+ description="Migrate from the SSLCA plugin to built-in Cfg SSL cert "
+ "generation")
+ parser.add_options([Bcfg2.Options.Common.repository])
+ parser.parse()
+
+ sslcadir = os.path.join(Bcfg2.Options.setup.repository, 'SSLCA')
+ cfgdir = os.path.join(Bcfg2.Options.setup.repository, 'Cfg')
+ for root, _, files in os.walk(sslcadir):
+ if not files:
+ continue
+ newpath = cfgdir + root[len(sslcadir):]
+ if not os.path.exists(newpath):
+ print("Creating %s and copying contents from %s" % (newpath, root))
+ shutil.copytree(root, newpath)
+ else:
+ print("Copying contents from %s to %s" % (root, newpath))
+ for fname in files:
+ newfpath = os.path.exists(os.path.join(newpath, fname))
+ if newfpath:
+ print("%s already exists, skipping" % newfpath)
+ else:
+ shutil.copy(os.path.join(root, fname), newpath)
+ cert = os.path.join(newpath, "cert.xml")
+ newcert = os.path.join(newpath, "sslcert.xml")
+ key = os.path.join(newpath, "key.xml")
+ newkey = os.path.join(newpath, "sslkey.xml")
+ if os.path.exists(cert):
+ os.rename(cert, newcert)
+ if os.path.exists(key):
+ os.rename(key, newkey)
+
+
+if __name__ == '__main__':
+ sys.exit(main())