By explicitly specifying the old and new SSH key type in various renew-crypto operations, this patch allows the switching of SSH key types to take place during a SSH key renewal operation.
Signed-off-by: Hrvoje Ribicic <[email protected]> --- lib/backend.py | 28 +++++++++++++----------- lib/client/gnt_cluster.py | 17 +++++++++++---- lib/cmdlib/cluster/__init__.py | 49 +++++++++++++++++++++++++----------------- lib/rpc_defs.py | 5 +++-- lib/server/noded.py | 7 +++--- src/Ganeti/OpCodes.hs | 4 +++- src/Ganeti/OpParams.hs | 20 +++++++++++++---- src/Ganeti/Rpc.hs | 12 +++++------ test/hs/Test/Ganeti/OpCodes.hs | 9 ++++++-- 9 files changed, 97 insertions(+), 54 deletions(-) diff --git a/lib/backend.py b/lib/backend.py index 65cb976..e20d45a 100644 --- a/lib/backend.py +++ b/lib/backend.py @@ -1885,7 +1885,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, @@ -1900,10 +1901,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 @@ -1927,8 +1930,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) @@ -1956,7 +1960,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 @@ -1981,15 +1985,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 @@ -2024,7 +2028,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, diff --git a/lib/client/gnt_cluster.py b/lib/client/gnt_cluster.py index c7f9048..59f1eb9 100644 --- a/lib/client/gnt_cluster.py +++ b/lib/client/gnt_cluster.py @@ -978,11 +978,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 @@ -1010,10 +1011,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\" " @@ -1194,7 +1199,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." @@ -1269,6 +1276,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) diff --git a/lib/cmdlib/cluster/__init__.py b/lib/cmdlib/cluster/__init__.py index ed6a3b8..5658646 100644 --- a/lib/cmdlib/cluster/__init__.py +++ b/lib/cmdlib/cluster/__init__.py @@ -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): diff --git a/lib/rpc_defs.py b/lib/rpc_defs.py index 88f0ece..6337285 100644 --- a/lib/rpc_defs.py +++ b/lib/rpc_defs.py @@ -567,8 +567,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."), ] diff --git a/lib/server/noded.py b/lib/server/noded.py index dddf493..eb7eb75 100644 --- a/lib/server/noded.py +++ b/lib/server/noded.py @@ -946,10 +946,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): diff --git a/src/Ganeti/OpCodes.hs b/src/Ganeti/OpCodes.hs index 70fde67..66094a2 100644 --- a/src/Ganeti/OpCodes.hs +++ b/src/Ganeti/OpCodes.hs @@ -281,7 +281,9 @@ $(genOpCode "OpCode" [t| () |], OpDoc.opClusterRenewCrypto, [ pNodeSslCerts - , pSshKeys + , pRenewSshKeys + , pSshKeyType + , pSshKeyBits , pVerbose , pDebug ], diff --git a/src/Ganeti/OpParams.hs b/src/Ganeti/OpParams.hs index 08c4be8..0a793c0 100644 --- a/src/Ganeti/OpParams.hs +++ b/src/Ganeti/OpParams.hs @@ -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 = diff --git a/src/Ganeti/Rpc.hs b/src/Ganeti/Rpc.hs index a43cc52..3fb128a 100644 --- a/src/Ganeti/Rpc.hs +++ b/src/Ganeti/Rpc.hs @@ -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 |] ]) diff --git a/test/hs/Test/Ganeti/OpCodes.hs b/test/hs/Test/Ganeti/OpCodes.hs index 17c361a..d529576 100644 --- a/test/hs/Test/Ganeti/OpCodes.hs +++ b/test/hs/Test/Ganeti/OpCodes.hs @@ -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" -> -- 2.6.0.rc2.230.g3dd15c0
