From 3dc66ebc5af4807caa5bee189eac73ef7d22b0e2 Mon Sep 17 00:00:00 2001
From: Iustin Pop <iustin@google.com>
Date: Sun, 22 Aug 2010 08:37:32 +0200
Subject: [PATCH] setup-ssh: Also use keys from the ssh-agent

Currently, setup-ssh only uses one disk-based key. This means that any
setup where we use keys from ssh-agent (which do not necessarily exist
on disk) will break when moving from the old method to setup-ssh.

This patch moves the SSH key handling to separate functions, and uses
both the disk key (first) and the agent keys for login.

The patch also fixes the root_logger setup level (I tried to hard to
reduce noise and broke the debug level, sorry).

Signed-off-by: Iustin Pop <iustin@google.com>
Reviewed-by: Michael Hanselmann <hansmi@google.com>
---
 tools/setup-ssh | 71 ++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 62 insertions(+), 9 deletions(-)

diff --git a/tools/setup-ssh b/tools/setup-ssh
index 8f4cfd194..b7a56c5c0 100755
--- a/tools/setup-ssh
+++ b/tools/setup-ssh
@@ -210,7 +210,7 @@ def SetupLogging(options):
     stderr_handler.setLevel(logging.WARNING)
 
   root_logger = logging.getLogger("")
-  root_logger.setLevel(logging.INFO)
+  root_logger.setLevel(logging.NOTSET)
   root_logger.addHandler(stderr_handler)
   root_logger.addHandler(file_handler)
 
@@ -221,14 +221,16 @@ def SetupLogging(options):
   paramiko_logger.setLevel(logging.WARNING)
 
 
-def main():
-  """Main routine.
+def LoadPrivateKeys(options):
+  """Load the list of available private keys
 
-  """
-  (options, args) = ParseOptions()
+  It loads the standard ssh key from disk and then tries to connect to
+  the ssh agent too.
 
-  SetupLogging(options)
+  @rtype: list
+  @return: a list of C{paramiko.PKey}
 
+  """
   if options.key_type == "rsa":
     pkclass = paramiko.RSAKey
   elif options.key_type == "dsa":
@@ -244,6 +246,58 @@ def main():
     logging.critical("Can't load private key %s: %s", options.private_key, err)
     sys.exit(1)
 
+  try:
+    agent = paramiko.Agent()
+    agent_keys = agent.get_keys()
+  except paramiko.SSHException, err:
+    # this will only be seen when the agent is broken/uses invalid
+    # protocol; for non-existing agent, get_keys() will just return an
+    # empty tuple
+    logging.warning("Can't connect to the ssh agent: %s; skipping its use",
+                    err)
+    agent_keys = []
+
+  return [private_key] + list(agent_keys)
+
+
+def LoginViaKeys(transport, username, keys):
+  """Try to login on the given transport via a list of keys.
+
+  @param transport: the transport to use
+  @param username: the username to login as
+  @type keys: list
+  @param keys: list of C{paramiko.PKey} to use for authentication
+  @rtype: boolean
+  @return: True or False depending on whether the login was
+      successfull or not
+
+  """
+  for private_key in keys:
+    try:
+      transport.auth_publickey(username, private_key)
+      fpr = ":".join("%02x" % ord(i) for i in private_key.get_fingerprint())
+      if isinstance(private_key, paramiko.AgentKey):
+        logging.debug("Authentication via the ssh-agent key %s", fpr)
+      else:
+        logging.debug("Authenticated via public key %s", fpr)
+      return True
+    except paramiko.SSHException:
+      continue
+  else:
+    # all keys exhausted
+    return False
+
+
+def main():
+  """Main routine.
+
+  """
+  (options, args) = ParseOptions()
+
+  SetupLogging(options)
+
+  all_keys = LoadPrivateKeys(options)
+
   passwd = None
   username = constants.GANETI_RUNAS
   ssh_port = netutils.GetDaemonPort("ssh")
@@ -261,10 +315,9 @@ def main():
     transport = paramiko.Transport((host, ssh_port))
     transport.start_client()
     try:
-      try:
-        transport.auth_publickey(username, private_key)
+      if LoginViaKeys(transport, username, all_keys):
         logging.info("Authenticated to %s via public key", host)
-      except paramiko.SSHException:
+      else:
         logging.warning("Authentication to %s via public key failed, trying"
                         " password", host)
         if passwd is None:
-- 
GitLab