with ssh() pt2: Transfering Python code and executing it remotely
Yesterday4 I left when the problem got really interesting: how to transfer code
to another machine and execute it there. I already advanced part of the solution:
use pickle
to convert something returned by ast.parse()
into something
transferable. Let's see how hard it really is:
import paramiko import ast import pickle p= ast.parse ('print ("yes!")') pick= pickle.dumps (p) c= paramiko.SSHClient() c.load_host_keys ('/home/mdione/.ssh/known_hosts') c.connect ('localhost', allow_agent=False, password='foobarbaz') (i, o, e)= c.exec_command ('''python -c "import pickle from ast import Print, Module, Str import sys c= pickle.load (sys.stdin) code= compile (c, 'remote', 'exec') exec (code)"''') i.write (pick) o.readline ()
This happily prints 'yes!' on the last line.
There are a lot of caveats in this code. First, this doesn't work on Python3,
only because there's no official/working port of paramiko
for that version.
Jan N. Schulze a.k.a. nischu7
has made a port,
which looks quite active (last commit from around a month ago), but I tried it with
Python 3.3 and didn't work out of the box. Furthermore, even when pickle
's doc
says that it automatically detects the format of the stream, which means that
technically I could pickle something in Python2 and unpickle it back in Python3,
the same does not happen with the ast
module. Hence, I'm also using Python2 in
the remote2. This implies that I will have to check if the reconstruction works
and if the reconstructed code actually compile()
's. But I already knew that.
Second, this assumes that you have the remote machine already in the known_hosts
file. Third, I'm importing things from ast
specifically for reconstructing the
parsed code (ast.dump (p)
returns "Module(body=[Print(dest=None, values=[Str(s='yes!')], nl=True)])"
).
I hadn't checked yet, but somehow from ast import *
is not enough. Last, the
transfered code is simple enough, makes no references to local or remote variables
(for whichever definition or local and remote; I will have to be consistent in the
future when using those words), nor references other modules, present or not in
the remote machine (there, remote is the machine mentioned in the parameter of
ssh()
[^3]3)1. But this is a promising step.
Another thing to notice is that the code is sent via stdin
. This might cause
trouble with script expecting things that way, let's see:
import paramiko import ast import pickle p= ast.parse ('foo= raw_input (); print (foo)') pick= pickle.dumps (p) c= paramiko.SSHClient() c.load_host_keys ('/home/mdione/.ssh/known_hosts') c.connect ('localhost', allow_agent=False, password='foobarbaz') command= '''python -c "import pickle from ast import Print, Module, Str, Assign, Name, Call, Load, Store, dump import sys c= pickle.loads (sys.stdin.read (%d)) code= compile (c, 'remote', 'exec') exec (code)"''' % len (pick) (i, o, e)= c.exec_command (command) i.write (pick) i.write ('bar!\n') o.readline ()
This works, but only after someone tells you that you should use raw_input()
instead of input()
, which triggers the realization that you're reading Python3's
doc but using Python2. Damn you, paramiko
!
So, in conclusion, This technique starts to show promise. The good thing about
it is that it barely requires any setup. Future developments could include a ssh
client cache. The next step is to get the variables in the remote machine and
gluing it with the previous developments.