summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris St. Pierre <chris.a.st.pierre@gmail.com>2012-09-20 16:23:25 -0400
committerChris St. Pierre <chris.a.st.pierre@gmail.com>2012-09-20 16:23:25 -0400
commit48c584194e4e5ec4b3561b2d6448ba4728ab0739 (patch)
treea4e2900d06d260ebde50cdf861769ef096c638af
parentcf0583059bbcecbb655924afdbf16d51122703b2 (diff)
downloadbcfg2-48c584194e4e5ec4b3561b2d6448ba4728ab0739.tar.gz
bcfg2-48c584194e4e5ec4b3561b2d6448ba4728ab0739.tar.bz2
bcfg2-48c584194e4e5ec4b3561b2d6448ba4728ab0739.zip
Encryption: improved docs, made algorithm configurable
-rw-r--r--doc/server/encryption.txt209
-rw-r--r--doc/server/index.txt1
-rw-r--r--doc/server/plugins/connectors/properties.txt48
-rw-r--r--doc/server/plugins/generators/cfg.txt64
-rwxr-xr-xsrc/lib/Bcfg2/Encryption.py149
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedCheetahGenerator.py6
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenerator.py31
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenshiGenerator.py26
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Properties.py61
-rwxr-xr-xsrc/sbin/bcfg2-crypt36
10 files changed, 446 insertions, 185 deletions
diff --git a/doc/server/encryption.txt b/doc/server/encryption.txt
new file mode 100644
index 000000000..bc18e140c
--- /dev/null
+++ b/doc/server/encryption.txt
@@ -0,0 +1,209 @@
+.. -*- mode: rst -*-
+
+.. _server-encryption:
+
+=====================
+Bcfg2 Data Encryption
+=====================
+
+.. versionadded:: 1.3.0
+
+Bcfg2 supports encrypting some data on the disk, which can help
+protect sensitive data from other people who need access to the Bcfg2
+repository but are perhaps not authorized to see all data. It
+supports multiple passphrases, which can be used to enforce
+separations between teams, environments, etc.
+
+.. note::
+
+ This feature is *not* intended to secure the files against a
+ malicious attacker who has gained access to your Bcfg2 server, as
+ the encryption passphrases are held in plaintext in
+ ``bcfg2.conf``. This is only intended to make it easier to use a
+ single Bcfg2 repository with multiple admins who should not
+ necessarily have access to each other's sensitive data.
+
+Two types of data can be encrypted:
+
+* :ref:`server-plugins-generators-cfg` files can be encrypted
+ as whole files. See :ref:`server-plugins-generators-cfg-encryption`
+ for more details.
+* :ref:`server-plugins-connectors-properties` data can be encrypted on
+ a per-element basis. See
+ :ref:`server-plugins-connectors-properties-encryption` for more
+ details.
+
+In general, Properties encryption is preferred for a few reasons:
+
+* It plays nicely with your VCS. If you change an encrypted Cfg file,
+ then all you can see in your VCS log is that the file changed, no
+ details about how it changed. With an encrypted Properties file,
+ you can see which element changed (although obviously not the
+ changed content).
+* It is faster when you have more than one passphrase. When
+ decrypting a Cfg file, Bcfg2 simply brute-forces it with all known
+ passphrases; when decrypting a Properties element, the passphrase is
+ given by name so only one passphrase must be tried.
+* A Cfg file can only be encrypted with a single passphrase;
+ Properties files can use different passphrases for different
+ elements. If you are using different passphrases to segregate data
+ amongst different teams, this lets teams collaborate more closely on
+ files and other data.
+
+.. _bcfg2-crypt:
+
+bcfg2-crypt
+===========
+
+Encrypting and decrypting :ref:`server-plugins-generators-cfg` and
+:ref:`server-plugins-connectors-properties` files can be done with the
+``bcfg2-crypt`` tool, which mostly tries to do the right thing. I.e.,
+it encrypts plaintext files, decrypts encrypted files, and
+automatically discovers if a file is Cfg or Properties. Its usage is
+thus generally very simple, e.g.::
+
+ bcfg2-crypt foo.conf
+ bcfg2-crypt foo.xml
+
+Since the behavior of ``bcfg2-crypt`` varies significantly depending
+on whether you are dealing with a Cfg or Properties files, these are
+documented separately below. It's also well worthwhile to familiarize
+yourself with the man page for ``bcfg2-crypt``.
+
+Encrypting Cfg Files
+--------------------
+
+To encrypt a Cfg file, you can simply run::
+
+ bcfg2-crypt foo.conf
+
+This will write the encrypted data to ``foo.conf.crypt``. Once you
+are satisfied that the file has been encrypted as you wish, you can
+remove the plaintext version, or you can use the ``--remove`` flag of
+``bcfg2-crypt``.
+
+To decrypt a file, simply run ``bcfg2-crypt`` again::
+
+ bcfg2-crypt foo.conf.crypt
+
+On Cfg files, ``bcfg2-crypt`` is more-or-less equivalent to the
+following commands (encryption and decryption, respectively)::
+
+ openssl enc -aes-256-cbc -k <passphrase> -in foo.conf \
+ -out foo.conf.crypt -a
+ openssl enc -d -aes-256-cbc -k <passphrase> -in foo.conf.crypt \
+ -out foo.conf -a
+
+Those commands can be used in lieu of ``bcfg2-crypt`` if you hate
+convenience.
+
+Encrypting Properties Files
+---------------------------
+
+To encrypt or decrypt a properties file, simply run::
+
+ bcfg2-crypt foo.xml
+
+If the top-level tag of a Properties file is not ``<Properties>``,
+then you need to use the ``--properties`` flag to ``bcfg2-crypt``::
+
+ bcfg2-crypt --properties foo.xml
+
+The first time you run ``bcfg2-crypt`` on a Properties file, it will
+encrypt all character data of all elements. Additionally, it will add
+``encrypted="<key name>"`` to each element that has encrypted character
+data. It also adds ``encryption="true"`` to the top-level
+``<Properties>`` tag as a flag to the server that it should try to
+decrypt the data in that file. (If you are using Properties schemas,
+you will need to make sure to add support for these attributes.) On
+subsequent runs, only those elements flagged with ``encrypted="*"``
+are encrypted or decrypted.
+
+To decrypt a Properties file, simply re-run ``bcfg2-crypt``::
+
+ bcfg2-crypt foo.xml
+
+This decrypts the encrypted elements, but it does *not* remove the
+``encrypted`` attribute; this way, you can decrypt a Properties
+file, modify the contents, and then simply re-run ``bcfg2-crypt`` to
+encrypt it again. If you added elements that you also want to be
+encrypted, you can either add the ``encrypted`` attribute to
+them manually, or run::
+
+ bcfg2-crypt --xpath '*' foo.xml
+
+You can also use the ``--xpath`` option to specify more restrictive
+XPath expressions to only encrypt a subset of elements, or to encrypt
+different elements with different passphrases. Alternatively, you can
+manally set the ``encrypted`` attribute on various elements and
+``bcfg2-crypt`` will automatically do the right thing. You can also
+run bcfg2-crypt in interactive mode to interactively select which
+attributes should be encrypted::
+
+ bcfg2-crypt -I foo.xml
+
+If you want to use different passphrases within a single Properties
+file, you must manually set the ``encrypted`` attribute.
+
+.. _server-encryption-configuration:
+
+Configuring Encryption
+======================
+
+Passphrases
+-----------
+
+To configure encryption, add a ``[encryption]`` section to
+``bcfg2.conf`` with any number of name-passphrase pairs.
+
+For instance::
+
+ [encryption]
+ foo_team=P4ssphr4se
+ bar_team=Pa55phra5e
+
+.. note::
+
+ The name of a passphrase **cannot** be "algorithm"; that
+ configuration option is reserved for configuring the cipher
+ algorithm.
+
+This would define two separate encryption passphrases, presumably for
+use by two separate teams. The passphrase names are completely
+arbitrary.
+
+Note that this does entail a chicken-and-egg problem. In order for
+the Bcfg2 server to be able to decrypt encrypted files, the
+passphrases must exist in ``bcfg2.conf`` in plaintext; but, if you're
+encrypting data, presumably you don't want to include those plaintext
+passphrases in your Bcfg2 repository, so you'll want to encrypt
+``bcfg2.conf``. The best way to solve this is:
+
+#. On your Bcfg2 server, manually add the ``[encryption]`` section to
+ ``bcfg2.conf`` and restart the Bcfg2 server.
+#. Update ``bcfg2.conf`` in your Bcfg2 repository with the
+ passphrases, and encrypt it.
+
+The first (manual) step breaks the mutual dependency.
+
+Algorithm
+---------
+
+By default, Bcfg2 uses the AES-256-CBC cipher algorithm. If you wish
+to change this, you can set the ``algorithm`` option in the
+``[encryption]`` section of ``bcfg2.conf``::
+
+ [encryption]
+ algorithm = bf_cbc
+
+The value of ``algorithm`` must be a valid OpenSSL cipher algorithm
+according the naming model of the Python :mod:`M2Crypto` module. To
+get a list of valid algorithms, you can run::
+
+ openssl list-cipher-algorithms | grep -v ' => ' | \
+ tr 'A-Z-' 'a-z_' | sort -u
+
+Encryption API
+==============
+
+.. automodule:: Bcfg2.Encryption
diff --git a/doc/server/index.txt b/doc/server/index.txt
index b28924034..505fc8004 100644
--- a/doc/server/index.txt
+++ b/doc/server/index.txt
@@ -32,3 +32,4 @@ clients.
backends
database
caching
+ encryption
diff --git a/doc/server/plugins/connectors/properties.txt b/doc/server/plugins/connectors/properties.txt
index 9e1100610..0fb7e8511 100644
--- a/doc/server/plugins/connectors/properties.txt
+++ b/doc/server/plugins/connectors/properties.txt
@@ -171,7 +171,7 @@ in ``bcfg2.conf``::
[properties]
writes_enabled = false
-.. _server-plugins-connectors-properties-encrypted:
+.. _server-plugins-connectors-properties-encryption:
Encrypted Properties data
=========================
@@ -180,10 +180,10 @@ Encrypted Properties data
You can encrypt selected data in Properties files to protect that data
from other people who need access to the repository. See
-:ref:`server-plugins-generators-cfg-configuring-encryption` for
-details on configuring encryption passphrases. The data is decrypted
-transparently on-the-fly by the server; you never need to decrypt the
-data in your templates.
+:ref:`server-encryption-configuration` for details on configuring
+encryption passphrases. The data is decrypted transparently
+on-the-fly by the server; you never need to decrypt the data in your
+templates.
.. note::
@@ -202,43 +202,11 @@ with the other data in the file. Only character content of an element
can be encrypted; attribute content and XML elements themselves cannot
be encrypted.
-To encrypt a file, use ``bcfg2-crypt``, e.g.::
+To encrypt or decrypt a file, use :ref:`bcfg2-crypt`.
- bcfg2-crypt foo.xml
+See :ref:`server-encryption` for more details on encryption in Bcfg2
+in general.
-If the top-level tag of a Properties file is not ``<Properties>``,
-then you need to use the ``--properties`` flag to ``bcfg2-crypt``::
-
- bcfg2-crypt --properties foo.xml
-
-The first time you run ``bcfg2-crypt`` on a Properties file, it will
-encrypt all character data of all elements. Additionally, it will add
-``encrypted="<key name>"`` to each element that has encrypted character
-data. It also adds ``encryption="true"`` to the top-level
-``<Properties>`` tag as a flag to the server that it should try to
-decrypt the data in that file. (If you are using Properties schemas,
-you will need to make sure to add support for these attributes.) On
-subsequent runs, only those elements flagged with ``encrypted="*"``
-are encrypted or decrypted.
-
-To decrypt a Properties file, simply re-run ``bcfg2-crypt``::
-
- bcfg2-crypt foo.xml
-
-This decrypts the encrypted elements, but it does *not* remove the
-``encrypted`` attribute; this way, you can decrypt a Properties
-file, modify the contents, and then simply re-run ``bcfg2-crypt`` to
-encrypt it again. If you added elements that you also want to be
-encrypted, you can either add the ``encrypted`` attribute to
-them manually, or run::
-
- bcfg2-crypt --xpath '*' foo.xml
-
-You can also use the ``--xpath`` option to specify more restrictive
-XPath expressions to only encrypt a subset of elements, or to encrypt
-different elements with different passphrases. Alternatively, you can
-manally set the ``encrypted`` attribute on various elements and
-``bcfg2-crypt`` will automatically do the right thing.
Accessing Properties contents from TGenshi
==========================================
diff --git a/doc/server/plugins/generators/cfg.txt b/doc/server/plugins/generators/cfg.txt
index 6c848fddb..2987e21b9 100644
--- a/doc/server/plugins/generators/cfg.txt
+++ b/doc/server/plugins/generators/cfg.txt
@@ -139,6 +139,8 @@ using different host-specific or group-specific files. For example:
Cfg/etc/fstab/fstab.H_host.example.com.genshi
Cfg/etc/fstab/fstab.G50_server.cheetah
+.. _server-plugins-generators-cfg-encryption:
+
Encrypted Files
===============
@@ -146,7 +148,7 @@ Encrypted Files
Bcfg2 allows you to encrypt files stored in ``Cfg/`` to protect the
data in them from other people who need access to the repository. See
-also :ref:`server-plugins-connectors-properties-encrypted` for
+also :ref:`server-plugins-connectors-properties-encryption` for
information on encrypting elements in Properties files, which is often
more friendly for tracking changes in a VCS.
@@ -159,6 +161,9 @@ more friendly for tracking changes in a VCS.
single Bcfg2 repository with multiple admins who should not
necessarily have access to each other's sensitive data.
+See :ref:`server-encryption` for more details on encryption in Bcfg2
+in general.
+
Encrypting Files
----------------
@@ -175,62 +180,7 @@ either order, e.g.::
Cfg/etc/foo.conf/foo.conf.G10_foo.genshi.crypt
Cfg/etc/foo.conf/foo.conf.H_bar.example.com.crypt.cheetah
-To encrypt a file, you can use ``bcfg2-crypt``, e.g.::
-
- bcfg2-crypt foo.conf
-
-Once you are satisfied that the file has been encrypted as you wish,
-you can remove the plaintext version, or you can use the ``--remove``
-flag of ``bcfg2-crypt``.
-
-To decrypt a file, simply run ``bcfg2-crypt`` again::
-
- bcfg2-crypt foo.conf
-
-See the ``bcfg2-crypt`` man page for more information.
-
-``bcfg2-crypt`` simply performs an AES256 encryption, and is
-more-or-less equivalent to the following commands (encryption and
-decryption, respectively::
-
- openssl enc -aes-256-cbc -k <passphrase> -in foo.conf -out foo.conf.crypt -a
- openssl enc -d -aes-256-cbc -k <passphrase> -in foo.conf.crypt -out foo.conf -a
-
-.. _server-plugins-generators-cfg-configuring-encryption:
-
-Configuring Encryption
-----------------------
-
-To configure encryption, add a ``[encryption]`` section to
-``bcfg2.conf`` with any number of name-passphrase pairs. When
-decrypting a file, _all_ passphrases will be tried; the passphrase
-name is currently purely cosmetic, but at some point in the future the
-ability to give Bcfg2 a "hint" about which passphrase to use will be
-added.
-
-For instance::
-
- [encryption]
- foo_team=P4ssphr4se
- bar_team=Pa55phra5e
-
-This would define two separate encryption passphrases, presumably for
-use by two separate teams. The passphrase names are completely
-arbitrary.
-
-Note that this does entail a chicken-and-egg problem. In order for
-the Bcfg2 server to be able to decrypt encrypted files, the
-passphrases must exist in ``bcfg2.conf`` in plaintext; but, if you're
-encrypting data, presumably you don't want to include those plaintext
-passphrases in your Bcfg2 repository, so you'll want to encrypt
-``bcfg2.conf``. The best way to solve this is:
-
-#. On your Bcfg2 server, manually add the ``[encryption]`` section to
- ``bcfg2.conf`` and restart the Bcfg2 server.
-#. Update ``bcfg2.conf`` in your Bcfg2 repository with the
- passphrases, and encrypt it.
-
-The first (manual) step breaks the mutual dependency.
+To encrypt or decrypt a file, use :ref:`bcfg2-crypt`.
Deltas
======
diff --git a/src/lib/Bcfg2/Encryption.py b/src/lib/Bcfg2/Encryption.py
index 5e70a1d10..5eb7ffe8e 100755
--- a/src/lib/Bcfg2/Encryption.py
+++ b/src/lib/Bcfg2/Encryption.py
@@ -1,18 +1,37 @@
-#!/usr/bin/python -Ott
+""" Bcfg2.Encryption provides a number of convenience methods for
+handling encryption in Bcfg2. See :ref:`server-encryption` for more
+details. """
import os
-import base64
from M2Crypto import Rand
from M2Crypto.EVP import Cipher, EVPError
-from Bcfg2.Compat import StringIO, md5
+from Bcfg2.Compat import StringIO, md5, b64encode, b64decode
+#: Constant representing the encryption operation for
+#: :class:`M2Crypto.EVP.Cipher`, which uses a simple integer. This
+#: makes our code more readable.
ENCRYPT = 1
+
+#: Constant representing the decryption operation for
+#: :class:`M2Crypto.EVP.Cipher`, which uses a simple integer. This
+#: makes our code more readable.
DECRYPT = 0
+
+#: Default cipher algorithm. To get a full list of valid algorithms,
+#: you can run::
+#:
+#: openssl list-cipher-algorithms | grep -v ' => ' | \
+#: tr 'A-Z-' 'a-z_' | sort -u
ALGORITHM = "aes_256_cbc"
+
+#: Default initialization vector. For best security, you should use a
+#: unique IV for each message. :func:`ssl_encrypt` does this in an
+#: automated fashion.
IV = '\0' * 16
Rand.rand_seed(os.urandom(1024))
+
def _cipher_filter(cipher, instr):
inbuf = StringIO(instr)
outbuf = StringIO()
@@ -27,54 +46,127 @@ def _cipher_filter(cipher, instr):
outbuf.close()
return rv
+
def str_encrypt(plaintext, key, iv=IV, algorithm=ALGORITHM, salt=None):
- """ encrypt a string """
+ """ Encrypt a string with a key. For a higher-level encryption
+ interface, see :func:`ssl_encrypt`.
+
+ :param plaintext: The plaintext data to encrypt
+ :type plaintext: string
+ :param key: The key to encrypt the data with
+ :type key: string
+ :param iv: The initialization vector
+ :type iv: string
+ :param algorithm: The cipher algorithm to use
+ :type algorithm: string
+ :param salt: The salt to use
+ :type salt: string
+ :returns: string - The decrypted data
+ """
cipher = Cipher(alg=algorithm, key=key, iv=iv, op=ENCRYPT, salt=salt)
return _cipher_filter(cipher, plaintext)
-
+
+
def str_decrypt(crypted, key, iv=IV, algorithm=ALGORITHM):
- """ decrypt a string """
+ """ Decrypt a string with a key. For a higher-level decryption
+ interface, see :func:`ssl_decrypt`.
+
+ :param crypted: The raw binary encrypted data
+ :type crypted: string
+ :param key: The encryption key to decrypt with
+ :type key: string
+ :param iv: The initialization vector
+ :type iv: string
+ :param algorithm: The cipher algorithm to use
+ :type algorithm: string
+ :returns: string - The decrypted data
+ """
cipher = Cipher(alg=algorithm, key=key, iv=iv, op=DECRYPT)
return _cipher_filter(cipher, crypted)
-
+
+
def ssl_decrypt(data, passwd, algorithm=ALGORITHM):
- """ decrypt openssl-encrypted data """
+ """ Decrypt openssl-encrypted data. This can decrypt data
+ encrypted by :func:`ssl_encrypt`, or ``openssl enc``. It performs
+ a base64 decode first if the data is base64 encoded, and
+ automatically determines the salt and initialization vector (both
+ of which are embedded in the encrypted data).
+
+ :param data: The encrypted data (either base64-encoded or raw
+ binary) to decrypt
+ :type data: string
+ :param passwd: The password to use to decrypt the data
+ :type passwd: string
+ :param algorithm: The cipher algorithm to use
+ :type algorithm: string
+ :returns: string - The decrypted data
+ """
# base64-decode the data if necessary
try:
- data = base64.b64decode(data)
+ data = b64decode(data)
except TypeError:
# already decoded
pass
-
+
salt = data[8:16]
hashes = [md5(passwd + salt).digest()]
- for i in range(1,3):
- hashes.append(md5(hashes[i-1] + passwd + salt).digest())
+ for i in range(1, 3):
+ hashes.append(md5(hashes[i - 1] + passwd + salt).digest())
key = hashes[0] + hashes[1]
iv = hashes[2]
-
- return str_decrypt(data[16:], key=key, iv=iv)
+
+ return str_decrypt(data[16:], key=key, iv=iv, algorithm=algorithm)
+
def ssl_encrypt(plaintext, passwd, algorithm=ALGORITHM, salt=None):
- """ encrypt data in a format that is openssl compatible """
+ """ Encrypt data in a format that is openssl compatible.
+
+ :param plaintext: The plaintext data to encrypt
+ :type plaintext: string
+ :param passwd: The password to use to encrypt the data
+ :type passwd: string
+ :param algorithm: The cipher algorithm to use
+ :type algorithm: string
+ :param salt: The salt to use. If none is provided, one will be
+ randomly generated.
+ :type salt: bytes
+ :returns: string - The base64-encoded, salted, encrypted string.
+ The string includes a trailing newline to make it fully
+ compatible with openssl command-line tools.
+ """
if salt is None:
salt = Rand.rand_bytes(8)
-
+
hashes = [md5(passwd + salt).digest()]
- for i in range(1,3):
- hashes.append(md5(hashes[i-1] + passwd + salt).digest())
+ for i in range(1, 3):
+ hashes.append(md5(hashes[i - 1] + passwd + salt).digest())
key = hashes[0] + hashes[1]
iv = hashes[2]
-
- crypted = str_encrypt(plaintext, key=key, salt=salt, iv=iv)
- return base64.b64encode("Salted__" + salt + crypted) + "\n"
+
+ crypted = str_encrypt(plaintext, key=key, salt=salt, iv=iv,
+ algorithm=algorithm)
+ return b64encode("Salted__" + salt + crypted) + "\n"
+
+
+def get_algorithm(setup):
+ """ Get the cipher algorithm from the config file. This is used
+ in case someone uses the OpenSSL algorithm name (e.g.,
+ "AES-256-CBC") instead of the M2Crypto name (e.g., "aes_256_cbc"),
+ and to handle errors in a sensible way and deduplicate this code.
+
+ :param setup: The Bcfg2 option set to extract passphrases from
+ :type setup: Bcfg2.Options.OptionParser
+ :returns: dict - a dict of ``<passphrase name>``: ``<passphrase>``
+ """
+ return setup.cfp.get("encryption", "algorithm",
+ default=ALGORITHM).lower().replace("-", "_")
def get_passphrases(setup):
""" Get all candidate encryption passphrases from the config file.
:param setup: The Bcfg2 option set to extract passphrases from
:type setup: Bcfg2.Options.OptionParser
- returns: dict - a dict of <passphrase name>: <passphrase>
+ :returns: dict - a dict of ``<passphrase name>``: ``<passphrase>``
"""
section = "encryption"
if setup.cfp.has_section(section):
@@ -83,11 +175,13 @@ def get_passphrases(setup):
else:
return dict()
-def bruteforce_decrypt(crypted, passphrases=None, setup=None):
+
+def bruteforce_decrypt(crypted, passphrases=None, setup=None,
+ algorithm=ALGORITHM):
""" Convenience method to decrypt the given encrypted string by
trying the given passphrases or all passphrases (as returned by
- :func:`Bcfg2.Encryption.passphrases`) sequentially until one is
- found that works.
+ :func:`get_passphrases`) sequentially until one is found that
+ works.
Either ``passphrases`` or ``setup`` must be provided.
@@ -97,6 +191,8 @@ def bruteforce_decrypt(crypted, passphrases=None, setup=None):
:type passphrases: list
:param setup: A Bcfg2 option set to extract passphrases from
:type setup: Bcfg2.Options.OptionParser
+ :param algorithm: The cipher algorithm to use
+ :type algorithm: string
:returns: string - The decrypted data
:raises: :class:`M2Crypto.EVP.EVPError`, if the data cannot be decrypted
"""
@@ -104,8 +200,7 @@ def bruteforce_decrypt(crypted, passphrases=None, setup=None):
passphrases = get_passphrases(setup).values()
for passwd in passphrases:
try:
- return ssl_decrypt(crypted, passwd)
+ return ssl_decrypt(crypted, passwd, algorithm=algorithm)
except EVPError:
pass
raise EVPError("Failed to decrypt")
-
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedCheetahGenerator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedCheetahGenerator.py
index 3e714c01f..9eed633c4 100644
--- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedCheetahGenerator.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedCheetahGenerator.py
@@ -2,12 +2,14 @@
.cheetah.crypt files)"""
from Bcfg2.Server.Plugins.Cfg.CfgCheetahGenerator import CfgCheetahGenerator
-from Bcfg2.Server.Plugins.Cfg.CfgEncryptedGenerator import CfgEncryptedGenerator
+from Bcfg2.Server.Plugins.Cfg.CfgEncryptedGenerator \
+ import CfgEncryptedGenerator
+
class CfgEncryptedCheetahGenerator(CfgCheetahGenerator, CfgEncryptedGenerator):
""" CfgEncryptedCheetahGenerator lets you encrypt your Cheetah
:ref:`server-plugins-generators-cfg` files on the server """
-
+
#: handle .crypt.cheetah or .cheetah.crypt files
__extensions__ = ['cheetah.crypt', 'crypt.cheetah']
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenerator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenerator.py
index 71e407d17..f8d08b394 100644
--- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenerator.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenerator.py
@@ -2,15 +2,17 @@
:ref:`server-plugins-generators-cfg` files on the server. """
import logging
-import Bcfg2.Server.Plugin
+from Bcfg2.Server.Plugin import PluginExecutionError
from Bcfg2.Server.Plugins.Cfg import CfgGenerator, SETUP
try:
- from Bcfg2.Encryption import bruteforce_decrypt, EVPError
- have_crypto = True
+ from Bcfg2.Encryption import bruteforce_decrypt, EVPError, \
+ get_algorithm
+ HAS_CRYPTO = True
except ImportError:
- have_crypto = False
+ HAS_CRYPTO = False
+
+LOGGER = logging.getLogger(__name__)
-logger = logging.getLogger(__name__)
class CfgEncryptedGenerator(CfgGenerator):
""" CfgEncryptedGenerator lets you encrypt your plaintext
@@ -21,10 +23,10 @@ class CfgEncryptedGenerator(CfgGenerator):
def __init__(self, fname, spec, encoding):
CfgGenerator.__init__(self, fname, spec, encoding)
- if not have_crypto:
- msg = "Cfg: M2Crypto is not available: %s" % entry.get("name")
- logger.error(msg)
- raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
+ if not HAS_CRYPTO:
+ msg = "Cfg: M2Crypto is not available"
+ LOGGER.error(msg)
+ raise PluginExecutionError(msg)
__init__.__doc__ = CfgGenerator.__init__.__doc__
def handle_event(self, event):
@@ -35,19 +37,20 @@ class CfgEncryptedGenerator(CfgGenerator):
except UnicodeDecodeError:
crypted = open(self.name, mode='rb').read()
except:
- logger.error("Failed to read %s" % self.name)
+ LOGGER.error("Failed to read %s" % self.name)
return
# todo: let the user specify a passphrase by name
try:
- self.data = bruteforce_decrypt(crypted, setup=SETUP)
+ self.data = bruteforce_decrypt(crypted, setup=SETUP,
+ algorithm=get_algorithm(SETUP))
except EVPError:
msg = "Failed to decrypt %s" % self.name
- logger.error(msg)
- raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
+ LOGGER.error(msg)
+ raise PluginExecutionError(msg)
handle_event.__doc__ = CfgGenerator.handle_event.__doc__
def get_data(self, entry, metadata):
if self.data is None:
- raise Bcfg2.Server.Plugin.PluginExecutionError("Failed to decrypt %s" % self.name)
+ raise PluginExecutionError("Failed to decrypt %s" % self.name)
return CfgGenerator.get_data(self, entry, metadata)
get_data.__doc__ = CfgGenerator.get_data.__doc__
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenshiGenerator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenshiGenerator.py
index 0d5d98ba6..6fd70e69f 100644
--- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenshiGenerator.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenshiGenerator.py
@@ -1,15 +1,17 @@
""" Handle encrypted Genshi templates (.crypt.genshi or .genshi.crypt
files) """
+import logging
from Bcfg2.Compat import StringIO
+from Bcfg2.Server.Plugin import PluginExecutionError
+from Bcfg2.Server.Plugins.Cfg import SETUP
from Bcfg2.Server.Plugins.Cfg.CfgGenshiGenerator import CfgGenshiGenerator
-from Bcfg2.Server.Plugins.Cfg.CfgEncryptedGenerator import CfgEncryptedGenerator
try:
- from Bcfg2.Encryption import bruteforce_decrypt
+ from Bcfg2.Encryption import bruteforce_decrypt, get_algorithm
+ HAS_CRYPTO = True
except ImportError:
- # CfgGenshiGenerator will raise errors if crypto doesn't exist
- pass
+ HAS_CRYPTO = False
try:
from genshi.template import TemplateLoader
@@ -17,21 +19,25 @@ except ImportError:
# CfgGenshiGenerator will raise errors if genshi doesn't exist
TemplateLoader = object
+LOGGER = logging.getLogger(__name__)
+
class EncryptedTemplateLoader(TemplateLoader):
""" Subclass :class:`genshi.template.TemplateLoader` to decrypt
the data on the fly as it's read in using
:func:`Bcfg2.Encryption.bruteforce_decrypt` """
def _instantiate(self, cls, fileobj, filepath, filename, encoding=None):
- plaintext = StringIO(bruteforce_decrypt(fileobj.read()))
+ plaintext = \
+ StringIO(bruteforce_decrypt(fileobj.read(),
+ algorithm=get_algorithm(SETUP)))
return TemplateLoader._instantiate(self, cls, plaintext, filepath,
filename, encoding=encoding)
-
+
class CfgEncryptedGenshiGenerator(CfgGenshiGenerator):
""" CfgEncryptedGenshiGenerator lets you encrypt your Genshi
:ref:`server-plugins-generators-cfg` files on the server """
-
+
#: handle .crypt.genshi or .genshi.crypt files
__extensions__ = ['genshi.crypt', 'crypt.genshi']
@@ -39,3 +45,9 @@ class CfgEncryptedGenshiGenerator(CfgGenshiGenerator):
#: when it's read in
__loader_cls__ = EncryptedTemplateLoader
+ def __init__(self, fname, spec, encoding):
+ CfgGenshiGenerator.__init__(self, fname, spec, encoding)
+ if not HAS_CRYPTO:
+ msg = "Cfg: M2Crypto is not available"
+ LOGGER.error(msg)
+ raise PluginExecutionError(msg)
diff --git a/src/lib/Bcfg2/Server/Plugins/Properties.py b/src/lib/Bcfg2/Server/Plugins/Properties.py
index 1b925ce46..590d536a9 100644
--- a/src/lib/Bcfg2/Server/Plugins/Properties.py
+++ b/src/lib/Bcfg2/Server/Plugins/Properties.py
@@ -5,46 +5,50 @@ import copy
import logging
import lxml.etree
import Bcfg2.Server.Plugin
+from Bcfg2.Server.Plugin import PluginExecutionError
try:
from Bcfg2.Encryption import ssl_decrypt, get_passphrases, \
- bruteforce_decrypt, EVPError
- have_crypto = True
+ get_algorithm, bruteforce_decrypt, EVPError
+ HAS_CRYPTO = True
except ImportError:
- have_crypto = False
+ HAS_CRYPTO = False
-logger = logging.getLogger(__name__)
+LOGGER = logging.getLogger(__name__)
SETUP = None
class PropertyFile(Bcfg2.Server.Plugin.StructFile):
- """Class for properties files."""
+ """ Class for properties files. """
+
def write(self):
""" Write the data in this data structure back to the property
file """
if not SETUP.cfp.getboolean("properties", "writes_enabled",
default=True):
- msg = "Properties files write-back is disabled in the configuration"
- logger.error(msg)
- raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
+ msg = "Properties files write-back is disabled in the " + \
+ "configuration"
+ LOGGER.error(msg)
+ raise PluginExecutionError(msg)
try:
self.validate_data()
- except Bcfg2.Server.Plugin.PluginExecutionError:
+ except PluginExecutionError:
msg = "Cannot write %s: %s" % (self.name, sys.exc_info()[1])
- logger.error(msg)
- raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
+ LOGGER.error(msg)
+ raise PluginExecutionError(msg)
try:
open(self.name,
- "wb").write(lxml.etree.tostring(self.xdata,
- xml_declaration=False,
- pretty_print=True).decode('UTF-8'))
+ "wb").write(
+ lxml.etree.tostring(self.xdata,
+ xml_declaration=False,
+ pretty_print=True).decode('UTF-8'))
return True
except IOError:
err = sys.exc_info()[1]
msg = "Failed to write %s: %s" % (self.name, err)
- logger.error(msg)
- raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
+ LOGGER.error(msg)
+ raise PluginExecutionError(msg)
def validate_data(self):
""" ensure that the data in this object validates against the
@@ -55,31 +59,34 @@ class PropertyFile(Bcfg2.Server.Plugin.StructFile):
schema = lxml.etree.XMLSchema(file=schemafile)
except:
err = sys.exc_info()[1]
- raise Bcfg2.Server.Plugin.PluginExecutionError("Failed to process schema for %s: %s" % (self.name, err))
+ raise PluginExecutionError("Failed to process schema for %s: "
+ "%s" % (self.name, err))
else:
# no schema exists
return True
if not schema.validate(self.xdata):
- raise Bcfg2.Server.Plugin.PluginExecutionError("Data for %s fails to validate; run bcfg2-lint for more details" % self.name)
+ raise PluginExecutionError("Data for %s fails to validate; run "
+ "bcfg2-lint for more details" %
+ self.name)
else:
return True
def Index(self):
Bcfg2.Server.Plugin.StructFile.Index(self)
if self.xdata.get("encryption", "false").lower() != "false":
- if not have_crypto:
+ if not HAS_CRYPTO:
msg = "Properties: M2Crypto is not available: %s" % self.name
- logger.error(msg)
- raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
+ LOGGER.error(msg)
+ raise PluginExecutionError(msg)
for el in self.xdata.xpath("//*[@encrypted]"):
try:
el.text = self._decrypt(el)
except EVPError:
msg = "Failed to decrypt %s element in %s" % (el.tag,
self.name)
- logger.error(msg)
- raise Bcfg2.Server.PluginExecutionError(msg)
+ LOGGER.error(msg)
+ raise PluginExecutionError(msg)
def _decrypt(self, element):
if not element.text.strip():
@@ -88,14 +95,18 @@ class PropertyFile(Bcfg2.Server.Plugin.StructFile):
try:
passphrase = passes[element.get("encrypted")]
try:
- return ssl_decrypt(element.text, passphrase)
+ return ssl_decrypt(element.text, passphrase,
+ algorithm=get_algorithm(SETUP))
except EVPError:
# error is raised below
pass
except KeyError:
- return bruteforce_decrypt(element.text, passphrases=passes.values())
+ return bruteforce_decrypt(element.text,
+ passphrases=passes.values(),
+ algorithm=get_algorithm(SETUP))
raise EVPError("Failed to decrypt")
+
class PropDirectoryBacked(Bcfg2.Server.Plugin.DirectoryBacked):
__child__ = PropertyFile
patterns = re.compile(r'.*\.xml$')
diff --git a/src/sbin/bcfg2-crypt b/src/sbin/bcfg2-crypt
index 1af1771cf..bae4ad8ef 100755
--- a/src/sbin/bcfg2-crypt
+++ b/src/sbin/bcfg2-crypt
@@ -125,7 +125,9 @@ class Encryptor(object):
return self.unchunk(crypted, plaintext)
def _encrypt(self, plaintext, passphrase, name=None):
- return Bcfg2.Encryption.ssl_encrypt(plaintext, passphrase)
+ return Bcfg2.Encryption.ssl_encrypt(
+ plaintext, passphrase,
+ Bcfg2.Encryption.get_algorithm(self.setup))
def decrypt(self, fname):
try:
@@ -198,8 +200,8 @@ class Encryptor(object):
return True
except IOError:
err = sys.exc_info()[1]
- self.logger.error("Error writing encrypted data from %s to %s: %s" %
- (fname, new_fname, err))
+ self.logger.error("Error writing encrypted data from %s to %s: %s"
+ % (fname, new_fname, err))
return False
except EncryptionChunkingError:
err = sys.exc_info()[1]
@@ -217,8 +219,8 @@ class Encryptor(object):
return True
except IOError:
err = sys.exc_info()[1]
- self.logger.error("Error writing encrypted data from %s to %s: %s" %
- (fname, new_fname, err))
+ self.logger.error("Error writing encrypted data from %s to %s: %s"
+ % (fname, new_fname, err))
return False
def get_passphrase(self, chunk):
@@ -248,7 +250,9 @@ class Encryptor(object):
return None
def _decrypt(self, crypted, passphrase):
- return Bcfg2.Encryption.ssl_decrypt(crypted, passphrase)
+ return Bcfg2.Encryption.ssl_decrypt(
+ crypted, passphrase,
+ Bcfg2.Encryption.get_algorithm(self.setup))
class CfgEncryptor(Encryptor):
@@ -268,8 +272,10 @@ class PropertiesEncryptor(Encryptor):
if name is None:
name = "true"
if plaintext.text and plaintext.text.strip():
- plaintext.text = Bcfg2.Encryption.ssl_encrypt(plaintext.text,
- passphrase).strip()
+ plaintext.text = Bcfg2.Encryption.ssl_encrypt(
+ plaintext.text,
+ passphrase,
+ Bcfg2.Encryption.get_algorithm(self.setup)).strip()
plaintext.set("encrypted", name)
return plaintext
@@ -334,8 +340,10 @@ class PropertiesEncryptor(Encryptor):
if not crypted.text or not crypted.text.strip():
self.logger.warning("Skipping empty element %s" % crypted.tag)
return crypted
- crypted.text = Bcfg2.Encryption.ssl_decrypt(crypted.text,
- passphrase).strip()
+ crypted.text = Bcfg2.Encryption.ssl_decrypt(
+ crypted.text,
+ passphrase,
+ Bcfg2.Encryption.get_algorithm(self.setup)).strip()
return crypted
@@ -419,8 +427,8 @@ def main():
"ignoring for this file" % fname)
else:
if setup['xpath']:
- logger.info("Cannot use xpath with Cfg file %s, ignoring xpath "
- "for this file" % fname)
+ logger.info("Cannot use xpath with Cfg file %s, ignoring "
+ "xpath for this file" % fname)
if setup['interactive']:
logger.info("Cannot use interactive mode with Cfg file %s, "
"ignoring -I for this file" % fname)
@@ -449,6 +457,7 @@ def main():
data = xform(fname)
if not data:
print("Failed to %s %s, skipping" % (xform.__name__, fname))
+ continue
if setup['crypt_stdout']:
if len(setup['args']) > 1:
print("----- %s -----" % fname)
@@ -458,7 +467,8 @@ def main():
else:
write(fname, data=data)
- if setup['remove'] and encryptor.get_encrypted_filename(fname) != fname:
+ if (setup['remove'] and
+ encryptor.get_encrypted_filename(fname) != fname):
try:
os.unlink(fname)
except IOError: