LGTM, thanks On Fri, 20 Nov 2015 at 10:59 'Hrvoje Ribicic' via ganeti-devel < [email protected]> wrote:
> This patch makes sure that the parameters introduced in previous > patches propagates wherever SSH keys are generated and used, allowing > Ganeti to use different types of SSH keys. With tis patch, the key type > can be set only at cluster initialization time. > > Signed-off-by: Hrvoje Ribicic <[email protected]> > --- > lib/backend.py | 83 > +++++++++++++--------- > lib/bootstrap.py | 4 +- > lib/client/gnt_cluster.py | 7 +- > lib/client/gnt_node.py | 11 ++- > lib/cmdlib/cluster/__init__.py | 7 +- > lib/cmdlib/cluster/verify.py | 3 +- > lib/rpc_defs.py | 4 +- > lib/server/noded.py | 8 +-- > lib/ssh.py | 23 +++--- > lib/tools/common.py | 6 +- > lib/tools/prepare_node_join.py | 9 ++- > lib/tools/ssh_update.py | 13 +++- > src/Ganeti/Constants.hs | 6 ++ > test/py/ganeti.backend_unittest.py | 20 ++++-- > test/py/ganeti.client.gnt_cluster_unittest.py | 4 +- > test/py/ganeti.ssh_unittest.py | 29 +++++++- > test/py/ganeti.tools.prepare_node_join_unittest.py | 6 +- > 17 files changed, 167 insertions(+), 76 deletions(-) > > diff --git a/lib/backend.py b/lib/backend.py > index 64b55e0..19409e5 100644 > --- a/lib/backend.py > +++ b/lib/backend.py > @@ -967,8 +967,8 @@ def > _VerifyClientCertificate(cert_file=pathutils.NODED_CLIENT_CERT_FILE): > return (None, utils.GetCertificateDigest(cert_filename=cert_file)) > > > -def _VerifySshSetup(node_status_list, my_name, > - pub_key_file=pathutils.SSH_PUB_KEYS): > +def _VerifySshSetup(node_status_list, my_name, ssh_key_type, > + ganeti_pub_keys_file=pathutils.SSH_PUB_KEYS): > """Verifies the state of the SSH key files. > > @type node_status_list: list of tuples > @@ -977,8 +977,10 @@ def _VerifySshSetup(node_status_list, my_name, > is_potential_master_candidate, online) > @type my_name: str > @param my_name: name of this node > - @type pub_key_file: str > - @param pub_key_file: filename of the public key file > + @type ssh_key_type: one of L{constants.SSHK_ALL} > + @param ssh_key_type: type of key used on nodes > + @type ganeti_pub_keys_file: str > + @param ganeti_pub_keys_file: filename of the public keys file > > """ > if node_status_list is None: > @@ -994,16 +996,16 @@ def _VerifySshSetup(node_status_list, my_name, > > result = [] > > - if not os.path.exists(pub_key_file): > + if not os.path.exists(ganeti_pub_keys_file): > result.append("The public key file '%s' does not exist. Consider > running" > " 'gnt-cluster renew-crypto --new-ssh-keys" > - " [--no-ssh-key-check]' to fix this." % pub_key_file) > + " [--no-ssh-key-check]' to fix this." % > ganeti_pub_keys_file) > return result > > pot_mc_uuids = [uuid for (uuid, _, _, _, _) in node_status_list] > offline_nodes = [uuid for (uuid, _, _, _, online) in node_status_list > if not online] > - pub_keys = ssh.QueryPubKeyFile(None) > + pub_keys = ssh.QueryPubKeyFile(None, key_file=ganeti_pub_keys_file) > > if potential_master_candidate: > # Check that the set of potential master candidates matches the > @@ -1026,14 +1028,14 @@ def _VerifySshSetup(node_status_list, my_name, > > (_, key_files) = \ > ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=False, > dircheck=False) > - (_, dsa_pub_key_filename) = key_files[constants.SSHK_DSA] > + (_, node_pub_key_file) = key_files[ssh_key_type] > > my_keys = pub_keys[my_uuid] > > - dsa_pub_key = utils.ReadFile(dsa_pub_key_filename) > - if dsa_pub_key.strip() not in my_keys: > + node_pub_key = utils.ReadFile(node_pub_key_file) > + if node_pub_key.strip() not in my_keys: > result.append("The dsa key of node %s does not match this node's > key" > - " in the pub key file." % (my_name)) > + " in the pub key file." % my_name) > if len(my_keys) != 1: > result.append("There is more than one key for node %s in the public > key" > " file." % my_name) > @@ -1152,8 +1154,9 @@ def VerifyNode(what, cluster_name, all_hvparams): > result[constants.NV_CLIENT_CERT] = _VerifyClientCertificate() > > if constants.NV_SSH_SETUP in what: > + node_status_list, key_type = what[constants.NV_SSH_SETUP] > result[constants.NV_SSH_SETUP] = \ > - _VerifySshSetup(what[constants.NV_SSH_SETUP], my_name) > + _VerifySshSetup(node_status_list, my_name, key_type) > if constants.NV_SSH_CLUTTER in what: > result[constants.NV_SSH_CLUTTER] = \ > _VerifySshClutter(what[constants.NV_SSH_SETUP], my_name) > @@ -1774,8 +1777,8 @@ def RemoveNodeSshKey(node_uuid, node_name, > return result_msgs > > > -def _GenerateNodeSshKey(node_uuid, node_name, ssh_port_map, > - pub_key_file=pathutils.SSH_PUB_KEYS, > +def _GenerateNodeSshKey(node_uuid, node_name, ssh_port_map, ssh_key_type, > + ssh_key_bits, pub_key_file=pathutils.SSH_PUB_KEYS, > ssconf_store=None, > noded_cert_file=pathutils.NODED_CERT_FILE, > run_cmd_fn=ssh.RunSshCmdWithStdin, > @@ -1788,6 +1791,10 @@ def _GenerateNodeSshKey(node_uuid, node_name, > ssh_port_map, > @param node_name: name of the node whose key is remove > @type ssh_port_map: dict of str to int > @param ssh_port_map: mapping of node names to their SSH port > + @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 > > """ > if not ssconf_store: > @@ -1802,7 +1809,7 @@ def _GenerateNodeSshKey(node_uuid, node_name, > ssh_port_map, > data = {} > _InitSshUpdateData(data, noded_cert_file, ssconf_store) > cluster_name = data[constants.SSHS_CLUSTER_NAME] > - data[constants.SSHS_GENERATE] = {constants.SSHS_SUFFIX: suffix} > + data[constants.SSHS_GENERATE] = (ssh_key_type, ssh_key_bits, suffix) > > run_cmd_fn(cluster_name, node_name, pathutils.SSH_UPDATE, > ssh_port_map.get(node_name), data, > @@ -1877,8 +1884,8 @@ def _ReplaceMasterKeyOnMaster(root_keyfiles): > > > def RenewSshKeys(node_uuids, node_names, master_candidate_uuids, > - potential_master_candidates, > - pub_key_file=pathutils.SSH_PUB_KEYS, > + potential_master_candidates, ssh_key_type, ssh_key_bits, > + ganeti_pub_keys_file=pathutils.SSH_PUB_KEYS, > ssconf_store=None, > noded_cert_file=pathutils.NODED_CERT_FILE, > run_cmd_fn=ssh.RunSshCmdWithStdin): > @@ -1892,8 +1899,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 pub_key_file: str > - @param pub_key_file: file path of the the public key file > + @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 ganeti_pub_keys_file: str > + @param ganeti_pub_keys_file: file path of the the public key file > @type noded_cert_file: str > @param noded_cert_file: path of the noded SSL certificate file > @type run_cmd_fn: function > @@ -1915,8 +1926,8 @@ def RenewSshKeys(node_uuids, node_names, > master_candidate_uuids, > > (_, root_keyfiles) = \ > ssh.GetAllUserFiles(constants.SSH_LOGIN_USER, mkdir=False, > dircheck=False) > - (_, dsa_pub_keyfile) = root_keyfiles[constants.SSHK_DSA] > - old_master_key = utils.ReadFile(dsa_pub_keyfile) > + (_, node_pub_keyfile) = root_keyfiles[ssh_key_type] > + old_master_key = utils.ReadFile(node_pub_keyfile) > > node_uuid_name_map = zip(node_uuids, node_names) > > @@ -1935,7 +1946,8 @@ def RenewSshKeys(node_uuids, node_names, > master_candidate_uuids, > master_candidate = node_uuid in master_candidate_uuids > potential_master_candidate = node_name in potential_master_candidates > > - keys_by_uuid = ssh.QueryPubKeyFile([node_uuid], key_file=pub_key_file) > + keys_by_uuid = ssh.QueryPubKeyFile([node_uuid], > + key_file=ganeti_pub_keys_file) > if not keys_by_uuid: > raise errors.SshUpdateError("No public key of node %s (UUID %s) > found," > " not generating a new key." > @@ -1943,7 +1955,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(dsa_pub_keyfile, > + old_pub_key = ssh.ReadRemoteSshPubKeys(node_pub_keyfile, > node_name, cluster_name, > ssh_port_map[node_name], > False, # ask_key > @@ -1968,15 +1980,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, > - pub_key_file=pub_key_file, > + _GenerateNodeSshKey(node_uuid, node_name, ssh_port_map, ssh_key_type, > + ssh_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(dsa_pub_keyfile, > + pub_key = ssh.ReadRemoteSshPubKeys(node_pub_keyfile, > node_name, cluster_name, > ssh_port_map[node_name], > False, # ask_key > @@ -1986,8 +1998,8 @@ def RenewSshKeys(node_uuids, node_names, > master_candidate_uuids, > " (UUID %s)" % (node_name, node_uuid)) > > if potential_master_candidate: > - ssh.RemovePublicKey(node_uuid, key_file=pub_key_file) > - ssh.AddPublicKey(node_uuid, pub_key, key_file=pub_key_file) > + ssh.RemovePublicKey(node_uuid, key_file=ganeti_pub_keys_file) > + ssh.AddPublicKey(node_uuid, pub_key, key_file=ganeti_pub_keys_file) > > logging.debug("Add ssh key of node '%s'.", node_name) > node_errors = AddNodeSshKey( > @@ -1995,7 +2007,8 @@ def RenewSshKeys(node_uuids, node_names, > master_candidate_uuids, > to_authorized_keys=master_candidate, > to_public_keys=potential_master_candidate, > get_public_keys=True, > - pub_key_file=pub_key_file, ssconf_store=ssconf_store, > + pub_key_file=ganeti_pub_keys_file, > + ssconf_store=ssconf_store, > noded_cert_file=noded_cert_file, > run_cmd_fn=run_cmd_fn) > if node_errors: > @@ -2004,12 +2017,14 @@ def RenewSshKeys(node_uuids, node_names, > master_candidate_uuids, > # Renewing the master node's key > > # Preserve the old keys for now > - old_master_keys_by_uuid = _GetOldMasterKeys(master_node_uuid, > pub_key_file) > + old_master_keys_by_uuid = _GetOldMasterKeys(master_node_uuid, > + ganeti_pub_keys_file) > > # 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, > - pub_key_file=pub_key_file, > + ssh_key_type, ssh_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, > @@ -2018,16 +2033,16 @@ def RenewSshKeys(node_uuids, node_names, > master_candidate_uuids, > new_master_key_dict = _GetNewMasterKey(root_keyfiles, master_node_uuid) > > # Replace master key in the master nodes' public key file > - ssh.RemovePublicKey(master_node_uuid, key_file=pub_key_file) > + ssh.RemovePublicKey(master_node_uuid, key_file=ganeti_pub_keys_file) > for pub_key in new_master_key_dict[master_node_uuid]: > - ssh.AddPublicKey(master_node_uuid, pub_key, key_file=pub_key_file) > + ssh.AddPublicKey(master_node_uuid, pub_key, > key_file=ganeti_pub_keys_file) > > # Add new master key to all node's public and authorized keys > logging.debug("Add new master key to all nodes.") > node_errors = AddNodeSshKey( > master_node_uuid, master_node_name, potential_master_candidates, > to_authorized_keys=True, to_public_keys=True, > - get_public_keys=False, pub_key_file=pub_key_file, > + get_public_keys=False, pub_key_file=ganeti_pub_keys_file, > ssconf_store=ssconf_store, noded_cert_file=noded_cert_file, > run_cmd_fn=run_cmd_fn) > if node_errors: > diff --git a/lib/bootstrap.py b/lib/bootstrap.py > index 69f75dd..370b4c7 100644 > --- a/lib/bootstrap.py > +++ b/lib/bootstrap.py > @@ -714,7 +714,7 @@ def InitCluster(cluster_name, mac_prefix, # pylint: > disable=R0913, R0914 > utils.AddHostToEtcHosts(hostname.name, hostname.ip) > > if modify_ssh_setup: > - ssh.InitSSHSetup() > + ssh.InitSSHSetup(ssh_key_type, ssh_key_bits) > > if default_iallocator is not None: > alloc_script = utils.FindFile(default_iallocator, > @@ -817,7 +817,7 @@ def InitCluster(cluster_name, mac_prefix, # pylint: > disable=R0913, R0914 > > master_uuid = cfg.GetMasterNode() > if modify_ssh_setup: > - ssh.InitPubKeyFile(master_uuid) > + ssh.InitPubKeyFile(master_uuid, ssh_key_type) > # set up the inter-node password and certificate > _InitGanetiServerSetup(hostname.name, cfg) > > diff --git a/lib/client/gnt_cluster.py b/lib/client/gnt_cluster.py > index 2de389e..16120a7 100644 > --- a/lib/client/gnt_cluster.py > +++ b/lib/client/gnt_cluster.py > @@ -1216,8 +1216,9 @@ def _BuildGanetiPubKeys(options, > pub_key_file=pathutils.SSH_PUB_KEYS, cl=None, > if not cl: > cl = GetClient() > > - (cluster_name, master_node, modify_ssh_setup) = \ > - cl.QueryConfigValues(["cluster_name", "master_node", > "modify_ssh_setup"]) > + (cluster_name, master_node, modify_ssh_setup, ssh_key_type) = \ > + cl.QueryConfigValues(["cluster_name", "master_node", > "modify_ssh_setup", > + "ssh_key_type"]) > > # In case Ganeti is not supposed to modify the SSH setup, simply exit > and do > # not update this file. > @@ -1242,7 +1243,7 @@ def _BuildGanetiPubKeys(options, > pub_key_file=pathutils.SSH_PUB_KEYS, cl=None, > > _, pub_key_filename, _ = \ > ssh.GetUserFiles(constants.SSH_LOGIN_USER, mkdir=False, > dircheck=False, > - kind=constants.SSHK_DSA, _homedir_fn=homedir_fn) > + kind=ssh_key_type, _homedir_fn=homedir_fn) > > # get the key file of the master node > pub_key = utils.ReadFile(pub_key_filename) > diff --git a/lib/client/gnt_node.py b/lib/client/gnt_node.py > index b1ce8dc..4c26231 100644 > --- a/lib/client/gnt_node.py > +++ b/lib/client/gnt_node.py > @@ -230,12 +230,17 @@ def _SetupSSH(options, cluster_name, node, ssh_port, > cl): > (_, cert_pem) = \ > > utils.ExtractX509Certificate(utils.ReadFile(pathutils.NODED_CERT_FILE)) > > + (ssh_key_type, ssh_key_bits) = \ > + cl.QueryConfigValues(["ssh_key_type", "ssh_key_bits"]) > + > data = { > constants.SSHS_CLUSTER_NAME: cluster_name, > constants.SSHS_NODE_DAEMON_CERTIFICATE: cert_pem, > constants.SSHS_SSH_HOST_KEY: host_keys, > constants.SSHS_SSH_ROOT_KEY: root_keys, > constants.SSHS_SSH_AUTHORIZED_KEYS: candidate_keys, > + constants.SSHS_SSH_KEY_TYPE: ssh_key_type, > + constants.SSHS_SSH_KEY_BITS: ssh_key_bits, > } > > ssh.RunSshCmdWithStdin(cluster_name, node, pathutils.PREPARE_NODE_JOIN, > @@ -244,9 +249,9 @@ def _SetupSSH(options, cluster_name, node, ssh_port, > cl): > use_cluster_key=False, > ask_key=options.ssh_key_check, > strict_host_check=options.ssh_key_check) > > - (_, dsa_pub_keyfile) = root_keyfiles[constants.SSHK_DSA] > - pub_key = ssh.ReadRemoteSshPubKeys(dsa_pub_keyfile, node, cluster_name, > - ssh_port, options.ssh_key_check, > + (_, pub_keyfile) = root_keyfiles[ssh_key_type] > + pub_key = ssh.ReadRemoteSshPubKeys(pub_keyfile, node, cluster_name, > ssh_port, > + options.ssh_key_check, > options.ssh_key_check) > # Unfortunately, we have to add the key with the node name rather than > # the node's UUID here, because at this point, we do not have a UUID > yet. > diff --git a/lib/cmdlib/cluster/__init__.py > b/lib/cmdlib/cluster/__init__.py > index 51474d6..ed6a3b8 100644 > --- a/lib/cmdlib/cluster/__init__.py > +++ b/lib/cmdlib/cluster/__init__.py > @@ -172,11 +172,16 @@ class LUClusterRenewCrypto(NoHooksLU): > node_uuids = [uuid for (uuid, _) in nodes_uuid_names] > potential_master_candidates = self.cfg.GetPotentialMasterCandidates() > master_candidate_uuids = self.cfg.GetMasterCandidateUuids() > + > + cluster_info = self.cfg.GetClusterInfo() > + > result = self.rpc.call_node_ssh_keys_renew( > [master_uuid], > node_uuids, node_names, > master_candidate_uuids, > - potential_master_candidates) > + potential_master_candidates, > + cluster_info.ssh_key_type, > + cluster_info.ssh_key_bits) > result[master_uuid].Raise("Could not renew the SSH keys of all nodes") > > def Exec(self, feedback_fn): > diff --git a/lib/cmdlib/cluster/verify.py b/lib/cmdlib/cluster/verify.py > index e6fc13f..772ea9a 100644 > --- a/lib/cmdlib/cluster/verify.py > +++ b/lib/cmdlib/cluster/verify.py > @@ -1873,7 +1873,8 @@ class LUClusterVerifyGroup(LogicalUnit, > _VerifyErrors): > } > > if self.cfg.GetClusterInfo().modify_ssh_setup: > - node_verify_param[constants.NV_SSH_SETUP] = > self._PrepareSshSetupCheck() > + node_verify_param[constants.NV_SSH_SETUP] = \ > + (self._PrepareSshSetupCheck(), > self.cfg.GetClusterInfo().ssh_key_type) > if self.op.verify_clutter: > node_verify_param[constants.NV_SSH_CLUTTER] = True > > diff --git a/lib/rpc_defs.py b/lib/rpc_defs.py > index 021807c..8163735 100644 > --- a/lib/rpc_defs.py > +++ b/lib/rpc_defs.py > @@ -565,7 +565,9 @@ _NODE_CALLS = [ > ("node_uuids", None, "UUIDs of the nodes whose key is renewed"), > ("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")], > + ("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")], > None, None, "Renew all SSH key pairs of all nodes nodes."), > ] > > diff --git a/lib/server/noded.py b/lib/server/noded.py > index c73897c..871b4e1 100644 > --- a/lib/server/noded.py > +++ b/lib/server/noded.py > @@ -945,10 +945,10 @@ class > NodeRequestHandler(http.server.HttpServerHandler): > > """ > (node_uuids, node_names, master_candidate_uuids, > - potential_master_candidates) = params > - return backend.RenewSshKeys(node_uuids, node_names, > - master_candidate_uuids, > - potential_master_candidates) > + potential_master_candidates, ssh_key_type, ssh_key_bits) = params > + return backend.RenewSshKeys(node_uuids, node_names, > master_candidate_uuids, > + potential_master_candidates, ssh_key_type, > + ssh_key_bits) > > @staticmethod > def perspective_node_ssh_key_remove(params): > diff --git a/lib/ssh.py b/lib/ssh.py > index 59ecbf9..d2684fc 100644 > --- a/lib/ssh.py > +++ b/lib/ssh.py > @@ -677,15 +677,18 @@ def QueryPubKeyFile(target_uuids, > key_file=pathutils.SSH_PUB_KEYS, > return result > > > -def InitSSHSetup(error_fn=errors.OpPrereqError, _homedir_fn=None, > - _suffix=""): > +def InitSSHSetup(key_type, key_bits, error_fn=errors.OpPrereqError, > + _homedir_fn=None, _suffix=""): > """Setup the SSH configuration for the node. > > This generates a dsa keypair for root, adds the pub key to the > permitted hosts and adds the hostkey to its own known hosts. > > + @param key_type: the type of SSH keypair to be generated > + @param key_bits: the key length, in bits, to be used > + > """ > - priv_key, _, auth_keys = GetUserFiles(constants.SSH_LOGIN_USER, > + priv_key, _, auth_keys = GetUserFiles(constants.SSH_LOGIN_USER, > kind=key_type, > mkdir=True, > _homedir_fn=_homedir_fn) > > new_priv_key_name = priv_key + _suffix > @@ -696,7 +699,7 @@ def InitSSHSetup(error_fn=errors.OpPrereqError, > _homedir_fn=None, > utils.CreateBackup(name) > utils.RemoveFile(name) > > - result = utils.RunCmd(["ssh-keygen", "-t", "dsa", > + result = utils.RunCmd(["ssh-keygen", "-b", str(key_bits), "-t", > key_type, > "-f", new_priv_key_name, > "-q", "-N", ""]) > if result.failed: > @@ -706,16 +709,18 @@ def InitSSHSetup(error_fn=errors.OpPrereqError, > _homedir_fn=None, > AddAuthorizedKey(auth_keys, utils.ReadFile(new_pub_key_name)) > > > -def InitPubKeyFile(master_uuid, key_file=pathutils.SSH_PUB_KEYS): > +def InitPubKeyFile(master_uuid, key_type, > key_file=pathutils.SSH_PUB_KEYS): > """Creates the public key file and adds the master node's SSH key. > > @type master_uuid: str > @param master_uuid: the master node's UUID > + @type key_type: one of L{constants.SSHK_ALL} > + @param key_type: the type of ssh key to be used > @type key_file: str > @param key_file: name of the file containing the public keys > > """ > - _, pub_key, _ = GetUserFiles(constants.SSH_LOGIN_USER) > + _, pub_key, _ = GetUserFiles(constants.SSH_LOGIN_USER, kind=key_type) > ClearPubKeyFile(key_file=key_file) > key = utils.ReadFile(pub_key) > AddPublicKey(master_uuid, key, key_file=key_file) > @@ -1069,7 +1074,7 @@ def RunSshCmdWithStdin(cluster_name, node, basecmd, > port, data, > > def ReadRemoteSshPubKeys(pub_key_file, node, cluster_name, port, ask_key, > strict_host_check): > - """Fetches the public DSA SSH key from a node via SSH. > + """Fetches a public SSH key from a node via SSH. > > @type pub_key_file: string > @param pub_key_file: a tuple consisting of the file name of the public > DSA key > @@ -1087,7 +1092,7 @@ def ReadRemoteSshPubKeys(pub_key_file, node, > cluster_name, port, ask_key, > > result = utils.RunCmd(ssh_cmd) > if result.failed: > - raise errors.OpPrereqError("Could not fetch a public DSA SSH key from > node" > + raise errors.OpPrereqError("Could not fetch a public SSH key (%s) > from node" > " '%s': ran command '%s', failure reason: > '%s'." > - % (node, cmd, result.fail_reason)) > + % (pub_key_file, node, cmd, > result.fail_reason)) > return result.stdout > diff --git a/lib/tools/common.py b/lib/tools/common.py > index a9149f6..ca8288a 100644 > --- a/lib/tools/common.py > +++ b/lib/tools/common.py > @@ -191,11 +191,13 @@ def LoadData(raw, data_check): > return serializer.LoadAndVerifyJson(raw, data_check) > > > -def GenerateRootSshKeys(error_fn, _suffix="", _homedir_fn=None): > +def GenerateRootSshKeys(key_type, key_bits, error_fn, _suffix="", > + _homedir_fn=None): > """Generates root's SSH keys for this node. > > """ > - ssh.InitSSHSetup(error_fn=error_fn, _homedir_fn=_homedir_fn, > _suffix=_suffix) > + ssh.InitSSHSetup(key_type, key_bits, error_fn=error_fn, > + _homedir_fn=_homedir_fn, _suffix=_suffix) > > > def GenerateClientCertificate( > diff --git a/lib/tools/prepare_node_join.py > b/lib/tools/prepare_node_join.py > index 82a35dc..fa45a58 100644 > --- a/lib/tools/prepare_node_join.py > +++ b/lib/tools/prepare_node_join.py > @@ -50,7 +50,7 @@ from ganeti.tools import common > _SSH_KEY_LIST_ITEM = \ > ht.TAnd(ht.TIsLength(3), > ht.TItems([ > - ht.TElemOf(constants.SSHK_ALL), > + ht.TSshKeyType, > ht.Comment("public")(ht.TNonEmptyString), > ht.Comment("private")(ht.TNonEmptyString), > ])) > @@ -64,6 +64,8 @@ _DATA_CHECK = ht.TStrictDict(False, True, { > constants.SSHS_SSH_ROOT_KEY: _SSH_KEY_LIST, > constants.SSHS_SSH_AUTHORIZED_KEYS: > ht.TDictOf(ht.TNonEmptyString, ht.TListOf(ht.TNonEmptyString)), > + constants.SSHS_SSH_KEY_TYPE: ht.TSshKeyType, > + constants.SSHS_SSH_KEY_BITS: ht.TPositive, > }) > > > @@ -172,7 +174,10 @@ def UpdateSshRoot(data, dry_run, _homedir_fn=None): > if dry_run: > logging.info("This is a dry run, not replacing the SSH keys.") > else: > - common.GenerateRootSshKeys(error_fn=JoinError, > _homedir_fn=_homedir_fn) > + ssh_key_type = data.get(constants.SSHS_SSH_KEY_TYPE) > + ssh_key_bits = data.get(constants.SSHS_SSH_KEY_BITS) > + common.GenerateRootSshKeys(ssh_key_type, ssh_key_bits, > error_fn=JoinError, > + _homedir_fn=_homedir_fn) > > if authorized_keys: > if dry_run: > diff --git a/lib/tools/ssh_update.py b/lib/tools/ssh_update.py > index f9d1b6d..b37972e 100644 > --- a/lib/tools/ssh_update.py > +++ b/lib/tools/ssh_update.py > @@ -62,7 +62,13 @@ _DATA_CHECK = ht.TStrictDict(False, True, { > ht.TItems( > [ht.TElemOf(constants.SSHS_ACTIONS), > ht.TDictOf(ht.TNonEmptyString, ht.TListOf(ht.TNonEmptyString))]), > - constants.SSHS_GENERATE: ht.TDictOf(ht.TNonEmptyString, ht.TString), > + constants.SSHS_GENERATE: > + ht.TItems( > + [ht.TSshKeyType, # The type of key to generate > + ht.TPositive, # The number of bits in the key > + ht.TString]), # The suffix > + constants.SSHS_SSH_KEY_TYPE: ht.TSshKeyType, > + constants.SSHS_SSH_KEY_BITS: ht.TPositive, > }) > > > @@ -190,11 +196,12 @@ def GenerateRootSshKeys(data, dry_run): > """ > generate_info = data.get(constants.SSHS_GENERATE) > if generate_info: > - suffix = generate_info[constants.SSHS_SUFFIX] > + key_type, key_bits, suffix = generate_info > if dry_run: > logging.info("This is a dry run, not generating any files.") > else: > - common.GenerateRootSshKeys(SshUpdateError, _suffix=suffix) > + common.GenerateRootSshKeys(key_type, key_bits, SshUpdateError, > + _suffix=suffix) > > > def Main(): > diff --git a/src/Ganeti/Constants.hs b/src/Ganeti/Constants.hs > index 1a6ceca..c9ca540 100644 > --- a/src/Ganeti/Constants.hs > +++ b/src/Ganeti/Constants.hs > @@ -4730,6 +4730,12 @@ sshsSshPublicKeys = "public_keys" > sshsNodeDaemonCertificate :: String > sshsNodeDaemonCertificate = "node_daemon_certificate" > > +sshsSshKeyType :: String > +sshsSshKeyType = "ssh_key_type" > + > +sshsSshKeyBits :: String > +sshsSshKeyBits = "ssh_key_bits" > + > -- Number of maximum retries when contacting nodes per SSH > -- during SSH update operations. > sshsMaxRetries :: Integer > diff --git a/test/py/ganeti.backend_unittest.py b/test/py/ > ganeti.backend_unittest.py > index 1a0a51d..e441621 100755 > --- a/test/py/ganeti.backend_unittest.py > +++ b/test/py/ganeti.backend_unittest.py > @@ -1052,6 +1052,7 @@ class > TestAddRemoveGenerateNodeSshKey(testutils.GanetiTestCase): > backend._GenerateNodeSshKey( > test_node_uuid, test_node_name, > self._ssh_file_manager.GetSshPortMap(self._SSH_PORT), > + "rsa", 2048, > pub_key_file=self._pub_key_file, > ssconf_store=self._ssconf_mock, > noded_cert_file=self.noded_cert_file, > @@ -1656,8 +1657,8 @@ class TestVerifySshSetup(testutils.GanetiTestCase): > self._read_file_mock = self._read_file_patcher.start() > self._read_file_mock.return_value = self._NODE1_KEYS[0] > self.tmpdir = tempfile.mkdtemp() > - self.pub_key_file = os.path.join(self.tmpdir, "pub_key_file") > - open(self.pub_key_file, "w").close() > + self.pub_keys_file = os.path.join(self.tmpdir, "pub_keys_file") > + open(self.pub_keys_file, "w").close() > > def tearDown(self): > super(testutils.GanetiTestCase, self).tearDown() > @@ -1672,7 +1673,8 @@ class TestVerifySshSetup(testutils.GanetiTestCase): > self._query_mock.return_value = self._PUB_KEY_RESULT > result = backend._VerifySshSetup(self._NODE_STATUS_LIST, > self._NODE1_NAME, > - pub_key_file=self.pub_key_file) > + "dsa", > + > ganeti_pub_keys_file=self.pub_keys_file) > self.assertEqual(result, []) > > def testMissingKey(self): > @@ -1683,7 +1685,8 @@ class TestVerifySshSetup(testutils.GanetiTestCase): > self._query_mock.return_value = pub_key_missing > result = backend._VerifySshSetup(self._NODE_STATUS_LIST, > self._NODE1_NAME, > - pub_key_file=self.pub_key_file) > + "dsa", > + > ganeti_pub_keys_file=self.pub_keys_file) > self.assertTrue(self._NODE2_UUID in result[0]) > > def testUnknownKey(self): > @@ -1694,7 +1697,8 @@ class TestVerifySshSetup(testutils.GanetiTestCase): > self._query_mock.return_value = pub_key_missing > result = backend._VerifySshSetup(self._NODE_STATUS_LIST, > self._NODE1_NAME, > - pub_key_file=self.pub_key_file) > + "dsa", > + > ganeti_pub_keys_file=self.pub_keys_file) > self.assertTrue("unkownnodeuuid" in result[0]) > > def testMissingMasterCandidate(self): > @@ -1705,7 +1709,8 @@ class TestVerifySshSetup(testutils.GanetiTestCase): > self._query_mock.return_value = self._PUB_KEY_RESULT > result = backend._VerifySshSetup(self._NODE_STATUS_LIST, > self._NODE1_NAME, > - pub_key_file=self.pub_key_file) > + "dsa", > + > ganeti_pub_keys_file=self.pub_keys_file) > self.assertTrue(self._NODE1_UUID in result[0]) > > def testSuperfluousNormalNode(self): > @@ -1716,7 +1721,8 @@ class TestVerifySshSetup(testutils.GanetiTestCase): > self._query_mock.return_value = self._PUB_KEY_RESULT > result = backend._VerifySshSetup(self._NODE_STATUS_LIST, > self._NODE1_NAME, > - pub_key_file=self.pub_key_file) > + "dsa", > + > ganeti_pub_keys_file=self.pub_keys_file) > self.assertTrue(self._NODE3_UUID in result[0]) > > > diff --git a/test/py/ganeti.client.gnt_cluster_unittest.py b/test/py/ > ganeti.client.gnt_cluster_unittest.py > index 595864a..c2cb9f5 100755 > --- a/test/py/ganeti.client.gnt_cluster_unittest.py > +++ b/test/py/ganeti.client.gnt_cluster_unittest.py > @@ -382,6 +382,7 @@ class TestBuildGanetiPubKeys(testutils.GanetiTestCase): > _PUB_KEY = "master_public_key" > _MODIFY_SSH_SETUP = True > _AUTH_KEYS = "a\nb\nc" > + _SSH_KEY_TYPE = "dsa" > > def _setUpFakeKeys(self): > os.makedirs(os.path.join(self.tmpdir, ".ssh")) > @@ -412,7 +413,8 @@ class TestBuildGanetiPubKeys(testutils.GanetiTestCase): > self.mock_cl = mock.Mock() > self.mock_cl.QueryConfigValues = mock.Mock() > self.mock_cl.QueryConfigValues.return_value = \ > - (self._CLUSTER_NAME, self._MASTER_NODE_NAME, self._MODIFY_SSH_SETUP) > + (self._CLUSTER_NAME, self._MASTER_NODE_NAME, self._MODIFY_SSH_SETUP, > + self._SSH_KEY_TYPE) > > self._get_online_nodes_mock = mock.Mock() > self._get_online_nodes_mock.return_value = \ > diff --git a/test/py/ganeti.ssh_unittest.py b/test/py/ > ganeti.ssh_unittest.py > index 9ec2397..b13dda1 100755 > --- a/test/py/ganeti.ssh_unittest.py > +++ b/test/py/ganeti.ssh_unittest.py > @@ -279,6 +279,30 @@ class TestSshKeys(testutils.GanetiTestCase): > "ssh-dss AAAAB3asdfasdfaYTUCB laracroft@test\n" > "ssh-dss AasdfliuobaosfMAAACB frodo@test\n") > > + def testOtherKeyTypes(self): > + key_rsa = "ssh-rsa AAAAimnottypingallofthathere0jfJs22 test@test" > + key_ed25519 = "ssh-ed25519 > AAAAC3NzaC1lZDI1NTE5AAAAIOlcZ6cpQTGow0LZECRHWn9"\ > + "7Yvn16J5un501T/RcbfuF fast@secure" > + key_ecdsa = "ecdsa-sha2-nistp256 AAAAE2VjZHNtoolongk/TNhVbEg= > secure@secure" > + > + def _ToFileContent(keys): > + return '\n'.join(keys) + '\n' > + > + ssh.AddAuthorizedKeys(self.tmpname, [key_rsa, key_ed25519, key_ecdsa]) > + self.assertFileContent(self.tmpname, > + _ToFileContent([self.KEY_A, self.KEY_B, > key_rsa, > + key_ed25519, key_ecdsa])) > + > + ssh.RemoveAuthorizedKey(self.tmpname, key_ed25519) > + self.assertFileContent(self.tmpname, > + _ToFileContent([self.KEY_A, self.KEY_B, > key_rsa, > + key_ecdsa])) > + > + ssh.RemoveAuthorizedKey(self.tmpname, key_rsa) > + ssh.RemoveAuthorizedKey(self.tmpname, key_ecdsa) > + self.assertFileContent(self.tmpname, > + _ToFileContent([self.KEY_A, self.KEY_B])) > + > > class TestPublicSshKeys(testutils.GanetiTestCase): > """Test case for the handling of the list of public ssh keys.""" > @@ -450,13 +474,14 @@ class TestGetUserFiles(testutils.GanetiTestCase): > return self.tmpdir > > def testNewKeysOverrideOldKeys(self): > - ssh.InitSSHSetup(_homedir_fn=self._GetTempHomedir) > + ssh.InitSSHSetup("dsa", 1024, _homedir_fn=self._GetTempHomedir) > self.assertFileContentNotEqual(self.priv_filename, self._PRIV_KEY) > self.assertFileContentNotEqual(self.pub_filename, self._PUB_KEY) > > def testSuffix(self): > suffix = "_pinkbunny" > - ssh.InitSSHSetup(_homedir_fn=self._GetTempHomedir, _suffix=suffix) > + ssh.InitSSHSetup("dsa", 1024, _homedir_fn=self._GetTempHomedir, > + _suffix=suffix) > self.assertFileContent(self.priv_filename, self._PRIV_KEY) > self.assertFileContent(self.pub_filename, self._PUB_KEY) > self.assertTrue(os.path.exists(self.priv_filename + suffix)) > diff --git a/test/py/ganeti.tools.prepare_node_join_unittest.py b/test/py/ > ganeti.tools.prepare_node_join_unittest.py > index a76db15..7901199 100755 > --- a/test/py/ganeti.tools.prepare_node_join_unittest.py > +++ b/test/py/ganeti.tools.prepare_node_join_unittest.py > @@ -164,6 +164,8 @@ class TestUpdateSshDaemon(unittest.TestCase): > (constants.SSHK_ECDSA, "ecdsapriv", "ecdsapub"), > (constants.SSHK_RSA, "rsapriv", "rsapub"), > ], > + constants.SSHS_SSH_KEY_TYPE: "dsa", > + constants.SSHS_SSH_KEY_BITS: 1024, > } > runcmd_fn = compat.partial(self._RunCmd, failcmd) > if failcmd: > @@ -228,7 +230,9 @@ class TestUpdateSshRoot(unittest.TestCase): > data = { > constants.SSHS_SSH_ROOT_KEY: [ > (constants.SSHK_DSA, "privatedsa", "ssh-dss pubdsa"), > - ] > + ], > + constants.SSHS_SSH_KEY_TYPE: "dsa", > + constants.SSHS_SSH_KEY_BITS: 1024, > } > > prepare_node_join.UpdateSshRoot(data, False, > -- > 2.1.4 > > -- Helga Velroyen Software Engineer [email protected] Google Germany GmbH Dienerstraße 12 80331 München Geschäftsführer: Matthew Scott Sucherman, Paul Terence Manicle Registergericht und -nummer: Hamburg, HRB 86891 Sitz der Gesellschaft: Hamburg Diese E-Mail ist vertraulich. Wenn Sie nicht der richtige Adressat sind, leiten Sie diese bitte nicht weiter, informieren Sie den Absender und löschen Sie die E-Mail und alle Anhänge. Vielen Dank. This e-mail is confidential. If you are not the right addressee please do not forward it, please inform the sender, and please erase this e-mail including any attachments. Thanks.
