pysftp throwing paramiko.ssh_exception.SSHException even though known_hosts file provided?

2.3k views Asked by At

Getting error

paramiko.ssh_exception.SSHException: No hostkey for host target.org found.

when using pysftp (for a connection that requires a specific port), even though I am providing the same known_hosts file that was initially used to connect to that location (following the post here). Ie. did...

[airflow@airflowetl reporting]$ sftp -oPort=15259 [email protected]
The authenticity of host '[target.org]:15259 ([205.172.2.88]:15259)' can't be established.
RSA key fingerprint is SHA256:UM6OflG0rkcYohes7qOlYoJZ4TIqVd0JQSh7HXYZQVA.
RSA key fingerprint is MD5:33:c2:30:22:57:5b:57:98:2f:11:07:4d:a3:4a:10:0f.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[target.org]:15259,[205.172.2.88]:15259' (RSA) to the list of known hosts.
password
Enter password for my_user
Password:
Connected to target.org.
sftp> ls
csv_drop        test_results    
sftp> exit

and then copied the /home/me/.ssh/known_hosts to another location

[airflow@airflowetl .ssh]$ ls -lha
total 8.0K
drwx------   2 airflow airflows   25 Oct 19 17:31 .
drwx------. 32 airflow airflows 4.0K Oct 19 17:31 ..
-rw-r--r--   1 airflow airflows  777 Oct 19 17:32 known_hosts
[airflow@airflowetl .ssh]$ ls -lha /home/airflow/projects/backups/reporting/configs/known_hosts 
-rw-r--r-- 1 airflow airflows 777 Oct 19 17:34 /home/airflow/projects/backups/reporting/configs/known_hosts

(noting that the permissions are the name for the original and copy) that gets used like...


# connect to ftp location
assert os.path.isfile(os.path.join(PROJECT_HOME, "configs", "known_hosts"))
cnopts = pysftp.CnOpts(knownhosts=os.path.join(PROJECT_HOME, "configs", "known_hosts"))
#cnopts = pysftp.CnOpts(knownhosts="/home/airflow/.ssh/known_hosts")
HOSTNAME = "target.org"
PORT = 15259
sftp = pysftp.Connection(HOSTNAME,
                         port=PORT,
                         username=CREDS["sink"]["ftp_creds"]["username"],
                         password=CREDS["sink"]["ftp_creds"]["password"],
                         cnopts=cnopts)
print(sftp.pwd())
print(sftp.listdir())

sftp.cwd(CONF["reporting_configs"]["to"]["share_folder_path"])
print(sftp.pwd())
print(sftp.listdir())

Yet, when running the script I get the error saying it could not find the hostkey (Note that even using the original known_hosts file path throws the same error (and happens whether using hostname or the IP)). What could be causing this? Any more debugging steps I could try to get more info? Don't have much experience with this kind of thing.

Looking in the debugger while running, I see that the cnopts hostkeys entries does seem to contain the right hostkey...

<HostKeyEntry ['[target.org]:15259', '[205.172.2.88]:15259']: <paramiko.rsakey.RSAKey object at 0x7f8d752d4208>>

Note that the hostkey entry is '[target.org]:15259' (ie. it is combining the specified port), even though the name provided as the service name to the connection function is just 'target.org'

The full traceback looks like...

Traceback (most recent call last):
  File "./source/local2ftp.2.py", line 52, in <module>
    cnopts=cnopts)
  File "/home/airflow/projects/backups/reporting/venv/lib/python3.6/site-packages/pysftp/__init__.py", line 132, in __init__
    self._tconnect['hostkey'] = self._cnopts.get_hostkey(host)
  File "/home/airflow/projects/backups/reporting/venv/lib/python3.6/site-packages/pysftp/__init__.py", line 71, in get_hostkey
    raise SSHException("No hostkey for host %s found." % host)
paramiko.ssh_exception.SSHException: No hostkey for host target.org found.
Exception ignored in: <bound method Connection.__del__ of <pysftp.Connection object at 0x7f1a61df6f98>>
Traceback (most recent call last):
  File "/home/airflow/projects/backups/reporting/venv/lib/python3.6/site-packages/pysftp/__init__.py", line 1013, in __del__
    self.close()
  File "/home/airflow/projects/backups/reporting/venv/lib/python3.6/site-packages/pysftp/__init__.py", line 784, in close
    if self._sftp_live:
AttributeError: 'Connection' object has no attribute '_sftp_live'

Note that the user I used to initially connect via sftp in the command line and am using in the pysftp script is not the user running the script (the user I used to connect to the sftp server is a special set of credentials for connecting). I don't know if this is relevant.

2

There are 2 answers

0
lampShadesDrifter On BEST ANSWER

TLDR: The code expects the pystfp.Connection hostname to match that in the hostkey file (eg. ~/.ssh/known_hosts), but if the rsa entry in the hostkey file entry was created with a connection that specified the port and thus caused the rsa entry in known_hosts to be formatted in a way that pysftp could not understand / handle,

like...

[airflow@airflowetl reporting]$ sftp -oPort=15259 [email protected]

The rsa entry that gets created looks like...

'[target.org]:15259,[205.172.2.88]:15259 ssh-rsa AAAAB3HJGVJGCTCKHGVYTVKUH===...'

so when you use this hostkey file for the knownhosts in...

cnopts = pysftp.CnOpts(knownhosts=os.path.join(path, to, hostkey, or, known_hosts, file))
hostname = "target.org"
port = 15259
sftp = pysftp.Connection(hostname, port, username=user, password=pass, cnopts=cnopts)

the code ends up checking the hostkey file for an entry that looks like

"target.org"

(what you entered as the hostname) but only finds

"[target.org]:15259"

(the entry in the hostkey / known_hosts file formatted for the specific port you connected on) so throws an error.

Note also that you can't just use "[target.org]:15259" as the hostname for the pysftp.Connection() function either just to get it to match when the pysftp code does this internal check because it will say that it does not recognize the service name since (apparently) that's not a valid servicename representation (ie. it won't just know to separate that string into the hostname and port components).

What I had to do was...

  • Copy the known_hosts file that was created when I initially connected to the target.org host
  • then in that file, manually edit it to look like
'target.org,205.172.2.88 ssh-rsa AAAAB3HJGVJGCTCKHGVYTVKUH===...'

Specifically in the code, the pysftp.Connection() function...

  1. Tries to get the hostkey
  2. and uses the underlying Paraminko module to try to find the hostkey entry from the knownhosts file given in the cnopt arg
  3. which uses the function here to match the string literal entries from the hostkey file with the hostname that you entered as the pysftp.Connection arg (which, if you connected to using a specific port, created an rsa entry that when taken as a literal is not formatted the way you likely are passing in the hostname arg and is not even a valid host name), thus confusing the matching logic and causing it to not find any matches
  4. thus causing an exception to be raised, here back in the pysftp code
1
Martin Prikryl On

The pysftp does not support the host key entries with the port.

  • Either skip pysftp and use Paramiko directly. See pysftp vs. Paramiko.
  • Or hack it by replacing the [target.org]:15259 with target.org in the known_hosts file.