Recipe 15.10. Performing Remote Logins with SSH
Credit: Peter Cogolo, Anna Martelli Ravenscroft
Problem
You need to
send commands, using the SSH protocol, to one or more logins that can
be on a local machine or a remote machine.
Solution
SSH is a secure replacement for the old Telnet protocol. One way to
use SSH from a Python program is with the third-party
paramiko package:
# auto_ssh.py - remote control via ssh
import os, sys, paramiko
from getpass import getpass
paramiko.util.log_to_file('auto_ssh.log', 0)
def parse_user(user, default_host, default_port):
''' given name[@host[:port]], returns name, host, int(port),
applying defaults for hose and/or port if necessary
'''
if '@' not in user:
return user, default_host, default_port
user, host = user.split('@', 1)
if ':' in host:
host, port = host.split(':', 1)
else:
port = default_port
return user, host, int(port)
def autoSsh(users, cmds, host='localhost', port=22, timeout=5.0,
maxsize=2000, passwords=None):
''' run commands for given users, w/default host, port, and timeout,
emitting to standard output all given commands and their
responses (no more than 'maxsize' characters of each response).
'''
if passwords is None:
passwords = { }
for user in users:
if user not in passwords:
passwords[user] = getpass("Enter user '%s' password: " % user)
for user in users:
user, host, port = parse_user(user, default_host, default_port)
try:
transport = paramiko.Transport((host, port))
transport.connect(username=user, password=passwords[user])
channel = transport.open_session( )
if timeout: channel.settimeout(timeout)
for cmd in cmd_list:
channel.exec_command(cmd)
response = channel.recv(max_size)
print 'CMD %r(%r) -> %s' % (cmd, user, response)
except Exception, err:
print "ERR: unable to process %r: %s" % (user, err)
if _ _name_ _ == '_ _main_ _':
logname = os.environ.get("LOGNAME", os.environ.get("USERNAME"))
host = 'localhost'
port = 22
usage = ""
usage: %s [-h host] [-p port] [-f cmdfile] [-c "command"] user1 user2 ...
-c command
-f command file
-h default host (default: localhost)
-p default host (default: 22)
Example: %s -c "echo $HOME" %s
same as: %s -c "echo $HOME" %s@localhost:22
"" % (sys.argv[0], sys.argv[0], logname, sys.argv[0], logname)
import getopt
optlist, user_list = getopt.getopt(sys.argv[1:], 'c:f:h:p:')
if not user_list:
print usage
sys.exit(1)
cmd_list = [ ]
for opt, optarg in optlist:
if opt == '-f':
for r in open(optarg, 'rU'):
if r.rstrip( ):
cmd_list.append(r)
elif opt == '-c':
command = optarg
if command[0] == '"' and command[-1] == '"':
command = command[1:-1]
cmd_list.append(command)
elif opt == '-h':
host = optarg
elif opt == '-p':
port = optarg
else:
print 'unknown option %r' % opt
print usage
sys.exit(1)
autoSsh(user_list, cmd_list, host=host, port=port)
Discussion
The third-party extension paramiko package lets
you easily automate access to all sorts of SSH services, even from
non-Unix machines. paramiko even lets you write
your own SSH servers in Python. In this recipe, however, we use
paramiko on the client side, as a more secure
alternative to the similar use of telnetlib shown
previously in Recipe 15.9.Production code generally has to be made more robust, but this recipe
should be enough to get you started in the right direction. The
recipe's autoSsh function first
ensures it knows passwords for all the users (asking interactively
for the passwords of users it doesn't know about).
Then, it loops over all the users, parsing strings such as
foo@bar:2222 to mean user foo
at host bar, port 2222, and
defaulting the host and port values, if necessary.The loop body relies on two types of objects supplied by
paramiko, transport and
Channel. The transport is constructed by giving it
the (host,
port) pair
and then a connection is made with a username and password.
(Alternatively, depending on the SSH server, one might connect using
a private key, but this recipe uses just a password.) The channel is
obtained from the transport, and the recipe then sets a timeout (by
default, 6 seconds) to ensure that no long-term
hanging occurs in case of problems with an SSH
server or the network path to it. Finally, an inner loop over all
commands sends each command, receives a response (up to a maximum
length in bytes, 2000 by default), and prints the command and
response.
SSH, the Secure ShellThe SSH protocol is secure, powerful, and flexible. No password ever travels on the network in the clear, and the connection stream itself gets encrypted. Besides single commands (as used in this recipe) and entire interactive shell sessions, SSH allows secure copying of files in either direction and secure remote tunneling of X11 GUI sessions and other TCP/IP-based network protocols. Moreover, unlike other secure transport-level protocols such as SSL/TLS, SSH does not require certificates signed by some kind of "central authority". You can learn more about SSH from the OpenSSH's web page at http://www.openssh.com/, Steve Allen's pages at http://www.ucolick.org/~sla/ssh/, and Kimmon Suominen's tutorial at http://kimmo.suominen.com/docs/ssh/as well as from Richard Silverman and Daniel J. Barrett, SSH: The Secure Shell, The Definitive Guide (O'Reilly). |
See Also
paramiko's home page at
http://www.lag.net/~robey/paramiko/;
paramiko requires another third-party extension to
Python, the Python Cryptography Toolkit, whose home page is at
http://www.amk.ca/python/code/crypto; docs on
SSH at http://www.openssh.com/,
http://www.ucolick.org/~sla/ssh/,
http://kimmo.suominen.com/docs/ssh/; Richard
Silverman and Daniel J. Barrett, SSH: The Secure Shell,
The Definitive Guide (O'Reilly); Recipe 15.9.