summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Server/Plugins/SSHbase.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/Bcfg2/Server/Plugins/SSHbase.py')
-rw-r--r--src/lib/Bcfg2/Server/Plugins/SSHbase.py173
1 files changed, 109 insertions, 64 deletions
diff --git a/src/lib/Bcfg2/Server/Plugins/SSHbase.py b/src/lib/Bcfg2/Server/Plugins/SSHbase.py
index 0152fcf70..7736bd050 100644
--- a/src/lib/Bcfg2/Server/Plugins/SSHbase.py
+++ b/src/lib/Bcfg2/Server/Plugins/SSHbase.py
@@ -5,27 +5,25 @@ import os
import sys
import socket
import shutil
-import logging
import tempfile
-from itertools import chain
-from subprocess import Popen, PIPE
+import lxml.etree
+import Bcfg2.Options
import Bcfg2.Server.Plugin
+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
-
-LOGGER = logging.getLogger(__name__)
+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):
""" class to handle key data for HostKeyEntrySet """
- def __init__(self, name, specific, encoding):
- Bcfg2.Server.Plugin.SpecificData.__init__(self,
- name,
- specific,
- encoding)
- self.encoding = encoding
-
def __lt__(self, other):
return self.name < other.name
@@ -42,49 +40,62 @@ class KeyData(Bcfg2.Server.Plugin.SpecificData):
entry.text = b64encode(self.data)
else:
try:
- entry.text = u_str(self.data, self.encoding)
+ entry.text = u_str(self.data, Bcfg2.Options.setup.encoding)
except UnicodeDecodeError:
msg = "Failed to decode %s: %s" % (entry.get('name'),
sys.exc_info()[1])
- LOGGER.error(msg)
- LOGGER.error("Please verify you are using the proper encoding")
+ self.logger.error(msg)
+ self.logger.error("Please verify you are using the proper "
+ "encoding")
raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
except ValueError:
msg = "Error in specification for %s: %s" % (entry.get('name'),
sys.exc_info()[1])
- LOGGER.error(msg)
- LOGGER.error("You need to specify base64 encoding for %s" %
- entry.get('name'))
+ self.logger.error(msg)
+ self.logger.error("You need to specify base64 encoding for %s"
+ % entry.get('name'))
raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
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 """
def __init__(self, basename, path):
- if basename.startswith("ssh_host_key"):
- encoding = "base64"
- else:
- encoding = None
- Bcfg2.Server.Plugin.EntrySet.__init__(self, basename, path, KeyData,
- encoding)
+ Bcfg2.Server.Plugin.EntrySet.__init__(self, basename, path, KeyData)
self.metadata = {'owner': 'root',
'group': 'root',
'type': 'file'}
- if encoding is not None:
- self.metadata['encoding'] = encoding
+ if basename.startswith("ssh_host_key"):
+ self.metadata['encoding'] = "base64"
if basename.endswith('.pub'):
self.metadata['mode'] = '0644'
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 """
def __init__(self, path):
Bcfg2.Server.Plugin.EntrySet.__init__(self, "ssh_known_hosts", path,
- KeyData, None)
+ KeyData)
self.metadata = {'owner': 'root',
'group': 'root',
'type': 'file',
@@ -92,7 +103,6 @@ class KnownHostsEntrySet(Bcfg2.Server.Plugin.EntrySet):
class SSHbase(Bcfg2.Server.Plugin.Plugin,
- Bcfg2.Server.Plugin.Caching,
Bcfg2.Server.Plugin.Connector,
Bcfg2.Server.Plugin.Generator,
Bcfg2.Server.Plugin.PullTarget):
@@ -125,9 +135,13 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
"ssh_host_rsa_key.pub",
"ssh_host_key.pub"]
- def __init__(self, core, datastore):
- Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore)
- Bcfg2.Server.Plugin.Caching.__init__(self)
+ options = [
+ Bcfg2.Options.Option(
+ cf=("sshbase", "passphrase"), dest="sshbase_passphrase",
+ help="Passphrase used to encrypt generated private SSH host keys")]
+
+ def __init__(self, core):
+ Bcfg2.Server.Plugin.Plugin.__init__(self, core)
Bcfg2.Server.Plugin.Connector.__init__(self)
Bcfg2.Server.Plugin.Generator.__init__(self)
Bcfg2.Server.Plugin.PullTarget.__init__(self)
@@ -139,7 +153,8 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
# do so once
self.badnames = dict()
- core.fam.AddMonitor(self.data, self)
+ self.fam = Bcfg2.Server.FileMonitor.get_fam()
+ self.fam.AddMonitor(self.data, self)
self.static = dict()
self.entries = dict()
@@ -152,9 +167,15 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
self.entries["/etc/ssh/" + keypattern] = \
HostKeyEntrySet(keypattern, self.data)
self.Entries['Path']["/etc/ssh/" + keypattern] = self.build_hk
+ self.cmd = Executor()
- def expire_cache(self, key=None):
- self.__skn = False
+ @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."""
@@ -180,20 +201,19 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
newnames.add(name.split('.')[0])
try:
newips.update(self.get_ipcache_entry(name)[0])
- except: # pylint: disable=W0702
+ except PluginExecutionError:
continue
names[cmeta.hostname].update(newnames)
names[cmeta.hostname].update(cmeta.addresses)
names[cmeta.hostname].update(newips)
# TODO: Only perform reverse lookups on IPs if an
# option is set.
- if True:
- for ip in newips:
- try:
- names[cmeta.hostname].update(
- self.get_namecache_entry(ip))
- except: # pylint: disable=W0702
- continue
+ for ip in newips:
+ try:
+ names[cmeta.hostname].update(
+ self.get_namecache_entry(ip))
+ except socket.herror:
+ continue
names[cmeta.hostname] = sorted(names[cmeta.hostname])
pubkeys = [pubk for pubk in list(self.entries.keys())
@@ -254,7 +274,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
@@ -264,7 +288,7 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
self.skn = False
return
- if event.filename in ['info', 'info.xml', ':info']:
+ if event.filename == 'info.xml':
for entry in list(self.entries.values()):
entry.handle_event(event)
return
@@ -291,7 +315,8 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
if self.ipcache[client]:
return self.ipcache[client]
else:
- raise socket.gaierror
+ raise PluginExecutionError("No cached IP address for %s" %
+ client)
else:
# need to add entry
try:
@@ -300,14 +325,17 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
self.ipcache[client] = (ipaddr, client)
return (ipaddr, client)
except socket.gaierror:
- ipaddr = Popen(["getent", "hosts", client],
- stdout=PIPE).stdout.read().strip().split()
- if ipaddr:
- self.ipcache[client] = (ipaddr, client)
- return (ipaddr, client)
+ result = self.cmd.run(["getent", "hosts", client])
+ if result.success:
+ ipaddr = result.stdout.strip().split()
+ if ipaddr:
+ self.ipcache[client] = (ipaddr, client)
+ return (ipaddr, client)
self.ipcache[client] = False
- self.logger.error("Failed to find IP address for %s" % client)
- raise socket.gaierror
+ msg = "Failed to find IP address for %s: %s" % (client,
+ result.error)
+ self.logger.error(msg)
+ raise PluginExecutionError(msg)
def get_namecache_entry(self, cip):
"""Build a cache of name lookups from client IP addresses."""
@@ -316,7 +344,7 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
if self.namecache[cip]:
return self.namecache[cip]
else:
- raise socket.gaierror
+ raise socket.herror
else:
# add an entry that has not been cached
try:
@@ -327,7 +355,7 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
self.namecache[cip] = []
self.namecache[cip].extend(rvlookup[1])
return self.namecache[cip]
- except socket.gaierror:
+ except socket.herror:
self.namecache[cip] = False
self.logger.error("Failed to find any names associated with "
"IP address %s" % cip)
@@ -377,13 +405,15 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
msg = "%s still not registered" % filename
self.logger.error(msg)
raise Bcfg2.Server.Plugin.PluginExecutionError(msg)
- self.core.fam.handle_events_in_interval(1)
+ self.fam.handle_events_in_interval(1)
tries += 1
try:
self.entries[entry.get('name')].bind_entry(entry, metadata)
is_bound = True
except Bcfg2.Server.Plugin.PluginExecutionError:
- pass
+ print("Failed to bind %s: %s") % (
+ lxml.etree.tostring(entry),
+ sys.exc_info()[1])
def GenerateHostKeyPair(self, client, filename):
"""Generate new host key pair for client."""
@@ -406,19 +436,34 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin,
cmd = ["ssh-keygen", "-q", "-f", temploc, "-N", "",
"-t", keytype, "-C", "root@%s" % client]
self.debug_log("SSHbase: Running: %s" % " ".join(cmd))
- proc = Popen(cmd, stdout=PIPE, stdin=PIPE)
- err = proc.communicate()[1]
- if proc.wait():
+ result = self.cmd.run(cmd)
+ if not result.success:
raise PluginExecutionError("SSHbase: Error running ssh-keygen: %s"
- % err)
+ % 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)