Handle SSH key changes in upgrades and downgrades
authorHrvoje Ribicic <riba@google.com>
Thu, 5 Nov 2015 13:13:58 +0000 (14:13 +0100)
committerHrvoje Ribicic <riba@google.com>
Fri, 20 Nov 2015 10:14:17 +0000 (11:14 +0100)
When performing an upgrade of an old cluster, it is necessary to set
the SSH key parameters to the exact same values earlier versions
implicitly used - DSA with 1024 bits.

In the other direction, we simply do not permit downgrades if keys
other than DSA are being used. Triggering a gnt-cluster renew-crypto
might be time-consuming and surprising for the user, so we are simply
throwing out an error message, explaining that the downgrade cannot be
performed in the current state of the cluster.

Signed-off-by: Hrvoje Ribicic <riba@google.com>
Reviewed-by: Helga Velroyen <helgav@google.com>

lib/tools/cfgupgrade.py
test/py/cfgupgrade_unittest.py

index 43cf2c6..ffa6f3f 100644 (file)
@@ -307,24 +307,33 @@ class CfgUpgrade(object):
     cluster = self.config_data.get("cluster", None)
     if cluster is None:
       raise Error("Cannot find cluster")
+
     ipolicy = cluster.setdefault("ipolicy", None)
     if ipolicy:
       self.UpgradeIPolicy(ipolicy, constants.IPOLICY_DEFAULTS, False)
     ial_params = cluster.get("default_iallocator_params", None)
+
     if not ial_params:
       cluster["default_iallocator_params"] = {}
+
     if not "candidate_certs" in cluster:
       cluster["candidate_certs"] = {}
+
     cluster["instance_communication_network"] = \
       cluster.get("instance_communication_network", "")
+
     cluster["install_image"] = \
       cluster.get("install_image", "")
+
     cluster["zeroing_image"] = \
       cluster.get("zeroing_image", "")
+
     cluster["compression_tools"] = \
       cluster.get("compression_tools", constants.IEC_DEFAULT_TOOLS)
+
     if "enabled_user_shutdown" not in cluster:
       cluster["enabled_user_shutdown"] = False
+
     cluster["data_collectors"] = cluster.get("data_collectors", {})
     for name in constants.DATA_COLLECTOR_NAMES:
       cluster["data_collectors"][name] = \
@@ -332,6 +341,14 @@ class CfgUpgrade(object):
             name, dict(active=True,
                        interval=constants.MOND_TIME_INTERVAL * 1e6))
 
+    # These parameters are set to pre-2.16 default values, which
+    # differ from post-2.16 default values
+    if "ssh_key_type" not in cluster:
+      cluster["ssh_key_type"] = constants.SSHK_DSA
+
+    if "ssh_key_bits" not in cluster:
+      cluster["ssh_key_bits"] = 1024
+
   @OrFail("Upgrading groups")
   def UpgradeGroups(self):
     cl_ipolicy = self.config_data["cluster"].get("ipolicy")
@@ -699,10 +716,42 @@ class CfgUpgrade(object):
 
   # DOWNGRADE ------------------------------------------------------------
 
+  @OrFail("Removing SSH parameters")
+  def DowngradeSshKeyParams(self):
+    """Removes the SSH key type and bits parameters from the config.
+
+    Also fails if these have been changed from values appropriate in lower
+    Ganeti versions.
+
+    """
+    # pylint: disable=E1103
+    # Because config_data is a dictionary which has the get method.
+    cluster = self.config_data.get("cluster", None)
+    if cluster is None:
+      raise Error("Can't find the cluster entry in the configuration")
+
+    def _FetchAndDelete(key):
+      val = cluster.get(key, None)
+      if key in cluster:
+        del cluster[key]
+      return val
+
+    ssh_key_type = _FetchAndDelete("ssh_key_type")
+    _FetchAndDelete("ssh_key_bits")
+
+    if ssh_key_type is not None and ssh_key_type != "dsa":
+      raise Error("The current Ganeti setup is using non-DSA SSH keys, and"
+                  " versions below 2.16 do not support these. To downgrade,"
+                  " please perform a gnt-cluster renew-crypto using the "
+                  " --new-ssh-keys and --ssh-key-type=dsa options, generating"
+                  " DSA keys that older versions can also use.")
+
   def DowngradeAll(self):
     self.config_data["version"] = version.BuildVersion(DOWNGRADE_MAJOR,
                                                        DOWNGRADE_MINOR, 0)
-    return True
+
+    self.DowngradeSshKeyParams()
+    return not self.errors
 
   def _ComposePaths(self):
     # We need to keep filenames locally because they might be renamed between
index 9d5d950..3f58766 100755 (executable)
@@ -74,6 +74,8 @@ def GetMinimalConfig():
         "cpu-avg-load": { "active": True, "interval": 5000000 },
         "xen-cpu-avg-load": { "active": True, "interval": 5000000 },
       },
+      "ssh_key_type": "dsa",
+      "ssh_key_bits": 1024,
     },
     "instances": {},
     "disks": {},