Лучший способ запуска удаленных команд через SSH в Twisted? - PullRequest
12 голосов
/ 06 января 2011

У меня есть скрученное приложение, которое теперь должно отслеживать процессы, запущенные на нескольких блоках.То, как я вручную делаю это «ssh и ps», теперь я бы хотел, чтобы мое приложение было изменено.У меня есть 2 варианта.

Использование paramiko или использование силы twisted.conch

Я действительно хочу использовать twisted.conch, но мои исследования привели меня к убеждению, что это в первую очередь предназначено для созданияSSHS-серверы и SSHClients.Однако мое требование простое: remoteExecute(some_cmd)

Я смог выяснить, как это сделать, используя paramiko, но я не хотел вставлять paramiko в свое скрученное приложение, прежде чем посмотреть, как это сделать, используяtwisted.conch

Фрагменты кода, использующие twisted о том, как запустить remote_cmds, используя ssh, будут высоко оценены.Благодаря.

1 Ответ

16 голосов
/ 06 января 2011

Followup - К счастью, билет, на который я ссылаюсь ниже, теперь решен. Более простой API будет включен в следующую версию Twisted. Оригинальный ответ по-прежнему является верным способом использования Conch и может раскрыть некоторые интересные подробности о том, что происходит, но из Twisted 13.1 и далее, если вы просто хотите запустить команду и обработать ее ввод-вывод, этот более простой интерфейс будет работать .


К сожалению, требуется большое количество кода для выполнения команды в SSH с использованием клиентских API 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()

Несколько замечаний по этому поводу:

  • Он использует новые API конечных точек, представленные в Twisted 10.1. Это можно сделать непосредственно на reactor.connectTCP, но я сделал это в качестве конечной точки, чтобы сделать его более полезным; конечные точки можно легко поменять местами без кода, который фактически запрашивает соединение.
  • Он вообще не проверяет ключ хоста! _CommandTransport.verifyHostKey это то место, где вы бы это реализовали. Взгляните на twisted/conch/client/default.py, чтобы найти подсказки о том, что вы можете делать.
  • Требуется $USER, чтобы быть удаленным именем пользователя, которое вы можете использовать в качестве параметра.
  • Вероятно, это работает только с аутентификацией ключа. Если вы хотите включить аутентификацию по паролю, вам, вероятно, нужно сделать подкласс SSHUserAuthClient и переопределить getPassword, чтобы что-то сделать.
  • Здесь видны почти все слои SSH и Conch:
    • _CommandTransport внизу, простой старый протокол, который реализует транспортный протокол SSH. Это создает ...
    • _CommandConnection, который реализует части протокола согласования соединения SSH. Как только это завершится, ...
    • _CommandChannel используется для связи с вновь открытым каналом SSH. _CommandChannel делает фактический exec для запуска вашей команды. После открытия канала создается экземпляр ...
    • StdoutEcho или любой другой протокол, который вы предоставляете. Этот протокол будет получать выходные данные от команды, которую вы выполняете, и может записывать в стандартный ввод команды.

См. http://twistedmatrix.com/trac/ticket/4698 о продвижении Twisted с поддержкой меньшего количества кода.

...