Как автоматизировать вход в VPN, который ждёт код токена
Клиент VPN в командной строке, который запрашивает ввод в терминале, блокирует скрипт, передающий только пароль через канал. Выполните эти пять проверок, прежде чем браться за expect.
Скрипт, запускающий клиент VPN от поставщика, зависает до истечения тайм-аута, а журнал показывает, что клиент просит код подтверждения, который никто не вводил. Пароль был передан, поэтому очевидный вывод — пароль неверен. Это не так. Запрос кода — отдельный разговор на отдельном канале, и ваш скрипт его ни разу не открывал.
Пройдите пять проверок ниже по порядку. Остановитесь на первой неудачной, потому что каждый следующий шаг предполагает, что предыдущие выполнены.
Проверка 1: читает ли клиент стандартный ввод вообще?
Проверьте до написания любой автоматизации: передайте клиенту фиктивный секрет через канал и посмотрите, что произойдёт. Если он печатает приглашение и затем ждёт, канал он не читает.
Многие клиенты открывают устройство терминала напрямую или отказываются принимать секрет из потока, который не является терминалом, чтобы случайный канал не утёк пароль. OpenVPN — задокументированное исключение, потому что --auth-user-pass принимает путь к файлу. У клиентов поставщиков, запрашивающих одноразовый код, обычно нет аналогичного флага, и их приглашение ждёт терминала, которого ваш скрипт так и не предоставил.
Проверка 2: есть ли у дочернего процесса терминал?
pty выделяет дочернему процессу терминал, что превращает приглашения в читаемый вывод, на который можно отвечать по порядку.
import pexpect
child = pexpect.spawn("openconnect vpn.example.net", encoding="utf-8", timeout=30)
child.expect("GROUP:")
child.sendline("staff")
child.expect("USERNAME:")
child.sendline("alice")
child.expect("PASSWORD:")
child.sendline(PASSWORD)
child.expect("Connected as", timeout=60)Строки приглашений выше приведены для примера; копируйте точный текст, который печатает ваш клиент, включая регистр и пунктуацию. У каждого ожидания есть тайм-аут, а закрытый дочерний процесс бросает исключение вместо возврата статуса, поэтому оберните последовательность и считайте конец файла неудачей. Обратите внимание, чего этот блок не делает: дочерний процесс умирает вместе со скриптом, потому что pty не является механизмом отсоединения, а disown — встроенная команда оболочки, которая не достаёт до процесса, созданного вашим скриптом.
Проверка 3: есть ли поддерживаемый неинтерактивный ввод?
Считывание экрана ломается при обновлениях клиента, поэтому сначала ищите документированный флаг.
Проверьте вывод справки на наличие опции токена или одноразового кода и проверьте, принимает ли служба код, добавленный к полю пароля. Там, где клиент читает файл учётных данных, дайте ему файл вместо приглашения:
openvpn --config office.ovpn --auth-user-pass <(printf "%s\n%s\n" "$USER_NAME" "$PASSWORD")Подстановка процессов даёт клиенту читаемый путь в системах, которые предоставляют /dev/fd. Секрет остаётся секретом с точки зрения клиента, поэтому держите его вне журналов, истории оболочки и списков процессов. Если клиент отвергает /dev/fd или открывает путь к учётным данным более одного раза, запишите настоящий файл со строгими правами и удалите его в том же скрипте.
Проверка 4: переживёт ли туннель скрипт?
По умолчанию туннель умирает вместе с родителем, что противоположно тому, что нужно скрипту начальной загрузки.
disown -h помогает только тогда, когда вы запустили клиент как задание оболочки, потому что она правит таблицу заданий этой оболочки и не достаёт до процесса, созданного скриптом через pty. Менеджер служб — лучшее место для всего, что должно переподключаться без присмотра, потому что он перезапускает клиент и записывает, почему тот умер. В любом случае после каждого переподключения ожидайте новый код: одноразовый токен расходуется при первой успешной аутентификации.
Смотрите, что sudo делает с последовательностью. Запрос пароля sudo — не запрос VPN, и ответ на него паролем VPN — самый частый способ, которым в остальном правильный порядок ожиданий всё равно ломается.
Проверка 5: должен ли код человека быть в скрипте?
Это проверка, которую люди пропускают. Токен принадлежит человеку, а туннель, который должен подниматься по расписанию, принадлежит машине.
Если задание должно переподключаться без присмотра, спросите, может ли машинный сертификат или служебная учётная запись заменить интерактивный вход. Автоматизация чьего-то одноразового кода превращает второй фактор в сохранённый секрет, а это изменение политики, а не удобство скрипта. Там, где служба поддерживает оба варианта, выберите учётные данные, соответствующие тому, как задание действительно работает. Запишите ответ, потому что следующий человек спросит, почему скрипт держит токен.
Как выглядит работающий скрипт
Скрипт печатает шаг, до которого дошёл, никогда не секрет, и завершается с ненулевым кодом при любом неудачном ожидании.
Проверьте его, намеренно сломав: неверный пароль, неверная группа, истёкший токен. Каждая неудача должна остановиться на названном шаге, а не зависнуть. Затем запустите его один раз против настоящей службы и проверьте туннель снаружи скрипта, а не изнутри.