web-dev-qa-db-ja.com

ParamikoとのネストされたSSHセッション

Pythonに書き込んだBashスクリプトを書き直しています。そのスクリプトの核心は

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

Paramikoを使用した入れ子の認証に問題があります。私の正確な状況に対応する例は見つかりませんでしたが、リモートホストでSudoの例を見つけることができました。

最初の方法 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()

2番目 はチャネルを作成し、ソケットのようなsendおよびrecv

stdin.writeSudoと連携させることができましたが、リモートホスト上のsshでは機能しません。

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

...プリント...

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

疑似端末エラーにより、元のコマンドの-tフラグを思い出したので、チャネルを使用して2番目の方法に切り替えました。 ssh.exec_command以降の代わりに、私は:

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)

...しかし、「---- send ssh cmd ----」と出力し、プロセスを強制終了するまでハングします。

私はPythonに不慣れで、ネットワークについてあまり知識がありません。最初のケースでは、なぜパスワードの送信がSudoではなくssh?プロンプトは異なりますか?paramikoはこれに適したライブラリですか?

31
mqsoh

なんとか解決策を見つけましたが、少し手作業が必要です。誰かがより良い解決策を持っているなら、教えてください。

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

注意すべきことは、これの代わりに

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

...あなたはこれを求めている

chan = ssh.invoke_Shell()

私が子供の頃にTradeWarsスクリプトを書こうとしたときに10年間コーディングをあきらめたときのことを思い出します。 :)

27
mqsoh

以下は、paramikoのみ(およびポート転送)を使用した小さな例です。

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

別のssh接続のチャネルを使用してssh接続を作成できます。詳細は here を参照してください。

7
David Lim

既製のソリューションについては、pxpectプロジェクトからpxsshをチェックしてください。 sshls.pyとssh_tunnel.pyの例を見てください。

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

1
snies

シナスの答えはうまくいきますが、私にとって非常に長いコマンドからのすべての出力を提供しませんでした。ただし、chan.makefile()を使用すると、すべての出力を取得できます。

以下は、ttyを必要とし、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