0

I have a Python script that uses pysftp to create an SFTP connection to a remote host as shown:

 cnopts = pysftp.CnOpts(knownhosts='known_hosts') # REFERENCE A STATIC FILE
 sftp = pysftp.Connection(host=parm0, username=parm1, password=parm2, cnopts=cnopts)

The file "known_hosts" lives in the same directory where the Python script lives. I would like to eliminate the static know_hosts file and pass a file like object to pysftp.CnOpts() like this:

 remote_public_key = subprocess.getoutput('ssh-keyscan myRemoteSFTPServer.com')
 key_buffer.write(remote_public_key)
 key_buffer.seek(0)
 cnopts = pysftp.CnOpts(knownhosts=key_buffer.read()) # REFERENCE A FILE LIKE OBJECT
 sftp = pysftp.Connection(host=parm0, username=parm1, password=parm2, cnopts=cnopts)     
 

However, this fails. Is it possible to pass a file like object as a parameter to pysftp.CnOpts, and if so, how do I do it?

  • 2
    See [Verify host key with pysftp](https://stackoverflow.com/q/38939454/850848), particularly the section of my answer starting *"If you do not want to use an external file"* + For new development avoid using abandoned pysftp. Use Paramiko directly. – Martin Prikryl Feb 23 '22 at 21:23
  • 1
    Though verifying the key using a result of `ssh-keyscan` is pointless. It's as bad as blindly accepting any key. This is just wrong. – Martin Prikryl Feb 23 '22 at 21:25

2 Answers2

-1

Did you try

cnopts = pysftp.CnOpts(knownhosts=key_buffer)

to just pass in the file-like object? If you do key_buffer.read(), that'd be equivalent to

cnopts = pysftp.CnOpts(knownhosts="# 127.0.0.1:22 SSH-2.0-OpenSSH_8.1...")

or whatever is in the buffer, and pysftp would likely try to interpret that as a filename.

Failing that,

with tempfile.NamedTemporaryFile("wb") as key_buffer:
    remote_public_key = subprocess.getoutput('ssh-keyscan myRemoteSFTPServer.com')
    key_buffer.write(remote_public_key)
    key_buffer.seek(0)
    cnopts = pysftp.CnOpts(knownhosts=key_buffer.name)
    sftp = pysftp.Connection(host=parm0, username=parm1, password=parm2, cnopts=cnopts)     

(NamedTemporaryFiles are automagically deleted at the end of the with block.)

AKX
  • 152,115
  • 15
  • 115
  • 172
-1

Thanks @AKX. I ended up rewriting my Python script to use Paramiko instead of pysftp and have a working solution. First I created a function called get_remote Server_key to generate an SSH key. Then I called the add_server_key(pkey) method of the Paramiko Transport object. This way I don't have to pass a file like object.

def get_remote_server_key(ip: str, port: int=22) -> paramiko.pkey.PKey:
    # Returns PKey for given server
    # :param ip: IP or Hostname
    # :param port: Port
    # :return: Returns PKey
    my_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    my_socket.settimeout(10)
    my_socket.connect((ip, port))
    my_transport = paramiko.Transport(my_socket)
    my_transport.start_client()
    ssh_key = my_transport.get_remote_server_key()
    my_transport.close()
    my_socket.close()
    return ssh_key 

def getSFTPConnection(server, port, uid, pw):
    client = paramiko.SSHClient()
    **pkey = get_remote_server_key(server,port)**
    trans = paramiko.Transport((server, port))
    **trans.add_server_key(pkey)**
    trans.connect(username=uid, password=pw)
    sftp = paramiko.SFTPClient.from_transport(trans)
    return sftp

sftp=getSFTPConnection(server, port, uid, password)
  • 1
    This is just complicated and inefficient way to do the same unsafe thing as `cnopts.hostkeys = None` does. This is wrong and I've told you so already in a comment to your question. – Martin Prikryl Feb 26 '22 at 10:36
  • @AKX Based on Martin's comment I modified my code to go back to using the local known_hosts file and avoid using ssh-keyscan and eliminated the get_remote_server_key function. – CentennialJim Feb 28 '22 at 16:12
  • 1
    The `get_remote_server_key` is that same what `ssh-keyscan` does. You are basically asking the server – Hey, what's your hotkey? And then you connect to the same server and you believe that you are safe because the hostkey is the same as you got before. That's not secure, that's foolish. You are completely missing the point of host key verification. – Martin Prikryl Feb 28 '22 at 17:47
  • I get it. That's why I responded to AKX saying I removed get_remote_server_key() and the call to ssh-keyscan. I'm now using the Parmiko SSHClient class to load the key from the local known_hosts file by calling client.load_system_host_keys('known_hosts'). I got the key from the SA via encrypted email and added it to the local known_hosts file. – CentennialJim Mar 01 '22 at 20:49
  • The problem was I didn't have access to the known_hosts file programmatically because it is located in a directory that is locked down in an Azure VM environment. Incidentally, I don't appreciate being called foolish or being scolded on a public forum. Everyone makes mistakes when learning, and I'm not your child. Humility makes one great. Arrogance makes him loathed. – CentennialJim Mar 01 '22 at 21:14