summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris St. Pierre <chris.a.st.pierre@gmail.com>2013-10-07 09:51:59 -0400
committerChris St. Pierre <chris.a.st.pierre@gmail.com>2013-10-07 09:51:59 -0400
commit45863fb1d059b420fef3739acb75e15d234f30e3 (patch)
tree79af358133fbb32179b3ee9e51fb761c863dda31
parent57aa79fa5acf676694e6d653d9d04753383723fb (diff)
downloadbcfg2-45863fb1d059b420fef3739acb75e15d234f30e3.tar.gz
bcfg2-45863fb1d059b420fef3739acb75e15d234f30e3.tar.bz2
bcfg2-45863fb1d059b420fef3739acb75e15d234f30e3.zip
SSHbase: support encryption of generated ssh keys
-rw-r--r--doc/server/plugins/generators/sshbase.txt11
-rw-r--r--src/lib/Bcfg2/Server/Plugins/SSHbase.py71
2 files changed, 77 insertions, 5 deletions
diff --git a/doc/server/plugins/generators/sshbase.txt b/doc/server/plugins/generators/sshbase.txt
index 2b6c8640b..641b9c598 100644
--- a/doc/server/plugins/generators/sshbase.txt
+++ b/doc/server/plugins/generators/sshbase.txt
@@ -160,6 +160,17 @@ in order to permit :ref:`pulling with bcfg2-admin
<server-admin-pull>`. You should almost certainly set ``sensitive``
to "true" in ``info.xml``.
+Encryption
+==========
+
+SSHbase can optionally encrypt the private keys that it generates. To
+enable this feature, set the ``passphrase`` option in the
+``[sshbase]`` section of ``bcfg2.conf`` to the name of the passphrase
+that should be used to encrypt all SSH keys. (The passphrases are
+enumerated in the ``[encryption]`` section.) See
+:ref:`server-encryption` for more details on Bcfg2 encryption in
+general.
+
Blog post
=========
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)