Скрипт auth-user-pass-verify в OpenVPN: правильный код выхода
Статус выхода хука — это решение, а не строка журнала. Сопоставьте результаты проверяющего с 0, 1 или отложенной проверкой, читайте файл учётных данных как данные и отказывайте при ошибке.
OpenVPN действует ровно по одному выходу скрипта входа: по статусу выхода. Всё остальное, что делает скрипт, — украшение. Хук может дойти до нужного проверяющего, разобрать ответ и напечатать дружелюбную строку, но вернуть статус, означающий обратное задуманному автором. Прочитайте сначала контракт и напишите адаптер вокруг него, и весь этот класс ошибок исчезнет.
Контракт невелик: определите отправленную учётную запись, задайте утверждённому проверяющему нужный вопрос и принимайте только документированный положительный результат этого проверяющего для этого запроса. Всё остальное — инфраструктура.
Три статуса, которые хук может вернуть
Для auth-user-pass-verify руководство OpenVPN 2.6 определяет код выхода 0 как принятие, 1 как отказ и 2 как отложенную аутентификацию. Это единственные три ответа, которые понимает сервер, и у каждого разные последствия для соединения. Скрипт, завершающийся статусом последней команды, фактически позволил вызову журналирования или шагу очистки проголосовать. Записывайте статус явно в каждой ветви, включая ветви, которые так и не дошли до проверяющего.
Успех HTTP и успех аутентификации — разные уровни, и их смешение — самая частая причина ошибки этого хука. Ответ 202 означает, что запрос принят в обработку, а не что пароль проверен. Определите ожидаемый статус, форму ответа и окончательный вердикт из выданного вам контракта API и не придумывайте имя поля только потому, что оно выглядит правдоподобно.
Если ответ действительно приходит позже, это отложенный путь, и его нужно строить именно так. Отложенная попытка, которая никогда не завершается, удерживает ресурсы до истечения тайм-аута.
Читайте файл учётных данных ровно так, как он записан
При документированном методе через файл сервер записывает имя пользователя и пароль первыми двумя строками временного файла, передаёт этот путь скрипту и удаляет файл, когда скрипт возвращается. Читайте эти две строки как данные: никогда не подключайте файл через source, не выполняйте его и не вставляйте его содержимое в командную строку, где его увидит таблица процессов. Сохраняйте пароль байт в байт: нормализация, какую вы применили бы к имени пользователя, тихо меняет учётные данные. Каталог для этого временного файла — решение развёртывания, поэтому убедитесь, что он существует и не читается всеми на хосте.
Держите собственные учётные данные проверяющего отдельно от пользовательских: они принадлежат интеграции со шлюзом, живут в хранилище секретов платформы и требуют только права проверять, поэтому не являются доказательством того, что аутентифицировался конкретный человек. Не позволяйте хуку стать и вторым проверяющим паролей. Ручное переписывание сравнения хешей не становится безопасным от оптимизации цикла, а поддерживаемый проверяющий уже понимает солевую и дорогостоящую схему хранилища учётных записей.
Решите, что значит неклассифицируемый ответ
Тайм-ауты, ошибки транспорта, искажённые ответы и недоступный бэкенд имеют одно общее свойство: ни один из них не является положительной проверкой. Хук должен закрыто отказывать при каждом из них и записывать причину, по которой оператор может действовать. Эта причина принадлежит защищённому журналу, а не сообщению, возвращаемому человеку, который пытается подключиться.
Будьте так же внимательны к путям, которые вообще не доходят до проверяющего. Отсутствующее значение конфигурации, нечитаемый секрет или неустановленная зависимость должны давать отказ с отдельной операционной меткой, а не случайное принятие. Проверяйте такие ветви, удаляя зависимость в лаборатории, а не рассуждая о них. Там, где сбой журналирования может изменить статус выхода, журналирование стало частью решения, и его нужно сделать безвредным.
Конфигурация сервера здесь тоже важна: определённые пользователем скрипты учитываются только тогда, когда работающая конфигурация разрешает их на требуемом уровне безопасности, и эта настройка — осознанный выбор, а не значение по умолчанию, которое нужно повысить.
Проверяйте вердикт, а не напечатанную строку
Постройте тест, который напрямую проверяет статус выхода. Напишите образец файла учётных данных, запустите на нём хук с заведомо хорошей и заведомо плохой учётной записью и читайте статус, который сообщает оболочка, а не текст, который печатает скрипт. Эта единственная проверка ловит большинство ошибок контракта ещё до того, как клиент подключится.
Затем прогоните те же случаи через настоящего клиента против нерабочего слушателя и подтвердите, что записал сервер: принято, отклонено или отложено. Покройте отключённую учётную запись, неверный пароль, искажённый ответ проверяющего, тайм-аут и недоступный проверяющий. Если какой-либо случай попадает в состояние, которое вы не можете назвать, адаптер не завершён.
Держите соответствие результата проверяющего статусу выхода рядом со скриптом в репозитории, чтобы следующий инженер мог прочитать контракт без обратной разработки кода.