SSH with Twisted - Running Commands on a Remote Server (
Page 5 of 5 )
This lab demonstrates how to write an SSH client. You can use twisted.conch to communicate with a server using SSH: logging in, executing commands, and
capturing the output.
How Do I Do That?
There are several classes that work together to make up a twisted.conch.ssh SSH client. The transport.SSHClientTransport
class sets up the connection and verifies the identity of the server. The
userauth.SSHUserAuthClient
logs in using your authentication credentials. The
connection.SSHConnection
class takes over once you’ve logged in, and creates one or more
channel.SSHChannel
objects, which you then use to communicate with the server over a secure channel. Example 10-4 shows how you can use these classes to make an SSH client that logs into a server, runs a command, and prints the output.
Example 10-4. sshclient.py
from twisted.conch import error
from twisted.conch.ssh import transport, connection, keys, userauth, channel, common from twisted.internet import defer, protocol, reactor
class ClientCommandTransport(transport.SSHClientTransport):
def __init__(self, username, password, command)
:
self.username = username
self.password = password
self.command = command
def verifyHostKey(self, pubKey, fingerprint):
# in a real app, you should verify that the fingerprint matches
# the one you expected to get from this server
return defer.succeed(True)
def connectionSecure(self):
self.requestService(
PasswordAuth(self.username, self.password,
ClientConnection(self.command)))
class PasswordAuth(userauth.SSHUserAuthClient):
def __init__(self, user, password, connection):
userauth.SSHUserAuthClient.__init__(self, user, connection)
self.password = password
def getPassword(self, prompt=None):
return defer.succeed(self.password)
class ClientConnection(connection.SSHConnection):
def __init__(self, cmd, *args, **kwargs):
connection.SSHConnection.__init__(self)
self.command = cmd
def serviceStarted(self):
self.openChannel(CommandChannel(self.command, conn=self))
class CommandChannel(channel.SSHChannel):
name = 'session'
def __init__(self, command, *args, **kwargs):
channel.SSHChannel.__init__(self, *args, **kwargs)
self.command = command
def channelOpen(self, data):
self.conn.sendRequest(
self, 'exec', common.NS(self.command), wantReply=True).addCallback(
self._gotResponse)
def _gotResponse(self, _):
self.conn.sendEOF(self)
def dataReceived(self, data):
print data
def closed(self):
reactor.stop()
class ClientCommandFactory(protocol.ClientFactory):
def __init__(self, username, password, command):
self.username = username
self.password = password
self.command = command
def buildProtocol(self, addr):
protocol = ClientCommandTransport(
self.username, self.password, self.command)
return protocol
if __name__ == "__main__":
import sys, getpass
server = sys.argv[1]
command = sys.argv[2]
username = raw_input("Username: ")
password = getpass.getpass("Password: ")
factory = ClientCommandFactory(username, password, command)
reactor.connectTCP(server, 22, factory)
reactor.run()
Run sshclient.py with two arguments: a hostname and a command. It will ask for your username and password, log into the server, execute the command, and print the output. For example, you could run the who command to get a list of who’s currently logged in to the server:
$ python sshclient.py myserver.example.com who
Username: abe
Password: password
root pts/0 Jun 11 21:35 (192.168.0.13)
phil pts/2 Jun 22 13:58 (192.168.0.1)
phil pts/3 Jun 22 13:58 (192.168.0.1)
How Does That Work?
The ClientCommandTransport in Example 10-4 handles the initial connection to the SSH server. Its verifyHostKey method checks to make sure the server’s public key matches your expectations. Typically, you’d remember each server the first time you connected, and then check on subsequent connections to make sure that another server wasn’t maliciously trying to pass itself off as the server you expected. Here, it just returns a True value without bothering to check the key. The connectionSecure method is called as soon as the initial encrypted connection has been established. This is the appropriate time to send your login credentials, by passing a
userauth.SSHUserAuthClient to self.requestService, along with a connection.SSHConnection object that should manage the connection after authentication succeeds.
The
PasswordAuth
inherits from
userauth.SSHUserAuthClient
. It has to implement only a single method,
getPassword
, which returns the password it will use to log in. If you wanted to use public key authentication, you’d implement the methods
getPublicKey
and
getPrivateKey
instead, returning the appropriate key as a string in each case.
The
ClientConnection
class in Example 10-4 will have its
serviceStarted
method called as soon as the client has successfully logged in. It calls
self.openChannel
with a
CommandChannel
object, which is a subclass of
channel.SSHChannel
. This object is used to work with an authenticated channel to the SSH server. Its
channelOpen
method is called when the channel is ready. At this point, you can call
self.conn.sendRequest
to send a command to the server. You have to encode data sent over SSH as a spe
cially formatted network string; to get a string in this format, pass it to the
twisted.conch.common.NS
function. Set the keyword argument
wantReply
to
True
if you’re interested in getting a response from the command; this setting will cause
sendRequest
to return a
Deferred
that will be called back when the command is completed. (If you don’t set
wantReply
to
True
,
sendRequest
will return
None
.) As data is received from the server, it will be passed to
dataReceived
. Once you’re done using the channel, close it by calling
self.conn.sendEOF
. The
closed
method will be called to let you know when the channel has been successfully closed.