Closed lastochka364 closed 1 year ago
Hello...
I've got a suggestion for how you might make this work in a pretty simple way, but first let me cover some of your initial questions:
The error "Inappropriate ioctl for device" is definitely because you didn't allocate a pty and it wasn't to be able to blank our the entry of the passphrase. However, an easy workaround for this is to specify "--new-passphrase" as an argument to puttygen.. In fact, you can even specify "--new-passphrase /dev/stdin" and still feed the passphrase to stdin, and that works without having a pty, and also avoids needing to specify the passphrase twice.
Most of the rest of the issues you are running into sound like the kinds of things you'll run into when trying to do interactive I/O with a shell, rather than specifying the command to run when you start up the session. It's hard to know when a command finishes executing in this case, unless you send something like "exit" to the shell after you run puttygen. However, there are easier ways to deal with this, at least in this case.
Basically, the key points are to pass the command to run when calling run()
instead of trying to use a shell, and to pass in the passphrase without puttygen prompting for it. Here's an example which runs the command and outputs the "ls" output on the resulting file:
import asyncio, asyncssh
async def run_puttygen(host, key_file, passphrase, opts):
async with asyncssh.connect(host) as conn:
result = await conn.run(f'puttygen {opts} -o {key_file} --new-passphrase /dev/stdin; '
f'ls -l {key_file}', input=passphrase)
return result.stdout
print(asyncio.run(run_puttygen('localhost', '~/my_key.ppk', 'qwe', '-t rsa -b 4096')), end='')
As you can see, you can pass in multiple commands to execute sequentially without resorting to having to send the commands as input to an interactive shell. This also allows you to use the input=
to pass in a passphrase, and by telling puttygen to get the passphrase from /dev/stdin, it avoids needing a pty and avoids needing to output the passphrase multiple times or add newlines to it.
You can tweak the set of commands to run if you want to do something other than get the \ls\ output back. Basically, anything you write to stdout will end up being returned by the call to run_puttygen()
.
Because you are running a command, you don't have to worry about the shell never exiting, but as noted above you could have solved that by sending an explicit "exit" command as well.
As for term_type
, it doesn't really matter what you set it to in this case, as you're not running something like a curses application or other program which relies on sending terminal escape sequences. A good generic value is 'ansi'. That said, the only point of setting term_type
here would be to avoid needing the request_pty='force'
. In my version, you don't need either of these, though.
By the way, if you want output from both stdout and stderr to use in error handling, you could modify run_puttygen()
to do something like return result.stdout, result.stderr
to get back a tuple of both stdout and stderr output. You could also return the result
object, which has a bunch of other information about the result of running the commands. See the SSHCompletedProcess class for details.
Just tested it - works. I haven't seen anything about --new-passphrase arg in puttygen when was googling my problem, just being too focused on pty "issue".
Seems like I need to recall the KISS. Thanks for your time and such quick response.
Hello to everyone who is reading this!
First of all, I want to thank @ronf for such an amazing project, as asyncssh! All your work is an unrealistic scale for me, but I'm still very pleasured in using this tool, and just to say - it's very well-documented, special thanks for this! :)
I will use the third person form of communication, as I believe that the application described here, and hopefully, the solutions to the problems may be useful for others. Warning: in this issue will be many questions, and even more code, but I'll try my best to fully describe the problems I encountered and also highlight them all.
Task and problems
Task: generate an SSH key on a remote host using the
puttygen
command.Problems:
or in non-interactive, like this
i'm getting the next error for both cases:
of course, both errors are going to stderr.
request_pty='force'
andterm_type='ansi'
,I encounter two problems:
ls -l ~
:process.write_eof()
working fine for these cases, but not for pty mode. Instead ofwrite_eof()
I need to catch anTimeoutError()
exception in try-except-else-finally clause and then close the session.I also want to mention two important things:
I have already implemented .ppk key generation, but with ParallelSSH. It also supports pty, but I assume that the core of that library just works in some another way: it uses greenlets, python threads and just different command(s) execution handling.
I found a clue to use pty in these stackoverflow Q&A's: question and answer, pointed from previous question.
Specifications
Now, let's move to the deep describing of the issues. I will start with some requiered specifications and then move on to my code snippets to demonstate what logic I'm trying to imlement, of course adding some explanations and comments too.
pip freeze
...Code snippets and main ideas
The main idea is to implement 5 general approaches for handling the next cases:
SSHClient.__init__()
;ssh-keygen
) with any of the logic above.Now, the code itself.
Just the __init__ with fields, some of their purpose is described above.
Next, let's see the
_try_to_connect()
method.I assume that this method will try to connect to the host, specified in classes fields, and handle all the possible raises of errors from asyncssh module, and then re-raise them to next method - execute():
This is the main nethod that I'm using to execute command(s) on host and get some output, of course.
Unit-testing and deep problem explanation
Now, let's reproduce the problem itself: we will execute some simple unit-tests that I prepared.
I dropped some of tests to reduce the code filling in the issue, but such tests as
test_execute_with_sudo()
,test_execute_with_user_to_exec()
andtest_execute_with_user_to_exec_and_sudo()
are correctly passing forTestSSH
andTestInteractiveSSH
cases.Now, if we execute all of this bunch of code with
python -m unittest tests.test_base_ssh
, the next output will arrive:So, as we can see, I managed to handle the
puttygen
command execution inTestPtySSH.test_execute_puttygen()
withassertIsInstance()
, pointing it toTimeoutError()
. This demonstrates that shell itself is never closed only if the specified timeout is not reached inprocess.wait(timeout)
. We also can mention that the key itself was not generated (or even just not saved, I assume).Thoughts
puttygen
takes some time first before prompting me to input the passphrase. This time is different at every execution, and I think it's because of randomized key generation. So, maybe I just need to wait before the program is done and prompts me to enter the data? But I just have no idea how to implement that waiting...Questions
puttygen
command execution correctly?term_type
arg. Watching tests and sources of asyncssh I still could neither understand what types of these are actual can be useful for me, nor what the difference between them.