Secure copying file using Paramiko Зачастую возникает задача копирован

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
Secure copying file using Paramiko
==================================
Зачастую возникает задача копирования файлов между хостами. Если вы пишите
шелл-скрипт, то чаще всего эта задача решается при помощи `sftp` либо `rsync`.
Для rsync я не встречал хорошего Python-интсрумента, а вот для sftp (и ssh)
есть отличная библиотека [paramiko](http://www.lag.net/paramiko/). Сегодня
я расскажу как безопасно копировать файлы через
[sftp](http://en.wikipedia.org/wiki/SSH_file_transfer_protocol) при помощи
paramiko.
ssh хорошо спроектированный многоуровневый инструмент, поэтому для эффективной
работы не плохо бы представлять хотя бы в общих чертах, как оно работает.
Про что я буду рассказывать: как скопировать файлы с удаленного хоста.
Порядок действий примерно таков:
1. **Проверить, что хост, к которому мы подключаемся тот, за который себя
выдает.** Дело в том, что при первом соединении по ssh, ssh-клиент показывает
вам отпечаток (fingerprint) открытого ключа сервера. По-хорошему, вы должны
по другому каналу связи выяснить у администратора, действительно ли такой
отпечаток у удаленного хоста. В дальнейшем, ssh при каждом коннекте
запрашивает ключ удаленной стороны и *сравнивает с уже сохраненным*. Обычно
список сохраненных открытых ключей хранится в `~/.ssh/known_hosts`.
2. **Осуществить аутентификацию по ключу.** ssh подерживает различные
механизмы аутентификации. Нас интересует
[аутентификация по ключу](http://www.openbsd.ru/docs/ssh.html). Т.е. создается
пара ключей: открытый и закрытый -- открытый ключ помещается на удаленную
сторону, закрытый -- хранится на на нашей стороне (обычно -- в `~/.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`),
и загрузить ключ:
import os
import paramiko
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, если не удается -- как RSA, ну а уж если и
так не получается, то тогда всё же вызывать исключение.
### Копируем файлы
Теперь собираем всё вместе и копируем файлы с удаленной стороны:
import paramiko
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)
return sftp.open(path)
А дальше, работаем как с обычным открытым на чтение файлом...
Это малая часть того, что умеет Paramiko. Надеюсь, я привлек ваше внимание к
этой отличной библиотеке и вы запишите <http://www.lag.net/paramiko/> в свои
закладки ;-)