Pergunta

Eu estou reescrevendo um Bash script que eu escrevi em Python. O cerne desse roteiro era

ssh -t first.com "ssh second.com very_remote_command"

Eu estou tendo um problema com a autenticação aninhada com paramiko. Eu não era capaz de encontrar qualquer exemplos que lidam com a minha situação precisa, mas eu era capaz de encontrar exemplos com sudo em um host remoto.

O primeiro método gravações stdin

ssh.connect('127.0.0.1', username='jesse', password='lol')
stdin, stdout, stderr = ssh.exec_command("sudo dmesg")
stdin.write('lol\n')
stdin.flush()

O segundo cria um canal e utiliza o tipo encaixe < em> Enviar e recv .

Eu era capaz de conseguir stdin.write para trabalhar com sudo , mas ele não funciona com o ssh no host remoto.

import paramiko

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('first.com', username='luser', password='secret')
stdin, stdout, stderr = ssh.exec_command('ssh luser@second.com')
stdin.write('secret')
stdin.flush()
print '---- out ----'
print stdout.readlines()
print '---- error ----'
print stderr.readlines()

ssh.close()

... impressões ...

---- out ----
[]
---- error ----
['Pseudo-terminal will not be allocated because stdin is not a terminal.\r\n', 'Permission denied, please try again.\r\n', 'Permission denied, please try again.\r\n', 'Permission denied (publickey,password,keyboard-interactive).\r\n']

O erro pseudo-terminal me lembrou da bandeira -t no meu comando original, então eu troquei para o segundo método, usando um canal. Em vez de ssh.exec_command e, mais tarde, eu tenho:

t = ssh.get_transport()
chan = t.open_session()
chan.get_pty()
print '---- send ssh cmd ----'
print chan.send('ssh luser@second.com')
print '---- recv ----'
print chan.recv(9999)
chan = t.open_session()
print '---- send password ----'
print chan.send('secret')
print '---- recv ----'
print chan.recv(9999)

... mas ele imprime '---- send ssh cmd ----' e apenas a trava até que eu matar o processo.

Eu sou novo para Python e não muito bem informados sobre redes. No primeiro caso, por que enviar o trabalho senha com sudo , mas não com ssh ? as instruções são diferentes? É paramiko mesmo na biblioteca certa para isso?

Foi útil?

Solução

Eu consegui encontrar uma solução, mas requer um pouco de trabalho manual. Se alguém tem uma solução melhor, por favor me diga.

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('first.com', username='luser', password='secret')

chan = ssh.invoke_shell()

# Ssh and wait for the password prompt.
chan.send('ssh second.com\n')
buff = ''
while not buff.endswith('\'s password: '):
    resp = chan.recv(9999)
    buff += resp

# Send the password and wait for a prompt.
chan.send('secret\n')
buff = ''
while not buff.endswith('some-prompt$ '):
    resp = chan.recv(9999)
    buff += resp

# Execute whatever command and wait for a prompt again.
chan.send('ls\n')
buff = ''
while not buff.endswith('some-prompt$ '):
    resp = chan.recv(9999)
    buff += resp

# Now buff has the data I need.
print 'buff', buff

ssh.close()

A coisa a notar é que, em vez disso

t = ssh.get_transport()
chan = t.open_session()
chan.get_pty()

... você quer este

chan = ssh.invoke_shell()

Isso me lembra de quando eu tentei escrever um script TradeWars quando eu era criança e deu-se a codificação por dez anos. :)

Outras dicas

Aqui está um pequeno exemplo usando paramiko única (e encaminhamento de porta):

import paramiko as ssh

class SSHTool():
    def __init__(self, host, user, auth,
                 via=None, via_user=None, via_auth=None):
        if via:
            t0 = ssh.Transport(via)
            t0.start_client()
            t0.auth_password(via_user, via_auth)
            # setup forwarding from 127.0.0.1:<free_random_port> to |host|
            channel = t0.open_channel('direct-tcpip', host, ('127.0.0.1', 0))
            self.transport = ssh.Transport(channel)
        else:
            self.transport = ssh.Transport(host)
        self.transport.start_client()
        self.transport.auth_password(user, auth)

    def run(self, cmd):
        ch = self.transport.open_session()
        ch.set_combine_stderr(True)
        ch.exec_command(cmd)
        retcode = ch.recv_exit_status()
        buf = ''
        while ch.recv_ready():
            buf += ch.recv(1024)
        return (buf, retcode)

# The example below is equivalent to
# $ ssh 10.10.10.10 ssh 192.168.1.1 uname -a
# The code above works as if these 2 commands were executed:
# $ ssh -L <free_random_port>:192.168.1.1:22 10.10.10.10
# $ ssh 127.0.0.1:<free_random_port> uname -a
host = ('192.168.1.1', 22)
via_host = ('10.10.10.10', 22)

ssht = SSHTool(host, 'user1', 'pass1',
    via=via_host, via_user='user2', via_auth='pass2')

print ssht.run('uname -a')

Você pode criar ssh conexão usando o canal de outra conexão ssh. Consulte aqui para obter mais detalhes.

Para uma solução pronta confira pxssh do projeto pxpect. Veja os exemplos sshls.py e ssh_tunnel.py.

http://www.noah.org/wiki/Pexpect

A resposta de SINAS funciona bem, mas não forneceu toda a saída de comandos muito longos para mim. No entanto, usando chan.makefile () permite-me para recuperar toda a saída.

A seguir funciona em um sistema que requer avisos tty e também para a senha sudo

ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
ssh.connect("10.10.10.1", 22, "user", "password")
chan=ssh.get_transport().open_session()
chan.get_pty()
f = chan.makefile()
chan.exec_command("sudo dmesg")
chan.send("password\n")
print f.read()
ssh.close()
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top