How to use ssh-agent in windows in python?

Cameron Simpson cs at cskk.id.au
Wed May 29 18:41:25 EDT 2019


On 29May2019 22:37, Fc Zwtyds <fczwtyds at gmail.com> wrote:
>在 2019-05-27 11:19, Cameron Simpson 写道:
>>The output of "ssh-agent -s" is Bourne shell variable assignment 
>>syntax. You need to parse that [...]
>
>  I want to rewrite the shell script to python script so I have had 
>changed the "ssh-agent -s" to 'ssh-agent cmd' in python script for the 
>consistence on windows.

I'm not sure you need to do that. In fact, I'm fairly sure you don't.

When you run "ssh-agent cmd" the agent starts as before, but instead of 
reporting its communication socket and process id it dispatches command 
and runs for the duration of that command, then exits.

Before ssh-agent dispatches "cmd" (here I mean a generic command, though 
that command might well be "cmd.exe"), it first puts the necessary 
environment variables into the command's environment.

There are two such values: the communication socket and the ssh-agent 
process id. The socket is so that the other ssh commands can talk to it, 
and the process id is so that it can be terminated when no longer 
wanted. For the "ssh-agent cmd" form there's no need to use the process 
id, since ssh-agent itself will exit after "cmd" finishes.

Let's look at what "ssh-agent -s" produces. This is on a UNIX system (my 
Mac):

  [~]fleet*> ssh-agent -s
  SSH_AUTH_SOCK=/Users/cameron/tmp/ssh-vuvXU6vrCAxz/agent.50746; export SSH_AUTH_SOCK;
  SSH_AGENT_PID=50754; export SSH_AGENT_PID;
  echo Agent pid 50754;

You can see the socket path and the process id there. The paths will be 
a bit different on Windows.

So if I do a process listing (this is a UNIX "ps" command, you will need 
to do the equivalent Windows thing) and search for that process id we 
see:

  [~]fleet*1> ps ax | grep 62928
  62928   ??  Ss     0:00.00 ssh-agent -s
  66204 s027  S+     0:00.00 grep 62928

So there's the ssh-agent process and also the "grep" command itself 
because the process id is present on its command line.

Note that at this point my shell (cmd.exe equivalent in Windows) does 
not know about the new ssh-agent. This is because I haven't put those 
environment values into the shell environment: the output above is just 
written to the display. So when I go:

  [~]fleet*> ssh-add -l

it shows me 4 ssh keys. This is because I already have an agent which 
has some keys loaded. If my shell were talking to the new agent the list 
would be empty. Let's do that.

  [~]fleet*> SSH_AUTH_SOCK=/Users/cameron/tmp/ssh-vuvXU6vrCAxz/agent.50746
  [~]fleet*> SSH_AGENT_PID=50754
  [~]fleet*> export SSH_AUTH_SOCK SSH_AGENT_PID
  [~]fleet*> ssh-add -l
  The agent has no identities.

So now this shell is using the new agent. You see there's no magic here: 
other commands do not know about the agent until we tell then about it 
using these environment variables.

What I was suggesting is that you perform this process from Python, 
which I believe was your original plan.

So let's adapt the the comand you presented below, and also dig into why 
what you've tried hasn't worked. So, your subprocess call:

  import subprocess
  p = subprocess.Popen('cmd', stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = True, universal_newlines = True)
  outinfo, errinfo = p.communicate('ssh-agent cmd\n')
  print('outinfo is \n %s' % stdoutinfo)
  print('errinfo is \n %s' % stderrinfo)

This does the follow things:

1: Start a subprocess running "cmd". You get back a Popen object "p" for 
use with that process.

2: Run p.communicate("ssh-agent cmd\n"). This sends that to the input of 
the "cmd" subprocess and collects the output.

An important thing to know here is the .communicate is a complete 
interaction with the subprocess. The subprocess' _entire_ input is the 
string you've supplied, and the entire output of the subprocess until it 
completes is collected.

So, what's going on is this:

1: start "cmd"
2: send "ssh-agent cmd\n" to it.
3: collect output and wait for it to exit.

From "cmd"'s point of view:

1: Receive "ssh-agent cmd\n", causing it to run the "ssh-=agent cmd" 
command and then wait for it to exit.

2: ssh-agent starts and then runs another "cmd" and waits for _that_ to 
exit.

3: The second "cmd" processes its input, then exits. Note that becuase 
the input to the entire subprocess was "ssh-agent cmd\n", and the first 
"cmd" consumed that, there is no no more input data. So the second cmd 
exits immediately because there is no input.

4: The ssh-agent exist because the second "cmd" exited.

5: The first "cmd" sees the "ssh-agent cmd" command exit and looks for
another command to run from its input.

6: As before, there are no more input data. So the first "cmd" also 
exits.

And at that point .communicate sees the end of the output and returns 
it.

Because the subprocess is complete, your commented out "p.write" call 
fails. You can't do things that way with .communicate.

There are complicated (and error prone) ways to do more incremental 
input, not using .communicate. Let us not go there for now.

Instead, let's consider using communicate with your original plan, which 
was to reproduce a script when went:

  ssh-agent -s
  ssh-add id_rsa
  ... more ssh based commands here ...

You were using os.system(), which just dispatches a command and doesn't 
do anything about its input or output. We want to use .communicate 
because it lets us collect the agent information.

The important thing to note here is that the _only_ extra information 
the commands after "ssh-agent" require is the environment variables.

So you can go:

1: Dispatch "ssh-agent -s" with subprocess and use .communicate to 
collect the output.

2: Parse the environment values out of the output and install them in 
Python's os.environ dictionary.

3: Run your other ssh commands. because os.environ has the necessary 
values in it, they will communicate with the agent, which is still 
sitting around.

4: When finished, kill the agent to tidy up.

So start with this:

  p = subprocess.Popen('ssh-agent -s', stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = True, universal_newlines = True)
  outinfo, errinfo = p.communicate('ssh-agent cmd\n')

You should get the output from ssh-agent in "outinfo". Remember, it is a 
single string looking like this:

  SSH_AUTH_SOCK=/Users/cameron/tmp/ssh-vuvXU6vrCAxz/agent.50746; export SSH_AUTH_SOCK;
  SSH_AGENT_PID=50754; export SSH_AGENT_PID;
  echo Agent pid 50754;

Obviously the specific paths and numbers will vary.

So you want to parse that. Untested code:

  import os

  # break the output data into lines
  lines = outinfo.split('\n')
  for line in lines:
    # trim leading and trailing whitespace
    line = line.strip()
    # ignore blank/empty lines
    if not line:
      continue
    # break off the part before the semicolon
    left, right = line.split(';', 1)
    if '=' in left:
      # get variable and value, put into os.environ
      varname, varvalue = left.split('=', 1)
      print("got", varname, "=', "varvalue)
      os.environ[varname]=varvalue

The you could just use os.system() to run the other commands, because 
the environment now has the necessary environment settings.

See how you go.

Cheers,
Cameron Simpson <cs at cskk.id.au> (formerly cs at zip.com.au)


More information about the Python-list mailing list