From 45863fb1d059b420fef3739acb75e15d234f30e3 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Mon, 7 Oct 2013 09:51:59 -0400 Subject: SSHbase: support encryption of generated ssh keys --- src/lib/Bcfg2/Server/Plugins/SSHbase.py | 71 ++++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/lib/Bcfg2/Server/Plugins/SSHbase.py b/src/lib/Bcfg2/Server/Plugins/SSHbase.py index f3f711b77..9b1e63831 100644 --- a/src/lib/Bcfg2/Server/Plugins/SSHbase.py +++ b/src/lib/Bcfg2/Server/Plugins/SSHbase.py @@ -12,6 +12,12 @@ from itertools import chain from Bcfg2.Utils import Executor from Bcfg2.Server.Plugin import PluginExecutionError from Bcfg2.Compat import any, u_str, b64encode # pylint: disable=W0622 +try: + from Bcfg2.Server.Encryption import ssl_encrypt, bruteforce_decrypt, \ + EVPError + HAS_CRYPTO = True +except ImportError: + HAS_CRYPTO = False class KeyData(Bcfg2.Server.Plugin.SpecificData): @@ -51,6 +57,17 @@ class KeyData(Bcfg2.Server.Plugin.SpecificData): if entry.text in ['', None]: entry.set('empty', 'true') + def handle_event(self, event): + Bcfg2.Server.Plugin.SpecificData.handle_event(self, event) + if event.filename.endswith(".crypt"): + if self.data is None: + return + # todo: let the user specify a passphrase by name + try: + self.data = bruteforce_decrypt(self.data) + except EVPError: + raise PluginExecutionError("Failed to decrypt %s" % self.name) + class HostKeyEntrySet(Bcfg2.Server.Plugin.EntrySet): """ EntrySet to handle all kinds of host keys """ @@ -66,6 +83,12 @@ class HostKeyEntrySet(Bcfg2.Server.Plugin.EntrySet): else: self.metadata['mode'] = '0600' + def specificity_from_filename(self, fname, specific=None): + if fname.endswith(".crypt"): + fname = fname[0:-6] + return Bcfg2.Server.Plugin.EntrySet.specificity_from_filename( + self, fname, specific=specific) + class KnownHostsEntrySet(Bcfg2.Server.Plugin.EntrySet): """ EntrySet to handle the ssh_known_hosts file """ @@ -110,6 +133,11 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, "ssh_host_rsa_key.pub", "ssh_host_key.pub"] + options = [ + Bcfg2.Options.Option( + cf=("sshbase", "passphrase"), dest="sshbase_passphrase", + help="Passphrase used to encrypt generated private SSH host keys")] + def __init__(self, core, datastore): Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) Bcfg2.Server.Plugin.Generator.__init__(self) @@ -138,6 +166,14 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, self.Entries['Path']["/etc/ssh/" + keypattern] = self.build_hk self.cmd = Executor() + @property + def passphrase(self): + """ The passphrase used to encrypt private keys """ + if HAS_CRYPTO and Bcfg2.Options.setup.sshbase_passphrase: + return Bcfg2.Options.setup.passphrases[ + Bcfg2.Options.setup.sshbase_passphrase] + return None + def get_skn(self): """Build memory cache of the ssh known hosts file.""" if not self.__skn: @@ -236,7 +272,11 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, return for entry in list(self.entries.values()): - if entry.specific.match(event.filename): + if event.filename.endswith(".crypt"): + fname = event.filename[0:-6] + else: + fname = event.filename + if entry.specific.match(fname): entry.handle_event(event) if any(event.filename.startswith(kp) for kp in self.keypatterns @@ -369,6 +409,11 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, self.entries[entry.get('name')].bind_entry(entry, metadata) is_bound = True except Bcfg2.Server.Plugin.PluginExecutionError: + import lxml.etree + + print "failed to bind %s: %s" % ( + lxml.etree.tostring(entry), + sys.exc_info()[1]) pass def GenerateHostKeyPair(self, client, filename): @@ -397,13 +442,29 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, raise PluginExecutionError("SSHbase: Error running ssh-keygen: %s" % result.error) + if self.passphrase: + self.debug_log("SSHbase: Encrypting private key for %s" % fileloc) + try: + data = ssl_encrypt(open(temploc).read(), self.passphrase) + except IOError: + raise PluginExecutionError("Unable to read temporary SSH key: " + "%s" % sys.exc_info()[1]) + except EVPError: + raise PluginExecutionError("Unable to encrypt SSH key: %s" % + sys.exc_info()[1]) + try: + open("%s.crypt" % fileloc, "wb").write(data) + except IOError: + raise PluginExecutionError("Unable to write encrypted SSH " + "key: %s" % sys.exc_info()[1]) + try: - shutil.copy(temploc, fileloc) + if not self.passphrase: + shutil.copy(temploc, fileloc) shutil.copy("%s.pub" % temploc, publoc) except IOError: - err = sys.exc_info()[1] - raise PluginExecutionError("Temporary SSH keys not found: %s" % - err) + raise PluginExecutionError("Unable to copy temporary SSH key: %s" % + sys.exc_info()[1]) try: os.unlink(temploc) -- cgit v1.2.3-1-g7c22