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 --- man/bcfg2-admin.8 | 2 +- man/bcfg2-build-reports.8 | 2 +- man/bcfg2-crypt.8 | 12 ++- man/bcfg2-info.8 | 2 +- man/bcfg2-lint.8 | 2 +- man/bcfg2-lint.conf.5 | 2 +- man/bcfg2-reports.8 | 2 +- man/bcfg2-server.8 | 2 +- man/bcfg2.1 | 2 +- man/bcfg2.conf.5 | 2 +- src/lib/Bcfg2/Client/Frame.py | 8 +- src/lib/Bcfg2/Options.py | 17 ++-- src/sbin/bcfg2-crypt | 174 ++++++++++++++++++++++++------------ tools/manpagegen/bcfg2-crypt.8.ronn | 10 ++- 14 files changed, 157 insertions(+), 82 deletions(-) diff --git a/man/bcfg2-admin.8 b/man/bcfg2-admin.8 index bed51ff9a..e5bcc55f3 100644 --- a/man/bcfg2-admin.8 +++ b/man/bcfg2-admin.8 @@ -1,5 +1,5 @@ . -.TH "BCFG2\-ADMIN" "8" "August 2012" "" "" +.TH "BCFG2\-ADMIN" "8" "September 2012" "" "" . .SH "NAME" \fBbcfg2\-admin\fR \- Perform repository administration tasks diff --git a/man/bcfg2-build-reports.8 b/man/bcfg2-build-reports.8 index 3c61e8356..55aff8b31 100644 --- a/man/bcfg2-build-reports.8 +++ b/man/bcfg2-build-reports.8 @@ -1,5 +1,5 @@ . -.TH "BCFG2\-BUILD\-REPORTS" "8" "August 2012" "" "" +.TH "BCFG2\-BUILD\-REPORTS" "8" "September 2012" "" "" . .SH "NAME" \fBbcfg2\-build\-reports\fR \- Generate state reports for Bcfg2 clients diff --git a/man/bcfg2-crypt.8 b/man/bcfg2-crypt.8 index a73f3e066..64c10a902 100644 --- a/man/bcfg2-crypt.8 +++ b/man/bcfg2-crypt.8 @@ -1,11 +1,11 @@ . -.TH "BCFG2\-CRYPT" "8" "August 2012" "" "" +.TH "BCFG2\-CRYPT" "8" "September 2012" "" "" . .SH "NAME" \fBbcfg2\-crypt\fR \- Bcfg2 encryption and decryption utility . .SH "SYNOPSIS" -\fBbcfg2\-crypt\fR [\fI\-C configfile\fR] [\-\-decrypt|\-\-encrypt] [\-\-cfg|\-\-properties] [\-\-remove] [\-\-xpath \fIxpath\fR] [\-p \fIpassphrase\-or\-name\fR] [\-v] \fIfilename\fR [\fIfilename\fR\.\.\.] +\fBbcfg2\-crypt\fR [\fI\-C configfile\fR] [\-\-decrypt|\-\-encrypt] [\-\-cfg|\-\-properties] [\-\-stdout] [\-\-remove] [\-\-xpath \fIxpath\fR] [\-p \fIpassphrase\-or\-name\fR] [\-v] [\-I] \fIfilename\fR [\fIfilename\fR\.\.\.] . .SH "DESCRIPTION" \fBbcfg2\-crypt\fR performs encryption and decryption of Cfg and Properties files\. It\'s often sufficient to run \fBbcfg2\-crypt\fR with only the name of the file you wish to encrypt or decrypt; it can usually figure out what to do\. @@ -29,6 +29,10 @@ Tell \fBbcfg2\-crypt\fR that an XML file should be encrypted in its entirety rat Tell \fBbcfg2\-crypt\fR 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 \fB\fR in your Properties files\. See \fIMODES\fR below for details\. . .TP +\fB\-\-stdout\fR +Print the resulting file to stdout instead of writing it to a file\. +. +.TP \fB\-\-remove\fR Remove the plaintext file after it has been encrypted\. Only meaningful for Cfg files\. . @@ -45,6 +49,10 @@ Specify the name of a passphrase specified in the \fB[encryption]\fR section of Be verbose\. . .TP +\fB\-I\fR +When encrypting a Properties file, interactively select the elements whose data should be encrypted\. +. +.TP \fB\-h\fR Display help and exit\. . diff --git a/man/bcfg2-info.8 b/man/bcfg2-info.8 index bb515079f..9362776a1 100644 --- a/man/bcfg2-info.8 +++ b/man/bcfg2-info.8 @@ -1,5 +1,5 @@ . -.TH "BCFG2\-INFO" "8" "August 2012" "" "" +.TH "BCFG2\-INFO" "8" "September 2012" "" "" . .SH "NAME" \fBbcfg2\-info\fR \- Creates a local version of the Bcfg2 server core for state observation diff --git a/man/bcfg2-lint.8 b/man/bcfg2-lint.8 index 7a5a69b7a..a6bfffb8a 100644 --- a/man/bcfg2-lint.8 +++ b/man/bcfg2-lint.8 @@ -1,5 +1,5 @@ . -.TH "BCFG2\-LINT" "8" "August 2012" "" "" +.TH "BCFG2\-LINT" "8" "September 2012" "" "" . .SH "NAME" \fBbcfg2\-lint\fR \- Check Bcfg2 specification for validity, common mistakes, and style diff --git a/man/bcfg2-lint.conf.5 b/man/bcfg2-lint.conf.5 index d23afa8dc..55dfa5cc2 100644 --- a/man/bcfg2-lint.conf.5 +++ b/man/bcfg2-lint.conf.5 @@ -1,5 +1,5 @@ . -.TH "BCFG2\-LINT\.CONF" "5" "August 2012" "" "" +.TH "BCFG2\-LINT\.CONF" "5" "September 2012" "" "" . .SH "NAME" \fBbcfg2\-lint\.conf\fR \- configuration parameters for bcfg2\-lint diff --git a/man/bcfg2-reports.8 b/man/bcfg2-reports.8 index b2c0cad43..78b594047 100644 --- a/man/bcfg2-reports.8 +++ b/man/bcfg2-reports.8 @@ -1,5 +1,5 @@ . -.TH "BCFG2\-REPORTS" "8" "August 2012" "" "" +.TH "BCFG2\-REPORTS" "8" "September 2012" "" "" . .SH "NAME" \fBbcfg2\-reports\fR \- Query reporting system for client status diff --git a/man/bcfg2-server.8 b/man/bcfg2-server.8 index 955f541c9..7bbfb87c3 100644 --- a/man/bcfg2-server.8 +++ b/man/bcfg2-server.8 @@ -1,5 +1,5 @@ . -.TH "BCFG2\-SERVER" "8" "August 2012" "" "" +.TH "BCFG2\-SERVER" "8" "September 2012" "" "" . .SH "NAME" \fBbcfg2\-server\fR \- Server for client configuration specifications diff --git a/man/bcfg2.1 b/man/bcfg2.1 index 2aa219756..231155797 100644 --- a/man/bcfg2.1 +++ b/man/bcfg2.1 @@ -1,5 +1,5 @@ . -.TH "BCFG2" "1" "August 2012" "" "" +.TH "BCFG2" "1" "September 2012" "" "" . .SH "NAME" \fBbcfg2\fR \- Bcfg2 client tool diff --git a/man/bcfg2.conf.5 b/man/bcfg2.conf.5 index b62d223c9..c7f8bf4ed 100644 --- a/man/bcfg2.conf.5 +++ b/man/bcfg2.conf.5 @@ -1,5 +1,5 @@ . -.TH "BCFG2\.CONF" "5" "August 2012" "" "" +.TH "BCFG2\.CONF" "5" "September 2012" "" "" . .SH "NAME" \fBbcfg2\.conf\fR \- configuration parameters for Bcfg2 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: diff --git a/tools/manpagegen/bcfg2-crypt.8.ronn b/tools/manpagegen/bcfg2-crypt.8.ronn index a164d47f1..64077ec3b 100644 --- a/tools/manpagegen/bcfg2-crypt.8.ronn +++ b/tools/manpagegen/bcfg2-crypt.8.ronn @@ -3,7 +3,7 @@ bcfg2-crypt(8) -- Bcfg2 encryption and decryption utility ## SYNOPSIS -`bcfg2-crypt` [<-C configfile>] [--decrypt|--encrypt] [--cfg|--properties] [--remove] [--xpath ] [-p ] [-v] [...] +`bcfg2-crypt` [<-C configfile>] [--decrypt|--encrypt] [--cfg|--properties] [--stdout] [--remove] [--xpath ] [-p ] [-v] [-I] [...] ## DESCRIPTION @@ -35,6 +35,10 @@ what to do. than `` in your Properties files. See [MODES] below for details. + * `--stdout`: + Print the resulting file to stdout instead of writing it to a + file. + * `--remove`: Remove the plaintext file after it has been encrypted. Only meaningful for Cfg files. @@ -53,6 +57,10 @@ what to do. * `-v`: Be verbose. + * `-I`: + When encrypting a Properties file, interactively select the + elements whose data should be encrypted. + * `-h`: Display help and exit. -- cgit v1.2.3-1-g7c22