Skip to content

Instantly share code, notes, and snippets.

@toejough
Last active February 25, 2024 10:51
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save toejough/436540622530c35404e6 to your computer and use it in GitHub Desktop.
Save toejough/436540622530c35404e6 to your computer and use it in GitHub Desktop.
SSH Agent Forwarding in Python: Paramiko's undocumented API

What

A how-to for ssh-agent forwarding via Paramiko. Specifically, I used Paramiko v1.15.2 in this example.

Why

Paramiko's docs do not document the API required to do ssh-agent forwarding. I ended up finding out how by reading pull requests for ssh-agent forwarding features in frameworks that use Paramiko under the covers, like fabric and ansible.

Update:

Besides attempting to document this process here, I've opened a bug with Paramiko to document this API in their official docs.

How

# get a paramiko transport - get it directly, or from a client. call it "t"
session = t.get_session()
paramiko.agent.AgentRequestHandler(s)  # <---UNDOCUMENTED
# do whatever you want with your session

Full example

agent.py:

# Author: toejough
# Website: https://github.com/toejough


'''
Tries to hop connections via 'Public-key' auth type and SSH agent.
'''


# [ Imports ]
# [ - Python ]
from argparse import ArgumentParser
import sys
import time
# [ - Third Party ]
import paramiko


# [ Main ]
# Arg parsing
p = ArgumentParser(description=__doc__)
p.add_argument(
    'host',
    help='The host to connect to',
    metavar='<hostname>'
)
p.add_argument(
    'port',
    help='The port to connect to',
    metavar='<port>',
    type=int
)
p.add_argument(
    'username',
    help='The username to connect as',
    metavar='<username>'
)
p.add_argument(
    '--debug',
    help='Print verbose messages and full stack traces on internal failures',
    action='store_true'
)
args = p.parse_args()
host, port, username = args.host, args.port, args.username
# Connection Attempt
try:
    # Start the client
    client = paramiko.client.SSHClient()
    client.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
    client.load_system_host_keys()
    client.connect(host, port, username)
    # get a session
    s = client.get_transport().open_session()
    # set up the agent request handler to handle agent requests from the server
    paramiko.agent.AgentRequestHandler(s)  # <--UNDOCUMENTED??!!
    # get a shell
    s.get_pty()
    s.invoke_shell()
except Exception as e:
    # if debugging, just re-raise the error so the full stacktrace is printed
    if args.debug:
        raise
    # On failure, print failure only (not full bt, as is the default without the try/catch)
    print e
    exit(1)


def recv():
    while s.recv_ready():
            print s.recv(sys.maxint)
    while s.recv_stderr_ready():
        print >> sys.stderr, s.recv_sterr(sys.maxint)


def send(text):
    s.sendall(text + "\n")
    time.sleep(0.1)
    recv()


time.sleep(0.1)
recv()
# Play.
import pdb; pdb.set_trace()  # XXX BREAKPOINT
exit(0)

Now, from Bash:

eval `ssh-agent`
ssh-add ~/.ssh/id_rsa
python agent.py
@porridge
Copy link

You might also want to point out that it's not useful to set up an AgentRequestHandler if there is no local agent, since in this case the connection will hang with a stack trace such as the one below as soon as a remote process tries to talk to the agent over the forwarded channel:

Exception in thread Thread-82:
Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 810, in __bootstrap_inner
    self.run()
  File "/usr/lib/python2.7/dist-packages/paramiko/agent.py", line 116, in run
    self._communicate()
  File "/usr/lib/python2.7/dist-packages/paramiko/agent.py", line 128, in _communicate
    events = select([self._agent._conn, self.__inr], [], [], 0.5)
TypeError: argument must be an int, or have a fileno() method.

One way to test whether a local agent is in place is to check paramiko.Agent.get_keys() for non-emptiness.

@giappv
Copy link

giappv commented Apr 19, 2016

I got "Hang" when running this script, no response from command line

@July-NetEase
Copy link

I used paramikio as a ssh server,but sth really made me soconfused, who can help me.

when user log onto the jumpserver and ssh to the remote server. sth is wrong
1、ssh.connect()
I found _agent.get_keys() is empty though allow_agent is true
how can i got the agent keys

2、whether I can turn on the SSH agent forwarding use paramiko as the OpenSSH does

@PaulSinghDev
Copy link

To add on to @porridge's suggestion, you may need to check with the following:

paramiko.Agent().get_keys()

Otherwise you will get an error along the lines of:

Traceback (most recent call last):
  File "REDACTED/copyFilesToServer.py", line 65, in <module>
    if not paramiko.Agent.get_keys():
           ^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: AgentSSH.get_keys() missing 1 required positional argument: 'self'

I'm new to python so could be wrong but I think the error is a result of the class not being instantiated which is causing it to be unable to reference itself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment