web-dev-qa-db-fra.com

Session SSH imbriquée avec Paramiko

Je suis en train de réécrire un script Bash que j'ai écrit en Python. Le noeud de ce script était

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

J'ai un problème avec l'authentification imbriquée avec paramiko. Je n'ai pas pu trouver d'exemples concernant ma situation précise, mais j'ai pu trouver des exemples avec Sudo sur un hôte distant.

La première méthode écrit sur 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()

Le second crée un canal et utilise le type de socket send et recv.

J'ai pu faire en sorte que stdin.write fonctionne avec Sudo, mais cela ne fonctionne pas avec ssh sur l'hôte distant.

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 [email protected]')
stdin.write('secret')
stdin.flush()
print '---- out ----'
print stdout.readlines()
print '---- error ----'
print stderr.readlines()

ssh.close()

... des impressions ...

---- 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']

L'erreur pseudo-terminale m'a rappelé l'indicateur -t dans ma commande d'origine. Je suis donc passé à la deuxième méthode, à l'aide d'un canal. Au lieu de ssh.exec_command et plus tard, j'ai:

t = ssh.get_transport()
chan = t.open_session()
chan.get_pty()
print '---- send ssh cmd ----'
print chan.send('ssh [email protected]')
print '---- recv ----'
print chan.recv(9999)
chan = t.open_session()
print '---- send password ----'
print chan.send('secret')
print '---- recv ----'
print chan.recv(9999)

... mais il affiche '---- send ssh cmd ----' et se bloque jusqu'à ce que je tue le processus.

Je connais Python pour la première fois et je ne connais pas très bien les réseaux. Dans le premier cas, pourquoi l'envoi du mot de passe fonctionne-t-il avec Sudo mais pas avec ssh? Les invites sont-elles différentes? Paramiko est-elle même la bonne bibliothèque pour cela?

29
mqsoh

J'ai réussi à trouver une solution, mais cela demande un peu de travail manuel. Si quelqu'un a une meilleure solution, merci de me le dire.

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()

La chose à noter est qu'au lieu de cela

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

...tu veux ça

chan = ssh.invoke_Shell()

Cela me rappelle quand j'ai essayé d'écrire un script TradeWars quand j'étais enfant et que j'ai abandonné le codage pendant dix ans. :)

25
mqsoh

Voici un petit exemple utilisant uniquement paramiko (et la redirection de port):

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')
14
Sinas

Vous pouvez créer une connexion SSH en utilisant le canal d'une autre connexion SSH. Voir ici pour plus de détails.

7
David Lim

Pour une solution prête à l'emploi, consultez pxssh du projet pxpect. Regardez les exemples sshls.py et ssh_tunnel.py.

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

1
snies

La réponse de Sinas fonctionne bien mais ne m'a pas fourni tout le résultat de très longues commandes. Cependant, utiliser chan.makefile () me permet de récupérer toutes les sorties.

Ce qui suit fonctionne sur un système nécessitant tty et demande également un mot de passe 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()
0
user2945126