Allow SSH key property changes
authorHrvoje Ribicic <riba@google.com>
Wed, 4 Nov 2015 13:24:03 +0000 (13:24 +0000)
committerHrvoje Ribicic <riba@google.com>
Fri, 20 Nov 2015 10:14:15 +0000 (11:14 +0100)
By explicitly specifying the old and new SSH key type in the SSH key
renewal, this patch allows the switching of SSH key types to take place
during such an operation.

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

lib/backend.py
lib/client/gnt_cluster.py
lib/cmdlib/cluster/__init__.py
lib/rpc_defs.py
lib/server/noded.py
src/Ganeti/OpCodes.hs
src/Ganeti/OpParams.hs
src/Ganeti/Rpc.hs
test/hs/Test/Ganeti/OpCodes.hs

index 19409e5..7ebbdb9 100644 (file)
@@ -1884,7 +1884,8 @@ def _ReplaceMasterKeyOnMaster(root_keyfiles):
 
 
 def RenewSshKeys(node_uuids, node_names, master_candidate_uuids,
-                 potential_master_candidates, ssh_key_type, ssh_key_bits,
+                 potential_master_candidates, old_key_type, new_key_type,
+                 new_key_bits,
                  ganeti_pub_keys_file=pathutils.SSH_PUB_KEYS,
                  ssconf_store=None,
                  noded_cert_file=pathutils.NODED_CERT_FILE,
@@ -1899,10 +1900,12 @@ def RenewSshKeys(node_uuids, node_names, master_candidate_uuids,
   @type master_candidate_uuids: list of str
   @param master_candidate_uuids: list of UUIDs of master candidates or
     master node
-  @type ssh_key_type: One of L{constants.SSHK_ALL}
-  @param ssh_key_type: the type of SSH key to be generated
-  @type ssh_key_bits: int
-  @param ssh_key_bits: the length of the key to be generated
+  @type old_key_type: One of L{constants.SSHK_ALL}
+  @param old_key_type: the type of SSH key already present on nodes
+  @type new_key_type: One of L{constants.SSHK_ALL}
+  @param new_key_type: the type of SSH key to be generated
+  @type new_key_bits: int
+  @param new_key_bits: the length of the key to be generated
   @type ganeti_pub_keys_file: str
   @param ganeti_pub_keys_file: file path of the the public key file
   @type noded_cert_file: str
@@ -1926,8 +1929,9 @@ def RenewSshKeys(node_uuids, node_names, master_candidate_uuids,
 
   (_, root_keyfiles) = \
     ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=False, dircheck=False)
-  (_, node_pub_keyfile) = root_keyfiles[ssh_key_type]
-  old_master_key = utils.ReadFile(node_pub_keyfile)
+  (_, old_pub_keyfile) = root_keyfiles[old_key_type]
+  (_, new_pub_keyfile) = root_keyfiles[new_key_type]
+  old_master_key = utils.ReadFile(old_pub_keyfile)
 
   node_uuid_name_map = zip(node_uuids, node_names)
 
@@ -1955,7 +1959,7 @@ def RenewSshKeys(node_uuids, node_names, master_candidate_uuids,
 
     if master_candidate:
       logging.debug("Fetching old SSH key from node '%s'.", node_name)
-      old_pub_key = ssh.ReadRemoteSshPubKeys(node_pub_keyfile,
+      old_pub_key = ssh.ReadRemoteSshPubKeys(old_pub_keyfile,
                                              node_name, cluster_name,
                                              ssh_port_map[node_name],
                                              False, # ask_key
@@ -1980,15 +1984,15 @@ def RenewSshKeys(node_uuids, node_names, master_candidate_uuids,
                       " key. Not deleting that key on the node.", node_name)
 
     logging.debug("Generating new SSH key for node '%s'.", node_name)
-    _GenerateNodeSshKey(node_uuid, node_name, ssh_port_map, ssh_key_type,
-                        ssh_key_bits, pub_key_file=ganeti_pub_keys_file,
+    _GenerateNodeSshKey(node_uuid, node_name, ssh_port_map, new_key_type,
+                        new_key_bits, pub_key_file=ganeti_pub_keys_file,
                         ssconf_store=ssconf_store,
                         noded_cert_file=noded_cert_file,
                         run_cmd_fn=run_cmd_fn)
 
     try:
       logging.debug("Fetching newly created SSH key from node '%s'.", node_name)
-      pub_key = ssh.ReadRemoteSshPubKeys(node_pub_keyfile,
+      pub_key = ssh.ReadRemoteSshPubKeys(new_pub_keyfile,
                                          node_name, cluster_name,
                                          ssh_port_map[node_name],
                                          False, # ask_key
@@ -2023,7 +2027,7 @@ def RenewSshKeys(node_uuids, node_names, master_candidate_uuids,
   # Generate a new master key with a suffix, don't touch the old one for now
   logging.debug("Generate new ssh key of master.")
   _GenerateNodeSshKey(master_node_uuid, master_node_name, ssh_port_map,
-                      ssh_key_type, ssh_key_bits,
+                      new_key_type, new_key_bits,
                       pub_key_file=ganeti_pub_keys_file,
                       ssconf_store=ssconf_store,
                       noded_cert_file=noded_cert_file,
index 16120a7..93ab24c 100644 (file)
@@ -979,11 +979,12 @@ def _ReadAndVerifyCert(cert_filename, verify_private_key=False):
   return pem
 
 
+# pylint: disable=R0913
 def _RenewCrypto(new_cluster_cert, new_rapi_cert, # pylint: disable=R0911
                  rapi_cert_filename, new_spice_cert, spice_cert_filename,
                  spice_cacert_filename, new_confd_hmac_key, new_cds,
                  cds_filename, force, new_node_cert, new_ssh_keys,
-                 verbose, debug):
+                 ssh_key_type, ssh_key_bits, verbose, debug):
   """Renews cluster certificates, keys and secrets.
 
   @type new_cluster_cert: bool
@@ -1011,10 +1012,14 @@ def _RenewCrypto(new_cluster_cert, new_rapi_cert, # pylint: disable=R0911
   @param new_node_cert: Whether to generate new node certificates
   @type new_ssh_keys: bool
   @param new_ssh_keys: Whether to generate new node SSH keys
+  @type ssh_key_type: One of L{constants.SSHK_ALL}
+  @param ssh_key_type: The type of SSH key to be generated
+  @type ssh_key_bits: int
+  @param ssh_key_bits: The length of the key to be generated
   @type verbose: boolean
-  @param verbose: show verbose output
+  @param verbose: Show verbose output
   @type debug: boolean
-  @param debug: show debug output
+  @param debug: Show debug output
 
   """
   ToStdout("Updating certificates now. Running \"gnt-cluster verify\" "
@@ -1195,7 +1200,9 @@ def _RenewCrypto(new_cluster_cert, new_rapi_cert, # pylint: disable=R0911
     cl = GetClient()
     renew_op = opcodes.OpClusterRenewCrypto(
         node_certificates=new_node_cert or new_cluster_cert,
-        ssh_keys=new_ssh_keys)
+        renew_ssh_keys=new_ssh_keys,
+        ssh_key_type=ssh_key_type,
+        ssh_key_bits=ssh_key_bits)
     SubmitOpCode(renew_op, cl=cl)
 
   ToStdout("All requested certificates and keys have been replaced."
@@ -1277,6 +1284,8 @@ def RenewCrypto(opts, args):
                       opts.force,
                       opts.new_node_cert,
                       opts.new_ssh_keys,
+                      opts.ssh_key_type,
+                      opts.ssh_key_bits,
                       opts.verbose,
                       opts.debug > 0)
 
index ed6a3b8..5658646 100644 (file)
@@ -87,17 +87,6 @@ class LUClusterRenewCrypto(NoHooksLU):
     self.share_locks = ShareAll()
     self.share_locks[locking.LEVEL_NODE] = 0
 
-  def CheckPrereq(self):
-    """Check prerequisites.
-
-    This checks whether the cluster is empty.
-
-    Any errors are signaled by raising errors.OpPrereqError.
-
-    """
-    self._ssh_renewal_suppressed = \
-      not self.cfg.GetClusterInfo().modify_ssh_setup and self.op.ssh_keys
-
   def _RenewNodeSslCertificates(self, feedback_fn):
     """Renews the nodes' SSL certificates.
 
@@ -159,9 +148,12 @@ class LUClusterRenewCrypto(NoHooksLU):
 
     self.cfg.SetCandidateCerts(digest_map)
 
-  def _RenewSshKeys(self):
+  def _RenewSshKeys(self, feedback_fn):
     """Renew all nodes' SSH keys.
 
+    @type feedback_fn: function
+    @param feedback_fn: logging function, see L{ganeti.cmdlist.base.LogicalUnit}
+
     """
     master_uuid = self.cfg.GetMasterNode()
 
@@ -175,25 +167,42 @@ class LUClusterRenewCrypto(NoHooksLU):
 
     cluster_info = self.cfg.GetClusterInfo()
 
+    new_ssh_key_type = self.op.ssh_key_type
+    if new_ssh_key_type is None:
+      new_ssh_key_type = cluster_info.ssh_key_type
+
+    new_ssh_key_bits = self.op.ssh_key_bits
+    if new_ssh_key_bits is None:
+      new_ssh_key_bits = cluster_info.ssh_key_bits
+
     result = self.rpc.call_node_ssh_keys_renew(
       [master_uuid],
       node_uuids, node_names,
       master_candidate_uuids,
       potential_master_candidates,
-      cluster_info.ssh_key_type,
-      cluster_info.ssh_key_bits)
+      cluster_info.ssh_key_type, # Old key type
+      new_ssh_key_type,          # New key type
+      new_ssh_key_bits)          # New key bits
     result[master_uuid].Raise("Could not renew the SSH keys of all nodes")
 
+    # After the keys have been successfully swapped, time to commit the change
+    # in key type
+    cluster_info.ssh_key_type = new_ssh_key_type
+    cluster_info.ssh_key_bits = new_ssh_key_bits
+    self.cfg.Update(cluster_info, feedback_fn)
+
   def Exec(self, feedback_fn):
     if self.op.node_certificates:
       feedback_fn("Renewing Node SSL certificates")
       self._RenewNodeSslCertificates(feedback_fn)
-    if self.op.ssh_keys and not self._ssh_renewal_suppressed:
-      feedback_fn("Renewing SSH keys")
-      self._RenewSshKeys()
-    elif self._ssh_renewal_suppressed:
-      feedback_fn("Cannot renew SSH keys if the cluster is configured to not"
-                  " modify the SSH setup.")
+
+    if self.op.renew_ssh_keys:
+      if self.cfg.GetClusterInfo().modify_ssh_setup:
+        feedback_fn("Renewing SSH keys")
+        self._RenewSshKeys(feedback_fn)
+      else:
+        feedback_fn("Cannot renew SSH keys if the cluster is configured to not"
+                    " modify the SSH setup.")
 
 
 class LUClusterActivateMasterIp(NoHooksLU):
index 8163735..19d127a 100644 (file)
@@ -566,8 +566,9 @@ _NODE_CALLS = [
     ("node_names", None, "Names of the nodes whose key is renewed"),
     ("master_candidate_uuids", None, "List of UUIDs of master candidates."),
     ("potential_master_candidates", None, "Potential master candidates"),
-    ("ssh_key_type", None, "The type of key to generate"),
-    ("ssh_key_bits", None, "The length of the key to generate")],
+    ("old_key_type", None, "The type of key previously used"),
+    ("new_key_type", None, "The type of key to generate"),
+    ("new_key_bits", None, "The length of the key to generate")],
     None, None, "Renew all SSH key pairs of all nodes nodes."),
   ]
 
index 871b4e1..a5e05dd 100644 (file)
@@ -945,10 +945,11 @@ class NodeRequestHandler(http.server.HttpServerHandler):
 
     """
     (node_uuids, node_names, master_candidate_uuids,
-     potential_master_candidates, ssh_key_type, ssh_key_bits) = params
+     potential_master_candidates, old_key_type, new_key_type,
+     new_key_bits) = params
     return backend.RenewSshKeys(node_uuids, node_names, master_candidate_uuids,
-                                potential_master_candidates, ssh_key_type,
-                                ssh_key_bits)
+                                potential_master_candidates, old_key_type,
+                                new_key_type, new_key_bits)
 
   @staticmethod
   def perspective_node_ssh_key_remove(params):
index 70fde67..66094a2 100644 (file)
@@ -281,7 +281,9 @@ $(genOpCode "OpCode"
      [t| () |],
      OpDoc.opClusterRenewCrypto,
      [ pNodeSslCerts
-     , pSshKeys
+     , pRenewSshKeys
+     , pSshKeyType
+     , pSshKeyBits
      , pVerbose
      , pDebug
      ],
index 08c4be8..0a793c0 100644 (file)
@@ -299,7 +299,9 @@ module Ganeti.OpParams
   , pEnabledDataCollectors
   , pDataCollectorInterval
   , pNodeSslCerts
-  , pSshKeys
+  , pSshKeyBits
+  , pSshKeyType
+  , pRenewSshKeys
   , pNodeSetup
   , pVerifyClutter
   , pLongSleep
@@ -1895,11 +1897,21 @@ pNodeSslCerts =
   defaultField [| False |] $
   simpleField "node_certificates" [t| Bool |]
 
-pSshKeys :: Field
-pSshKeys =
+pSshKeyBits :: Field
+pSshKeyBits =
+  withDoc "The number of bits of the SSH key Ganeti uses" .
+  optionalField $ simpleField "ssh_key_bits" [t| Positive Int |]
+
+pSshKeyType :: Field
+pSshKeyType =
+  withDoc "The type of the SSH key Ganeti uses" .
+  optionalField $ simpleField "ssh_key_type" [t| SshKeyType |]
+
+pRenewSshKeys :: Field
+pRenewSshKeys =
   withDoc "Whether to renew SSH keys" .
   defaultField [| False |] $
-  simpleField "ssh_keys" [t| Bool |]
+  simpleField "renew_ssh_keys" [t| Bool |]
 
 pNodeSetup :: Field
 pNodeSetup =
index a43cc52..3fb128a 100644 (file)
@@ -650,9 +650,9 @@ instance Rpc RpcCallExportList RpcResultExportList where
   rpcResultFill _ res = fromJSValueToRes res RpcResultExportList
 
 -- ** Job Queue Replication
-  
+
 -- | Update a job queue file
-  
+
 $(buildObject "RpcCallJobqueueUpdate" "rpcCallJobqueueUpdate"
   [ simpleField "file_name" [t| String |]
   , simpleField "content" [t| String |]
@@ -702,9 +702,9 @@ instance Rpc RpcCallJobqueueRename RpcResultJobqueueRename where
              $ JsonDecodeError ("Expected JSNull, got " ++ show (pp_value res))
 
 -- ** Watcher Status Update
-      
+
 -- | Set the watcher status
-      
+
 $(buildObject "RpcCallSetWatcherPause" "rpcCallSetWatcherPause"
   [ optionalField $ timeAsDoubleField "time"
   ])
@@ -724,9 +724,9 @@ instance Rpc RpcCallSetWatcherPause RpcResultSetWatcherPause where
            ("Expected JSNull, got " ++ show (pp_value res))
 
 -- ** Queue drain status
-      
+
 -- | Set the queu drain flag
-      
+
 $(buildObject "RpcCallSetDrainFlag" "rpcCallSetDrainFlag"
   [ simpleField "value" [t| Bool |]
   ])
index 17c361a..d529576 100644 (file)
@@ -168,8 +168,13 @@ instance Arbitrary OpCodes.OpCode where
       "OP_TAGS_DEL" ->
         arbitraryOpTagsDel
       "OP_CLUSTER_POST_INIT" -> pure OpCodes.OpClusterPostInit
-      "OP_CLUSTER_RENEW_CRYPTO" -> OpCodes.OpClusterRenewCrypto <$>
-         arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+      "OP_CLUSTER_RENEW_CRYPTO" -> OpCodes.OpClusterRenewCrypto
+         <$> arbitrary -- Node SSL certificates
+         <*> arbitrary -- renew_ssh_keys
+         <*> arbitrary -- ssh_key_type
+         <*> arbitrary -- ssh_key_bits
+         <*> arbitrary -- verbose
+         <*> arbitrary -- debug
       "OP_CLUSTER_DESTROY" -> pure OpCodes.OpClusterDestroy
       "OP_CLUSTER_QUERY" -> pure OpCodes.OpClusterQuery
       "OP_CLUSTER_VERIFY" ->