Rework ssh known-hosts handling.
authorIustin Pop <iustin@google.com>
Fri, 24 Aug 2007 09:20:37 +0000 (09:20 +0000)
committerIustin Pop <iustin@google.com>
Fri, 24 Aug 2007 09:20:37 +0000 (09:20 +0000)
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
lib/cmdlib.py
lib/constants.py
lib/ssh.py
scripts/gnt-instance

index 7b362d5..fea9da9 100644 (file)
@@ -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"
index 04122f4..26057f3 100644 (file)
@@ -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):
index 09dd712..856f39c 100644 (file)
@@ -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
index bd08487..7cf772a 100644 (file)
@@ -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)
 
index d0dc22c..a4f173a 100755 (executable)
@@ -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)