diff options
Diffstat (limited to 'src/sbin')
-rwxr-xr-x | src/sbin/bcfg2-crypt | 174 | ||||
-rwxr-xr-x | src/sbin/bcfg2-info | 38 |
2 files changed, 156 insertions, 56 deletions
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: diff --git a/src/sbin/bcfg2-info b/src/sbin/bcfg2-info index 9ee02fd71..95a18b608 100755 --- a/src/sbin/bcfg2-info +++ b/src/sbin/bcfg2-info @@ -42,6 +42,7 @@ buildall <directory> [<hostnames*>] - Build configs for all clients in directory buildallfile <directory> <filename> [<hostnames*>] - Build config file for all clients in directory buildfile <filename> <hostname> - Build config file for hostname (not written to disk) buildbundle <bundle> <hostname> - Render a templated bundle for hostname (not written to disk) +automatch <propertyfile> <hostname> - Perform automatch on a Properties file bundles - Print out group/bundle information clients - Print out client/profile information config - Print out the configuration of the Bcfg2 server @@ -427,6 +428,43 @@ class infoCore(cmd.Cmd, Bcfg2.Server.Core.BaseCore): else: print('Usage: buildbundle filename hostname') + def do_automatch(self, args): + alist = args.split() + force = False + for arg in alist: + if arg == '-f': + alist.remove('-f') + force = True + if len(alist) != 2: + print("Usage: automatch [-f] <propertiesfile> <hostname>") + return + + if 'Properties' not in self.plugins: + print("Properties plugin not enabled") + return + + pname, client = alist + try: + automatch = self.setup.cfp.getboolean("properties", + "automatch", + default=False) + + pfile = self.plugins['Properties'].store.entries[pname] + if (not force and + not automatch and + pfile.xdata.get("automatch", "false").lower() != "true"): + print("Automatch not enabled on %s" % pname) + else: + metadata = self.build_metadata(client) + print(lxml.etree.tostring(pfile.XMLMatch(metadata), + xml_declaration=False, + pretty_print=True).decode('UTF-8')) + except: + err = sys.exc_info()[1] + print("Failed to automatch %s for host %s: %s" % (pname, + client, + err)) + def do_bundles(self, _): """Print out group/bundle info.""" data = [('Group', 'Bundles')] |