Копирование файлов (paramiko и sftp)

Зачастую возникает задача копирования файлов между хостами. Если вы пишите шелл-скрипт, то чаще всего эта задача решается при помощи sftp либо rsync. Для rsync я не встречал хорошего Python-инструмента, а вот для sftp (и ssh) есть отличная библиотека paramiko. О ней и пойдет сегодня речь.

ssh — это хорошо спроектированный многоуровневый инструмент, поэтому для эффективной работы не плохо бы представлять, хотя бы в общих чертах, как он работает. Про что я буду рассказывать: как скопировать файлы с удаленной машины. Порядок действий примерно таков:

  1. Проверить, что хост, к которому мы подключаемся тот, за который себя выдает. Дело в том, что при первом соединении по ssh, ssh-клиент показывает вам отпечаток (fingerprint) открытого ключа сервера. По-хорошему, вы должны по другому каналу связи выяснить у администратора, действительно ли такой отпечаток у удаленного хоста. В дальнейшем, ssh при каждом коннекте запрашивает ключ удаленной стороны и сравнивает с уже сохраненным. Обычно список сохраненных открытых ключей хранится в ~/.ssh/known_hosts.

  2. Осуществить аутентификацию по ключу. ssh подерживает различные методы аутентификации. Нас интересует аутентификация по ключу. Т.е. создается пара ключей: открытый и закрытый — открытый ключ помещается на удаленную сторону, закрытый — хранится на на нашей стороне (обычно — в ~/.ssh/id_dsa или ~/.ssh/id_rsa, в зависимости от типа ключа — RSA или DSA). В дальнейшем, при соединении ssh не спрашивает пароль аккаунта на удаленной стороне, а проверяет наличие на удаленной стороне открытого ключа, комплементарного локальному закрытому. Если закрытый ключ запаролен — он будет спрашивать пароль закрытого ключа. Но для наших целей хватит и безпарольного ключа ;-)

  3. Далее, установить канал связи и скопировать с удаленного хоста определенные файлы.

Поехали.

Ключ хоста

Итак, нам нужно раздобыть список сохраненных ключей и найти нужный нам ключ по имени хоста:

import os
import paramiko

def get_host_key(host):
    hostkeytype = None
    hostkey = None
    # try to load host key from known hosts
    try:
        host_keys = paramiko.util.load_host_keys(
            os.path.expanduser("~/.ssh/known_hosts"))
    except IOError:
        host_keys = {}
    if host in host_keys:
        hostkeytype = host_keys[host].keys()[0]
        hostkey = host_keys[host][hostkeytype]
    return hostkeytype, hostkey

Здесь мы используем os.path.expanduser для того, чтобы ~ “развернуть” в домашний каталог текущего пользователя. Далее, при помощи paramiko.util.load_host_keys парсим этот файл, и если находим нужный нам хост, узнаем тип его ключа (RSA или DSA) и собственно сам ключ.

Закрытый ключ пользователя

Вторым пунктом стоит аутентификация по ключу. Если нам не указали где искать ключ, мы должны посмотреть в обычном месте (~/.ssh/id_rsa и ~/.ssh/id_dsa) и загрузить ключ:

def get_private_key(keyfile=None):
    key = None
    keytype = None
    if keyfile is None:
        keyfiles = [os.path.expanduser('~/.ssh/id_%s' % keytype)
                    for keytype in ('dsa', 'rsa')]
    else:
        keyfiles = [keyfile,]
    for kf in keyfiles:
        try:
            key = paramiko.RSAKey.from_private_key_file(kf)
            keytype = 'ssh-rsa'
        except (IOError, paramiko.SSHException), e:
            try:
                key = paramiko.DSSKey.from_private_key_file(kf)
                keytype = 'ssh-dsa'
            except (IOError, paramiko.SSHException), e:
                pass
    if key is None:
        raise paramiko.SSHException('No rsa or dsa keys are available')        
return keytype, key

Здесь единственный нюанс — paramiko для RSA и DSA ключей использует отдельные классы, а спрашивать о типе ключа пользователя нам не хочется, так что мы пытаемся вначале загрузить ключ как RSA, если не удается — как DSA, ну а если и так не получается, то тогда всё же вызывать исключение.

Копируем файлы

Теперь собираем всё вместе и копируем файлы с удаленной стороны:

def get_remote_file(user, host, path, pkeyfile=None):
    hostkeytype, hostkey = get_host_key(host)
    userkeytype, userkey = get_private_key(pkeyfile)
    t = paramiko.Transport((host, 22))
    t.connect(hostkey=hostkey, username=user, pkey=userkey)
    sftp = paramiko.SFTPClient.from_transport(t)
    sftp.get(path, os.path.basename(path))
    # вместо sftp.get можно использовать sftp.open,
    # и дальше как с обычным файлом, но только не забудьте
    # закрыть транспорт...
    t.close()

Полный пример, как обычно,

Это малая часть того, что умеет Paramiko. Надеюсь, я привлек ваше внимание к этой отличной библиотеке и вы запишите в свои закладки, или зафолловите robey/paramiko на github ;-)

P.S.Еще есть рецепт 576810, чем то похожий на мой пример, но мне он не нравится, ни стилем, ни кодом.

Подписаться Комментировать

Комментарии

11.08.2009 13:26 phlinten

Спасибо.интересно. Аутентификация проходит успешно, а файл не скачивает — пермишион денайтед, какие права нужны на файл, не соображу, или файл должен принадлежат тому пользователю, от кого запущен скрипт?

И зачем тут: keyfiles = [keyfile,] запятая?)

Форма комментирования для «Копирование файлов (paramiko и sftp)»

Обязательное поле. Не больше 30 символов.

Обязательное поле

Пожалуйста, введите символы, которые вы видите на изображении

11.08.2009 22:50 Юревич Юрий

И зачем тут: keyfiles = [keyfile,] запятая?)

Запятая не обязательна. Это моя привычка.

файл не скачивает — пермишион денайтед, какие права нужны на файл, не соображу, или файл должен принадлежат тому пользователю, от кого запущен скрипт?

  1. на удаленной машине удаленному пользователю нужно прочитать файл
  2. на локальной машине локальному пользователю нужно записать файл