From 91167b2c01e5a0abafd0c111fa3546601e1f5cf0 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Thu, 13 Sep 2012 10:39:40 -0400 Subject: bcfg2-crypt: added -I option, made --stdout better, updated man page --- src/lib/Bcfg2/Client/Frame.py | 8 +- src/lib/Bcfg2/Options.py | 17 +++-- src/sbin/bcfg2-crypt | 174 ++++++++++++++++++++++++++++-------------- 3 files changed, 129 insertions(+), 70 deletions(-) (limited to 'src') diff --git a/src/lib/Bcfg2/Client/Frame.py b/src/lib/Bcfg2/Client/Frame.py index 508e3b616..2fb81d6ba 100644 --- a/src/lib/Bcfg2/Client/Frame.py +++ b/src/lib/Bcfg2/Client/Frame.py @@ -7,6 +7,7 @@ import logging import sys import time import Bcfg2.Client.Tools +from Bcfg2.Compat import input def cmpent(ent1, ent2): @@ -150,12 +151,7 @@ class Frame(object): else: iprompt = prompt % (entry.tag, entry.get('name')) try: - # py3k compatibility - try: - ans = raw_input(iprompt.encode(sys.stdout.encoding, - 'replace')) - except NameError: - ans = input(iprompt) + ans = input(iprompt.encode(sys.stdout.encoding, 'replace')) if ans in ['y', 'Y']: ret.append(entry) except EOFError: diff --git a/src/lib/Bcfg2/Options.py b/src/lib/Bcfg2/Options.py index 34f4b2bc6..6ac9cafb1 100644 --- a/src/lib/Bcfg2/Options.py +++ b/src/lib/Bcfg2/Options.py @@ -21,9 +21,9 @@ DEFAULT_INSTALL_PREFIX = '/usr' class DefaultConfigParser(ConfigParser.ConfigParser): - def __init__(self,*args,**kwargs): + def __init__(self, *args, **kwargs): """Make configuration options case sensitive""" - ConfigParser.ConfigParser.__init__(self,*args,**kwargs) + ConfigParser.ConfigParser.__init__(self, *args, **kwargs) self.optionxform = str def get(self, section, option, **kwargs): @@ -100,7 +100,7 @@ class Option(object): rv.append("%s %s" % (self.cmd, self.odesc)) else: rv.append("%s" % self.cmd) - + if self.cf: if self.cmd: rv.append("; ") @@ -175,9 +175,10 @@ class Option(object): % (self.deprecated_cf[0], self.deprecated_cf[1], self.cf[0], self.cf[1])) return - except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): + except (ConfigParser.NoSectionError, + ConfigParser.NoOptionError): pass - + # Default value not cooked self.value = self.default @@ -907,8 +908,8 @@ DECRYPT = \ default=False, cmd='--decrypt', long_arg=True) -DECRYPT_STDOUT = \ - Option('Decrypt the specified file to stdout', +CRYPT_STDOUT = \ + Option('Decrypt or encrypt the specified file to stdout', default=False, cmd='--stdout', long_arg=True) @@ -968,7 +969,7 @@ SERVER_COMMON_OPTIONS = dict(repo=SERVER_REPOSITORY, CRYPT_OPTIONS = dict(encrypt=ENCRYPT, decrypt=DECRYPT, - decrypt_stdout=DECRYPT_STDOUT, + crypt_stdout=CRYPT_STDOUT, passphrase=CRYPT_PASSPHRASE, xpath=CRYPT_XPATH, properties=CRYPT_PROPERTIES, diff --git a/src/sbin/bcfg2-crypt b/src/sbin/bcfg2-crypt index cb1b956fb..1af1771cf 100755 --- a/src/sbin/bcfg2-crypt +++ b/src/sbin/bcfg2-crypt @@ -3,16 +3,18 @@ import os import sys +import copy import logging import lxml.etree import Bcfg2.Logger import Bcfg2.Options +from Bcfg2.Server import XMLParser +from Bcfg2.Compat import input try: import Bcfg2.Encryption except ImportError: err = sys.exc_info()[1] - print("Import failed '%s'. Is M2Crypto installed?" % - err) + print("Could not import %s. Is M2Crypto installed?" % err) raise SystemExit(1) LOGGER = None @@ -103,7 +105,8 @@ class Encryptor(object): self.logger.error("Error reading %s, skipping: %s" % (fname, err)) return False - self.set_passphrase() + if not self.set_passphrase(): + return False crypted = [] try: @@ -119,22 +122,7 @@ class Encryptor(object): self.logger.error("Error getting data to encrypt from %s: %s" % (fname, err)) return False - - new_fname = self.get_encrypted_filename(fname) - try: - open(new_fname, "wb").write(self.unchunk(crypted, plaintext)) - self.logger.info("Wrote encrypted data to %s" % new_fname) - 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)) - return False - except EncryptionChunkingError: - err = sys.exc_info()[1] - self.logger.error("Error assembling encrypted data from %s: %s" % - (fname, err)) - return False + return self.unchunk(crypted, plaintext) def _encrypt(self, plaintext, passphrase, name=None): return Bcfg2.Encryption.ssl_encrypt(plaintext, passphrase) @@ -200,6 +188,25 @@ class Encryptor(object): (fname, err)) return False + def write_encrypted(self, fname, data=None): + if data is None: + data = self.decrypt(fname) + new_fname = self.get_encrypted_filename(fname) + try: + open(new_fname, "wb").write(data) + self.logger.info("Wrote encrypted data to %s" % new_fname) + 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)) + return False + except EncryptionChunkingError: + err = sys.exc_info()[1] + self.logger.error("Error assembling encrypted data from %s: %s" % + (fname, err)) + return False + def write_decrypted(self, fname, data=None): if data is None: data = self.decrypt(fname) @@ -262,12 +269,12 @@ class PropertiesEncryptor(Encryptor): name = "true" if plaintext.text and plaintext.text.strip(): plaintext.text = Bcfg2.Encryption.ssl_encrypt(plaintext.text, - passphrase) + passphrase).strip() plaintext.set("encrypted", name) return plaintext def chunk(self, data): - xdata = lxml.etree.XML(data) + xdata = lxml.etree.XML(data, parser=XMLParser) if self.setup['xpath']: elements = xdata.xpath(self.setup['xpath']) if not elements: @@ -276,7 +283,28 @@ class PropertiesEncryptor(Encryptor): else: elements = xdata.xpath('//*[@encrypted]') if not elements: - elements = list(xdata.getiterator()) + elements = list(xdata.getiterator(tag=lxml.etree.Element)) + + # filter out elements without text data + for el in elements[:]: + if not el.text: + elements.remove(el) + + if self.setup['interactive']: + for element in elements[:]: + if len(element): + elt = copy.copy(element) + for child in elt.iterchildren(): + elt.remove(child) + else: + elt = element + print(lxml.etree.tostring( + elt, + xml_declaration=False).decode("UTF-8").strip()) + ans = input("Encrypt this element? [y/N] ") + if not ans.lower().startswith("y"): + elements.remove(element) + # this is not a good use of a generator, but we need to # generate the full list of elements in order to ensure that # some exist before we know what to return @@ -291,7 +319,9 @@ class PropertiesEncryptor(Encryptor): while xdata.getparent() != None: xdata = xdata.getparent() xdata.set("encryption", "true") - return lxml.etree.tostring(xdata, xml_declaration=False).decode('UTF-8') + 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") @@ -304,13 +334,13 @@ class PropertiesEncryptor(Encryptor): if not crypted.text or not crypted.text.strip(): self.logger.warning("Skipping empty element %s" % crypted.tag) return crypted - rv = Bcfg2.Encryption.ssl_decrypt(crypted.text, passphrase) - crypted.text = rv + crypted.text = Bcfg2.Encryption.ssl_decrypt(crypted.text, + passphrase).strip() return crypted def main(): - optinfo = dict() + optinfo = dict(interactive=Bcfg2.Options.INTERACTIVE) optinfo.update(Bcfg2.Options.CRYPT_OPTIONS) optinfo.update(Bcfg2.Options.CLI_COMMON_OPTIONS) setup = Bcfg2.Options.OptionParser(optinfo) @@ -321,18 +351,33 @@ def main(): if not setup['args']: print(setup.hm) raise SystemExit(1) - elif setup['encrypt'] and (setup['decrypt_stdout'] or setup['decrypt']): - print("You cannot specify both --encrypt and --decrypt or --stdout") - 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 + + if setup['decrypt']: + if setup['encrypt']: + print("You cannot specify both --encrypt and --decrypt") + raise SystemExit(1) + elif setup['remove']: + print("--remove cannot be used with --decrypt, ignoring") + setup['remove'] = Bcfg2.Options.CRYPT_REMOVE.default + elif setup['interactive']: + print("Cannot decrypt interactively") + setup['interactive'] = False + + if setup['cfg']: + if setup['properties']: + print("You cannot specify both --cfg and --properties") + raise SystemExit(1) + if setup['xpath']: + print("Specifying --xpath with --cfg is nonsensical, ignoring " + "--xpath") + setup['xpath'] = Bcfg2.Options.CRYPT_XPATH.default + if setup['interactive']: + print("You cannot use interactive mode with --cfg, ignoring -I") + setup['interactive'] = False + elif setup['properties']: + if setup['remove']: + print("--remove cannot be used with --properties, ignoring") + setup['remove'] = Bcfg2.Options.CRYPT_REMOVE.default logger = get_logger(setup['verbose']) @@ -369,32 +414,49 @@ def main(): if props: encryptor = props_crypt + if setup['remove']: + logger.info("Cannot use --remove with Properties file %s, " + "ignoring for this file" % fname) else: + if setup['xpath']: + 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) encryptor = cfg_crypt + data = None if setup['encrypt']: - if not encryptor.encrypt(fname): - print("Failed to encrypt %s, skipping" % fname) - elif setup['decrypt'] or setup['decrypt_stdout']: - data = encryptor.decrypt(fname) - if not data: - print("Failed to decrypt %s, skipping" % fname) - continue - if setup['decrypt_stdout']: - if len(setup['args']) > 1: - print("----- %s -----" % fname) - print(data) - if len(setup['args']) > 1: - print("") - else: - encryptor.write_decrypted(fname, data=data) + xform = encryptor.encrypt + write = encryptor.write_encrypted + elif setup['decrypt']: + xform = encryptor.decrypt + write = encryptor.write_decrypted else: logger.info("Neither --encrypt nor --decrypt specified, " "determining mode") - if not encryptor.decrypt(fname): + data = encryptor.decrypt(fname) + if data: + write = encryptor.write_decrypted + else: logger.info("Failed to decrypt %s, trying encryption" % fname) - if not encryptor.encrypt(fname): - print("Failed to encrypt %s, skipping" % fname) + data = None + xform = encryptor.encrypt + write = encryptor.write_encrypted + + if data is None: + data = xform(fname) + if not data: + print("Failed to %s %s, skipping" % (xform.__name__, fname)) + if setup['crypt_stdout']: + if len(setup['args']) > 1: + print("----- %s -----" % fname) + print(data) + if len(setup['args']) > 1: + print("") + else: + write(fname, data=data) if setup['remove'] and encryptor.get_encrypted_filename(fname) != fname: try: -- cgit v1.2.3-1-g7c22