在Twisted中通过ssh运行远程命令的最佳方法?

Jef*_*ose 12 python ssh twisted

我有一个扭曲的应用程序,现在需要监视在几个盒子上运行的进程.我手动做的方式是'ssh和ps',现在我想要扭曲的应用程序.我有2个选择.

使用paramiko或利用的力量twisted.conch

我真的想用,twisted.conch但我的研究让我相信它主要用于创建SSHServers和SSHClients.但是我的要求很简单remoteExecute(some_cmd)

我能够弄清楚如何使用paramiko但我不想坚持paramiko我的扭曲的应用程序,然后再看看如何使用twisted.conch

twisted关于如何remote_cmds使用ssh 运行的代码片段将受到高度赞赏.谢谢.

Jea*_*one 16

跟进 - 很高兴,我在下面引用的票现已解决.更简单的API将包含在Twisted的下一个版本中.原始答案仍然是使用Conch的有效方式,并且可能会显示一些有关正在发生的事情的有趣细节,但是从Twisted 13.1开始,如果您只想运行命令并处理它的I/O,这个更简单的界面将起作用.


遗憾的是,使用Conch客户端API在SSH上执行命令需要大量代码.Conch让你处理很多不同的层次,即使你只是想要明智无聊的默认行为.但是,这当然是可能的.这里有一些代码,我一直想完成并添加到Twisted以简化这种情况:

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()
Run Code Online (Sandbox Code Playgroud)

有些事情需要注意:

  • 它使用Twisted 10.1中引入的新端点API.可以直接执行此操作reactor.connectTCP,但我将其作为端点使其更有用; 端点可以轻松交换,而无需实际请求连接的代码.
  • 它根本没有主机密钥验证! _CommandTransport.verifyHostKey是你实现它的地方.看看twisted/conch/client/default.py有关您可能想要做什么事情的一些提示.
  • 它需要$USER是远程用户名,您可能希望将其作为参数.
  • 它可能只适用于密钥身份验证.如果要启用密码身份验证,则可能需要子类化SSHUserAuthClient和覆盖getPassword以执行某些操作.
  • 几乎所有的SSH和海螺层都在这里可见:
    • _CommandTransport在底部,是一个实现SSH传输协议的普通旧协议.它创造了......
    • _CommandConnection它实现协议的SSH连接协商部分.一旦完成,...
    • _CommandChannel用于与新打开的SSH通道通信. _CommandChannel做实际的exec来启动你的命令.打开频道后,它会创建一个......的实例
    • StdoutEcho或者你提供的任何其他协议.该协议将从您执行的命令获得输出,并可以写入命令的stdin.

请参阅http://twistedmatrix.com/trac/ticket/4698,了解使用较少代码支持此功能的Twisted进度.