3

I'm using Paramiko to execute bash scripts on a remote server. In some of these scripts, there are ssh connections to other servers. If I use bash only, no Python, my DSA key is forwarded and used by the bash script on the first remote server to connect to the second remote server. When I use Paramiko it's not the case.

Bash example:

Jean@mydesktop:~ & ssh root@firstserver
root@firstserver:~ # ssh root@secondserver hostname
secondserver.mydomain.org

Using Paramiko:

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import paramiko

class SSHSession:


    def __init__(self, server_address, user='root', port=22):
        self.connected = False
        self.server_address = server_address
        self.user           = user
        self.port           = port


    def connect(self, clear_channel=True):
        try:
            if self.server_address == None:
                raise ValueError('No hostname')
        except:
            raise ValueError('No hostname')
        else:
            try:
                self.ssh_client = paramiko.SSHClient()
                self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
                self.ssh_client.connect(self.server_address, username=self.user)
                #self.transport = self.ssh_client.get_transport()
                #self.channel   = self.transport.open_forward_agent_channel()
                self.channel   = self.ssh_client.invoke_shell()
            except:
                self.connected = False
                return False
            else:
                self.connected = True
                return True

    def exec_command(self, command, newline='\r'):
        if not self.connected:
            raise Exception('Not connected')
        else:
            timeout = 31536000 # 365 days in seconds
            self.channel.settimeout(timeout)
            line_buffer    = ''
            channel_buffer = ''
            end_string     = 'CLIENT_EXPECT_CMD_OK'
            print('[SEND   ] >>', command)
            self.channel.send(command + ' ; echo ' + end_string + newline)
            while True:
                channel_buffer = self.channel.recv(1).decode('UTF-8')
                if len(channel_buffer) == 0:
                    raise Exception('connection lost with server: ' + self.server_address)
                    break 
                channel_buffer  = channel_buffer.replace('\r', '')
                if channel_buffer != '\n':
                    line_buffer += channel_buffer
                else:
                    if line_buffer == end_string:
                        break
                    print('[RECEIVE] <<', line_buffer)
                    line_buffer   = ''


    def disconnect(self):
        self.ssh_client.close()


    def __enter__(self):
        self.connect()
        return self


    def __exit__(self, _type, value, traceback):
        self.disconnect()


if __name__ == "__main__":
    server_address = 'firstserver'
    ssh_user       = 'root'
    with SSHSession(server_address) as ssh_session:
        ssh_session.exec_command('hostname')
        ssh_session.exec_command('ssh root@secondserver hostname')

Output is :

[SEND   ] >> hostname
[RECEIVE] << [root@firstserver ~]# hostname ; echo CLIENT_EXPECT_CMD_OK
[RECEIVE] << firstserver.mydomain.fr
[SEND   ] >> ssh root@secondserver hostname
[RECEIVE] << [root@firstserver ~]# ssh root@secondserver hostname ; echo CLIENT_EXPECT_CMD_OK
[RECEIVE] << Permission denied (publickey,gssapi-keyex,gssapi-with-mic).

I tried:

self.transport = self.ssh_client.get_transport()
self.channel   = self.transport.open_forward_agent_channel()

instead of :

self.channel   = self.ssh_client.invoke_shell()

but then I get an error :

paramiko.ssh_exception.ChannelException: Administratively prohibited

Does someone knows if this is possible ? I found discutions suggesting that is it, but yet I don't find how to do this.

Jean Coiron
  • 632
  • 8
  • 24

2 Answers2

4

Ok it works now. I found these helpful posts :

Add paramiko ssh agent forwarding (optional) #4100
open_forward_agent_channel vs. open_session #89

The final code is :

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import paramiko

class SSHSession:


    def __init__(self, server_address, user='root', port=22):
        self.connected      = False
        self.server_address = server_address
        self.user           = user
        self.port           = port


    def connect(self):
        try:
            self.ssh_client = paramiko.SSHClient()
            self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            self.ssh_client.connect(self.server_address, username=self.user)
            self.transport     = self.ssh_client.get_transport()
            self.agent_channel = self.transport.open_session()
            self.agent_handler = paramiko.agent.AgentRequestHandler(self.agent_channel)
            self.channel       = self.ssh_client.invoke_shell()
        except:
            self.connected = False
        else:
            self.connected = True
        return self.connected

    def exec_command(self, command, newline='\r'):
        if not self.connected:
            raise Exception('Not connected')
        else:
            timeout = 31536000 # 365 days in seconds
            self.channel.settimeout(timeout)
            line_buffer    = ''
            channel_buffer = ''
            end_string     = 'CLIENT_EXPECT_CMD_OK'
            print('[SEND   ] >>', command)
            self.channel.send(command + ' ; echo ' + end_string + newline)
            while True:
                channel_buffer = self.channel.recv(1).decode('UTF-8')
                if len(channel_buffer) == 0:
                    raise Exception('connection lost with server: ' +     self.server_address)
                    break 
                channel_buffer  = channel_buffer.replace('\r', '')
                if channel_buffer != '\n':
                    line_buffer += channel_buffer
                else:
                    if line_buffer == end_string:
                        break
                    print('[RECEIVE] <<', line_buffer)
                    line_buffer   = ''


    def disconnect(self):
        self.ssh_client.close()


    def __enter__(self):
        self.connect()
        return self


    def __exit__(self, _type, value, traceback):
        self.disconnect()


if __name__ == "__main__":
    server_address = 'firstserver'
    ssh_user       = 'root'
    with SSHSession(server_address) as ssh_session:
        ssh_session.exec_command('hostname')
        ssh_session.exec_command('ssh root@secondserver hostname')

Output is :

[SEND   ] >> hostname
[RECEIVE] << [root@firstserver ~]# hostname ; echo CLIENT_EXPECT_CMD_OK
[RECEIVE] << firstserver.mydomain.fr
[SEND   ] >> ssh root@secondserver hostname
[RECEIVE] << [root@firstserver ~]# ssh root@secondserver hostname ; echo CLIENT_EXPECT_CMD_OK
[RECEIVE] << secondserver.mydomain.fr

The important part of the code to enable agent forwarding is :

self.agent_channel = self.transport.open_session()
self.agent_handler = paramiko.agent.AgentRequestHandler(self.agent_channel)
Jean Coiron
  • 632
  • 8
  • 24
0

You should set the key_filename when connecting:

key_filename (str or list(str)) - the filename, or list of filenames, of optional private key(s) to try for authentication

bosnjak
  • 8,424
  • 2
  • 21
  • 47
  • You mean something like this ? `self.ssh_client.connect(self.server_address, username=self.user, key_filename='/home/jean/.ssh/id_dsa')` I tried, the behaviour is the same. Actually I'd like to use the ssh agent of my Linux desktop without having to specify a key. My script must be used by several users, each having their own key. – Jean Coiron Apr 11 '14 at 09:10