ssh via python subprocess: how to bail if fingerprint absent (Are you sure you want to continue connecting?)

244
January 27, 2018, at 02:43 AM

I'm using a python script to manage ssh fingerprint problems after a workstation(s) is reimaged.

I attempt to connect, and if I get a "REMOTE HOST IDENTIFICATION HAS CHANGED!" error, then script removes the old fingerprint, scans for the new one and adds it.

This all works great, until I get a message like this:

 Warning: the ECDSA host key for 'workstation-1-s' differs from the key for the IP address '192.168.1.132'
Offending key for IP in /home/me/.ssh/known_hosts:16
Matching host key in /home/me/.ssh/known_hosts:60
Are you sure you want to continue connecting (yes/no)?

The script waits for user input before continuing and removing the offending key.

How can I get the script to push through, or enter "no" so the script can continue with its fingerprint repair job?

Here's the relevant method:

def ssh_fingerprint_changed(node):
    """
    Checks if a node's ssh fingerprint has changed or an old key is found, which can occur when a node is reimaged.
    It does this by attempting to connect via ssh and inspecting stdout for an error message.
    :param node: the ip or hostname of the node
    :return: True if the node's fingerprint doesn't match the client's records. Else False.
    """
    cmd = ["ssh", "-q", ADMIN_USER + "@" + node, "exit"]
    completed = subprocess.run(cmd, stdout=subprocess.PIPE, universal_newlines=True)
    if completed.stdout.find("REMOTE HOST IDENTIFICATION HAS CHANGED!") == -1:
        print("REMOTE HOST IDENTIFICATION HAS CHANGED!")
        return True
    elif completed.stdout.find("Offending key") == -1:
        print("Offending key found.") # need to type "no" before this prints
        return True
    return False
Answer 1

run (or legacy call) doesn't allow your controlling of the input/output of the process interactively. When you get the output, the process has already ended. So you're too late for the party.

Some would direct you to pexpect, or paramiko (which doesn't require calling ssh command).

Here's a workaround with Popen. I dropped your return logic. If you want to keep that, remember that at this point the process is still running, so you have to kill it (or wait for it to complete):

cmd = ["ssh", "-q", ADMIN_USER + "@" + node, "exit"]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True)
# loop on lines
for l in p.stdout:
    if b"Offending key" in l:
        print("Offending key found.")
        p.stdin.write(b"no\n")   # provide no + newline as the answer
rc = p.wait()  # wait for process to end, get return code

if you're sure that the only answer will be "no", and a given number of times, an alternative to the loop would be

out,err = p.communicate(b"no\n"*10)  # send 10 times no+linefeed

note the "b" prefix when scanning strings/writing data, as standard input/output/error are binary. Doesn't matter in python 2, but in python 3, omitting the b compares strings with bytes, and you'll never get a match.

Aside, I've done that with plink on Windows, but after a while, I got tired and rebuilt a version of plink with all security messages disabled/defaulting to the "optimistic" value. If the network is a company network behind firewalls and you're going to answer anything to get pass those prompts, better create a non-interactive tool from the start.

Rent Charter Buses Company
READ ALSO
Is there any way to import a png into python using only modules that come with python 3?

Is there any way to import a png into python using only modules that come with python 3?

I want to import a png using tkinterI have seen many tutorials to use PhotoImage but it does not accept the

173
python how do i put a variable in command string

python how do i put a variable in command string

I'm new to python and cannot figure out how to do the followingI want to put a variable inside a command but its not working

181
Global Name 'line' not defined [on hold]

Global Name 'line' not defined [on hold]

I am writing a function to read data from files in the given directory but running into scoping issues

241
How to get rid of the original yticks/yticklabels when I use ax.set_yscale('log')

How to get rid of the original yticks/yticklabels when I use ax.set_yscale('log')

I am plotting some values in a semilogy plotI need to use the

204