Tj Nielsen
Tj Nielsen

Reputation: 41

paramiko.ssh_exception.ProxyCommandFailure: 'Broken pipe')

I am trying to ssh tunnel from PC --> server1 ---> server2 ----> switch1 This is possible through a regular terminal with a simple: ssh switch1 it references my ssh_config which reads:

 Host server1
  user bill
  Hostname server1
  ForwardAgent yes
  IdentityFile ~/.ssh/id_rsa
  ProxyCommand none

 Host server2
  user bill
  Hostname server2
  IdentityFile ~/.ssh/id_rsa
  ProxyCommand ssh server1 /usr/bin/nc %h %p

 Host switch1
  user bill
  Hostname %h.omniture.com
  ProxyCommand ssh server2 /usr/bin/nc %h %p

Not a problem from regular terminal. But trying to build a python script to do it has proven to be difficult.

Script is as follows:

 import paramiko
 import subprocess
 import getpass
 import os


 def ssh_command(ip, user, passwd, command):
     client = paramiko.SSHClient()
     client.load_host_keys('/Users/bill/.ssh/known_hosts')
     client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
     config = paramiko.SSHConfig()
     if os.path.exists('/etc/ssh/ssh_config'):
         config.parse(open('/etc/ssh/ssh_config'))
     if os.path.exists(os.path.expanduser('~/.ssh/config')):
         config.parse(open(os.path.expanduser('~/.ssh/config')))

     host = config.lookup(ip)
     if 'proxycommand' in host:
                 proxy = paramiko.ProxyCommand(
                 subprocess.check_output(
                 [os.environ['SHELL'], '-c', 'echo %s' %
                 host['proxycommand']]
                 ).strip()
             )
     else:
         proxy = None

     client.connect(host['hostname'], username='bill',
     password=getpass.getpass(), sock=proxy)
     ssh_session = client.get_transport().open_session()
     if ssh_session.active:
         ssh_session.exec_command(command)
         print ssh_session.recv(1024)
     return
 ssh_command('sw-a-102.sin2', 'bill', getpass.getpass(), 'show ver')

The error I get is:

No handlers could be found for logger "paramiko.transport"
Traceback (most recent call last):
  File "/Users/bill/git/tools/python/dns-check/proxy-test.py", line 34, in <module>
    ssh_command('switch1', 'bill', getpass.getpass(), 'show ver')
  File "/Users/bill/git/tools/python/dns-check/proxy-test.py", line 28, in ssh_command
    client.connect(host['hostname'], username='bill', password=getpass.getpass(), sock=proxy)
  File "/Library/Python/2.7/site-packages/paramiko/client.py", line 265, in connect
    t.start_client()
  File "/Library/Python/2.7/site-packages/paramiko/transport.py", line 406, in start_client
    raise e
paramiko.ssh_exception.ProxyCommandFailure: ('ssh server2 /usr/bin/nc switch1 22', 'Broken pipe')

If I can get this to work using paramiko that would be great, if someone knows a better way, that would be good too. Thank you for your time.

Upvotes: 4

Views: 2938

Answers (1)

danny
danny

Reputation: 5270

There is a better way, still with Paramiko but without ProxyCommand configuration.

Reason being paramiko's proxy command support is buggy and prone to race conditions, which is the cause of the error above.

OTOH, SSH has native support for tunneling in the protocol itself and does not require external tools to achieve it.

import paramiko

# Make clients
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.MissingHostKeyPolicy())
server2_client = paramiko.SSHClient()
server2_client.set_missing_host_key_policy(paramiko.MissingHostKeyPolicy())
switch1_client = paramiko.SSHClient()
switch1_client.set_missing_host_key_policy(paramiko.MissingHostKeyPolicy())

# Connect to server1 normally
client.connect('server1')

# Make channel server1 -> server2
server2_chan = client.get_transport().open_channel('direct-tcpip', ('<server2 ip>', 22,), ('127.0.0.1', 0))

# Connect to server2 via server1's channel
server2_client.connect('<server2 ip>', sock=server1_chan)

# Make channel server2 -> switch1
switch1_chan = server2_client.get_transport().open_channel('direct-tcpip', ('<switch1 ip>', 22,), ('127.0.0.1', 0))

# Finally connect to switch1 via server2 channel
switch1_client.connect('switch1', sock=server2_chan)
switch1_client.exec_command(<command>)

Substitute server and switch names with their IP addresses.

See also a parallel SSH client based on paramiko with support for native SSH tunneling.

Upvotes: 2

Related Questions