Jump to: navigation, search

Difference between revisions of "Satori/SSHModuleProposal"

m
(Implementation)
 
(26 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
== SSH Module Proposal ==
 
== SSH Module Proposal ==
  
<big>As an initial platform to enable [[Satori/glossary|data plane discovery]], implement an SSH module using [https://github.com/paramiko/paramiko paramiko] by extending its [http://www.lag.net/paramiko/docs/paramiko.SSHClient-class.html SSHClient class]. </big>
+
:<big>Implement an SSH wrapper module to enable logging on to servers so we can do [[Satori/glossary|data plane discovery]].</big>
 +
:<big>This wiki page defines the full specification for the [https://blueprints.launchpad.net/satori/+spec/ssh-module ssh-module blueprint].</big>
  
 +
==== Interface ====
 +
  from satori import ssh
 +
  if proxy_ip:
 +
    proxy = ssh.connect(proxy_ip, port=22, username=proxy_user, password=proxy_pass)
 +
  else:
 +
    proxy = None
 +
 
 +
  connection = ssh.connect("10.1.1.20", username="root", password="Password", key=None, proxy=proxy)
 +
 
 +
  output = connection.remote_execute("sudo echo hello", with_exit_code=True)
 +
>>> {'stdout': 'hello', 'stderr': <nowiki>''</nowiki>, 'exit_code': 0}
  
==== Desired Interface ====
+
==== Requirements ====
>>> from satori.common.ssh import SSH
+
 
>>> client = SSH(host="123.456.789.11", host_password="pa$$word", host_username="Tobias")
+
Support the following logins:
>>> output = client.remote_execute("sudo echo hello", with_exit_code=True)
+
** password/username
>>> print output
+
** private key/username
{'stdout': 'hello', 'stderr': <nowiki>''</nowiki>, 'exit_code': 0}
+
** private key file/username
 +
** for all the above logins, the module should support using a proxy (a.k.a. bastion)
 +
 
 +
 
 +
Primary class methods:
 +
 
 +
* <tt>remote_execute()</tt>: to execute a remote command and return stderr and stdout
 +
* <tt>test_connection()</tt>: to test that a connection can be made
 +
 
 +
Additionally:
 +
* Implement an instance property, <code>platform_info</code>, that will return the remote host's platform info using (python >=2.4)'s [http://docs.python.org/2/library/platform.html platform module]
  
==== Requirements ====
+
 
 +
==== Implementation ====
 +
* Use [https://github.com/paramiko/paramiko paramiko] and extend its [http://www.lag.net/paramiko/docs/paramiko.SSHClient-class.html SSHClient class]. Wrap paramiko to make it simpler to use and understand the code from other parts of satori.
 +
* Accept a password string, private key string, or path to private key file for auth. Paramiko will automatically check in the standard places for ssh keys if nothing else is provided.
 
* Manage authentication mechanisms, retry authenticating, and prefer SSH keys.
 
* Manage authentication mechanisms, retry authenticating, and prefer SSH keys.
* Mange connecting and disconnecting when <code>remote_execute()</code> is called. (Lazy load the auth object)
+
** Implement/override a <code>connect()</code> method to do this.
 +
* Provide a method, <code>test_connection()</code>, for doing ''just that''. 
 +
* Manage connecting and disconnecting when <code>remote_execute()</code> is called. (Lazy load the auth object)
 
* Attempt to handle password prompts for non-passwordless sudoers
 
* Attempt to handle password prompts for non-passwordless sudoers
* Attempt to handle requirements for pty/tty by retrying with a pty channel <ref name="get_pty"/> if the system responds with "sudo requires a tty" or similar
+
* Attempt to handle hiccups with pty/tty rules by retrying the command with a pty channel<ref name="get_pty"/> if the system responds with "sudo requires a tty" or similar
* Support ssh proxy connections <ref name="sshproxy_connections"/>, and create an implementation that provides the same behavior whether connecting through a proxy or connecting to the remote host directly.
+
* Support ssh proxy connections<ref name="sshproxy_connections"/>, and create an implementation that provides the same behavior whether connecting through a proxy or connecting to the remote host directly.
 
* Implement an instance property,  <code>platform_info</code>, that will return the remote host's platform info using (python >=2.4)'s [http://docs.python.org/2/library/platform.html platform module]
 
* Implement an instance property,  <code>platform_info</code>, that will return the remote host's platform info using (python >=2.4)'s [http://docs.python.org/2/library/platform.html platform module]
** architecture, distro, version
+
 
** e.g. Ubuntu 12.04 x86_64 would return
+
For the platform requirements:
*** <code>{'arch': 'x86_64', 'dist': 'ubuntu', 'version': '12.04'}</code>
+
* Remote system requires python>=2.4
 +
* architecture, distro, version
 +
* e.g. Ubuntu 12.04 x86_64 would return
 +
** <code>{'arch': 'x86_64', 'dist': 'ubuntu', 'version': '12.04'}</code>
 +
 
 +
 
 +
  # function signature
 +
  def connect(host, credentials, port=22, timeout=None, proxy=None):
 +
      """Connect to remote host over SSH and return a client connection.
 +
     
 +
      credentials can have:
 +
          username (required)
 +
          password
 +
          private_key
 +
          private_key_file
 +
      proxy can have:
 +
          host (required if proxy supplied)
 +
          username (required if proxy supplied)
 +
          port
 +
          timeout
 +
          password
 +
          private_key
 +
          private_key_file
 +
      """
 +
 
  
 
<ref name="get_pty"> [https://github.com/paramiko/paramiko/blob/master/paramiko/channel.py#L122-L155 paramiko.channel.Channel.get_pty] See <tt>get_pty()</tt>.</ref>
 
<ref name="get_pty"> [https://github.com/paramiko/paramiko/blob/master/paramiko/channel.py#L122-L155 paramiko.channel.Channel.get_pty] See <tt>get_pty()</tt>.</ref>
 
<ref name="sshproxy_connections"> [https://github.com/paramiko/paramiko/blob/master/paramiko/client.py#L210-L212 paramiko.client.SSHClient.connect] See <tt>sock</tt> keyword argument.</ref>
 
<ref name="sshproxy_connections"> [https://github.com/paramiko/paramiko/blob/master/paramiko/client.py#L210-L212 paramiko.client.SSHClient.connect] See <tt>sock</tt> keyword argument.</ref>
 
<references />
 
<references />
 +
 +
 +
Notes:
 +
 +
We considered the following to stay within pylint's max 5 argument rule, but we decided to break the rule for practicality...
 +
 +
  # Simplest use case
 +
  from satori import ssh
 +
  connection = ssh.connect("10.1.1.20", username="root", password="Password", key=None)
 +
 
 +
  output = connection.remote_execute("sudo echo hello", with_exit_code=True, timeout=None)
 +
>>> {'stdout': 'hello', 'stderr': <nowiki>''</nowiki>, 'exit_code': 0}
 +
 +
 +
  # More options
 +
  from satori import ssh
 +
  client = ssh.Client("10.1.1.20", port=2222, timeout=1000, StrictHostKeyChecking=True)
 +
  client.set_credentials(username="root", password="Password")
 +
  if proxy_ip:
 +
    proxy = ssh.Client(proxy_ip, port=22)
 +
    proxy.set_credentials(username=proxy_user, password=proxy_pass)
 +
  else:
 +
    proxy = None
 +
  connection = client.connect(proxy=proxy)
 +
 
 +
  output = connection.remote_execute("sudo echo hello", with_exit_code=True, timeout=None)
 +
>>> {'stdout': 'hello', 'stderr': <nowiki>''</nowiki>, 'exit_code': 0}

Latest revision as of 17:25, 10 March 2014

SSH Module Proposal

Implement an SSH wrapper module to enable logging on to servers so we can do data plane discovery.
This wiki page defines the full specification for the ssh-module blueprint.

Interface

 from satori import ssh
 if proxy_ip:
   proxy = ssh.connect(proxy_ip, port=22, username=proxy_user, password=proxy_pass)
 else:
   proxy = None
 
 connection = ssh.connect("10.1.1.20", username="root", password="Password", key=None, proxy=proxy)
 
 output = connection.remote_execute("sudo echo hello", with_exit_code=True)
>>> {'stdout': 'hello', 'stderr': '', 'exit_code': 0}

Requirements

Support the following logins:

    • password/username
    • private key/username
    • private key file/username
    • for all the above logins, the module should support using a proxy (a.k.a. bastion)


Primary class methods:

  • remote_execute(): to execute a remote command and return stderr and stdout
  • test_connection(): to test that a connection can be made

Additionally:

  • Implement an instance property, platform_info, that will return the remote host's platform info using (python >=2.4)'s platform module


Implementation

  • Use paramiko and extend its SSHClient class. Wrap paramiko to make it simpler to use and understand the code from other parts of satori.
  • Accept a password string, private key string, or path to private key file for auth. Paramiko will automatically check in the standard places for ssh keys if nothing else is provided.
  • Manage authentication mechanisms, retry authenticating, and prefer SSH keys.
    • Implement/override a connect() method to do this.
  • Provide a method, test_connection(), for doing just that.
  • Manage connecting and disconnecting when remote_execute() is called. (Lazy load the auth object)
  • Attempt to handle password prompts for non-passwordless sudoers
  • Attempt to handle hiccups with pty/tty rules by retrying the command with a pty channel[1] if the system responds with "sudo requires a tty" or similar
  • Support ssh proxy connections[2], and create an implementation that provides the same behavior whether connecting through a proxy or connecting to the remote host directly.
  • Implement an instance property, platform_info, that will return the remote host's platform info using (python >=2.4)'s platform module

For the platform requirements:

  • Remote system requires python>=2.4
  • architecture, distro, version
  • e.g. Ubuntu 12.04 x86_64 would return
    • {'arch': 'x86_64', 'dist': 'ubuntu', 'version': '12.04'}


 # function signature
 def connect(host, credentials, port=22, timeout=None, proxy=None):
     """Connect to remote host over SSH and return a client connection.
     
     credentials can have:
         username (required)
         password
         private_key
         private_key_file
     proxy can have:
         host (required if proxy supplied)
         username (required if proxy supplied)
         port
         timeout
         password
         private_key
         private_key_file
     """


[1] [2]

  1. 1.0 1.1 paramiko.channel.Channel.get_pty See get_pty().
  2. 2.0 2.1 paramiko.client.SSHClient.connect See sock keyword argument.


Notes:

We considered the following to stay within pylint's max 5 argument rule, but we decided to break the rule for practicality...

 # Simplest use case
 from satori import ssh
 connection = ssh.connect("10.1.1.20", username="root", password="Password", key=None)
 
 output = connection.remote_execute("sudo echo hello", with_exit_code=True, timeout=None)
>>> {'stdout': 'hello', 'stderr': '', 'exit_code': 0}


 # More options
 from satori import ssh
 client = ssh.Client("10.1.1.20", port=2222, timeout=1000, StrictHostKeyChecking=True)
 client.set_credentials(username="root", password="Password")
 if proxy_ip:
   proxy = ssh.Client(proxy_ip, port=22)
   proxy.set_credentials(username=proxy_user, password=proxy_pass)
 else:
   proxy = None
 connection = client.connect(proxy=proxy)
 
 output = connection.remote_execute("sudo echo hello", with_exit_code=True, timeout=None)
>>> {'stdout': 'hello', 'stderr': '', 'exit_code': 0}