OpenVPN auth-user-pass: файл, stdin или запрос? Как выбрать
Выберите между запросом в консоли, файлом учётных данных, потоком и интерфейсом управления, а затем исправьте ошибки, которые только выглядят как неверный пароль.
Какой источник учётных данных использовать, решают три вещи: сидит ли человек за консолью, может ли секрет жить на диске и должен ли другой процесс отвечать на запрос. Если выбрать неправильно, клиент сообщит о проблеме со stdin, хотя пароль полностью верен, и это без причины отправляет большинство людей к серверу. Сначала выберите путь; сообщения об ошибках имеют смысл только тогда, когда вы знаете, какой путь их породил.
Три условия, которые это решают
Запрос в консоли — единственный путь, которому нужен человек, поэтому он единственный, который тихо отказывает в юните systemd или в контейнере. Если человек запускает клиент и вводит пароль, файл не участвует и ничего чувствительного не попадает на диск. Если клиент запускается при загрузке, тот же запрос превращается в зависание, и тайм-аут обычно выглядит как общая ошибка соединения, а не как ошибка учётных данных.
Файл учётных данных — правильный ответ, когда процесс запускается без присмотра и секрет может пережить диск при контролируемом наборе прав. Когда секрет вообще нельзя записывать — политика только хранилища, короткоживущий токен — нужен поток, который клиент может открыть, а это на практике означает подстановку процесса или помощника, пишущего в файловый дескриптор. Интерфейс управления покрывает четвёртый случай: контроллер, который уже управляет жизненным циклом туннеля и может отвечать на вопросы через сокет.
- Человек за консолью, без автоматизации: интерактивный запрос.
- Запуск без присмотра, секрет можно хранить: файл учётных данных с режимом 0600.
- Запуск без присмотра, секрет хранить нельзя: подстановка процесса или fd, в который пишет агент секретов.
- Клиентом владеет другой процесс: интерфейс управления.
Когда подходят два ответа, выбирайте тот, который держит секрет вне argv и вне истории shell. Пароль, переданный аргументом программы, может прочитать любой локальный пользователь через список процессов, и эту утечку трудно отменить.
Что на самом деле делает каждый входной путь
--auth-user-pass сам по себе запрашивает в консоли имя пользователя и пароль. Если дать имя файла, клиент берёт первую строку как имя пользователя, а вторую как пароль; ничего не поддерживает кавычки, комментарии или синтаксис ключ-значение. Это весь формат, поэтому всякий, кто редактирует его в ожидании файла INI, ломает его. Встроенный блок <auth-user-pass> содержит те же две строки внутри профиля, что делает сам профиль секретом и ставит его в тот же класс обращения, что и закрытый ключ.
Остальные два пути — это поток и сокет. OpenVPN нужен только читаемый файловый дескриптор, поэтому она примет подстановку процесса, запись /dev/fd на основе канала или имя файла, которое на самом деле является FIFO. Интерфейс управления иной по природе: клиент спрашивает значения у supervising-процесса вместо того, чтобы читать их сам. Эта разница важна при переподключении: файл учётных данных, FIFO, в который помощник секретов может записать снова, и интерфейс управления могут предоставить данные заново без человека; подстановка процесса не может, потому что дескриптор расходуется при первом чтении.
Файл должен быть ровно из двух строк
Два режима отказа покрывают большинство сломанных файлов учётных данных. Редактор Windows оставляет окончания CRLF, возврат каретки становится частью пароля, и сервер отклоняет значение, которое на экране выглядит правильным. Пустая вторая строка — пустая строка там, где должен быть пароль — оставляет клиента с пустым паролем, который сервер отклоняет, хотя значение на экране выглядит правильным. Лишний перевод строки после пароля безвреден. Записывайте файл из shell, а не из редактора, и проверяйте байты:
install -m 600 /dev/null /etc/openvpn/client.creds
printf '%s\n%s\n' "$VPN_USER" "$VPN_PASS" > /etc/openvpn/client.creds
od -c /etc/openvpn/client.creds | head -3Путь так же важен, как и содержимое. Относительный путь разрешается относительно рабочего каталога процесса, который для службы редко является каталогом, где вы тестировали. Используйте абсолютный путь, сделайте файл собственностью учётной записи, запускающей клиент, и держите режим 0600, чтобы группа и остальные не могли его прочитать. Руководство OpenVPN 2.6 перечисляет допустимые формы директивы; проверьте его, прежде чем изобретать третью.
Проверьте файл, прежде чем винить сервер. Запуск openvpn --config client.ovpn --auth-user-pass /etc/openvpn/client.creds --verb 4 печатает, был ли файл открыт и, позже, были ли учётные данные приняты, что подсказывает, какой слой менять дальше.
Потоки, stdin и ошибки, которые они порождают
Передача пароля через канал — первое, что пробует большинство, и путь с наибольшим числом сюрпризов. Подстановка процесса работает, потому что клиенту нужен только читаемый дескриптор: openvpn --config client.ovpn --verb 3 --auth-user-pass <(printf '%s\n%s\n' "$VPN_USER" "$VPN_PASS"). Передача /dev/stdin работает только тогда, когда stdin в момент запроса клиента ещё открыт и читаем, что неверно в конвейере, который его уже израсходовал.
ERROR: could not read Auth username from stdin имеет две разные причины. В некоторых клиентах 2.3.x сообщение приходило из дефекта обработки stdin, который исправили более поздние выпуски 2.3, поэтому такой старый клиент улучшается только обновлением. В современном клиенте та же формулировка означает, что клиент запросил ввод из консоли, а stdin не был терминалом — именно ситуация в юните systemd, задании cron или контейнере, запущенном без -t.
auth-nocache добавляет третий цикл, который выглядит как сломанный пароль. Он выбрасывает учётные данные из памяти после использования, поэтому переподключению, вызванному сменой сети, нечего воспроизводить, и запрос возвращается. Это директива, работающая как задумано. auth-nocache меняет то, что клиент держит в памяти, а не то, что сервер или поставщик идентичности думает об учётной записи, поэтому всё исправление — снова предоставить тот же файл, перечитанный при перезапуске, — а для управляемого соединения это предоставление должно приходить из утверждённого хранилища секретов, а не из пароля в скрипте.
Интерфейс управления, когда запросом владеет другой процесс
Контроллер, который уже наблюдает за туннелем, может также отвечать на его вопросы. Запустите клиент с --management 127.0.0.1 7505, читайте строки состояния и следите за >PASSWORD:Need 'Auth' username/password. Контроллер отвечает username "Auth" alice, за которым следует password "Auth" ..., отдельными командными строками. Токен 'Auth' повторяет идентификатор из запроса и должен совпадать, иначе ответ игнорируется и клиент ждёт.
Цена — это машина состояний, которой вы теперь владеете: порядок соединений, перезапуски клиента и локальный сокет, до которого другие пользователи на хосте не должны дотянуться. Вы покупаете учётные данные, предоставляемые во время выполнения, без файла на диске и без запроса, который может зависнуть. Если больше ничего в вашем стеке уже не говорит на этом протоколе, этот путь добавляет компонент, требующий обслуживания.
Сбои, которые выглядят как проблемы с учётными данными
Рукопожатие TLS, которое никогда не завершается, или VERIFY ERROR: depth=0, error=certificate signature failure — это проблема доверия, и она происходит раньше, чем читается какое-либо имя пользователя. Клиент proto tcp, направленный на UDP-слушатель, отказывает в том же месте: пакеты никогда не доходят до этапа аутентификации, поэтому каждый пароль кажется неверным по ассоциации. Держите проверку сертификатов включённой до отправки учётных данных; знакомый запрос ничего не говорит о том, кто получает пароль. Проверьте цепочку сертификатов, общее имя и транспорт, прежде чем трогать учётные данные.
Ошибки shell в скрипте, собирающем профиль, — это другой ложный след. Пропущенный пробел внутри [ ! -f "$file" ] порождает command not found, скрипт останавливается рано, профиль никогда не пересобирается, и клиент стартует с предыдущим. Эта ошибка принадлежит shell, а не аутентификации, и никакое переписывание пароля её не уберёт.
Не каждый запрос в этом потоке — это данные учётные данные. Парольная фраза закрытого ключа, вход через HTTP-прокси и пароль службы — это три разных секрета, запрашиваемых на разных этапах, и парольную фразу ключа обычно обрабатывает клиент или агент, а не auth-user-pass. Если запрос появляется до установления сеанса TLS, вы отвечаете на неправильный вопрос, и никакой сброс учётной записи не поможет. После исключения неправильного запроса оставшиеся проверки начинаются с самого раннего слоя:
- Читайте первую ошибку протокола, а не последнюю строку учётных данных: TLS, затем аутентификация, затем push.
- Убедитесь, что файл состоит из двух строк, не содержит возвратов каретки и указан абсолютным путём.
- Убедитесь, что профиль и командная строка не расходятся относительно директивы.
- Отделите «учётные данные приняты» от «трафик идёт» — маршрутизация и DNS это более поздний слой.
Когда клиент достигает этапа push, перестаньте смотреть на учётные данные и начните читать маршруты и DNS-серверы, которые прислал сервер. Это следующая проблема, и она не имеет ничего общего с паролем, который вы только что исправили.