Работа с ssh в Python

Всем добрый день.

Хочу рассказать про paramiko — модуль для работы с ssh в python.
С его помощью можно написать скрипт, который получит доступ к удаленному серверу (или многим) и что-то на нем сделает.

Кому интересно — прошу под кат.

Достаточно часто на работе требовалось выполнить очень однотипные действия на серверах клиентов. Действия пустяковые, наподобие «исправить строчку №12 в конфигурационном файле» или «заменить файл „version_017“ на „version_018“. Это было легко, пока серверов не накопилось *надцать штук. Также подобные задачи надоедали очень быстро, поэтому подобную работу старались поручить новичкам с формулировкой „для приобретения навыков работы с ssh“.

Поначалу самые простые задачи можно было решить стандартными средствами ssh — копированием файла и удаленным исполнением кода. Также пробовали использовать утилиту empty .

Я в то время как раз начинал учить python, решил посмотреть, что в нем есть для этих целей. Гугл услужливо подсказал про paramiko.

Paramiko (комбинация слов языка есперанто „параноик“ и „друг“ — »paranoja" + «amiko») — это модуль для python версии 2.3 и выше, который реализует ssh2 протокол для защищенного (с шифрованием и аутентификацией) соединения с удаленным компьютером. При подключении предоставляется высокоуровневое API для работы с ssh — создается обьект SSHClient. Для большего контроля можно передать сокет (или подобный обьект) классу Transport и работать с удаленным хостом в качестве сервера или клиента. Клиенту для аутентификации можно использовать пароль или приватный ключ и проверку ключа сервера.

Небольшой пример использования этого модуля. Комментарии будут ниже.

import paramiko 

host = '192.168.0.8'
user = 'login'
secret = 'password'
port = 22

client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(hostname=host, username=user, password=secret, port=port)
stdin, stdout, stderr = client.exec_command('ls -l')
data = stdout.read() + stderr.read()
client.close()



Получить данные для доступа к удаленному серверу можно любым удобным способом — прописать напрямую в коде, вынести в конфиг, базу данных и т.д. Очевидно, что если хостов несколько, то для их обхода нужно делать цикл.

Основным классом для подключения и удаленной работы является SSHClient. Он предоставляет нам «сессию» с которой мы можем работать далее.

В строчке client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) мы добавляем ключ сервера в список известных хостов — файл .ssh/known_hosts. Если при соединении с сервером ключа в нем не найдено, то по умолчанию ключ «отбивается» и вызввается SSHException.

Для соединения с сервером используем client.connect(). Авторизироваться можно как по комбинации логин-пароль так и по ключам. При соединении можно задать хост, имя пользователя, пароль, порт, ключ, и прочие параметры.

Строка client.exec_command('ls -l') — выполняет команду на удаленном сервере. Потоки ввода-вывода программы возврашщаются в файлообразные обьекты — stdin, stdout, stderr.

Возможно, я недоразобрался, но у меня не получилось получить права рута на уделенном сервере. Буду благодарен, если мне подскажут, как это сделать. Для выполнения действий с правами суперпользователя делал так:

stdin, stdout, stderr = ssh.exec_command('sudo -S rm *')
stdin.write('password' + '\n')
stdin.flush()


UPD: meph1st0 в комментариях предложил для этих целей воспользоваться классом Channel

import paramiko

client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(...)

channel = сlient.get_transport().open_session()
channel.get_pty()
channel.settimeout(5)
channel.exec_command('sudo ls')
channel.send(password+'\n')
print channel.recv(1024)

channel.close()
client.close()


Для передачи файлов по sftp можно использовать класс Transport. Для непосредственной передачи файлов используются команды put и get.

host = "example.com"
port = 22
transport = paramiko.Transport((host, port))
transport.connect(username='login', password='password')
sftp = paramiko.SFTPClient.from_transport(transport)

remotepath = '/path/to/remote/file.py'
localpath = '/path/to/local/file.py'

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

sftp.close()
transport.close()


Вкратце всё. Сайт проекта и документация — http://www.lag.net/paramiko/

UPD. В комметариях подсказали, что есть ряд других библиотек инструментов, которые также можно использовать для работы по ssh решения подобных задач. Из перечисленного: fabric, chef, puppet, libbsh2 (pylibbsh2), exscript и net-ssh-telnet на руби. Всем комментаторам спасибо.
Share post

Similar posts

Comments 15

    +3
    «исправить строчку №12 в конфигурационном файле» или «заменить файл „version_017“ на „version_018“
    звучит как «нам нужна VCS» =)

    client.connect(hostname='192.168.0.8', username='login', password='password'
    вот прям так у вас в скрипте и лежит?
      0
      подправил, спасибо.
      0
      Спасибо за статью. Мои 5 копеек: насколько я знаю, версии paramiko для python3 еще нет (и не известно, когда будет). Есть только несколько сторонних давно заброшенных портов.
        +5
        Попробуйте fabric. Он кстати внутри тоже paramiko использует.
          0
          уже не использует, они на pypi.python.org/pypi/ssh перешли (будет в след версии), теперь конфиги ssh не игнорятся
          +2
          Статья практически туториал по парамико…

          Для целей: «поменять 3 строчки» используйте fabric, быстрее, проще, надежнее

            +1
            Сам использую paramico, довльно удобно. Но для описавыемых вами задач менеджмента большого парка серверов («исправить строчку №12 в конфигурационном файле» или «заменить файл „version_017“ на „version_018“.) лучше использовать chef или puppet.
              0
              libbsh2 биндинговые либы намного стабильнее имхо.
              Пробовал и paramiko, и pylibssh2
              • UFO just landed and posted this here
                  0
                  И это правильно, и это красиво.
                  Но где шагов побольше и нужно какая-то логика, я применяю библиотеку net-ssh-telnet на руби. Приведу здесь же для коллекции и сравнения.

                  require 'net/ssh'
                  require 'net/ssh/telnet'
                  s = Net::SSH.start('hostname', 'user', :password => 'secret')
                  t = Net::SSH::Telnet.new("Session" => s)
                  
                  t.cmd("su - another_user") # можно переключиться на другой аккаунт
                  puts t.cmd("ls -l")  # и вывести stdout удаленной команды
                  


                  Хотя в целом это оболочка для упрощения net-ssh. Основной пакет поддерживает и scp, и sftp, и проброску туннелей, и вообще все что угодно.
                  0
                  Лично мне fabric не подошел, когда нужно было автоматизировать обновлений на сетевых устройствах cisco.
                  Пока остановился на exscript-е github.com/knipknap/exscript
                  Но его еще нужно доводить до ума, чтобы было можно работать с Cisco ASA например. (или с чем другим, где нет стандартного shell-а или стандартного приглашения)
                    0
                    > Возможно, я недоразобрался, но у меня не получилось получить права рута на уделенном сервере.

                    Не работать может по нескольким причинам, вероятно stderr.read() вернет 'sudo: no tty present and no askpass program specified\n', т.е. нужен псевдотерминал.

                    У SSHClient нет (или я плохо искал?) возможности получить pty, но можно воспользоваться классом Channel, экземпляр которого легко получить из SSHClient. Код ниже иллюстрирует данный способ:

                    import paramiko
                    client = paramiko.SSHClient()
                    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
                    client.connect(...)
                    channel = сlient.get_transport().open_session()
                    channel.get_pty()
                    channel.settimeout(5)
                    channel.exec_command('sudo ls')
                    channel.send(password+'\n')
                    print channel.recv(1024)
                    channel.close()
                    client.close()

                    Код отлаживал в терминале, кажется ничего не пропустил при копировании. В реальных условиях нужно смотреть, что sudo отдает в stdin и stderr и писать в stdin только в ответ на «вопросы».
                      0
                      добавил Ваш пример в топик.
                      0
                      Я бы не назвал chef и puppet библиотеками для работы по ssh :)
                        0
                        А еще на основе paramiko есть библиотечка для создания SSH тоннелей

                        Например:
                        pip install sshtunnel
                        python -m sshtunnel -U vagrant -P vagrant -L :3306 -R 127.0.0.1:3306 -p 2222 localhost
                        

                        Only users with full accounts can post comments. Log in, please.