From 821221737967d65208a5831ed3e7349d5f1e7d7e Mon Sep 17 00:00:00 2001 From: Iustin Pop <iustin@google.com> Date: Fri, 24 Aug 2007 09:20:37 +0000 Subject: [PATCH] Rework ssh known-hosts handling. This changes: - cluster setup, we no longer edit /etc/ssh/ssh_known_hosts but our own file - node add, we no longer remove root's known_hosts (twice) - gnt-instance console, both the LU and the script: since now the ssh setup is not standard, we need to build the ssh cmdline in the LU (instead of manually building it in the script) with the correct parameters and use the command line as returned in the script - ssh.py, many changes, split options in module-level constants so that building the command line in different places is easier/more logical - backend.py, we no longer remove root's known_hosts in Add node, and we allow our own known_hosts file to be uploaded Reviewed-by: imsnah --- lib/backend.py | 3 +-- lib/cmdlib.py | 31 +++++++++++++++++------------ lib/constants.py | 1 + lib/ssh.py | 47 +++++++++++++++++++++++++++++++++++--------- scripts/gnt-instance | 8 ++++---- 5 files changed, 62 insertions(+), 28 deletions(-) diff --git a/lib/backend.py b/lib/backend.py index 7b362d59a..fea9da975 100644 --- a/lib/backend.py +++ b/lib/backend.py @@ -122,7 +122,6 @@ def AddNode(dsa, dsapub, rsa, rsapub, ssh, sshpub): utils.RunCmd(["/etc/init.d/ssh", "restart"]) - utils.RemoveFile("/root/.ssh/known_hosts") return True @@ -790,7 +789,7 @@ def UploadFile(file_name, data, mode, uid, gid, atime, mtime): return False allowed_files = [constants.CLUSTER_CONF_FILE, "/etc/hosts", - "/etc/ssh/ssh_known_hosts"] + constants.SSH_KNOWN_HOSTS_FILE] allowed_files.extend(ssconf.SimpleStore().GetFileList()) if file_name not in allowed_files: logger.Error("Filename passed to UploadFile not in allowed" diff --git a/lib/cmdlib.py b/lib/cmdlib.py index 04122f4d1..26057f38d 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -350,10 +350,10 @@ def _UpdateKnownHosts(fullnode, ip, pubkey): pubkey - the public key of the cluster """ - if os.path.exists('/etc/ssh/ssh_known_hosts'): - f = open('/etc/ssh/ssh_known_hosts', 'r+') + if os.path.exists(constants.SSH_KNOWN_HOSTS_FILE): + f = open(constants.SSH_KNOWN_HOSTS_FILE, 'r+') else: - f = open('/etc/ssh/ssh_known_hosts', 'w+') + f = open(constants.SSH_KNOWN_HOSTS_FILE, 'w+') inthere = False @@ -405,12 +405,15 @@ def _UpdateKnownHosts(fullnode, ip, pubkey): save_lines = save_lines + add_lines # Write a new file and replace old. - fd, tmpname = tempfile.mkstemp('tmp', 'ssh_known_hosts_', '/etc/ssh') + fd, tmpname = tempfile.mkstemp('.tmp', 'known_hosts.', + constants.DATA_DIR) newfile = os.fdopen(fd, 'w') - newfile.write(''.join(save_lines)) - newfile.close() + try: + newfile.write(''.join(save_lines)) + finally: + newfile.close() logger.Debug("Wrote new known_hosts.") - os.rename(tmpname, '/etc/ssh/ssh_known_hosts') + os.rename(tmpname, constants.SSH_KNOWN_HOSTS_FILE) elif add_lines: # Simply appending a new line will do the trick. @@ -448,8 +451,6 @@ def _InitSSHSetup(node): node: the name of this host as a fqdn """ - utils.RemoveFile('/root/.ssh/known_hosts') - if os.path.exists('/root/.ssh/id_dsa'): utils.CreateBackup('/root/.ssh/id_dsa') if os.path.exists('/root/.ssh/id_dsa.pub'): @@ -1365,8 +1366,6 @@ class LUAddNode(LogicalUnit): raise errors.OpExecError("PEM must end with newline") logger.Info("copy cluster pass to %s and starting the node daemon" % node) - # remove first the root's known_hosts file - utils.RemoveFile("/root/.ssh/known_hosts") # and then connect with ssh to set password and start ganeti-noded # note that all the below variables are sanitized at this point, # either by being constants or by the checks above @@ -1442,7 +1441,7 @@ class LUAddNode(LogicalUnit): dist_nodes.remove(myself.name) logger.Debug("Copying hosts and known_hosts to all nodes") - for fname in ("/etc/hosts", "/etc/ssh/ssh_known_hosts"): + for fname in ("/etc/hosts", constants.SSH_KNOWN_HOSTS_FILE): result = rpc.call_upload_file(dist_nodes, fname) for to_node in dist_nodes: if not result[to_node]: @@ -2798,7 +2797,13 @@ class LUConnectConsole(NoHooksLU): hyper = hypervisor.GetHypervisor() console_cmd = hyper.GetShellCommandForConsole(instance.name) - return node, console_cmd + # build ssh cmdline + argv = ["ssh", "-q", "-t"] + argv.extend(ssh.KNOWN_HOSTS_OPTS) + argv.extend(ssh.BATCH_MODE_OPTS) + argv.append(node) + argv.append(console_cmd) + return "ssh", argv class LUAddMDDRBDComponent(LogicalUnit): diff --git a/lib/constants.py b/lib/constants.py index 09dd712d7..856f39c66 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -34,6 +34,7 @@ DATA_DIR = "/var/lib/ganeti" CLUSTER_CONF_FILE = DATA_DIR + "/config.data" SSL_CERT_FILE = DATA_DIR + "/server.pem" WATCHER_STATEFILE = DATA_DIR + "/restart_state" +SSH_KNOWN_HOSTS_FILE = DATA_DIR + "/known_hosts" NODE_INITD_SCRIPT = "/etc/init.d/ganeti" DEFAULT_NODED_PORT = 1811 diff --git a/lib/ssh.py b/lib/ssh.py index bd084870d..7cf772a98 100644 --- a/lib/ssh.py +++ b/lib/ssh.py @@ -29,6 +29,31 @@ import os from ganeti import logger from ganeti import utils from ganeti import errors +from ganeti import constants + + +__all__ = ["SSHCall", "CopyFileToNode", "VerifyNodeHostname", + "KNOWN_HOSTS_OPTS", "BATCH_MODE_OPTS", "ASK_KEY_OPTS"] + + +KNOWN_HOSTS_OPTS = [ + "-oGlobalKnownHostsFile=%s" % constants.SSH_KNOWN_HOSTS_FILE, + "-oUserKnownHostsFile=/dev/null", + ] + +# Note: BATCH_MODE conflicts with ASK_KEY +BATCH_MODE_OPTS = [ + "-oEscapeChar=none", + "-oBatchMode=yes", + "-oStrictHostKeyChecking=yes", + ] + +ASK_KEY_OPTS = [ + "-oStrictHostKeyChecking=ask", + "-oEscapeChar=none", + "-oHashKnownHosts=no", + ] + def SSHCall(hostname, user, command, batch=True, ask_key=False): """Execute a command on a remote node. @@ -40,22 +65,23 @@ def SSHCall(hostname, user, command, batch=True, ask_key=False): hostname: the target host, string user: user to auth as command: the command + batch: if true, ssh will run in batch mode with no prompting + ask_key: if true, ssh will run with StrictHostKeyChecking=ask, so that + we can connect to an unknown host (not valid in batch mode) Returns: `utils.RunResult` as for `utils.RunCmd()` """ - argv = ["ssh", "-q", "-oEscapeChar=none"] + argv = ["ssh", "-q"] + argv.extend(KNOWN_HOSTS_OPTS) if batch: - argv.append("-oBatchMode=yes") # if we are in batch mode, we can't ask the key if ask_key: raise errors.ProgrammerError("SSH call requested conflicting options") - if ask_key: - argv.append("-oStrictHostKeyChecking=ask") - argv.append("-oHashKnownHosts=no") - else: - argv.append("-oStrictHostKeyChecking=yes") + argv.extend(BATCH_MODE_OPTS) + elif ask_key: + argv.extend(ASK_KEY_OPTS) argv.extend(["%s@%s" % (user, hostname), command]) return utils.RunCmd(argv) @@ -79,8 +105,11 @@ def CopyFileToNode(node, filename): logger.Error("file %s must be an absolute path" % (filename)) return False - command = ["scp", "-q", "-p", "-oStrictHostKeyChecking=yes", - "-oBatchMode=yes", filename, "%s:%s" % (node, filename)] + command = ["scp", "-q", "-p"] + command.extend(KNOWN_HOSTS_OPTS) + command.extend(BATCH_MODE_OPTS) + command.append(filename) + command.append("%s:%s" % (node, filename)) result = utils.RunCmd(command) diff --git a/scripts/gnt-instance b/scripts/gnt-instance index d0dc22c55..a4f173ab5 100755 --- a/scripts/gnt-instance +++ b/scripts/gnt-instance @@ -290,14 +290,14 @@ def ConnectToInstanceConsole(opts, args): instance_name = args[0] op = opcodes.OpConnectConsole(instance_name=instance_name) - node, console_cmd = SubmitOpCode(op) + cmd, argv = SubmitOpCode(op) # drop lock and exec so other commands can run while we have console utils.Unlock("cmd") try: - os.execv("/usr/bin/ssh", ["ssh", "-qt", node, console_cmd]) + os.execvp(cmd, argv) finally: - sys.stderr.write("Can't run console command %s on node %s" % - (console_cmd, node)) + sys.stderr.write("Can't run console command %s with arguments:\n'%s'" % + (cmd, " ".join(argv))) os._exit(1) -- GitLab