From fff865e8f428f93c7718b9932552ea0261a95500 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Thu, 27 Oct 2011 08:49:09 -0400 Subject: Add a number of features to SSHbase: * Support for group-specific host keys * Support for fully static host- and group-specific ssh_known_hosts * (Support for totally generic host keys and ssh_known_hosts, too, but that's pretty useless.) * Support for info.xml, info, and :info files; only info.xml is likely to be useful, with the directive --- doc/server/info.txt | 25 ++- doc/server/plugins/generators/sshbase.txt | 99 +++++++-- doc/server/plugins/grouping/metadata.txt | 2 + src/lib/Bcfg2Py3k.py | 12 +- src/lib/Server/Plugin.py | 39 ++-- src/lib/Server/Plugins/Cfg.py | 9 +- src/lib/Server/Plugins/SSHbase.py | 321 ++++++++++++++++++------------ 7 files changed, 328 insertions(+), 179 deletions(-) diff --git a/doc/server/info.txt b/doc/server/info.txt index 97bb92a0d..ae2bf5cf6 100644 --- a/doc/server/info.txt +++ b/doc/server/info.txt @@ -3,19 +3,18 @@ .. NOTE: these are relative links (change when directory structure .. changes) -.. _Cfg: plugins/generators/cfg -.. _TGenshi: plugins/generators/tgenshi -.. _TCheetah: plugins/generators/tcheetah - .. _server-info: ==== Info ==== -Various file properties for entries served by the `Cfg`_, `TGenshi`_, -and `TCheetah`_ plugins are controlled through the use of ``:info``, -``info``, or ``info.xml`` files. +Various file properties for entries served by the :ref:`Cfg +`, :ref:`TGenshi +`, :ref:`TCheetah +`, and :ref:`SSHbase +` plugins are controlled through +the use of ``:info``, ``info``, or ``info.xml`` files. By default, these plugins are set to write files to the filesystem with owner **root**, group **root**, and mode **644** (read and write for @@ -82,6 +81,8 @@ specification. | | | execution | | +------------+-------------------+----------------------------------+---------+ +.. _server-info-info-xml: + info.xml files ============== @@ -94,9 +95,7 @@ files are XML, and work similarly to those used by :ref:`Rules The following specifies a different global set of permissions (root/sys/0651) than on clients in group webserver or named -"foo.example.com" (root/root/0652). - -.. code-block:: xml +"foo.example.com" (root/root/0652):: @@ -108,10 +107,10 @@ The following specifies a different global set of permissions -The following specifies a different set of permissions depending on -the path of the file. +.. versionadded:: 1.2.0 -.. code-block:: xml +You can also use the ```` directive to specify a different set +of permissions depending on the path of the file:: diff --git a/doc/server/plugins/generators/sshbase.txt b/doc/server/plugins/generators/sshbase.txt index 3697b62c4..5d679c7e5 100644 --- a/doc/server/plugins/generators/sshbase.txt +++ b/doc/server/plugins/generators/sshbase.txt @@ -8,7 +8,7 @@ SSHbase SSHbase is a purpose-built Bcfg2 plugin for managing ssh host keys. It is responsible for making ssh keys persist beyond a client rebuild and -building a proper ``ssh_known_hosts file``, including a correct localhost +building a proper ``ssh_known_hosts`` file, including a correct localhost record for the current system. It has two functions: @@ -26,32 +26,35 @@ Interacting with SSHbase ======================== * Pre-seeding with existing keys -- Currently existing keys will be - overwritten by new, sshbase-managed ones by default. Pre-existing keys - can be added to the repository by putting them in /SSHbase/.H_ + overwritten by new, sshbase-managed ones by default. Pre-existing + keys can be added to the repository by putting them in + ``/SSHbase/.H_`` -* Pre-seeding can also be performed using bcfg2-admin pull ConfigFile - /name/of/ssh/key +* Pre-seeding can also be performed using ``bcfg2-admin pull + ConfigFile /name/of/ssh/key`` -* Revoking existing keys -- deleting /SSHbase/\*.H_ - will remove keys for an existing client. +* Revoking existing keys -- deleting + ``/SSHbase/\*.H_`` will remove keys for an existing + client. Aliases ======= -SSHbase has support for Aliases listed in clients.xml. The address for -the entries are specified either through DNS (e.g. a CNAME), or via the +SSHbase has support for Aliases listed in :ref:`clients.xml +`. The address for the +entries are specified either through DNS (e.g. a CNAME), or via the address attribute to the Alias. Getting started =============== #. Add SSHbase to the **plugins** line in ``/etc/bcfg2.conf`` and - restart the server -- This enables the SSHbase plugin on the Bcfg2 + restart the server. This enables the SSHbase plugin on the Bcfg2 server. -#. Add Path entries for ``/etc/ssh/ssh_known_hosts``, and - ``/etc/ssh/ssh_host_dsa_key``, etc to a bundle or base. +#. Add Path entries for ``/etc/ssh/ssh_known_hosts``, + ``/etc/ssh/ssh_host_dsa_key``, ``/etc/ssh/ssh_host_dsa_key.pub``, + etc., to a bundle. #. Enjoy. @@ -59,6 +62,30 @@ At this point, SSHbase will generate new keys for any client without a recorded key in the repository, and will generate an ``ssh_known_hosts`` file appropriately. +Supported key formats +===================== + +SSHbase currently supports the following key formats: + +* RSA1 (``ssh_host_key``, ``ssh_host_key.pub``) +* RSA2 (``ssh_host_rsa_key``, ``ssh_host_rsa_key.pub``) +* DSA (``ssh_host_dsa_key``, ``ssh_host_dsa_key.pub``) +* ECDSA (``ssh_host_ecdsa_key``, ``ssh_host_ecdsa_key.pub``) + +Group-specific keys +=================== + +.. versionadded:: 1.2.0 + +In addition to host-specific keys, SSHbase also supports +group-specific keys, e.g., for a high-availability cluster or similar +application. Group-specific keys must be pre-seeded; SSHbase cannot +create group-specific keys itself. + +To use group-specific keys, simply create ``SSHbase/.Gxx_``. For instance, +``ssh_host_dsa_key.pub.G65_foo-cluster``. + Adding public keys for unmanaged hosts ====================================== @@ -82,6 +109,52 @@ The generated ``ssh_known_hosts`` file:: TEST1 TEST2 +Static ssh_known_hosts file +=========================== + +.. versionadded:: 1.2.0 + +You can also distribute a fully static ``ssh_known_hosts`` file on a +per-host or per-group basis by creating +``SSHbase/ssh_known_hosts.H_`` or +``SSHbase/ssh_known_hosts.Gxx_``. Those files will be +entirely static; Bcfg2 will not add any host keys to them itself. + +Permissions and Metadata +======================== + +.. versionadded:: 1.2.0 + +SSHbase supports use of an :ref:`info.xml ` file +to control the permissions and other metadata for the keys and +``ssh_known_hosts`` file. You can use the ```` directive in +``info.xml`` to change the metadata for different keys, e.g.:: + + + + + + + + + + +Default permissions are as follows: + ++----------------------------------+-------+-------+-------+-----------+----------+----------+ +| File | owner | group | perms | sensitive | paranoid | encoding | ++==================================+=======+=======+=======+===========+==========+==========+ +| ssh_known_hosts | root | root | 0644 | false | false | None | ++----------------------------------+-------+-------+-------+-----------+----------+----------+ +| ssh_host_key | root | root | 0600 | true | false | base64 | ++----------------------------------+-------+-------+-------+-----------+----------+----------+ +| ssh_host_key.pub | root | root | 0644 | false | false | base64 | ++----------------------------------+-------+-------+-------+-----------+----------+----------+ +| ssh_host_[rsa|dsa|ecdsa]_key | root | root | 0600 | true | false | None | ++----------------------------------+-------+-------+-------+-----------+----------+----------+ +| ssh_host_[rsa|dsa|ecdsa]_key.pub | root | root | 0644 | false | false | None | ++----------------------------------+-------+-------+-------+-----------+----------+----------+ + Blog post ========= diff --git a/doc/server/plugins/grouping/metadata.txt b/doc/server/plugins/grouping/metadata.txt index fc8605115..c52ac7612 100644 --- a/doc/server/plugins/grouping/metadata.txt +++ b/doc/server/plugins/grouping/metadata.txt @@ -25,6 +25,8 @@ modified from clients through use of the ``-p`` flag to ``bcfg2``. Clients are associated with profile groups in ``Metadata/clients.xml`` as shown below. +.. _server-plugins-grouping-metadata-clients-xml: + Metadata/clients.xml ==================== diff --git a/src/lib/Bcfg2Py3k.py b/src/lib/Bcfg2Py3k.py index 4803bf8b2..ee05b7e41 100644 --- a/src/lib/Bcfg2Py3k.py +++ b/src/lib/Bcfg2Py3k.py @@ -63,11 +63,17 @@ except ImportError: import http.client as httplib # print to file compatibility -def u_str(string): +def u_str(string, encoding=None): if sys.hexversion >= 0x03000000: - return string + if encoding is not None: + return string.encode(encoding) + else: + return string else: - return unicode(string) + if encoding is not None: + return unicode(string, encoding) + else: + return unicode(string) """ In order to use the new syntax for printing to a file, we need to do diff --git a/src/lib/Server/Plugin.py b/src/lib/Server/Plugin.py index c09a37ed8..190a205e6 100644 --- a/src/lib/Server/Plugin.py +++ b/src/lib/Server/Plugin.py @@ -934,9 +934,29 @@ class EntrySet: self.specific = re.compile(pattern) def get_matching(self, metadata): - return [item for item in list(self.entries.values()) \ + return [item for item in list(self.entries.values()) if item.specific.matches(metadata)] + def best_matching(self, metadata): + """ Return the appropriate interpreted template from the set of + available templates. """ + matching = self.get_matching(metadata) + + hspec = [ent for ent in matching if ent.specific.hostname] + if hspec: + return hspec[0] + + gspec = [ent for ent in matching if ent.specific.group] + if gspec: + gspec.sort(self.group_sortfunc) + return gspec[-1] + + aspec = [ent for ent in matching if ent.specific.all] + if aspec: + return aspec[0] + + raise PluginExecutionError + def handle_event(self, event): """Handle FAM events for the TemplateSet.""" action = event.code2str() @@ -1042,22 +1062,7 @@ class EntrySet: def bind_entry(self, entry, metadata): """Return the appropriate interpreted template from the set of available templates.""" self.bind_info_to_entry(entry, metadata) - matching = self.get_matching(metadata) - - hspec = [ent for ent in matching if ent.specific.hostname] - if hspec: - return hspec[0].bind_entry(entry, metadata) - - gspec = [ent for ent in matching if ent.specific.group] - if gspec: - gspec.sort(self.group_sortfunc) - return gspec[-1].bind_entry(entry, metadata) - - aspec = [ent for ent in matching if ent.specific.all] - if aspec: - return aspec[0].bind_entry(entry, metadata) - - raise PluginExecutionError + return self.best_matching(metadata).bind_entry(entry, metadata) class GroupSpool(Plugin, Generator): diff --git a/src/lib/Server/Plugins/Cfg.py b/src/lib/Server/Plugins/Cfg.py index f202628cd..0a791f171 100644 --- a/src/lib/Server/Plugins/Cfg.py +++ b/src/lib/Server/Plugins/Cfg.py @@ -12,6 +12,7 @@ import stat import sys import tempfile from subprocess import Popen, PIPE +from Bcfg2.Bcfg2Py3k import u_str import Bcfg2.Server.Plugin @@ -33,14 +34,6 @@ except: logger = logging.getLogger('Bcfg2.Plugins.Cfg') -# py3k compatibility -def u_str(string, encoding): - if sys.hexversion >= 0x03000000: - return string.encode(encoding) - else: - return unicode(string, encoding) - - # snipped from TGenshi def removecomment(stream): """A genshi filter that removes comments from the stream.""" diff --git a/src/lib/Server/Plugins/SSHbase.py b/src/lib/Server/Plugins/SSHbase.py index e4a9be44c..d31405a57 100644 --- a/src/lib/Server/Plugins/SSHbase.py +++ b/src/lib/Server/Plugins/SSHbase.py @@ -9,11 +9,77 @@ import sys import tempfile from subprocess import Popen, PIPE import Bcfg2.Server.Plugin +from Bcfg2.Bcfg2Py3k import u_str + +if sys.hexversion >= 0x03000000: + from functools import reduce + +import logging +logger = logging.getLogger(__name__) + +DEBUG = logger.error + +class KeyData(Bcfg2.Server.Plugin.SpecificData): + def __init__(self, name, specific, encoding): + Bcfg2.Server.Plugin.SpecificData.__init__(self, name, specific, + encoding) + self.encoding = encoding + + def bind_entry(self, entry, metadata): + entry.set('type', 'file') + if entry.get('encoding') == 'base64': + entry.text = binascii.b2a_base64(self.data) + else: + try: + entry.text = u_str(self.data, self.encoding) + except UnicodeDecodeError: + e = sys.exc_info()[1] + logger.error("Failed to decode %s: %s" % (entry.get('name'), e)) + logger.error("Please verify you are using the proper encoding.") + raise Bcfg2.Server.Plugin.PluginExecutionError + except ValueError: + e = sys.exc_info()[1] + logger.error("Error in specification for %s" % + entry.get('name')) + logger.error(str(e)) + logger.error("You need to specify base64 encoding for %s." % + entry.get('name')) + raise Bcfg2.Server.Plugin.PluginExecutionError + if entry.text in ['', None]: + entry.set('empty', 'true') + +class HostKeyEntrySet(Bcfg2.Server.Plugin.EntrySet): + 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) + self.metadata = {'owner': 'root', + 'group': 'root', + 'type': 'file'} + if encoding is not None: + self.metadata['encoding'] = encoding + if basename.endswith('.pub'): + self.metadata['perms'] = '0644' + else: + self.metadata['perms'] = '0600' + self.metadata['sensitive'] = 'true' + + +class KnownHostsEntrySet(Bcfg2.Server.Plugin.EntrySet): + def __init__(self, path): + Bcfg2.Server.Plugin.EntrySet.__init__(self, "ssh_known_hosts", path, + KeyData, None) + self.metadata = {'owner': 'root', + 'group': 'root', + 'type': 'file', + 'perms': '0644'} class SSHbase(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Generator, - Bcfg2.Server.Plugin.DirectoryBacked, Bcfg2.Server.Plugin.PullTarget): """ The sshbase generator manages ssh host keys (both v1 and v2) @@ -38,13 +104,6 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' - pubkeys = ["ssh_host_dsa_key.pub.H_%s", - "ssh_host_ecdsa_key.pub.H_%s", - "ssh_host_rsa_key.pub.H_%s", - "ssh_host_key.pub.H_%s"] - hostkeys = ["ssh_host_dsa_key.H_%s", - "ssh_host_rsa_key.H_%s", - "ssh_host_key.H_%s"] keypatterns = ["ssh_host_dsa_key", "ssh_host_ecdsa_key", "ssh_host_rsa_key", @@ -58,41 +117,39 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) Bcfg2.Server.Plugin.Generator.__init__(self) Bcfg2.Server.Plugin.PullTarget.__init__(self) - try: - Bcfg2.Server.Plugin.DirectoryBacked.__init__(self, self.data, - self.core.fam) - except OSError: - ioerr = sys.exc_info()[1] - self.logger.error("Failed to load SSHbase repository from %s" \ - % (self.data)) - self.logger.error(ioerr) - raise Bcfg2.Server.Plugin.PluginInitError - self.Entries = {'Path': - {'/etc/ssh/ssh_known_hosts': self.build_skn, - '/etc/ssh/ssh_host_dsa_key': self.build_hk, - '/etc/ssh/ssh_host_ecdsa_key': self.build_hk, - '/etc/ssh/ssh_host_rsa_key': self.build_hk, - '/etc/ssh/ssh_host_dsa_key.pub': self.build_hk, - '/etc/ssh/ssh_host_ecdsa_key.pub': self.build_hk, - '/etc/ssh/ssh_host_rsa_key.pub': self.build_hk, - '/etc/ssh/ssh_host_key': self.build_hk, - '/etc/ssh/ssh_host_key.pub': self.build_hk}} self.ipcache = {} self.namecache = {} self.__skn = False + core.fam.AddMonitor(self.data, self) + + self.static = dict() + self.entries = dict() + self.Entries['Path'] = dict() + + self.entries['/etc/ssh/ssh_known_hosts'] = KnownHostsEntrySet(self.data) + self.Entries['Path']['/etc/ssh/ssh_known_hosts'] = self.build_skn + for keypattern in self.keypatterns: + self.entries["/etc/ssh/" + keypattern] = HostKeyEntrySet(keypattern, + self.data) + self.Entries['Path']["/etc/ssh/" + keypattern] = self.build_hk + def get_skn(self): """Build memory cache of the ssh known hosts file.""" if not self.__skn: - self.__skn = "\n".join([value.data.decode() for key, value in \ - list(self.entries.items()) if \ - key.endswith('.static')]) - names = dict() # if no metadata is registered yet, defer if len(self.core.metadata.query.all()) == 0: self.__skn = False return self.__skn - for cmeta in self.core.metadata.query.all(): + + skn = [s.data.decode().rstrip() + for s in list(self.static.values())] + + mquery = self.core.metadata.query + + # build hostname cache + names = dict() + for cmeta in mquery.all(): names[cmeta.hostname] = set([cmeta.hostname]) names[cmeta.hostname].update(cmeta.aliases) newnames = set() @@ -114,20 +171,33 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, except: continue names[cmeta.hostname] = sorted(names[cmeta.hostname]) - # now we have our name cache - pubkeys = [pubk for pubk in list(self.entries.keys()) \ - if pubk.find('.pub.H_') != -1] + + pubkeys = [pubk for pubk in list(self.entries.keys()) + if pubk.endswith('.pub')] pubkeys.sort() - badnames = set() for pubkey in pubkeys: - hostname = pubkey.split('H_')[1] - if hostname not in names: - if hostname not in badnames: - badnames.add(hostname) - self.logger.error("SSHbase: Unknown host %s; ignoring public keys" % hostname) - continue - self.__skn += "%s %s" % (','.join(names[hostname]), - self.entries[pubkey].data.decode()) + for entry in self.entries[pubkey].entries.values(): + specific = entry.specific + if specific.hostname and specific.hostname in names: + hostnames = names[specific.hostname] + elif specific.group: + hostnames = \ + reduce(lambda x, y: x + y, + [names[cmeta.hostname] + for cmeta in \ + mquery.by_groups([specific.group])], []) + elif specific.all: + # a generic key for all hosts? really? + hostnames = reduce(lambda x, y: x + y, + list(names.values()), []) + if not hostnames: + self.logger.info("Unknown key %s, skipping" % + entry.name) + + skn.append("%s %s" % (','.join(hostnames), + entry.data.decode().rstrip())) + + self.__skn = "\n".join(skn) return self.__skn def set_skn(self, value): @@ -137,28 +207,37 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, def HandleEvent(self, event=None): """Local event handler that does skn regen on pubkey change.""" - Bcfg2.Server.Plugin.DirectoryBacked.HandleEvent(self, event) - if event and '_key.pub.H_' in event.filename: - self.skn = False - if event and event.filename.endswith('.static'): - self.skn = False - if not self.__skn: - if (len(list(self.entries.keys()))) >= (len(os.listdir(self.data)) - 1): - _ = self.skn - - def HandlesEntry(self, entry, _): - """Handle key entries dynamically.""" - return entry.tag == 'Path' and \ - ([fpat for fpat in self.keypatterns - if entry.get('name').endswith(fpat)] - or entry.get('name').endswith('ssh_known_hosts')) - - def HandleEntry(self, entry, metadata): - """Bind data.""" - if entry.get('name').endswith('ssh_known_hosts'): - return self.build_skn(entry, metadata) - else: - return self.build_hk(entry, metadata) + # skip events we don't care about + action = event.code2str() + if action == "endExist" or event.filename == self.data: + return + + for entry in list(self.entries.values()): + if entry.specific.match(event.filename): + entry.handle_event(event) + if event.filename.endswith(".pub"): + self.skn = False + return + + if event.filename in ['info', 'info.xml', ':info']: + for entry in list(self.entries.values()): + entry.handle_event(event) + return + + if event.filename.endswith('.static'): + if action == "deleted" and event.filename in self.static: + del self.static[event.filename] + self.skn = False + else: + self.static[event.filename] = \ + Bcfg2.Server.Plugin.FileBacked(os.path.join(self.data, + event.filename)) + self.static[event.filename].HandleEvent(event) + self.skn = False + return + + self.logger.warn("SSHbase: Got unknown event %s %s" % + (event.filename, action)) def get_ipcache_entry(self, client): """Build a cache of dns results.""" @@ -208,72 +287,64 @@ class SSHbase(Bcfg2.Server.Plugin.Plugin, def build_skn(self, entry, metadata): """This function builds builds a host specific known_hosts file.""" - client = metadata.hostname - entry.text = self.skn - hostkeys = [keytmpl % client for keytmpl in self.pubkeys \ - if (keytmpl % client) in self.entries] - hostkeys.sort() - for hostkey in hostkeys: - entry.text += "localhost,localhost.localdomain,127.0.0.1 %s" % ( - self.entries[hostkey].data.decode()) - permdata = {'owner': 'root', - 'group': 'root', - 'type': 'file', - 'perms': '0644'} - [entry.attrib.__setitem__(key, permdata[key]) for key in permdata] + try: + rv = self.entries[entry.get('name')].bind_entry(entry, metadata) + except Bcfg2.Server.Plugin.PluginExecutionError: + client = metadata.hostname + entry.text = self.skn + hostkeys = [] + for k in self.keypatterns: + if k.endswith(".pub"): + try: + hostkeys.append(self.entries["/etc/ssh/" + + k].best_matching(metadata)) + except Bcfg2.Server.Plugin.PluginExecutionError: + pass + hostkeys.sort() + for hostkey in hostkeys: + entry.text += "localhost,localhost.localdomain,127.0.0.1 %s" % ( + hostkey.data.decode().rstrip()) + self.entries[entry.get('name')].bind_info_to_entry(entry, metadata) def build_hk(self, entry, metadata): """This binds host key data into entries.""" - client = metadata.hostname - filename = "%s.H_%s" % (entry.get('name').split('/')[-1], client) - if filename not in list(self.entries.keys()): + rv = None + try: + rv = self.entries[entry.get('name')].bind_entry(entry, metadata) + except Bcfg2.Server.Plugin.PluginExecutionError: + filename = entry.get('name').split('/')[-1] self.GenerateHostKeyPair(client, filename) - # Service the FAM events queued up by the key generation so - # the data structure entries will be available for binding. - # - # NOTE: We wait for up to ten seconds. There is some potential - # for race condition, because if the file monitor doesn't get - # notified about the new key files in time, those entries - # won't be available for binding. In practice, this seems - # "good enough". - tries = 0 - while not filename in self.entries: - if tries >= 10: - self.logger.error("%s still not registered" % filename) - raise Bcfg2.Server.Plugin.PluginExecutionError - self.fam.handle_events_in_interval(1) - tries += 1 - keydata = self.entries[filename].data - permdata = {'owner': 'root', - 'group': 'root', - 'type': 'file'} - if entry.get('name')[-4:] == '.pub': - permdata['perms'] = '0644' - else: - permdata['perms'] = '0600' - permdata['sensitive'] = 'true' - [entry.attrib.__setitem__(key, permdata[key]) for key in permdata] - if "ssh_host_key.H_" == filename[:15]: - entry.attrib['encoding'] = 'base64' - entry.text = binascii.b2a_base64(keydata) - else: - entry.text = keydata + # Service the FAM events queued up by the key generation + # so the data structure entries will be available for + # binding. + # + # NOTE: We wait for up to ten seconds. There is some + # potential for race condition, because if the file + # monitor doesn't get notified about the new key files in + # time, those entries won't be available for binding. In + # practice, this seems "good enough". + tries = 0 + while rv is None: + if tries >= 10: + self.logger.error("%s still not registered" % filename) + raise Bcfg2.Server.Plugin.PluginExecutionError + self.fam.handle_events_in_interval(1) + tries += 1 + try: + rv = self.entries[entry.get('name')].bind_entry() + except Bcfg2.Server.Plugin.PluginExecutionError: + pass + return rv def GenerateHostKeyPair(self, client, filename): """Generate new host key pair for client.""" - filename = filename.split('.')[0] # no trailing ".pub", please - if filename == 'ssh_host_rsa_key': - hostkey = 'ssh_host_rsa_key.H_%s' % client - keytype = 'rsa' - elif filename == 'ssh_host_dsa_key': - hostkey = 'ssh_host_dsa_key.H_%s' % client - keytype = 'dsa' - elif filename == 'ssh_host_ecdsa_key': - hostkey = 'ssh_host_ecdsa_key.H_%s' % client - keytype = 'ecdsa' - elif filename == 'ssh_host_key': - hostkey = 'ssh_host_key.H_%s' % client - keytype = 'rsa1' + match = re.search(r'(ssh_host_(?:((?:ec)?d|rsa)_)?key)', filename) + if match: + hostkey = "%s.H_%s" % (match.group(1), client) + if match.group(2): + keytype = match.group(2) + else: + keytype = 'rsa1' else: return -- cgit v1.2.3-1-g7c22