Difference between revisions of "Satori/SSHModuleProposal"
< Satori
m |
Ziad Sawalha (talk | contribs) (→Implementation) |
||
| (7 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> | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| + | ==== 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 | + | 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 | + | ** for all the above logins, the module should support using a proxy (a.k.a. bastion) |
| − | |||
| − | |||
| − | |||
| − | Primary methods: | + | Primary class methods: |
* <tt>remote_execute()</tt>: to execute a remote command and return stderr and stdout | * <tt>remote_execute()</tt>: to execute a remote command and return stderr and stdout | ||
| 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.
- Implement/override a
- 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.0 1.1 paramiko.channel.Channel.get_pty See get_pty().
- ↑ 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}