Paramiko: nest ssh session to another machine while preserving paramiko functionality (ProxyJump)

前端 未结 2 760
渐次进展
渐次进展 2021-01-03 01:11

I\'m trying to use paramiko to bounce an SSH session via netcat:

 MyLocalMachine ----||----> MiddleMachine --(netcat)--> AnotherMachine
 (         


        
相关标签:
2条回答
  • 2021-01-03 01:53

    Better to post this as a proposed answer, you can do the following:

    Code is not tested nor will work as it is very incomplete. I would recommend to check this amazing tut for reference http://www.revsys.com/writings/quicktips/ssh-tunnel.html

    From the middle machine

    "ssh -f user@anothermachine -L 2000:localhost:22 -N"
    

    From localmachine:

    paramiko.connect(middlemachine, 2000)
    
    0 讨论(0)
  • 2021-01-03 01:58

    Update 15.05.18: added the missing code (copy-paste gods haven't been favorable to me).

    TL;DR: I managed to do it using simple exec_command call and a class that pretends to be a sock.

    To summarize:

    • This solution does not use any other port than 22. If you can manually connect to the machine by nesting ssh clients - it will work. It doesn't require any port forwarding nor configuration changes.
    • It works without prompting for password (everything is automatic)
    • It nests ssh sessions while preserving paramiko functionality.
    • You can nest sessions as many times as you want
    • It requires netcat (nc) installed on the proxy host - although anything that can provide basic netcat functionality (moving data between a socket and stdin/stdout) will work.

    So, here be the solution:

    The masquerader

    The following code defines a class that can be used in place of paramiko.ProxyCommand. It supplies all the methods that a standard socket object does. The init method of this class takes the 3-tupple that exec_command() normally returns:

    Note: It was tested extensively by me, but you shouldn't take anything for granted. It is a hack.

    import paramiko
    import time
    import socket     
    from select import select                                                       
    
    
    class ParaProxy(paramiko.proxy.ProxyCommand):                      
        def __init__(self, stdin, stdout, stderr):                             
            self.stdin = stdin                                                 
            self.stdout = stdout                                               
            self.stderr = stderr
            self.timeout = None
            self.channel = stdin.channel                                               
    
        def send(self, content):                                               
            try:                                                               
                self.stdin.write(content)                                      
            except IOError as exc:                                             
                raise socket.error("Error: {}".format(exc))                                                    
            return len(content)                                                
    
        def recv(self, size):                                                  
            try:
                buffer = b''
                start = time.time()
    
                while len(buffer) < size:
                    select_timeout = self._calculate_remaining_time(start)
                    ready, _, _ = select([self.stdout.channel], [], [],
                                         select_timeout)
                    if ready and self.stdout.channel is ready[0]:
                          buffer += self.stdout.read(size - len(buffer))
    
            except socket.timeout:
                if not buffer:
                    raise
    
            except IOError as e:
                return ""
    
            return buffer
    
        def _calculate_remaining_time(self, start):
            if self.timeout is not None:
                elapsed = time.time() - start
                if elapsed >= self.timeout:
                    raise socket.timeout()
                return self.timeout - elapsed
            return None                                   
    
        def close(self):                                                       
            self.stdin.close()                                                 
            self.stdout.close()                                                
            self.stderr.close()
            self.channel.close()                                                                                                                            
    

    The usage

    The following shows how I used the above class to solve my problem:

    # Connecting to MiddleMachine and executing netcat
    mid_cli = paramiko.SSHClient()
    mid_cli.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    mid_cli.connect(hostname='1.1.1.1', username='user', password='pass')
    io_tupple = mid_cli.exec_command('nc 2.2.2.2 22')
    
    # Instantiate the 'masquerader' class
    proxy = ParaProxy(*io_tupple)
    
    # Connecting to AnotherMachine and executing... anything...
    end_cli = paramiko.SSHClient()
    end_cli.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    end_cli.connect(hostname='2.2.2.2', username='user', password='pass', sock=proxy)
    end_cli.exec_command('echo THANK GOD FINALLY')
    

    Et voila.

    0 讨论(0)
提交回复
热议问题