Support DSA SSH keys in bootstrap
authorThomas Thrainer <thomasth@google.com>
Tue, 6 Aug 2013 15:10:15 +0000 (17:10 +0200)
committerThomas Thrainer <thomasth@google.com>
Wed, 7 Aug 2013 07:38:20 +0000 (09:38 +0200)
As outlined in issue 338, Ganeti failed to initialize a cluster if no
RSA SSH key is present on the master node. This patch extends Ganetis
support to DSA keys, so clusters with only DSA keys are possible now.

This fixes issue 338.

Signed-off-by: Thomas Thrainer <thomasth@google.com>
Reviewed-by: Helga Velroyen <helgav@google.com>

lib/bootstrap.py
lib/config.py
lib/objects.py
lib/ssh.py
src/Ganeti/Objects.hs
test/py/ganeti.config_unittest.py
test/py/ganeti.ssh_unittest.py
test/py/mocks.py
tools/sanitize-config

index 3cd5523..cc62303 100644 (file)
@@ -577,8 +577,17 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
                                errors.ECODE_INVAL)
 
   # set up ssh config and /etc/hosts
-  sshline = utils.ReadFile(pathutils.SSH_HOST_RSA_PUB)
-  sshkey = sshline.split(" ")[1]
+  rsa_sshkey = ""
+  dsa_sshkey = ""
+  if os.path.isfile(pathutils.SSH_HOST_RSA_PUB):
+    sshline = utils.ReadFile(pathutils.SSH_HOST_RSA_PUB)
+    rsa_sshkey = sshline.split(" ")[1]
+  if os.path.isfile(pathutils.SSH_HOST_DSA_PUB):
+    sshline = utils.ReadFile(pathutils.SSH_HOST_DSA_PUB)
+    dsa_sshkey = sshline.split(" ")[1]
+  if not rsa_sshkey and not dsa_sshkey:
+    raise errors.OpPrereqError("Failed to find SSH public keys",
+                               errors.ECODE_ENVIRON)
 
   if modify_etc_hosts:
     utils.AddHostToEtcHosts(hostname.name, hostname.ip)
@@ -606,7 +615,8 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable=R0913, R0914
   # init of cluster config file
   cluster_config = objects.Cluster(
     serial_no=1,
-    rsahostkeypub=sshkey,
+    rsahostkeypub=rsa_sshkey,
+    dsahostkeypub=dsa_sshkey,
     highest_used_port=(constants.FIRST_DRBD_PORT - 1),
     mac_prefix=mac_prefix,
     volume_group_name=vg_name,
index ec93244..ad99684 100644 (file)
@@ -1190,7 +1190,7 @@ class ConfigWriter:
     return self._config_data.cluster.enabled_hypervisors[0]
 
   @locking.ssynchronized(_config_lock, shared=1)
-  def GetHostKey(self):
+  def GetRsaHostKey(self):
     """Return the rsa hostkey from the config.
 
     @rtype: string
@@ -1200,6 +1200,16 @@ class ConfigWriter:
     return self._config_data.cluster.rsahostkeypub
 
   @locking.ssynchronized(_config_lock, shared=1)
+  def GetDsaHostKey(self):
+    """Return the dsa hostkey from the config.
+
+    @rtype: string
+    @return: the dsa hostkey
+
+    """
+    return self._config_data.cluster.dsahostkeypub
+
+  @locking.ssynchronized(_config_lock, shared=1)
   def GetDefaultIAllocator(self):
     """Get the default instance allocator for this cluster.
 
index 1f5a194..ad9f1d7 100644 (file)
@@ -1519,6 +1519,7 @@ class Cluster(TaggableObject):
   __slots__ = [
     "serial_no",
     "rsahostkeypub",
+    "dsahostkeypub",
     "highest_used_port",
     "tcpudp_port_pool",
     "mac_prefix",
index dba13df..9d519fd 100644 (file)
@@ -328,6 +328,10 @@ def WriteKnownHostsFile(cfg, file_name):
   """Writes the cluster-wide equally known_hosts file.
 
   """
-  utils.WriteFile(file_name, mode=0600,
-                  data="%s ssh-rsa %s\n" % (cfg.GetClusterName(),
-                                            cfg.GetHostKey()))
+  data = ""
+  if cfg.GetRsaHostKey():
+    data += "%s ssh-rsa %s\n" % (cfg.GetClusterName(), cfg.GetRsaHostKey())
+  if cfg.GetDsaHostKey():
+    data += "%s ssh-dss %s\n" % (cfg.GetClusterName(), cfg.GetDsaHostKey())
+
+  utils.WriteFile(file_name, mode=0600, data=data)
index 702efbc..3f5b513 100644 (file)
@@ -669,6 +669,7 @@ type UidPool = [(Int, Int)]
 -- * Cluster definitions
 $(buildObject "Cluster" "cluster" $
   [ simpleField "rsahostkeypub"           [t| String           |]
+  , simpleField "dsahostkeypub"           [t| String           |]
   , simpleField "highest_used_port"       [t| Int              |]
   , simpleField "tcpudp_port_pool"        [t| [Int]            |]
   , simpleField "mac_prefix"              [t| String           |]
index e2e75db..bf6c654 100755 (executable)
@@ -76,6 +76,7 @@ class TestConfigRunner(unittest.TestCase):
     cluster_config = objects.Cluster(
       serial_no=1,
       rsahostkeypub="",
+      dsahostkeypub="",
       highest_used_port=(constants.FIRST_DRBD_PORT - 1),
       mac_prefix="aa:00:00",
       volume_group_name="xenvg",
index a2c13cd..eedc852 100755 (executable)
@@ -46,8 +46,9 @@ class TestKnownHosts(testutils.GanetiTestCase):
     cfg = mocks.FakeConfig()
     ssh.WriteKnownHostsFile(cfg, self.tmpfile)
     self.assertFileContent(self.tmpfile,
-        "%s ssh-rsa %s\n" % (cfg.GetClusterName(),
-                             mocks.FAKE_CLUSTER_KEY))
+        "%s ssh-rsa %s\n%s ssh-dss %s\n" %
+        (cfg.GetClusterName(), mocks.FAKE_CLUSTER_KEY,
+         cfg.GetClusterName(), mocks.FAKE_CLUSTER_KEY))
 
 
 class TestGetUserFiles(unittest.TestCase):
index 82e286d..91bc918 100644 (file)
@@ -46,7 +46,10 @@ class FakeConfig:
   def GetNodeList(self):
     return ["a", "b", "c"]
 
-  def GetHostKey(self):
+  def GetRsaHostKey(self):
+    return FAKE_CLUSTER_KEY
+
+  def GetDsaHostKey(self):
     return FAKE_CLUSTER_KEY
 
   def GetClusterName(self):
index d70204a..5c4668b 100755 (executable)
@@ -85,6 +85,7 @@ def SanitizeSecrets(opts, cfg): # pylint: disable=W0613
 
   """
   cfg["cluster"]["rsahostkeypub"] = ""
+  cfg["cluster"]["dsahostkeypub"] = ""
   for instance in cfg["instances"].values():
     for disk in instance["disks"]:
       RandomizeDiskSecrets(disk)