summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/server/plugins/connectors/properties.txt72
-rw-r--r--doc/server/plugins/generators/cfg.txt33
-rw-r--r--src/lib/Bcfg2/Options.py45
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenerator.py2
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Properties.py58
-rwxr-xr-xsrc/sbin/bcfg2-crypt321
-rw-r--r--tools/manpagegen/bcfg2-crypt.8.ronn92
7 files changed, 612 insertions, 11 deletions
diff --git a/doc/server/plugins/connectors/properties.txt b/doc/server/plugins/connectors/properties.txt
index 7695e902c..19814a54f 100644
--- a/doc/server/plugins/connectors/properties.txt
+++ b/doc/server/plugins/connectors/properties.txt
@@ -33,6 +33,9 @@ be checked when running ``bcfg2-lint``. For instance, given::
``dns-config.xml`` will be validated against ``dns-config.xsd``.
+Although Properties files are technically freeform XML, the top-level
+XML tag should be ``<Properties>``.
+
Usage
=====
@@ -83,6 +86,75 @@ directly in one of several ways:
top-level element. (I.e., everything directly under the
``<Properties>`` tag.)
+.. _server-plugins-connectors-properties-encrypted:
+
+Encrypted Properties data
+=========================
+
+.. versionadded:: 1.3.0
+
+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.
+
+.. 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.
+
+Properties files are encrypted on a per-element basis; that is, rather
+than encrypting the whole file, only the character content of
+individual elements is encrypted. This makes it easier to track
+changes to the file in a VCS, and also lets unprivileged users work
+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.::
+
+ 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="true"`` to each element that has encrypted character
+data. It also adds ``encryption="<key name>"`` 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="true"``
+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="true"`` 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="true"`` 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.
+
+All encrypted elements in a single Properties file must be encrypted
+with the same passphrase.
+
Accessing Properties contents from TGenshi
==========================================
diff --git a/doc/server/plugins/generators/cfg.txt b/doc/server/plugins/generators/cfg.txt
index d97cf62e9..6c848fddb 100644
--- a/doc/server/plugins/generators/cfg.txt
+++ b/doc/server/plugins/generators/cfg.txt
@@ -145,7 +145,10 @@ Encrypted Files
.. versionadded:: 1.3.0
Bcfg2 allows you to encrypt files stored in ``Cfg/`` to protect the
-data in them from other people who need access to the repository.
+data in them from other people who need access to the repository. See
+also :ref:`server-plugins-connectors-properties-encrypted` for
+information on encrypting elements in Properties files, which is often
+more friendly for tracking changes in a VCS.
.. note::
@@ -172,21 +175,33 @@ 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 run::
+To encrypt a file, you can use ``bcfg2-crypt``, e.g.::
- openssl enc -aes-256-cbc -k <passphrase> -in foo.conf -out foo.conf.crypt -a
+ bcfg2-crypt foo.conf
Once you are satisfied that the file has been encrypted as you wish,
-you can remove the plaintext version.
+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
-To decrypt a file, you can run::
+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 ``[cfg:encryption]`` section to
+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
@@ -195,7 +210,7 @@ added.
For instance::
- [cfg:encryption]
+ [encryption]
foo_team=P4ssphr4se
bar_team=Pa55phra5e
@@ -210,8 +225,8 @@ 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 ``[cfg:encryption]`` section
- to ``bcfg2.conf`` and restart the Bcfg2 server.
+#. 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.
diff --git a/src/lib/Bcfg2/Options.py b/src/lib/Bcfg2/Options.py
index 0791ce343..6d3dd0a8c 100644
--- a/src/lib/Bcfg2/Options.py
+++ b/src/lib/Bcfg2/Options.py
@@ -730,6 +730,43 @@ CFG_VALIDATION = \
long_arg=True,
cook=get_bool)
+# bcfg2-crypt options
+ENCRYPT = \
+ Option('Encrypt the specified file',
+ default=False,
+ cmd='--encrypt',
+ long_arg=True)
+DECRYPT = \
+ Option('Decrypt the specified file',
+ default=False,
+ cmd='--decrypt',
+ long_arg=True)
+CRYPT_PASSPHRASE = \
+ Option('Encryption passphrase (name or passphrase)',
+ default=None,
+ cmd='-p',
+ odesc='<passphrase>')
+CRYPT_XPATH = \
+ Option('XPath expression to select elements to encrypt',
+ default=None,
+ cmd='--xpath',
+ odesc='<xpath>',
+ long_arg=True)
+CRYPT_PROPERTIES = \
+ Option('Encrypt the specified file as a Properties file',
+ default=False,
+ cmd="--properties",
+ long_arg=True)
+CRYPT_CFG = \
+ Option('Encrypt the specified file as a Cfg file',
+ default=False,
+ cmd="--cfg",
+ long_arg=True)
+CRYPT_REMOVE = \
+ Option('Remove the plaintext file after encrypting',
+ default=False,
+ cmd="--remove",
+ long_arg=True)
# Option groups
CLI_COMMON_OPTIONS = dict(configfile=CFILE,
@@ -754,6 +791,14 @@ SERVER_COMMON_OPTIONS = dict(repo=SERVER_REPOSITORY,
ca=SERVER_CA,
protocol=SERVER_PROTOCOL)
+CRYPT_OPTIONS = dict(encrypt=ENCRYPT,
+ decrypt=DECRYPT,
+ passphrase=CRYPT_PASSPHRASE,
+ xpath=CRYPT_XPATH,
+ properties=CRYPT_PROPERTIES,
+ cfg=CRYPT_CFG,
+ remove=CRYPT_REMOVE)
+
DRIVER_OPTIONS = \
dict(apt_install_path=CLIENT_APT_TOOLS_INSTALL_PATH,
apt_var_path=CLIENT_APT_TOOLS_VAR_PATH,
diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenerator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenerator.py
index 0839e3536..2c926fae7 100644
--- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenerator.py
+++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgEncryptedGenerator.py
@@ -10,7 +10,7 @@ except ImportError:
logger = logging.getLogger(__name__)
def passphrases():
- section = "cfg:encryption"
+ section = "encryption"
if SETUP.cfp.has_section(section):
return dict([(o, SETUP.cfp.get(section, o))
for o in SETUP.cfp.options(section)])
diff --git a/src/lib/Bcfg2/Server/Plugins/Properties.py b/src/lib/Bcfg2/Server/Plugins/Properties.py
index 680881858..a81cdadd2 100644
--- a/src/lib/Bcfg2/Server/Plugins/Properties.py
+++ b/src/lib/Bcfg2/Server/Plugins/Properties.py
@@ -5,11 +5,31 @@ import copy
import logging
import lxml.etree
import Bcfg2.Server.Plugin
+try:
+ from Bcfg2.Encryption import ssl_decrypt, EVPError
+ have_crypto = True
+except ImportError:
+ have_crypto = False
+
+logger = logging.getLogger(__name__)
+
+SETUP = None
+
+def passphrases():
+ section = "encryption"
+ if SETUP.cfp.has_section(section):
+ return dict([(o, SETUP.cfp.get(section, o))
+ for o in SETUP.cfp.options(section)])
+ else:
+ return dict()
-logger = logging.getLogger('Bcfg2.Plugins.Properties')
class PropertyFile(Bcfg2.Server.Plugin.StructFile):
"""Class for properties files."""
+ def __init__(self, name):
+ Bcfg2.Server.Plugin.StructFile.__init__(self, name)
+ self.passphrase = None
+
def write(self):
""" Write the data in this data structure back to the property
file """
@@ -47,6 +67,39 @@ class PropertyFile(Bcfg2.Server.Plugin.StructFile):
else:
return True
+ def Index(self):
+ Bcfg2.Server.Plugin.StructFile.Index(self)
+ if self.xdata.get("encryption", "false").lower() != "false":
+ logger.error("decrypting data in %s" % self.name)
+ if not have_crypto:
+ msg = "Properties: M2Crypto is not available: %s" % self.name
+ logger.error(msg)
+ raise Bcxfg2.Server.Plugin.PluginExecutionError(msg)
+ for el in self.xdata.xpath("*[@encrypted='true']"):
+ logger.error("decrypting data in %s in %s" % (el.tag, self.name))
+ try:
+ el.text = self._decrypt(el.text)
+ except EVPError:
+ msg = "Failed to decrypt %s element in %s" % (el.tag,
+ self.name)
+ logger.error(msg)
+ raise Bcfg2.Server.PluginExecutionError(msg)
+
+ def _decrypt(self, crypted):
+ if self.passphrase is None:
+ for passwd in passphrases().values():
+ try:
+ rv = ssl_decrypt(crypted, passwd)
+ self.passphrase = passwd
+ return rv
+ except EVPError:
+ pass
+ else:
+ try:
+ return ssl_decrypt(crypted, self.passphrase)
+ except EVPError:
+ pass
+ raise EVPError("Failed to decrypt")
class PropDirectoryBacked(Bcfg2.Server.Plugin.DirectoryBacked):
__child__ = PropertyFile
@@ -62,6 +115,7 @@ class Properties(Bcfg2.Server.Plugin.Plugin,
name = 'Properties'
def __init__(self, core, datastore):
+ global SETUP
Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore)
Bcfg2.Server.Plugin.Connector.__init__(self)
try:
@@ -72,5 +126,7 @@ class Properties(Bcfg2.Server.Plugin.Plugin,
(e.strerror, e.filename))
raise Bcfg2.Server.Plugin.PluginInitError
+ SETUP = core.setup
+
def get_additional_data(self, _):
return copy.copy(self.store.entries)
diff --git a/src/sbin/bcfg2-crypt b/src/sbin/bcfg2-crypt
new file mode 100755
index 000000000..b9b8f1ae5
--- /dev/null
+++ b/src/sbin/bcfg2-crypt
@@ -0,0 +1,321 @@
+#!/usr/bin/env python
+""" helper for encrypting/decrypting Cfg and Properties files """
+
+import os
+import sys
+import copy
+import logging
+import getpass
+import lxml.etree
+import Bcfg2.Logger
+import Bcfg2.Options
+import Bcfg2.Encryption
+
+LOGGER = None
+
+def get_logger(verbose=0):
+ """ set up logging according to the verbose level given on the
+ command line """
+ global LOGGER
+ if LOGGER is None:
+ LOGGER = logging.getLogger(sys.argv[0])
+ stderr = logging.StreamHandler()
+ if verbose:
+ level = logging.DEBUG
+ else:
+ level = logging.WARNING
+ LOGGER.setLevel(level)
+ LOGGER.addHandler(stderr)
+ syslog = logging.handlers.SysLogHandler("/dev/log")
+ syslog.setFormatter(logging.Formatter("%(name)s: %(message)s"))
+ LOGGER.addHandler(syslog)
+ return LOGGER
+
+
+class Encryptor(object):
+ def __init__(self, setup):
+ self.setup = setup
+ self.logger = get_logger()
+ self.passphrase = None
+ self.pname = None
+
+ def get_encrypted_filename(self, plaintext_filename):
+ return plaintext_filename
+
+ def get_plaintext_filename(self, encrypted_filename):
+ return encrypted_filename
+
+ def encrypt(self, fname):
+ if (not self.setup.cfp.has_section("encryption") or
+ self.setup.cfp.options("encryption") == 0):
+ self.logger.error("No passphrases available in %s" %
+ self.setup['configfile'])
+ return False
+ if not self.passphrase:
+ if self.setup['passphrase']:
+ if self.setup.cfp.has_option("encryption",
+ self.setup['passphrase']):
+ self.passphrase = \
+ self.setup.cfp.get("encryption",
+ self.setup['passphrase'])
+ self.pname = self.setup['passphrase']
+ else:
+ self.logger.error("Could not find passphrase %s in %s" %
+ (self.setup['passphrase'],
+ self.setup['configfile']))
+ else:
+ pnames = self.setup.cfp.options("encryption")
+ if len(pnames) == 1:
+ self.passphrase = self.setup.cfp.get(pnames[0])
+ self.pname = pnames[0]x
+ self.logger.info("Using passphrase %s" % pnames[0])
+ else:
+ name = None
+ while (not name or
+ not self.setup.cfp.has_option("encryption", name)):
+ print("Available passphrases: ")
+ for pname in pnames:
+ print(pname)
+ name = raw_input("Passphrase: ")
+ self.passphrase = self.setup.cfp.get("encryption", name)
+ self.pname = name
+ try:
+ plaintext = open(fname).read()
+ except IOError:
+ err = sys.exc_info()[1]
+ self.logger.error("Error reading %s, skipping: %s" (fname, err))
+ return False
+ crypted = self._encrypt(plaintext, self.passphrase, name=pname)
+ try:
+ open(self.get_encrypted_filename(fname), "wb").write(crypted)
+ self.logger.info("Wrote encrypted data to %s" %
+ self.get_encrypted_filename(fname))
+ return True
+ except IOError:
+ err = sys.exc_info()[1]
+ self.logger.error("Error writing encrypted data from %s to %s: %s" %
+ (fname, self.get_encrypted_filename(fname), err))
+ return False
+
+ def _encrypt(self, plaintext, passphrase, name=None):
+ return Bcfg2.Encryption.ssl_encrypt(plaintext, passphrase)
+
+ def decrypt(self, fname):
+ if (not self.setup.cfp.has_section("encryption") or
+ self.setup.cfp.options("encryption") == 0):
+ self.logger.error("No passphrases available in %s" %
+ self.setup['configfile'])
+ return False
+
+ try:
+ crypted = open(fname).read()
+ except IOError:
+ err = sys.exc_info()[1]
+ self.logger.error("Error reading %s, skipping: %s" (fname, err))
+ return False
+
+ plaintext = None
+ if self.setup['passphrase']:
+ if self.setup.cfp.has_option("encryption",
+ self.setup['passphrase']):
+ passphrase = self.setup.cfp.get("encryption",
+ self.setup['passphrase'])
+ else:
+ self.logger.error("Could not find passphrase %s in %s" %
+ (self.setup['passphrase'],
+ self.setup['configfile']))
+ try:
+ plaintext = self._decrypt(crypted, passphrase)
+ except Bcfg2.Encryption.EVPError:
+ self.logger.error("Could not decrypt %s with the specified passphrase" % fname)
+ return False
+ except:
+ err = sys.exc_info()[1]
+ self.logger.error("Error decrypting %s: %s" % (fname, err))
+ else:
+ # figure out the right passphrase
+ pname = self.get_decryption_passphrase(crypted)
+ if pname:
+ passphrase = self.setup.cfp.get('encryption', pname)
+ try:
+ plaintext = self._decrypt(crypted, passphrase)
+ except:
+ err = sys.exc_info()[1]
+ self.logger.error("Error decrypting %s: %s" %
+ (fname, err))
+ else:
+ for pname in self.setup.cfp.options('encryption'):
+ self.logger.debug("Trying passphrase %s" % pname)
+ passphrase = self.setup.cfp.get('encryption', pname)
+ try:
+ plaintext = self._decrypt(crypted, passphrase)
+ break
+ except Bcfg2.Encryption.EVPError:
+ pass
+ except:
+ err = sys.exc_info()[1]
+ self.logger.error("Error decrypting %s: %s" %
+ (fname, err))
+ if not plaintext:
+ self.logger.error("Could not decrypt %s with any passphrase in %s" %
+ (fname, self.setup['configfile']))
+ return False
+
+ try:
+ open(self.get_plaintext_filename(fname), "wb").write(plaintext)
+ self.logger.info("Wrote decrypted data to %s" %
+ self.get_plaintext_filename(fname))
+ return True
+ except IOError:
+ err = sys.exc_info()[1]
+ self.logger.error("Error writing encrypted data from %s to %s: %s" %
+ (fname, self.get_plaintext_filename(fname), err))
+ return False
+
+ def get_decryption_passphrase(self, crypted):
+ return None
+
+ def _decrypt(self, crypted, passphrase):
+ return Bcfg2.Encryption.ssl_decrypt(crypted, passphrase)
+
+
+class CfgEncryptor(Encryptor):
+ def get_encrypted_filename(self, plaintext_filename):
+ return plaintext_filename + ".crypt"
+
+ def get_plaintext_filename(self, encrypted_filename):
+ if encrypted_filename.endswith(".crypt"):
+ return encrypted_filename[:-6]
+ else:
+ return Encryptor.get_plaintext_filename(self, encrypted_filename)
+
+
+class PropertiesEncryptor(Encryptor):
+ def _encrypt(self, plaintext, passphrase, name=None):
+ xdata = lxml.etree.XML(plaintext)
+ if self.setup['xpath']:
+ elements = xdata.xpath(self.setup['xpath'])
+ else:
+ elements = xdata.xpath('*[@encrypted="true"]')
+ if not elements:
+ elements = list(xdata.getiterator())
+
+ for el in elements:
+ el.text = Bcfg2.Encryption.ssl_encrypt(el.text, passphrase)
+ el.set("encrypted", "true")
+ if name is None:
+ xdata.set("encryption", "true")
+ else:
+ xdata.set("encryption", name)
+ return lxml.etree.tostring(xdata)
+
+ def get_decryption_passphrase(self, crypted):
+ xdata = lxml.etree.XML(crypted)
+ pname = xdata.get("encryption")
+ if pname and pname.lower() != "true":
+ return pname
+ return None
+
+ def _decrypt(self, crypted, passphrase):
+ xdata = lxml.etree.XML(crypted)
+ if self.setup['xpath']:
+ elements = xdata.xpath(self.setup['xpath'])
+ else:
+ elements = xdata.xpath("*[@encrypted='true']")
+ if not elements:
+ self.logger.info("No elements found to decrypt")
+ return False
+ for el in elements:
+ if not el.text.strip():
+ self.logger.warning("Skipping empty element %s" % el.tag)
+ continue
+ el.text = Bcfg2.Encryption.ssl_decrypt(el.text, passphrase)
+ return lxml.etree.tostring(xdata)
+
+
+def main():
+ optinfo = dict()
+ optinfo.update(Bcfg2.Options.CRYPT_OPTIONS)
+ optinfo.update(Bcfg2.Options.CLI_COMMON_OPTIONS)
+ setup = Bcfg2.Options.OptionParser(optinfo)
+ setup.hm = "Usage: bcfg2-crypt [options] <filename>\nOptions:\n %s" % \
+ setup.buildHelpMessage()
+ setup.parse(sys.argv[1:])
+
+ if not setup['args']:
+ print(setup.hm)
+ raise SystemExit(1)
+ elif setup['encrypt'] and setup['decrypt']:
+ print("You cannot specify both --encrypt) and --decrypt")
+ raise SystemExit(1)
+ elif setup['cfg'] and setup['properties']:
+ print("You cannot specify both --cfg and --properties")
+ raise SystemExit(1)
+ elif setup['cfg'] and setup['properties']:
+ print("Specifying --xpath with --cfg is nonsensical, ignoring --xpath")
+ setup['xpath'] = Bcfg2.Options.CRYPT_XPATH.default
+ elif setup['decrypt'] and setup['remove']:
+ print("--remove cannot be used with --decrypt, ignoring")
+ setup['remove'] = Bcfg2.Options.CRYPT_REMOVE.default
+
+ logger = get_logger(setup['verbose'])
+
+ props_crypt = PropertiesEncryptor(setup)
+ cfg_crypt = CfgEncryptor(setup)
+
+ for fname in setup['args']:
+ if not os.path.exists(fname):
+ logger.error("%s does not exist, skipping" % fname)
+ continue
+
+ # figure out if we need to encrypt this as a Properties file
+ # or as a Cfg file
+ props = False
+ if setup['properties']:
+ props = True
+ elif setup['cfg']:
+ props = False
+ elif fname.endswith(".xml"):
+ try:
+ xroot = lxml.etree.parse(fname).getroot()
+ if xroot.tag == "Properties":
+ props = True
+ else:
+ props = False
+ except IOError:
+ err = sys.exc_info()[1]
+ logger.error("Error reading %s, skipping: %s" (fname, err))
+ continue
+ except lxml.etree.XMLSyntaxError:
+ props = False
+ else:
+ props = False
+
+ if props:
+ encryptor = props_crypt
+ else:
+ encryptor = cfg_crypt
+
+ if setup['encrypt']:
+ if not encryptor.encrypt(fname):
+ continue
+ elif setup['decrypt']:
+ if not encryptor.decrypt(fname):
+ continue
+ else:
+ logger.info("Neither --encrypt nor --decrypt specified, determining mode")
+ if not encryptor.decrypt(fname):
+ logger.info("Failed to decrypt %s, trying encryption" % fname)
+ if not encryptor.encrypt(fname):
+ continue
+
+ if setup['remove'] and encryptor.get_encrypted_filename(fname) != fname:
+ try:
+ os.unlink(fname)
+ except IOError:
+ err = sys.exc_info()[1]
+ logger.error("Error removing %s: %s" (fname, err))
+ continue
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/tools/manpagegen/bcfg2-crypt.8.ronn b/tools/manpagegen/bcfg2-crypt.8.ronn
new file mode 100644
index 000000000..edf9660da
--- /dev/null
+++ b/tools/manpagegen/bcfg2-crypt.8.ronn
@@ -0,0 +1,92 @@
+bcfg2-crypt(8) -- Bcfg2 encryption and decryption utility
+=========================================================
+
+## SYNOPSIS
+
+`bcfg2-crypt` [<-C configfile>] [--decrypt|--encrypt] [--cfg|--properties] [--remove] [--xpath <xpath>] [-p <passphrase-or-name>] [-v] <filename> [<filename>...]
+
+## DESCRIPTION
+
+`bcfg2-crypt` performs encryption and decryption of Cfg and Properties
+files. It's often sufficient to run `bcfg2-crypt` with only the name
+of the file you wish to encrypt or decrypt; it can usually figure out
+what to do.
+
+## OPTIONS
+
+ * `-C` <configfile>:
+ Specify alternate bcfg2.conf location
+
+ * `--decrypt`, `--encrypt`:
+ Specify which operation you'd like to perform. `bcfg2-crypt` can
+ usually determine which is necessary based on the contents of each
+ file.
+
+ * `--cfg`:
+ Tell `bcfg2-crypt` that an XML file should be encrypted in its
+ entirety rather than element-by-element. This is only necessary
+ if the file is an XML file whose name ends with `.xml` and whose
+ top-level tag is `<Properties>`. See [MODES] below for details.
+
+ * `--properties`:
+ Tell `bcfg2-crypt` to process a file as an XML Properties file,
+ and encrypt the text of each element separately. This is
+ necessary if, for example, you've used a different top-level tag
+ than `<Properties>` in your Properties files. See [MODES] below
+ for details.
+
+ * `--remove`:
+ Remove the plaintext file after it has been encrypted. Only
+ meaningful for Cfg files.
+
+ * `--xpath <xpath>`:
+ Encrypt the character content of all elements that match the
+ specified XPath expression. The default is `*[@encrypted="true"]`
+ or `*`; see [MODES] below for more details. Only meaningful for
+ Properties files.
+
+ * `-p <passphrase>`:
+ Specify the encryption/decryption passphrase. This can either be
+ the literal passphrase, or the name of a passphrase specified in
+ the `[encryption]` section of `bcfg2.conf`. If no passphrase is
+ specified, then a) when decrypting, all passphrases will be tried
+ sequentially; and b) when encrypting, you will be prompted for a
+ passphrase from `bcfg2.conf`. It is never necessary to specify
+ `-p` if you only have a single passphrase in `bcfg2.conf`.
+
+ * `-v`:
+ Be verbose.
+
+ * `-h`:
+ Display help and exit.
+
+## MODES
+
+`bcfg2-crypt` can encrypt Cfg files or Properties files; they are
+handled very differently.
+
+ * Cfg:
+ When `bcfg2-crypt` is used on a Cfg file, the entire file is
+ encrypted. This is the default behavior on files that are not
+ XML, or that are XML but whose top-level tag is not
+ `<Properties>`. This can be enforced by use of the `--cfg`
+ option.
+
+ * Properties:
+ When `bcfg2-crypt` is used on a Properties file, it encrypts the
+ character content of elements matching the XPath expression given
+ by `--xpath`. By default the expression is
+ `*[@encrypted="true"]`, which matches all elements with an
+ `encrypted` attribute set to `true`. If you are encrypting a file
+ and that expression doesn't match any elements, then the default
+ is `*`, which matches everything. When `bcfg2-crypt` encrypts the
+ character content of an element, it also adds the `encrypted`
+ attribute, but when it decrypts an element it does not remove it;
+ this lets you easily and efficiently run `bcfg2-crypt` against a
+ single Properties file to encrypt and decrypt it without needing
+ to specify a long list of options. See the online Bcfg2 docs on
+ Properties files for more information on how this works.
+
+## SEE ALSO
+
+bcfg2-server(8)