Убеждение состоит в том, что here-документ из четырёх строк — это полноценный неинтерактивный вход, и оно держится ровно до того момента, когда шлюз предложит две группы, напечатает баннер или запросит токен после пароля. Тогда тот же сценарий вводит пароль в меню, которое ждало числа. Клиент командной строки в Linux поддаётся сценариям, но только если вы дадите ему то, чего он действительно ждёт, а не то, что предполагало руководство.

Фиксированный here-документ — это миф

Клиент Cisco для Linux устанавливает небольшую программу в /opt/cisco/anyconnect/bin/vpn, и Secure Client 5 по-прежнему её поставляет. Пока служба vpnagentd работает, флаг -s читает ответы со стандартного ввода в том порядке, в котором клиент их запрашивает. Этот порядок не фиксирован между серверами.

Меню групп добавляет одну строку. Баннер входа добавляет ещё одну. Запрос токена после пароля полностью ломает сценарий из четырёх строк, потому что пятая строка никогда не должна была быть токеном. Сбой тихий: клиент получает неверную строку, попытка соединения заканчивается, а сценарий не сообщает ничего полезного.

Запустите клиент вручную один раз против того же шлюза и версии и запишите приглашения в порядке их появления. Эта запись — спецификация, которой должен соответствовать ваш сценарий.

Чего ждёт клиент

В обычном случае одной группы последовательность такова: имя пользователя, пароль и иногда подтверждение баннера. Первая строка here-документа становится индексом меню только тогда, когда сервер действительно показывает меню, поэтому один и тот же сценарий работает на одном сайте и срывается на следующем.

/opt/cisco/anyconnect/bin/vpn -s connect vpn.example.net <<EOF
0
alice
$VPN_PASSWORD
y
EOF

Читайте это как четыре ответа на четыре возможных вопроса, а не как фиксированный протокол. Ноль — это выбор меню, а не пароль, а завершающая y принимает баннер, который многие шлюзы никогда не присылают.

Используйте expect, когда порядок непредсказуем

Когда порядок приглашений меняется, перестаньте считать строки и вместо этого ждите приглашения. Сценарий expect запускает команду connect, ждёт приглашения имени пользователя, отправляет, ждёт приглашения пароля, отправляет, а затем ждёт состояния подключения. У каждого ожидания свой тайм-аут.

Считайте тайм-аут сбоем и выходите. Сценарий, который продолжает отправлять ввод после неожиданного приглашения, с удовольствием введёт пароль в оболочку, меню или что угодно ещё, что окажется на чтении. Так сценарий входа превращается в инцидент.

Expect даёт и запись, которую можно залогировать с удалённым секретом, и это ценнее кода выхода, когда шлюз после обновления меняет свои приглашения.

Секрету нужно где-то жить

У каждой версии этой проблемы одна слабость: пароль должен существовать в форме, которую клиент может прочитать. Передача его аргументом раскрывает его в таблице процессов. Вывод через конвейер часто попадает в историю оболочки. И то и другое читают другие локальные пользователи на общем промежуточном хосте.

install -m 600 /dev/null ~/.vpn-secret
umask 077; VPN_PASSWORD=$(cat ~/.vpn-secret) /opt/cisco/anyconnect/bin/vpn -s connect vpn.example.net

Файл с режимом 600, принадлежащий учётной записи, — это минимум, и он всё ещё секрет открытым текстом на диске. secret-tool lookup читает из связки ключей Linux, а в macOS ту же работу выполняет security find-generic-password -w. Там, где организация поддерживает аутентификацию сертификатом или SAML, предпочитайте её, потому что сохранённый пароль плюс MFA всё равно не завершит вход без человека.

Подтвердите туннель, а затем возьмите разрыв на себя

Не доверяйте отсутствию текста ошибки. Спросите у клиента его состояние и проверьте интерфейс независимо:

/opt/cisco/anyconnect/bin/vpn state
ip -brief address

Состояние должно читаться как Connected, а интерфейс туннеля должен иметь адрес. Затем запросите один ресурс, до которого туннель должен дотянуться, потому что подключённое состояние без маршрута — реальный исход, и его легко не заметить.

Отключайтесь через vpn disconnect и позвольте сценарию, запустившему туннель, взять разрыв на себя. Неудачная задача, оставившая сеанс открытым, расходует лицензионное место и выглядит как проблема аутентификации для следующего, кто попробует.

Порядок работы: подтвердите vpnagentd, зафиксируйте реальный порядок приглашений, выберите here-документ или expect, держите секрет вне argv и истории, проверьте состояние и один ресурс, затем отключитесь в том же сценарии.

Похожие статьи