103

How I can make SFTP transport through SSHClient on the remote server? I have a local host and two remote hosts. Remote hosts are backup server and web server. I need to find on backup server necessary backup file and put it on web server over SFTP. How can I make Paramiko's SFTP transport work with Paramiko's SSHClient?

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
Denis
  • 7,127
  • 8
  • 37
  • 58

5 Answers5

221

paramiko.SFTPClient

Sample Usage:

import paramiko
paramiko.util.log_to_file("paramiko.log")

# Open a transport
host,port = "example.com",22
transport = paramiko.Transport((host,port))

# Auth    
username,password = "bar","foo"
transport.connect(None,username,password)

# Go!    
sftp = paramiko.SFTPClient.from_transport(transport)

# Download
filepath = "/etc/passwd"
localpath = "/home/remotepasswd"
sftp.get(filepath,localpath)

# Upload
filepath = "/home/foo.jpg"
localpath = "/home/pony.jpg"
sftp.put(localpath,filepath)

# Close
if sftp: sftp.close()
if transport: transport.close()
evandrix
  • 6,041
  • 4
  • 27
  • 38
leoluk
  • 12,561
  • 6
  • 44
  • 51
  • 6
    Great answer. I'd add however that both `Transport` and `SFTPClient` implement the `__enter__`/`__exit__` interface thus can be used within a context manager e.g. `with Transport((host, port)) as transport:` – ayorgo Mar 12 '19 at 18:18
  • This implementation works however it does not cleanup processes. sftp-server process is forked with it and if you run it multiple times you can see there are a lot of processes exists after the completion of code. – Saurabh Nemade Jun 16 '20 at 11:27
  • 3
    While this *works*, with its use of the low-level `Transport` class, it bypasses a host key verification, what is a security flaw, as it makes the code susceptible to [Man-in-the-middle attacks](https://en.wikipedia.org/wiki/Man-in-the-middle_attack). Better is to use the right Paramiko SSH API, the `SSHClient`. See [my answer](https://stackoverflow.com/q/3635131/850848#66724076). – Martin Prikryl Mar 20 '21 at 16:44
47

The accepted answer "works". But with its use of the low-level Transport class, it bypasses a host key verification, what is a security flaw, as it makes the code susceptible to Man-in-the-middle attacks.

Better is to use the right Paramiko SSH API, the SSHClient, which does verify the host key:

import paramiko
paramiko.util.log_to_file("paramiko.log")

ssh = paramiko.SSHClient()
ssh.connect(host, username='user', password='password')
# or 
# key = paramiko.RSAKey.from_private_key_file('id_rsa')
# ssh.connect(host, username='user', pkey=key)

sftp = ssh.open_sftp()

sftp.get(remotepath, localpath)
# or
sftp.put(localpath, remotepath)

For details about verifying the host key, see:
Paramiko "Unknown Server"

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
7

If you have a SSHClient, you can also use open_sftp():

import paramiko


# lets say you have SSH client...
client = paramiko.SSHClient()

sftp = client.open_sftp()

# then you can use upload & download as shown above
...
Alon Gouldman
  • 3,025
  • 26
  • 29
  • 3
    First, this is not a standalone answer, but mere comment to @leoluk's answer. Second, if you have `SSHClient`, you can simply do `sftp = client.open_sftp()`. – Martin Prikryl Sep 22 '19 at 15:00
5

In addition to the first answer which is great but depends on username/password, the following shows how to use an ssh key:

from paramiko import Transport, SFTPClient, RSAKey
key = RSAKey(filename='path_to_my_rsakey')
con = Transport('remote_host_name_or_ip', 22)
con.connect(None,username='my_username', pkey=key)
sftp = SFTPClient.from_transport(con)
sftp.listdir(path='.')
David W.
  • 365
  • 5
  • 5
4

For those anyone need to integrate with an ssh/sftp server that requires a private key and want to perform host key verification for the known host by using a specific public key, here is a snippet code with paramiko:

import paramiko

sftp_hostname = "target.hostname.com"
sftp_username = "tartgetHostUsername"
sftp_private_key = "/path/to/private_key_file.pvt"
sftp_private_key_password = "private_key_file_passphrase_if_it_encrypted"
sftp_public_key = "/path/to/public_certified_file.pub"
sftp_port = 22
remote_path = "."
target_local_path = "/path/to/target/folder"

ssh = paramiko.SSHClient()
    
# Load target host public cert for host key verification
ssh.load_host_keys(sftp_public_key)

# Load encrypted private key and ssh connect
key = paramiko.RSAKey.from_private_key_file(sftp_private_key, sftp_private_key_password)
ssh.connect(host=sftp_hostname, port=sftp_port, username=sftp_username, pkey=key)

# Get the sftp connection
sftp_connection = ssh.open_sftp()

directory_list = sftp_connection.listdir(remote_path)

# ...

if sftp_connection: sftp_connection.close()
if ssh: ssh.close()

Notice that only certificates in classic Openssh format are supported, otherwise needs to be converted with the following commands (also for the latest Openssh formats):

$chmod 400 /path/to/private_key_file.pvt
$ssh-keygen -p -f /path/to/private_key_file.pvt -m pem -P <currentPassphrase> -N <newPassphrase>

In order to avoid man in the middle attack, it is important to do not use paramiko.AutoAddPolicy() and load the public host key programmatically as above or load it from ~/.ssh/known_hosts
The file must be in the format "<host_name> ssh-rsa AAAAB3NzaC1yc2EAAAA..."
In case you don't have the public key and you trust the target host (take care to mitm), you can download it using $ssh-keyscan target.hostname.com command.

The above code is the only way I found to avoid the following error during connection:

paramiko.ssh_exception.SSHException: Server 'x.y.z' not found in known_hosts

This error was prompted also with the following way to load the public certificates:

key = paramiko.RSAKey(data=decodebytes(sftp_public_key))
ssh_client.get_host_keys().add(sftp_hostname, 'ssh-rsa', key)

Also the following code was not able for me to load the certificate (tried also by encoding the certificate in base64):

ssh_client=paramiko.SSHClient()

rsa_key = paramiko.RSAKey.from_private_key_file(sftp_private_key, sftp_private_key_password)
rsa_key.load_certificate(sftp_public_key)

It always ends with:

  File "/usr/local/lib/python3.9/site-packages/paramiko/pkey.py", line 720, in from_string
    key_blob = decodebytes(b(fields[1]))
  File "/usr/lib64/python3.9/base64.py", line 538, in decodebytes
    return binascii.a2b_base64(s)
binascii.Error: Incorrect padding

The above code above worked for the SFTP integration with GoAnywhere.

I hope this is helpful, I've not found any working example and spent many hours in searches and tests. The implementations using pysftp wrapper it is now to be considered as discontinued from 2016.

fl4l
  • 1,580
  • 4
  • 21
  • 27