summaryrefslogtreecommitdiffstats
path: root/src/sbin/bcfg2-crypt
diff options
context:
space:
mode:
authorChris St. Pierre <chris.a.st.pierre@gmail.com>2012-06-06 09:31:14 -0400
committerChris St. Pierre <chris.a.st.pierre@gmail.com>2012-06-06 09:31:48 -0400
commit1291e5b09efb956d42e7ab83d485d41542f438f4 (patch)
treecde83f5ecbe7b72ab20e013dea06098742ebed26 /src/sbin/bcfg2-crypt
parentf46d4216cd4d6a4b272bfff1465a19a5649a93e7 (diff)
downloadbcfg2-1291e5b09efb956d42e7ab83d485d41542f438f4.tar.gz
bcfg2-1291e5b09efb956d42e7ab83d485d41542f438f4.tar.bz2
bcfg2-1291e5b09efb956d42e7ab83d485d41542f438f4.zip
added properties element encryption
added bcfg2-crypt utility for encrypting Properties and Cfg files
Diffstat (limited to 'src/sbin/bcfg2-crypt')
-rwxr-xr-xsrc/sbin/bcfg2-crypt321
1 files changed, 321 insertions, 0 deletions
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())