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?
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. :)
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')
Vous pouvez créer une connexion SSH en utilisant le canal d'une autre connexion SSH. Voir ici pour plus de détails.
Pour une solution prête à l'emploi, consultez pxssh du projet pxpect. Regardez les exemples sshls.py et ssh_tunnel.py.
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()