Jump to: navigation, search

Difference between revisions of "Satori/SSHModuleProposal"

(Implementation)
 
(11 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
== SSH Module Proposal ==
 
== SSH Module Proposal ==
  
<big>Implement an SSH wrapper module to enable logging on to servers so we can do [[Satori/glossary|data plane discovery]]</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>
 
 
==== Desired Interface ====
 
>>> from satori import ssh
 
>>> creds = {"username": "Tobias", "password": "pa$$word"}
 
>>> client = ssh.connect("123.456.789.11", credentials=creds)  # see signature in implementation section
 
>>> output = client.remote_execute("sudo echo hello", with_exit_code=True)
 
>>> print output
 
{'stdout': 'hello', 'stderr': <nowiki>''</nowiki>, 'exit_code': 0}
 
  
 +
==== 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}
  
 
==== Requirements ====
 
==== Requirements ====
  
Support the following login methods:
+
Support the following logins:
 
** password/username
 
** password/username
 
** private key/username
 
** private key/username
 
** private key file/username
 
** private key file/username
** for all the above methods, the module should support using a proxy (i.e. bastion)
+
** for all the above logins, the module should support using a proxy (a.k.a. bastion)
 
 
 
 
Note: basically, support the options as would commonly be used through the ssh command-line.
 
  
  
The main functions needed are:
+
Primary class methods:
  
* remote_execute: to execute a remote command and return stderr and stdout
+
* <tt>remote_execute()</tt>: to execute a remote command and return stderr and stdout
* test_connection: to test that a connection can be made
+
* <tt>test_connection()</tt>: to test that a connection can be made
  
 
Additionally:
 
Additionally:
Line 71: Line 71:
 
           private_key_file
 
           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}