Хук client-connect в OpenVPN: куда на самом деле попадает ваш адрес
Скрипт client-connect не может вернуть адрес, просто напечатав его. Посмотрите, как временный файл хука, код выхода и топология определяют IP-адрес клиента.
Возврат адреса из скрипта client-connect выглядит так, будто он должен задать IP-адрес клиента. Но это не так: единственный канал хука к серверу — временный файл, путь к которому приходит последним аргументом скрипта, и адрес нужно записать туда в синтаксисе ifconfig-push. Скрипт, который печатает адрес или завершается с нулём, ничего не записав, создаёт подключённого клиента с адресом из пула, а журнал сервера выглядит совершенно здоровым.
Путаница понятна, потому что несколько других хуков действительно отвечают через коды выхода или переменные окружения. У этого свой контракт, и контракт начинается с имени файла.
Хук записывает файл, а не возвращаемое значение
Объявление состоит из двух строк: script-security 2, чтобы разрешить вызов, затем client-connect /etc/openvpn/server/assign-address.sh. Когда клиент проходит аутентификацию, сервер запускает этот скрипт, добавляя сгенерированный путь к файлу последним аргументом; он будет $1 только если объявление не передаёт собственных дополнительных аргументов. Всё, что применяется к этому клиенту, пишется в тот файл обычными директивами, по одной в строке, в точности как они выглядели бы в конфигурации. Обёртка, добавляющая свои параметры, сдвигает позицию, и классический симптом — скрипт, пишущий по пути, который выглядит как --verbose.
Код выхода — вторая половина контракта. Ноль означает принять и прочитать файл; всё остальное отклоняет соединение, и именно так несопоставленная учётная запись получает отказ вместо тихого возврата в пул. Запишите полную директиву до возврата успеха, чтобы наполовину записанный файл никогда не стал конфигурацией клиента.
Две директивы заслуживают внимания, потому что они взаимодействуют со всем остальным. ifconfig-push фиксирует адрес, а отправленный route сообщает клиенту, что доступно через туннель; верните первую и забудьте вторую — получите клиента с адресом и без места, куда идти. Порядок в файле не важен, а полнота важна. Хук, отправляющий маршрут для префикса, до которого не дотянется ни один партнёр, — та же ошибка с другой стороны.
Входные данные приходят через переменные окружения: common_name, trusted_ip, ifconfig_pool_remote_ip и остальные. Относитесь ко всем им как к строкам извне сервера, никогда как к фрагментам оболочки.
Сопоставление адресов — это политика, а не арифметика
Соблазнительный короткий путь — разобрать имя сертификата и использовать его цифры как последний октет. Это не распределитель, и он отказывает обычными способами: два имени с одинаковым суффиксом конфликтуют, имя, оканчивающееся на 300, не является адресом, а сертификат, переименованный администратором, тихо перемещает устройство. Проверяйте принадлежность подсети, допустимый диапазон хостов, зарезервированные адреса и уникальность при каждом вызове, а сопоставление держите в таблице, которую человек может прочитать.
Кто управляет именем, тот управляет адресом, поэтому решите, что common_name на самом деле представляет в этом развёртывании. При включённом username-as-common-name идентичность — это логин, а не субъект сертификата. Никогда не передавайте это значение в вычисление оболочки и никогда не позволяйте клиенту влиять на путь к файлу.
Распределитель на основе реестра должен выдерживать конкурентность. Резервируйте адрес атомарно, обрабатывайте неудачную запись, не допуская полунастроенного клиента, и помечайте каждое выделение соединением, которому оно принадлежит, чтобы позднее отключение старой сессии не могло освободить адрес, который уже удерживает новая. Несколько экземпляров сервера, делящих один пул, делают это требование острее, а не необязательным.
Топология определяет, что означает ifconfig-push
С topology subnet на 10.77.0.0/24 ifconfig-push 10.77.0.20 255.255.255.0 — это адрес плюс его сетевая маска. При topology net30 второй аргумент вовсе не маска, а адрес партнёра, поэтому копирование формы подсети на сервер net30 даёт клиента, который подключается и не может пропускать трафик. Меняйте топологию осознанно, никогда ради того, чтобы пример выглядел согласованно. Сервер, уже работающий с net30, имеет живых клиентов на старой адресации, поэтому переключение — это миграция с окном обслуживания, а не правка одной строки.
Статические адреса должны находиться вне динамического пула и вне каждого другого статического назначения, включая те, что выдают обычные файлы client-config-dir. Если два механизма могут выдать один и тот же адрес разным клиентам, один из них в конце концов это сделает.
Проверьте четыре случая, прежде чем доверять этому
Запустите те же проверки для сопоставленной учётной записи, несопоставленной, повторного подключения и смоделированного отказа реестра. openvpn --config client.ovpn --verb 4 показывает назначенный адрес в ответе push, а на сервере grep -A2 PUSH_REPLY /var/log/openvpn/openvpn.log подтверждает, что было реально отправлено. Для несопоставленной учётной записи ожидаемый результат — отклонённое соединение, а не адрес из пула.
Затем осмотрите каталог, в который демон пишет эти временные файлы. Скрипт выполняется в пониженном контексте службы, поэтому путь, доступный для записи root, но не демону, превращает каждое соединение в отказ с ошибкой прав, погребённой несколькими строками ниже в журнале.
Выделение — это не авторизация: закреплённый адрес всё равно не даёт доступа к каждому назначению за сервером. Держите политику маршрутизации и брандмауэра в своём слое и проверяйте её относительно адреса, который вы только что закрепили.