Seguimiento - Afortunadamente, ahora se ha resuelto el ticket al que he hecho referencia a continuación. La API más simple se incluirá en la próxima versión de Twisted. La respuesta original sigue siendo una forma válida de utilizar Conch y puede revelar algunos detalles interesantes sobre lo que está sucediendo, pero a partir de Twisted 13.1 y en adelante, si solo desea ejecutar un comando y manejar su E/S, this simpler interface will work.
Se necesita una gran cantidad de código para ejecutar un comando en un SSH utilizando las API del cliente Conch. Conch te hace lidiar con muchas capas diferentes, incluso si solo quieres un comportamiento aburrido sensible por defecto. Sin embargo, es ciertamente posible. Aquí hay algo de código que he tenido la intención de terminar y añadir a Twisted para simplificar este caso:
import sys, os
from zope.interface import implements
from twisted.python.failure import Failure
from twisted.python.log import err
from twisted.internet.error import ConnectionDone
from twisted.internet.defer import Deferred, succeed, setDebugging
from twisted.internet.interfaces import IStreamClientEndpoint
from twisted.internet.protocol import Factory, Protocol
from twisted.conch.ssh.common import NS
from twisted.conch.ssh.channel import SSHChannel
from twisted.conch.ssh.transport import SSHClientTransport
from twisted.conch.ssh.connection import SSHConnection
from twisted.conch.client.default import SSHUserAuthClient
from twisted.conch.client.options import ConchOptions
# setDebugging(True)
class _CommandTransport(SSHClientTransport):
_secured = False
def verifyHostKey(self, hostKey, fingerprint):
return succeed(True)
def connectionSecure(self):
self._secured = True
command = _CommandConnection(
self.factory.command,
self.factory.commandProtocolFactory,
self.factory.commandConnected)
userauth = SSHUserAuthClient(
os.environ['USER'], ConchOptions(), command)
self.requestService(userauth)
def connectionLost(self, reason):
if not self._secured:
self.factory.commandConnected.errback(reason)
class _CommandConnection(SSHConnection):
def __init__(self, command, protocolFactory, commandConnected):
SSHConnection.__init__(self)
self._command = command
self._protocolFactory = protocolFactory
self._commandConnected = commandConnected
def serviceStarted(self):
channel = _CommandChannel(
self._command, self._protocolFactory, self._commandConnected)
self.openChannel(channel)
class _CommandChannel(SSHChannel):
name = 'session'
def __init__(self, command, protocolFactory, commandConnected):
SSHChannel.__init__(self)
self._command = command
self._protocolFactory = protocolFactory
self._commandConnected = commandConnected
def openFailed(self, reason):
self._commandConnected.errback(reason)
def channelOpen(self, ignored):
self.conn.sendRequest(self, 'exec', NS(self._command))
self._protocol = self._protocolFactory.buildProtocol(None)
self._protocol.makeConnection(self)
def dataReceived(self, bytes):
self._protocol.dataReceived(bytes)
def closed(self):
self._protocol.connectionLost(
Failure(ConnectionDone("ssh channel closed")))
class SSHCommandClientEndpoint(object):
implements(IStreamClientEndpoint)
def __init__(self, command, sshServer):
self._command = command
self._sshServer = sshServer
def connect(self, protocolFactory):
factory = Factory()
factory.protocol = _CommandTransport
factory.command = self._command
factory.commandProtocolFactory = protocolFactory
factory.commandConnected = Deferred()
d = self._sshServer.connect(factory)
d.addErrback(factory.commandConnected.errback)
return factory.commandConnected
class StdoutEcho(Protocol):
def dataReceived(self, bytes):
sys.stdout.write(bytes)
sys.stdout.flush()
def connectionLost(self, reason):
self.factory.finished.callback(None)
def copyToStdout(endpoint):
echoFactory = Factory()
echoFactory.protocol = StdoutEcho
echoFactory.finished = Deferred()
d = endpoint.connect(echoFactory)
d.addErrback(echoFactory.finished.errback)
return echoFactory.finished
def main():
from twisted.python.log import startLogging
from twisted.internet import reactor
from twisted.internet.endpoints import TCP4ClientEndpoint
# startLogging(sys.stdout)
sshServer = TCP4ClientEndpoint(reactor, "localhost", 22)
commandEndpoint = SSHCommandClientEndpoint("/bin/ls", sshServer)
d = copyToStdout(commandEndpoint)
d.addErrback(err, "ssh command/copy to stdout failed")
d.addCallback(lambda ignored: reactor.stop())
reactor.run()
if __name__ == '__main__':
main()
Algunas cosas a tener en cuenta al respecto:
- Utiliza las nuevas API de punto final introducidas en Twisted 10.1 . Es posible hacerlo directamente en
reactor.connectTCP
, pero lo hice como punto final para hacerlo más útil; Los puntos finales se pueden intercambiar fácilmente sin el código que realmente solicita una conexión.
- ¡No verifica la clave de host en absoluto!
_CommandTransport.verifyHostKey
es donde implementaría eso. Eche un vistazo a twisted/conch/client/default.py
para obtener algunos consejos sobre qué tipo de cosas puede hacer.
- Se necesita
$USER
para ser el nombre de usuario remoto, que es posible que desee ser un parámetro.
- Probablemente solo funcione con autenticación de clave. Si desea habilitar la autenticación de contraseña, probablemente necesite subclasificar
SSHUserAuthClient
y anular getPassword
para hacer algo.
- Casi todas las capas de SSH y Conch son visibles aquí:
_CommandTransport
es en la parte inferior, un protocolo simple y llano que implementa el protocolo de transporte SSH. Crea un ...
_CommandConnection
que implementa las partes de negociación de conexión SSH del protocolo. Una vez que se completa, un ...
_CommandChannel
se usa para hablar con un canal SSH recién abierto. _CommandChannel
hace el ejecutivo real para ejecutar su comando. Una vez que se abre el canal, crea una instancia de ...
StdoutEcho
, o cualquier otro protocolo que proporcione. Este protocolo obtendrá el resultado del comando que ejecuta, y puede escribir en el comando stdin del comando.
Ver http://twistedmatrix.com/trac/ticket/4698 para el progreso de trenzado en apoyar esto con menos código.
¡Muchas gracias exarkun! Me llamó la atención realmente porque, como mencionaste acertadamente, debería haber una solución simple y lista para usar de esta cosa trivial. Me alegro de que ya haya trabajo en esa dirección. Gracias de nuevo por la pronta respuesta. –