Handle SSL setup when downgrading
authorHelga Velroyen <helgav@google.com>
Wed, 1 Jul 2015 08:45:02 +0000 (10:45 +0200)
committerHelga Velroyen <helgav@google.com>
Mon, 6 Jul 2015 10:46:58 +0000 (12:46 +0200)
This patch will handle the downgrade of the SSL setup
from 2.12 to 2.11. Essentially, all client.pem and
ssconf_master_candidates_certs files will be deleted.
This will kick the cluster in a pre-2.11 mode wrt to
SSL and result in a nagging message to re-run
'gnt-cluster renew-crypto' when as output of 'gnt-cluster
verify'.

Signed-off-by: Helga Velroyen <helgav@google.com>
Reviewed-by: Petr Pudlak <pudlak@google.com>

lib/client/gnt_cluster.py
lib/tools/ssl_update.py
src/Ganeti/Constants.hs
tools/cfgupgrade

index a25c130..c56e2bb 100644 (file)
@@ -1071,6 +1071,7 @@ def _RenewCrypto(new_cluster_cert, new_rapi_cert, # pylint: disable=R0911
         constants.NDS_NODE_DAEMON_CERTIFICATE:
           utils.ReadFile(pathutils.NODED_CERT_FILE),
         constants.NDS_NODE_NAME: node_name,
+        constants.NDS_ACTION: constants.CRYPTO_ACTION_CREATE,
         }
 
       bootstrap.RunNodeSetupCmd(
@@ -2068,6 +2069,38 @@ def _VersionSpecificDowngrade():
   @return: True upon success
   """
   ToStdout("Performing version-specific downgrade tasks.")
+
+  nodes = ssconf.SimpleStore().GetOnlineNodeList()
+  cluster_name = ssconf.SimpleStore().GetClusterName()
+  ssh_ports = ssconf.SimpleStore().GetSshPortMap()
+
+  for node in nodes:
+    data = {
+      constants.NDS_CLUSTER_NAME: cluster_name,
+      constants.NDS_NODE_DAEMON_CERTIFICATE:
+        utils.ReadFile(pathutils.NODED_CERT_FILE),
+      constants.NDS_NODE_NAME: node,
+      constants.NDS_ACTION: constants.CRYPTO_ACTION_DELETE,
+      }
+
+    try:
+      bootstrap.RunNodeSetupCmd(
+          cluster_name,
+          node,
+          pathutils.SSL_UPDATE,
+          True, # debug
+          True, # verbose,
+          True, # use cluster key
+          False, # ask key
+          True, # strict host check
+          ssh_ports[node],
+          data)
+    except Exception as e: # pylint: disable=W0703
+      # As downgrading can fail if a node is temporarily unreachable
+      # only output the error, but do not abort the entire operation.
+      ToStderr("Downgrading SSL setup of node '%s' failed: %s." %
+               (node, e))
+
   return True
 
 
index 4d17d9d..88a24ee 100644 (file)
@@ -42,6 +42,7 @@ from ganeti import constants
 from ganeti import errors
 from ganeti import utils
 from ganeti import ht
+from ganeti import pathutils
 from ganeti.tools import common
 
 
@@ -49,6 +50,7 @@ _DATA_CHECK = ht.TStrictDict(False, True, {
   constants.NDS_CLUSTER_NAME: ht.TNonEmptyString,
   constants.NDS_NODE_DAEMON_CERTIFICATE: ht.TNonEmptyString,
   constants.NDS_NODE_NAME: ht.TNonEmptyString,
+  constants.NDS_ACTION: ht.TNonEmptyString,
   })
 
 
@@ -75,6 +77,37 @@ def ParseOptions():
   return common.VerifyOptions(parser, opts, args)
 
 
+def DeleteClientCertificate():
+  """Deleting the client certificate. This is necessary for downgrades."""
+  if os.path.exists(pathutils.NODED_CLIENT_CERT_FILE):
+    os.remove(pathutils.NODED_CLIENT_CERT_FILE)
+  else:
+    logging.debug("Trying to delete the client certificate '%s' which did not"
+                  " exist.", pathutils.NODED_CLIENT_CERT_FILE)
+
+
+def ClearMasterCandidateSsconfList():
+  """Clear the ssconf list of master candidate certs.
+
+  This is necessary when deleting the client certificates for a downgrade,
+  because otherwise the master cannot distribute the configuration to the
+  nodes via RPC during a downgrade anymore.
+
+  """
+  ssconf_file = os.path.join(
+    pathutils.DATA_DIR,
+    "%s%s" % (constants.SSCONF_FILEPREFIX,
+              constants.SS_MASTER_CANDIDATES_CERTS))
+  if os.path.exists:
+    os.remove(ssconf_file)
+  else:
+    logging.debug("Trying to delete the ssconf file '%s' which does not"
+                  " exist.", ssconf_file)
+
+
+# pylint: disable=E1103
+# This pyling message complains about 'data' as 'bool' not having a get
+# member, but obviously the type is wrongly inferred.
 def Main():
   """Main routine.
 
@@ -92,7 +125,17 @@ def Main():
     # is the same as on this node.
     common.VerifyCertificate(data, SslSetupError)
 
-    common.GenerateClientCertificate(data, SslSetupError)
+    action = data.get(constants.NDS_ACTION)
+    if not action:
+      raise SslSetupError("No Action specified.")
+
+    if action == constants.CRYPTO_ACTION_CREATE:
+      common.GenerateClientCertificate(data, SslSetupError)
+    elif action == constants.CRYPTO_ACTION_DELETE:
+      DeleteClientCertificate()
+      ClearMasterCandidateSsconfList()
+    else:
+      raise SslSetupError("Unsupported action: %s." % action)
 
   except Exception, err: # pylint: disable=W0703
     logging.debug("Caught unhandled exception", exc_info=True)
index 557bc1c..7d04720 100644 (file)
@@ -4385,8 +4385,17 @@ cryptoTypes = ConstantUtils.mkSet [cryptoTypeSslDigest]
 cryptoActionGet :: String
 cryptoActionGet = "get"
 
+cryptoActionCreate :: String
+cryptoActionCreate = "create"
+
+cryptoActionDelete :: String
+cryptoActionDelete = "delete"
+
 cryptoActions :: FrozenSet String
-cryptoActions = ConstantUtils.mkSet [cryptoActionGet]
+cryptoActions =
+  ConstantUtils.mkSet [ cryptoActionCreate
+                      , cryptoActionGet
+                      , cryptoActionDelete]
 
 -- Key word for master candidate cert list for bootstrapping.
 
@@ -4486,6 +4495,9 @@ ndsStartNodeDaemon = "start_node_daemon"
 ndsNodeName :: String
 ndsNodeName = "node_name"
 
+ndsAction :: String
+ndsAction = "action"
+
 -- * VCluster related constants
 
 vClusterEtcHosts :: String
index 9c2775d..9131fde 100755 (executable)
@@ -524,6 +524,11 @@ def DowngradeCluster(config_data):
   if "max_tracked_jobs" in cluster:
     del cluster["max_tracked_jobs"]
 
+  if "candidate_certs" in cluster:
+    # Clear the candidate certs to make people run 'gnt-cluster renew-crypto'
+    # after a downgrade from 2.12 to 2.11.
+    cluster["candidate_certs"] = {}
+
 
 def DowngradeGroups(config_data):
   for group in config_data["nodegroups"].values():