diff options
-rw-r--r-- | doc/server/encryption.txt | 20 | ||||
-rw-r--r-- | doc/server/plugins/connectors/properties.txt | 5 | ||||
-rw-r--r-- | doc/server/plugins/generators/cfg.txt | 6 | ||||
-rw-r--r-- | schemas/privkey.xsd | 17 | ||||
-rw-r--r-- | schemas/types.xsd | 7 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Cfg/CfgPrivateKeyCreator.py | 6 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Properties.py | 37 | ||||
-rwxr-xr-x | src/sbin/bcfg2-crypt | 19 | ||||
-rw-r--r-- | testsuite/Testsrc/Testlib/TestEncryption.py | 5 | ||||
-rw-r--r-- | testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py | 14 |
10 files changed, 92 insertions, 44 deletions
diff --git a/doc/server/encryption.txt b/doc/server/encryption.txt index bc18e140c..e84b9fb31 100644 --- a/doc/server/encryption.txt +++ b/doc/server/encryption.txt @@ -203,6 +203,26 @@ get a list of valid algorithms, you can run:: openssl list-cipher-algorithms | grep -v ' => ' | \ tr 'A-Z-' 'a-z_' | sort -u +Lax vs. Strict decryption +------------------------- + +By default, Bcfg2 expects to be able to decrypt every encrypted +datum. Depending on how encryption is implemented at your site, +though, that may not be possible. (For instance, if you use +encryption to protect data for your production environment from your +staging Bcfg2 server, then you would not expect the staging server to +be able to decrypt everything.) In this case, you want to enable lax +decryption in the ``[encryption]`` section of ``bcfg2.conf``: + + [encryption] + decrypt = lax + +This causes a failed decrypt to produce a warning only, not an error. + +This can be overridden by individual XML files by setting +``decrypt="strict"`` on the top-level tag (or, vice-versa; if strict +is the default an XML file can specify ``decrypt="lax"``. + Encryption API ============== diff --git a/doc/server/plugins/connectors/properties.txt b/doc/server/plugins/connectors/properties.txt index 1d276697a..da511736d 100644 --- a/doc/server/plugins/connectors/properties.txt +++ b/doc/server/plugins/connectors/properties.txt @@ -290,9 +290,8 @@ decrypted, parsing of the file is aborted. If you wish for parsing to continue, with unencryptable elements simply skipped, then you can set decryption to *lax* in one of two ways: -* Set ``decrypt=lax`` in the ``[properties]`` section of - ``bcfg2.conf`` to set lax decryption on all Properties files by - default; or +* Set ``decrypt=lax`` in the ``[encryption]`` section of + ``bcfg2.conf`` to set lax decryption on all files by default; or * Set the ``decrypt="lax"`` attribute on the top-level ``Properties`` tag of a Properties file to set lax decryption for a single file. diff --git a/doc/server/plugins/generators/cfg.txt b/doc/server/plugins/generators/cfg.txt index dcaeef4f8..e843b1d2d 100644 --- a/doc/server/plugins/generators/cfg.txt +++ b/doc/server/plugins/generators/cfg.txt @@ -411,6 +411,9 @@ structured as follows: .. xml:element:: PrivateKey :linktotype: +See :ref:`server-encryption` for more details on encryption in Bcfg2 +in general. + ``pubkey.xml`` ~~~~~~~~~~~~~~~ @@ -579,9 +582,6 @@ influenced by several options in the ``[sshkeys]`` section of | | It is best to pick a category that all clients have a | | | | | group from. | | | +----------------+---------------------------------------------------------+-----------------------+------------+ -| ``decrypt`` | If decrypt is set to ``lax``, then a key that cannot be | ``strict`` or ``lax`` | ``strict`` | -| | decrypted will produce a warning instead of an error. | | | -+----------------+---------------------------------------------------------+-----------------------+------------+ Deltas ====== diff --git a/schemas/privkey.xsd b/schemas/privkey.xsd index b8d9e317d..f6de534c1 100644 --- a/schemas/privkey.xsd +++ b/schemas/privkey.xsd @@ -5,6 +5,8 @@ </xsd:documentation> </xsd:annotation> + <xsd:include schemaLocation="types.xsd"/> + <xsd:complexType name="PrivateKeyGroupType"> <xsd:annotation> <xsd:documentation> @@ -133,6 +135,21 @@ </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> + <xsd:attribute name="decrypt" type="EncryptStrictnessEnum"> + <xsd:annotation> + <xsd:documentation> + Override the global strict/lax decryption setting in + ``bcfg2.conf``. + </xsd:documentation> + </xsd:annotation> + </xsd:attribute> </xsd:complexType> </xsd:element> </xsd:schema> diff --git a/schemas/types.xsd b/schemas/types.xsd index a6070279a..524b327c5 100644 --- a/schemas/types.xsd +++ b/schemas/types.xsd @@ -96,6 +96,13 @@ </xsd:restriction> </xsd:simpleType> + <xsd:simpleType name="EncryptStrictnessEnum"> + <xsd:restriction base="xsd:string"> + <xsd:enumeration value="strict"/> + <xsd:enumeration value="lax"/> + </xsd:restriction> + </xsd:simpleType> + <xsd:complexType name='ActionType'> <xsd:annotation> <xsd:documentation> diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgPrivateKeyCreator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgPrivateKeyCreator.py index 597f8f57b..aaeb65cd6 100644 --- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgPrivateKeyCreator.py +++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgPrivateKeyCreator.py @@ -215,8 +215,10 @@ class CfgPrivateKeyCreator(CfgCreator, StructFile): def Index(self): StructFile.Index(self) if HAS_CRYPTO: - strict = SETUP.cfp.get("sshkeys", "decrypt", - default="strict") == "strict" + strict = self.xdata.get( + "decrypt", + SETUP.cfp.get(Bcfg2.Encryption.CFG_SECTION, "decrypt", + default="strict")) == "strict" for el in self.xdata.xpath("//*[@encrypted]"): try: el.text = self._decrypt(el).encode('ascii', diff --git a/src/lib/Bcfg2/Server/Plugins/Properties.py b/src/lib/Bcfg2/Server/Plugins/Properties.py index b3c0a6ae5..a51dd8adc 100644 --- a/src/lib/Bcfg2/Server/Plugins/Properties.py +++ b/src/lib/Bcfg2/Server/Plugins/Properties.py @@ -205,28 +205,27 @@ class XMLPropertyFile(Bcfg2.Server.Plugin.StructFile, PropertyFile): def Index(self): Bcfg2.Server.Plugin.StructFile.Index(self) - if self.xdata.get("encryption", "false").lower() != "false": + strict = self.xdata.get( + "decrypt", + SETUP.cfp.get(Bcfg2.Encryption.CFG_SECTION, "decrypt", + default="strict")) == "strict" + for el in self.xdata.xpath("//*[@encrypted]"): if not HAS_CRYPTO: raise PluginExecutionError("Properties: M2Crypto is not " "available: %s" % self.name) - strict = self.xdata.get( - "decrypt", - SETUP.cfp.get("properties", "decrypt", - default="strict")) == "strict" - for el in self.xdata.xpath("//*[@encrypted]"): - try: - el.text = self._decrypt(el).encode('ascii', - 'xmlcharrefreplace') - except UnicodeDecodeError: - LOGGER.info("Properties: Decrypted %s to gibberish, " - "skipping" % el.tag) - except Bcfg2.Encryption.EVPError: - msg = "Properties: Failed to decrypt %s element in %s" % \ - (el.tag, self.name) - if strict: - raise PluginExecutionError(msg) - else: - LOGGER.warning(msg) + try: + el.text = self._decrypt(el).encode('ascii', + 'xmlcharrefreplace') + except UnicodeDecodeError: + LOGGER.info("Properties: Decrypted %s to gibberish, " + "skipping" % el.tag) + except Bcfg2.Encryption.EVPError: + msg = "Properties: Failed to decrypt %s element in %s" % \ + (el.tag, self.name) + if strict: + raise PluginExecutionError(msg) + else: + LOGGER.warning(msg) Index.__doc__ = Bcfg2.Server.Plugin.StructFile.Index.__doc__ def _decrypt(self, element): diff --git a/src/sbin/bcfg2-crypt b/src/sbin/bcfg2-crypt index 9eab7bd29..fde6af582 100755 --- a/src/sbin/bcfg2-crypt +++ b/src/sbin/bcfg2-crypt @@ -55,7 +55,7 @@ class Encryptor(object): def set_passphrase(self): """ set the passphrase for the current file """ - if (not self.setup.cfp.has_section("encryption") or + if (not self.setup.cfp.has_section(Bcfg2.Encryption.CFG_SECTION) or len(Bcfg2.Encryption.get_passphrases(self.setup)) == 0): self.logger.error("No passphrases available in %s" % self.setup['configfile']) @@ -70,9 +70,11 @@ class Encryptor(object): self.pname = self.setup['passphrase'] if self.pname: - if self.setup.cfp.has_option("encryption", self.pname): - self.passphrase = self.setup.cfp.get("encryption", - self.pname) + if self.setup.cfp.has_option(Bcfg2.Encryption.CFG_SECTION, + self.pname): + self.passphrase = \ + self.setup.cfp.get(Bcfg2.Encryption.CFG_SECTION, + self.pname) self.logger.debug("Using passphrase %s specified on command " "line" % self.pname) return True @@ -241,8 +243,10 @@ class Encryptor(object): self.logger.info("No passphrase given on command line or " "found in file") return False - elif self.setup.cfp.has_option("encryption", pname): - passphrase = self.setup.cfp.get("encryption", pname) + elif self.setup.cfp.has_option(Bcfg2.Encryption.CFG_SECTION, + pname): + passphrase = self.setup.cfp.get(Bcfg2.Encryption.CFG_SECTION, + pname) else: self.logger.error("Could not find passphrase %s in %s" % (pname, self.setup['configfile'])) @@ -339,13 +343,12 @@ class PropertiesEncryptor(Encryptor): # find root element while xdata.getparent() != None: xdata = xdata.getparent() - xdata.set("encryption", "true") return lxml.etree.tostring(xdata, xml_declaration=False, pretty_print=True).decode('UTF-8') def _get_passphrase(self, chunk): - pname = chunk.get("encrypted") or chunk.get("encryption") + pname = chunk.get("encrypted") if pname and pname.lower() != "true": return pname return None diff --git a/testsuite/Testsrc/Testlib/TestEncryption.py b/testsuite/Testsrc/Testlib/TestEncryption.py index 778d5b963..c03aa66e1 100644 --- a/testsuite/Testsrc/Testlib/TestEncryption.py +++ b/testsuite/Testsrc/Testlib/TestEncryption.py @@ -193,11 +193,6 @@ baz # test that different algorithms are used mock_passphrases.reset_mock() - self.assertRaises(EVPError, - bruteforce_decrypt, - crypted, setup=setup, algorithm=self.algo) - - mock_passphrases.reset_mock() crypted = ssl_encrypt(self.plaintext, passwd, algorithm=self.algo) self.assertEqual(self.plaintext, bruteforce_decrypt(crypted, setup=setup, diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py index 1a8619097..d66780a20 100644 --- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py +++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestProperties.py @@ -247,14 +247,17 @@ class TestXMLPropertyFile(TestPropertyFile, TestStructFile): TestStructFile.test_Index(self) pf = self.get_obj() - pf.xdata = lxml.etree.Element("Properties", encryption="true") + pf.xdata = lxml.etree.Element("Properties") + lxml.etree.SubElement(pf.xdata, "Crypted", encrypted="foo") pf.data = lxml.etree.tostring(pf.xdata) # extra test: crypto is not available, but properties file is # encrypted has_crypto = Bcfg2.Server.Plugins.Properties.HAS_CRYPTO Bcfg2.Server.Plugins.Properties.HAS_CRYPTO = False - self.assertRaises(PluginExecutionError, pf.Index) - Bcfg2.Server.Plugins.Properties.HAS_CRYPTO = has_crypto + try: + self.assertRaises(PluginExecutionError, pf.Index) + finally: + Bcfg2.Server.Plugins.Properties.HAS_CRYPTO = has_crypto @skipUnless(HAS_CRYPTO, "No crypto libraries found, skipping") def test_Index_crypto(self): @@ -262,7 +265,7 @@ class TestXMLPropertyFile(TestPropertyFile, TestStructFile): pf._decrypt = Mock() pf._decrypt.return_value = 'plaintext' pf.data = ''' -<Properties encryption="true" decrypt="strict"> +<Properties decrypt="strict"> <Crypted encrypted="foo"> crypted <Plain foo="bar">plain</Plain> @@ -274,6 +277,9 @@ class TestXMLPropertyFile(TestPropertyFile, TestStructFile): </Plain> </Properties>''' + print "HAS_CRYPTO: %s" % HAS_CRYPTO + print "Properties HAS_CRYPTO: %s" % Bcfg2.Server.Plugins.Properties.HAS_CRYPTO + # test successful decryption pf.Index() self.assertItemsEqual(pf._decrypt.call_args_list, |