Подтвердить что ты не робот

Лучший способ запуска удаленных команд через ssh в Twisted?

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

Используйте paramiko или используйте мощность twisted.conch

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

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

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

4b9b3361

Ответ 1

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


Для выполнения команды SSH с использованием клиентских API-интерфейсов Conch требуется, к сожалению, большой объем кода. 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 или любой другой протокол, который вы поставляете. Этот протокол получит результат из команды, которую вы выполняете, и можете написать команду stdin.

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