В этом разделе попытаемся подключиться к серверу по ssh и программно ввести пароль.
Примечание. Рекомендуется просто скопировать свой открытый ключ на сервер, чтобы не вводить пароль, но для целей демонстрации, пытаемся ввести пароль.
Чтобы взаимодействовать с процессом, необходимо назначить обратный вызов для STDOUT
. Сигнатура обратного вызова, которую будем использовать, принимает объект queue.Queue
в качестве второго аргумента, который будет использоваться для отправки STDIN
обратно процессу.
from sh import ssh def ssh_interact(line, stdin): line = line.strip() print(line) if line.endswith("password:"): stdin.put("correcthorsebatterystaple") ssh("10.10.10.100", _out=ssh_interact) # amoffat@10.10.10.100's password:
Если запустить код выше (заменив IP-адрес, к которому надо подключиться по SSH), заметите, что внутри обратного вызова ничего не выводится. Проблема связана с буферизацией STDOUT
. По умолчанию модуль sh
использует линейную буферизацию STDOUT
, что означает, что ssh_interact()
будет получать выходные данные только тогда, когда sh
встретит новую строку в выходных данных. Это проблема, потому что запрос пароля не имеет новой строки:
Поскольку новая строка никогда не встречается, в обратный вызов ssh_interact
ничего не отправляется. Итак, нужно изменить буферизацию STDOUT. Сделаем это с помощью специального аргумента _out_bufsize
. Мы установим его в 0 для небуферизованного вывода:
from sh import ssh def ssh_interact(line, stdin): line = line.strip() print(line) if line.endswith("password:"): stdin.put("correcthorsebatterystaple") ssh("10.10.10.100", _out=ssh_interact, _out_bufsize=0) # a # m # o # f # f # a # t # @ # 1 # 0 # . # ... # ... # r # d # :
Если запустить обновленную версию, то вылезет новая проблема. Это связано с тем, что фрагменты STDOUT, которые получает обратный вызов ssh_interact()
, не буферизированы и, следовательно, являются отдельными символами, а не целыми строками. Сейчас необходимо объединить эти посимвольные данные в строку, чтобы проверить, был ли отправлен шаблон 'password:'
, означающий, что SSH готов к вводу.
Было бы разумно инкапсулировать переменную, которую будем использовать для агрегирования, в какое-то замыкание или класс, но для простоты воспользуемся global
:
from sh import ssh import sys aggregated = "" def ssh_interact(char, stdin): global aggregated sys.stdout.write(char.encode()) sys.stdout.flush() aggregated += char if aggregated.endswith("password: "): stdin.put("correcthorsebatterystaple") ssh("10.10.10.100", _out=ssh_interact, _out_bufsize=0)
Пример все еще не работает. Есть две проблемы: первая заключается в том, что пароль должен заканчиваться новой строкой, как если бы вы набрали его и нажали клавишу возврата. Это связано с тем, что SSH не знает, насколько длинный пароль, и буферизует STDIN
.
Вторая проблема лежит глубже в SSH. SSH нуждается в TTY, прикрепленном к его STDIN для правильной работы. Это заставляет SSH поверить в то, что он взаимодействует с реальным пользователем в реальном сеансе терминала. Чтобы включить TTY, мы можем добавить специальный аргумент _tty_in
. Также нужно использовать специальный аргумент _unify_ttys
. Это говорит модулю sh
, чтобы STDOUT
и STDIN
поступали с одного псевдотерминала, что является требованием SSH:
from sh import ssh import sys aggregated = "" def ssh_interact(char, stdin): global aggregated sys.stdout.write(char.encode()) sys.stdout.flush() aggregated += char if aggregated.endswith("password: "): stdin.put("correcthorsebatterystaple\n") ssh("10.10.10.100", _out=ssh_interact, _out_bufsize=0, _tty_in=True, _unify_ttys=True)
Вот теперь скрипт удаленного входа работает!
Вышеупомянутый процесс можно упростить, используя Contrib Commands. Команда SSH contrib выполняет всю настройку специальныхключевых аргументов и предоставляет простой, но мощный интерфейс для входа в систему с паролем SSH.
from sh.contrib import ssh def ssh_interact(content, stdin): sys.stdout.write(content.cur_char) sys.stdout.flush() # Автоматически входит в систему с паролем, а затем представляет # последующий контент для обратного вызова `ssh_interact()` ssh("10.10.10.100", password="correcthorsebatterystaple", interact=ssh_interact)
Многие люди хотят научиться вводить пароль SSH с помощью сценария, т.к. постоянно встает необходимость выполнять удаленные команды на сервере. Вместо того, чтобы пытаться войти через SSH и затем посылать ввод команды для запуска через терминал, посмотрим, как можно сделать это по-другому.
Сначала откроем терминал и запустим команду ssh-copy-id yourservername
. Будет предложено ввести пароль для сервера. После ввода пароля, можно подключиться к серверу по SSH без необходимости повторного ввода пароля. Это значительно упрощает работу с модулем sh
.
Второе, что нужно сделать - это использовать возможность SSH передать команду для запуска серверу, к которому подключены по SSH. Вот как можно запустить команду ifconfig
в терминале на сервере, не используя напрямую оболочку этого сервера:
$ ssh amoffat@10.10.10.100 ifconfig
Если перевести это в код, используя модуль sh
, то получится:
import sh print(sh.ssh("amoffat@10.10.10.100", "ifconfig"))
Можно сделать еще лучше, воспользовавшись преимуществами sh
зашивать параметры в команду (например для привязки имени пользователя/ip сервера к объекту команды) :
import sh my_server = sh.ssh.bake("amoffat@10.10.10.100") print(my_server("ifconfig")) print(my_server("whoami"))
Теперь есть многоразовый командный объект, который можно использовать для вызова удаленных команд. Но есть место для еще одного улучшения. Можно использовать функцию подкоманд sh
, которая расширяет доступ к атрибутам в аргументы команд:
import sh my_server = sh.ssh.bake("amoffat@10.10.10.100") print(my_server.ifconfig()) print(my_server.whoami())