Настройка OpenVPN сервера с LDAP авторизацией в Docker Compose
1. Диагноз
Поднял OpenVPN. Сертификаты нарезал, .ovpn раздал. Через месяц отдел кадров привёл трёх новых сотрудников и попросил доступ «как у всех». Ты либо генеришь сертификат руками на каждого, либо вспоминаешь что где-то читал про LDAP.
Знакомо? Задача настройка openvpn сервера с ldap авторизацией в docker compose возникает ровно в этот момент — когда сертификатов становится больше пяти и увольнение сотрудника превращается в квест «а где мы храним отозванные crl.
Что получишь на выходе: VPN-сервер, который спрашивает логин-пароль из вашего LDAP или Active Directory, а не выдаёт доступ каждому у кого завалялся .ovpn файл трёхлетней давности. Уволили человека в AD — он тут же не может подключиться к VPN. Никаких лишних телодвижений.
Из жизни: однажды поднимал VPN у клиента после аудита безопасности, который нашёл 47 активных клиентских сертификатов при штате в 22 человека. Половина принадлежала людям, уволенным за два-три года до этого. Причина банальна — никто не вёл реестр выданных сертификатов, а CRL обновляли раз в квартал по остаточному принципу. LDAP такую ситуацию структурно исключает: нет отдельного пользователя в VPN, есть только отражение того, что уже есть в каталоге компании.
Время на весь цикл: час, если LDAP уже поднят и ты знаешь base DN. Три часа, если приходится разбираться в структуре чужого каталога методом тыка.
Нужно: сервер с Docker и Docker Compose, доступ к LDAP или AD (адрес, порт, bind DN, base DN), открытый порт 1194/udp снаружи и порт под веб-интерфейс.
В статье пройдём: архитектуру связки, установку OpenVPN с готовым Web UI, включение LDAP авторизации, проверку что всё работает, разбор типичных ошибок, альтернативный вариант без готового UI через auth-user-pass-verify, и профилактику — что бэкапить и как обновляться не роняя продакшн.
Отдельно проговорю то, что обычно упускают в статьях про VPN плюс LDAP: разницу между авторизацией на уровне подключения и авторизацией на уровне ресурса. LDAP-бинд отвечает только на вопрос «существует ли такой пользователь с таким паролем в каталоге». Он не отвечает на вопрос «имеет ли этот пользователь право видеть подсеть бухгалтерии». Второе делается отдельно — через группы в CCD (client-config-dir) или через маршрутизацию на файрволе. Смешивать эти два уровня — частая ошибка, из-за которой потом кто-то из отдела продаж внезапно видит шару финансового отдела.
Второй момент, который стоит понимать заранее: сертификат клиента и LDAP-логин — это два разных фактора, а не один и тот же механизм под разными именами. TLS-сертификат подтверждает что устройство доверенное (первый фактор), LDAP-бинд подтверждает что человек за устройством — тот, за кого себя выдаёт (второй фактор). Убрать сертификат и оставить только логин-пароль можно, но тогда VPN становится доступен с любого устройства при утечке пароля — для корпоративного контура это обычно неприемлемо.
Третий момент — откуда вообще берётся выбор конкретного инструмента для этой задачи. Готовых Web UI для OpenVPN на GitHub десятки, но реально живых, с поддержкой LDAP из коробки и обновлениями хотя бы раз в несколько месяцев — единицы. В статье используется именно тот вариант, где LDAP не приколочен сбоку отдельным патчем от энтузиаста три года назад, а поддерживается как часть основного функционала проекта с активной историей релизов. Это не единственно верный выбор, но осознанный — критерии подбора описаны отдельно в разделе про альтернативы.
2. Причины
Почему именно LDAP, а не просто сертификаты — и что ломается если делать по-другому.
| Причина держать VPN на сертификатах без LDAP | Почему это ломается на масштабе |
|---|---|
| Каждому клиенту — свой .crt и .key | Уволенного сотрудника нужно вручную отозвать через CRL, забыл — доступ живёт вечно |
| Пароль общий на всех | Утечка одного пароля — утечка доступа для всей компании, ротация превращается в рассылку по всем |
| Локальная база пользователей на самом VPN | Двойной источник правды — AD отдельно, VPN отдельно, рассинхрон гарантирован через месяц |
| Ручное управление доступом в Excel-табличке | Никто не помнит зачем у Иванова доступ с 2019 года, аудит невозможен |
| Один админ помнит все пароли наизусть | Админ в отпуске — доступ никто не даёт и не отзывает |
LDAP или AD решает это одним центром правды. Уволил в AD — через минуту (или сразу, если TTL сессии короткий) человек не может залогиниться в VPN. Никакого дублирования учёток.
Есть ещё практический довод в пользу LDAP, который обычно всплывает уже после внедрения, а не до: онбординг нового сотрудника перестаёт требовать участия сетевого инженера. HR или IT-support заводит учётку в AD по стандартному чек-листу приёма на работу, и человек в тот же день получает доступ к VPN без отдельного тикета «выпустите мне сертификат». До внедрения LDAP этот процесс почти всегда завязан на конкретного человека, который умеет генерировать сертификаты — а если этот человек в отпуске, новый сотрудник сидит без доступа до его возвращения.
Есть и обратная сторона, про которую молчат маркетинговые страницы готовых VPN-решений: LDAP добавляет вашему VPN точку отказа. Если контроллер домена лёг — лежит и авторизация в VPN, даже если сам туннель технически жив. Это не повод отказываться от связки, но повод держать резервный контроллер домена или хотя бы кэширующий LDAP-прокси рядом. Планируй отказоустойчивость каталога отдельно от планирования отказоустойчивости самого VPN — это разные системы с разными точками отказа.
Проверка отказоустойчивости LDAP на этапе планирования дешевле, чем разбор инцидента, когда весь офис не может подключиться в понедельник утром потому что единственный контроллер домена ушёл на плановую перезагрузку без предупреждения. Заложи хотя бы второй контроллер домена в качестве резервного LDAP_URL, если такая возможность есть в конфигурации UI, или держи под рукой процедуру быстрого переключения на резервный адрес вручную.
Ещё одна причина, которую сисадмины обнаруживают только когда уже поздно: аудит доступа. Регулятор, служба безопасности или просто здравый смысл рано или поздно спрашивают «кто и когда заходил в VPN за последний квартал». С локальной базой пользователей на самом сервере VPN этот вопрос решается ковырянием в логах вручную. С LDAP — централизованный лог событий аутентификации плюс сопоставление логина с реальным сотрудником из каталога, а не с абстрактным client047, который сгенерировали два года назад и забыли на кого.
3. Рецепт
Подготовка
Проверь среду перед стартом — три вещи, без них можно не начинать.
docker --version
docker compose version
ldapsearch -x -H ldap://ldap.example.local:389 -D "cn=binduser,dc=example,dc=local" -w 'пароль' -b "dc=example,dc=local" "(uid=testuser)"
Результат: должна вернуться запись пользователя testuser. Если получаешь «Can’t contact LDAP server» — проблема на сети или в firewall, дальше идти рано.
Системные требования
| Компонент | Версия на момент публикации |
|---|---|
| Docker Engine | 27.x |
| Docker Compose | v2 (плагин compose, не отдельный docker-compose 1.x) |
| Образ OpenVPN + Web UI | yyxx/openvpn (проект GavinTan/openvpn), актуальный релиз v2.5.2 |
| ОС хоста | Debian 12 / Ubuntu 22.04+ (любой Linux с поддержкой TUN) |
| LDAP-сервер | OpenLDAP 2.5+ либо Windows Server AD DS 2016+ |
На момент публикации актуальна версия v2.5.2. Перед установкой проверь свежие релизы на странице проекта — LDAP-модуль в нём допиливают, баги правят регулярно.
Таблица портов
| Порт | Протокол | Назначение | Доступен снаружи |
|---|---|---|---|
| 1194 | UDP | Туннель OpenVPN | Да |
| 8833 | TCP | Web UI администрирования | Нет — только через VPN или за реверс-прокси с ограничением по IP |
| 389 / 636 | TCP | LDAP / LDAPS до вашего каталога | Нет — внутренняя сеть или VPN-туннель до контроллера домена |
Шаг 1. Каталог и docker-compose.yml
Создай рабочую директорию и файл compose. Том ./data хранит всё — конфиги, сертификаты, базу пользователей веб-интерфейса.
mkdir -p /opt/openvpn && cd /opt/openvpn
services:
openvpn:
image: yyxx/openvpn:latest
container_name: openvpn
cap_add:
- NET_ADMIN
restart: unless-stopped
ports:
- "1194:1194/udp"
- "8833:8833"
volumes:
- ./data:/data
- /etc/localtime:/etc/localtime:ro
Результат: файл готов, никаких опечаток в отступах YAML — это первое место где всё падает молча.
Обрати внимание на том ./data — это не мелочь для галочки. Внутри него после первого запуска появится вся PKI-инфраструктура (CA, серверный сертификат, CRL), база пользователей веб-панели, конфиги клиентов и сама LDAP-конфигурация после того как её сохранишь через UI. По факту это единственное состояние, которое отличает твой инстанс от чистого образа. Потерял том — потерял всё, придётся заново генерировать PKI и рассылать новые .ovpn всем клиентам одновременно, а это письмо всей компании и день поддержки.
Если сервер стоит за NAT или ты арендуешь VPS у облачного провайдера — заранее пробрось 1194/udp на уровне провайдерского firewall или Security Group, а не только внутри iptables хоста. Забытое правило на уровне облака — самая частая причина «контейнер поднялся, а клиенты не коннектятся» в первые полчаса после разворачивания.
Шаг 2. Первый запуск
docker compose up -d
docker compose logs -f openvpn
Жди строку о старте веб-сервера на 8833 и OpenVPN-демона на 1194. Если контейнер перезапускается по кругу — смотри логи, в 90% случаев это конфликт портов с уже висящим на хосте OpenVPN.
Шаг 3. Вход в Web UI и базовая настройка
Открой http://<IP-сервера>:8833. Логин и пароль по умолчанию — admin/admin. Первым делом меняешь пароль, это не опция.
В разделе «Управление → Клиенты» сгенерируй тестовый .ovpn — убедись что базовый сертификатный контур работает ещё до включения LDAP. Так проще отделить проблемы туннеля от проблем авторизации.
Вот тут важно не спешить. Подключись этим тестовым конфигом с любого устройства и проверь что туннель поднимается, пинги идут, интернет через VPN работает — если включена соответствующая опция. Только после этого имеет смысл трогать LDAP. Если начинаешь сразу с LDAP и что-то не работает — непонятно, ломается сертификатный контур или сама авторизация, и диагностика превращается в гадание вместо последовательной проверки.
Заодно проверь раздел «Системные настройки → OpenVPN» — там задаются базовые параметры сервера: подсеть для клиентов, DNS-серверы которые будут проталкиваться клиентам, режим полного тоннелирования трафика или только доступа к внутренним ресурсам. По умолчанию проект работает в режиме split-tunnel (доступ только к внутренней сети), full-tunnel (весь трафик клиента через VPN) включается отдельной галкой. Для корпоративного доступа к ресурсам обычно достаточно split-tunnel — он не грузит канал сервера чужим интернет-трафиком.
Шаг 4. Настройка LDAP-подключения
В разделе системных настроек включаешь LDAP-аутентификацию. После включения локальные VPN-аккаунты веб-интерфейса перестают работать для клиентского логина — учти это до того как отключишь себе доступ.
| Параметр | Значение для OpenLDAP | Значение для Windows AD |
|---|---|---|
| LDAP_URL | ldaps://ldap.example.local:636 | ldaps://dc01.example.local:636 |
| LDAP_USER_ATTRIBUTE | uid | sAMAccountName |
| Base DN | ou=users,dc=example,dc=local | ou=vpn-users,dc=example,dc=local |
| Bind DN | cn=vpnbind,dc=example,dc=local | vpnbind@example.local |
LDAP_USER_ATTR_IPADDR_NAME и LDAP_USER_ATTR_CONFIG_NAME — опциональные поля. Если хочешь закрепить статический IP за пользователем через атрибут каталога, используй свободное текстовое поле вроде mobile или homePhone — большинство схем LDAP такие поля не занимают ничем важным.
Используй ldaps:// (TLS), а не plain ldap://. Пароли пользователей летят по сети в открытом виде на bind-запросе, если TLS не включён — это не гипотетический риск, а буквально пароль в tcpdump.
По факту разница между OpenLDAP и Active Directory в этой связке сводится к трём вещам. Первая — имя атрибута логина, о нём уже сказал выше. Вторая — формат bind DN: у OpenLDAP это обычно полный DN вида cn=vpnbind,dc=example,dc=local, у AD часто проще и надёжнее работает UPN-формат vpnbind@example.local вместо классического CN-пути. Третья — структура групп: в AD группы называются memberOf и лежат прямо в атрибутах пользователя, в OpenLDAP чаще используется отдельный groupOfNames объект, который надо разыскивать отдельным запросом. Если планируешь давать доступ по группам, а не поголовно всем в base DN — выясни это заранее, а не после того как раздал доступ всей компании вместо одного отдела.
Не перепутай base DN с bind DN — это классическая ошибка новичков в LDAP. Base DN — это точка входа в дерево каталога, откуда начинается поиск пользователя. Bind DN — это учётка, от имени которой сервис делает сам запрос на поиск. Перепутал местами — получишь либо «пользователь не найден» при живом пользователе, либо ошибку аутентификации самого сервиса.
Шаг 5. Генерация клиентского конфига под LDAP-аккаунт
После включения LDAP клиенты логинятся своей учёткой из каталога, а не по сертификату-с-паролем. Сертификат остаётся первым фактором (TLS), логин-пароль — вторым. Итого два фактора без всякого MFA — уже неплохо для старта.
docker compose exec openvpn cat /data/client.ovpn
Скачай файл, раздай пользователю, попроси ввести свой AD/LDAP логин при подключении. Пароль от VPN теперь — это пароль от домена. Второй пароль запоминать никому не нужно, и это единственная причина по которой пользователи это одобряют.
Шаг 5.1. Шифрование и параметры туннеля
По умолчанию проект генерирует конфиг с современным набором — AES-256-GCM для канала данных и TLS 1.2+ для управляющего канала. Трогать эти параметры без веской причины не стоит: устаревшие шифры вроде BF-CBC оставлены только для совместимости с древними клиентами и тянут за собой уязвимости, о которых сообщество OpenVPN писало ещё лет десять назад.
Раздельно стоит проверить длину ключа Diffie-Hellman — для новых установок это 2048 или 3072 бита, генерируется автоматически при первом запуске. Если мигрируешь с древней инсталляции, где DH-параметры на 1024 бита — пересоздай их, это не тот случай где «работает — не трогай» уместен.
docker compose exec openvpn openssl dhparam -out /data/pki/dh.pem 2048
Что касается full-tunnel против split-tunnel — выбор зависит от модели угроз, а не от удобства. Full-tunnel (весь трафик клиента через VPN) даёт контроль над тем что происходит с рабочим ноутбуком в публичном Wi-Fi аэропорта, но увеличивает нагрузку на канал сервера и требует настройки DNS-утечек отдельно. Split-tunnel (только внутренние ресурсы через VPN) легче для инфраструктуры, но оставляет остальной трафик пользователя вне вашего контроля. Для доступа к внутренним сервисам компании обычно достаточно split-tunnel; full-tunnel имеет смысл только если явно нужна защита трафика в недоверенных сетях.
Шаг 6. MFA и ограничение доступа по группам
Проект поддерживает MFA поверх LDAP — второй фактор настраивается пользователем самостоятельно через личную страницу после логина под своей учёткой. Включаешь опцию MFA для конкретного клиента в разделе «Клиенты», пользователь при следующем логине на пользовательской странице сканирует QR-код в приложении-аутентификаторе (Google Authenticator, Authy, любой TOTP-совместимый) и с этого момента при скачивании конфига в него подставляется актуальный TOTP-параметр.
Ограничение доступа по группам LDAP из коробки в этом проекте не такое гибкое как хотелось бы — на уровне UI фильтрации по memberOf нет. Рабочий обходной путь — завести отдельный base DN или отдельную OU именно под VPN-пользователей и добавлять туда только тех, кому доступ разрешён. Управление доступом идёт через членство в этой OU, а не через сложную фильтрацию атрибутов на лету. Это грубее чем полноценный RBAC, зато предсказуемо и не ломается при следующем обновлении образа.
Если нужна более тонкая маршрутизация — оставшийся контроль делай на уровне CCD (client-config-dir): для каждого клиента можно прописать индивидуальные push-маршруты и ограничения подсети, независимо от того как он аутентифицировался.
4. Проверка
Три команды, которые показывают что связка живая от начала до конца.
docker compose ps
Результат: контейнер openvpn в статусе Up, без Restarting.
docker compose logs --tail=50 openvpn | grep -i ldap
Результат: строки о успешном bind к LDAP-серверу при попытке логина, без ошибок invalid credentials на валидном пароле.
docker compose exec openvpn cat /data/openvpn-status.log
Результат: список активных клиентских подключений с реальным именем пользователя из LDAP, а не CN сертификата — если видишь именно логин пользователя, а не общий client01, авторизация встроена в цепочку правильно.
Четвёртая проверка, которую часто пропускают — реальный тест с невалидным паролем. Подключись тестовым клиентом и осознанно введи неправильный пароль. Сервер обязан отклонить подключение с чётким сообщением об ошибке аутентификации, а не молча пустить внутрь. Звучит очевидно, но именно эта проверка ловит ситуации когда LDAP включён в UI, но фактически не применяется из-за опечатки в пути к скрипту проверки или из-за того что старый сертификатный контур продолжает пускать всех подряд параллельно с новым LDAP-контуром.
docker compose logs --tail=20 openvpn | grep -i "denied\|invalid"
Пятая проверка — время отклика LDAP-бинда. Если аутентификация занимает больше двух-трёх секунд на одного пользователя, при массовом подключении в начале рабочего дня (все заходят в 9:00 одновременно) сервер захлебнётся очередью bind-запросов. Замерь время одного логина секундомером или через time при ручном ldapsearch — это дешевле чем разбираться с жалобами всего отдела в понедельник утром.
time ldapsearch -x -H ldaps://ldap.example.local:636 -D "cn=vpnbind,dc=example,dc=local" -w 'пароль' -b "ou=vpn-users,dc=example,dc=local" "(uid=testuser)"
5. Осложнения
| Ошибка | Причина | Решение | Команда |
|---|---|---|---|
| AUTH_FAILED сразу после ввода пароля | Неверный LDAP_USER_ATTRIBUTE — для AD часто по ошибке ставят uid вместо sAMAccountName | Смени атрибут на sAMAccountName для AD, перезапусти сервис |
|
| Can’t contact LDAP server в логах | Контейнер не видит LDAP-хост по сети — чаще всего firewall блокирует 636 порт между Docker-хостом и контроллером домена | Проверь доступность порта с хоста, открой правило firewall именно для исходящего 636/tcp |
|
| Web UI не открывается после смены пароля | Пароль сброшен, но сессия в браузере закэширована старым токеном | Очисти куки для домена/IP или открой в приватном окне | — |
| Клиент подключается, но интернета нет | Не настроен NAT/маскарадинг на хосте, или забыт redirect-gateway | Добавь iptables MASQUERADE правило для интерфейса tun, проверь push redirect-gateway в конфиге |
|
| Сертификат протух через год без предупреждения | Дефолтный срок жизни клиентских сертификатов — 1 год, без EASYRSA_CERT_EXPIRE это тихо ломается | Задай переменную окружения с нужным сроком до генерации сертификатов |
|
| После включения LDAP старые локальные VPN-аккаунты не логинятся | Это ожидаемое поведение — LDAP полностью заменяет локальную базу для клиентского логина | Заведи всех активных пользователей в LDAP заранее, до переключения флага | — |
| Клиент дропается ровно раз в час без видимой причины | Истёк tls-crypt/tls-auth ключ пересогласования, либо не совпадает reneg-sec на клиенте и сервере | Синхронизируй параметр reneg-sec в обоих конфигах, перегенерируй клиентский профиль |
|
| Web UI отвечает медленно при большом числе клиентов в LDAP | Base DN указывает на весь каталог целиком, а не на конкретную OU — каждый поиск перебирает тысячи записей | Сузь base DN до конкретной организационной единицы с VPN-пользователями | — |
Ну и запросы у вас — сказала база LDAP и повисла на bind-таймауте в 30 секунд, потому что кто-то забыл индекс на атрибуте поиска. Проверяй индексацию базового DN заранее, а не когда триста клиентов одновременно ловят таймаут в девять утра понедельника.
Отдельно про сертификат сервера при LDAPS. Если контроллер домена использует самоподписанный или внутренний корпоративный CA (частая ситуация для AD CS), контейнер OpenVPN может не доверять этому CA по умолчанию и рвать TLS-соединение до LDAP ещё на этапе установки сессии. Решение — положить корневой сертификат внутреннего CA в системное хранилище доверенных сертификатов внутри контейнера или указать путь до него явно в настройках LDAP, если UI это поддерживает. Диагностируется это одной командой openssl s_client с указанием LDAPS-хоста — если рукопожатие TLS падает с verify error, дело именно в доверии к CA, а не в самой LDAP-логике.
openssl s_client -connect dc01.example.local:636 -showcerts
Ещё одна практическая мелочь: если после смены пароля в AD пользователь продолжает логиниться старым паролем ещё какое-то время — это не баг проекта, а особенность LDAP-кэширования на стороне контроллера домена или прокси между OpenVPN и AD. Если такое поведение критично, проверь TTL кэша на промежуточных LDAP-прокси, если они есть в цепочке.
И последнее, что регулярно ловят на проде: время на хосте Docker расходится с реальным больше чем на пять минут, из-за чего Kerberos-часть проверки (если она вообще используется в цепочке до LDAP) начинает валиться с ошибками времени, которые выглядят как проблема авторизации, а на деле — забытый или сломанный NTP-клиент на хосте.
timedatectl status
6. Альтернативы
Если готовый Web UI с LDAP-модулем не подходит — три рабочих пути в обход.
| Вариант | Когда выбирать |
|---|---|
| OpenVPN + скрипт auth-user-pass-verify с ldapsearch | Нужен полный контроль над логикой проверки, готов писать и поддерживать bash-скрипт руками |
| OpenVPN Access Server (официальный, платный после 2 клиентов) | Нужна поддержка вендора и готовый LDAP/SAML коннектор без самостоятельной сборки |
| WireGuard + внешний identity-провайдер (headscale, Authelia) | Готов отказаться от OpenVPN ради меньшего оверхеда, LDAP тут не нативный, но прикручивается через прокси-слой аутентификации |
Почему в статье выбран именно yyxx/openvpn: LDAP там встроен в UI без танцев со скриптами, MFA идёт из коробки, проект живой — последний релиз вышел в июне 2026 года. Для связки «поднять за час и не читать исходники на Go» это самый короткий путь.
По деньгам разница ощутимая. OpenVPN Access Server — официальный продукт компании OpenVPN Inc, бесплатен на двух одновременных клиентов, дальше идёт по подписке за клиентское место. Для команды из десяти-двадцати человек это уже заметная строка в бюджете ежемесячно, при том что LDAP-модуль там по сути делает то же самое, что и открытый проект — проверяет логин-пароль об LDAP-сервер. WireGuard с внешним identity-провайдером обычно легче по нагрузке на канал и быстрее по установке соединения, но LDAP там не встроен нативно в сам протокол — потребуется отдельный слой вроде Authelia или oauth2-proxy перед точкой входа, что увеличивает число движущихся частей в системе и, соответственно, число мест где что-то может сломаться.
Если LDAP-модуль конкретного проекта не подходит под твою схему каталога (нестандартные атрибуты, кастомные OU), путь через auth-user-pass-verify скрипт остаётся универсальным запасным вариантом — он работает с любым LDAP независимо от того, что умеет конкретный Web UI.
#!/bin/bash
# /opt/app/checkpsw.sh - вызывается OpenVPN через auth-user-pass-verify
USERNAME="$1"
PASSWORD=$(cat "$2" | tail -n1)
ldapwhoami -x -H ldaps://ldap.example.local:636 -D "uid=${USERNAME},ou=users,dc=example,dc=local" -w "${PASSWORD}" >/dev/null 2>&1
exit $?
В server.conf такой скрипт подключается двумя строками, без которых он просто не вызовется — security 2 обязателен, без него OpenVPN откажется запускать внешние скрипты по умолчанию.
script-security 2
auth-user-pass-verify /opt/app/checkpsw.sh via-file
verify-client-cert require
username-as-common-name
Плюс этого пути — полная независимость от конкретного проекта и его темпов разработки. Минус — весь UI придётся либо писать самому, либо смириться с командной строкой и файлами логов вместо кнопок. Для команды из трёх админов, которые и так живут в терминале, это не минус вообще.
Ещё один вариант, который стоит упомянуть отдельно — PAM-модуль pam_ldap в связке с OpenVPN через plugin openvpn-auth-pam.so. Он даёт более стандартизированный путь интеграции через системный PAM-стек, знакомый любому кто настраивал SSH с LDAP-авторизацией. Компромисс тот же — никакого готового веб-интерфейса из коробки, вся логика в конфигах /etc/pam.d.
7. Профилактика
Мониторинг: следи за логами контейнера на предмет повторяющихся AUTH_FAILED с одного IP — это либо забытый скрипт с устаревшим паролем, либо перебор.
docker compose logs -f openvpn | grep -i "AUTH_FAILED"
Бэкап: том ./data целиком — в нём и PKI, и база пользователей веб-интерфейса, и конфиги клиентов. Без него восстановление после смерти хоста — это генерация всей PKI заново и рассылка новых .ovpn всем клиентам разом.
tar -czf openvpn-backup-$(date +%F).tar.gz /opt/openvpn/data
Периодичность — раз в сутки cron-задачей, хранить минимум 14 копий за пределами самого хоста. Капля никотина убивает лошадь, одна необъявленная перезапись тома при docker compose down -v убивает весь PKI без единого бэкапа под рукой.
0 3 * * * tar -czf /backup/openvpn-$(date +\%F).tar.gz /opt/openvpn/data
Восстановление из бэкапа — три команды, если под рукой чистый хост с тем же Docker Compose файлом. Останавливаешь контейнер, разворачиваешь архив на место тома, поднимаешь контейнер заново.
docker compose down
rm -rf /opt/openvpn/data
tar -xzf openvpn-backup-2026-08-20.tar.gz -C /
docker compose up -d
Проверь это восстановление хотя бы раз до того как оно понадобится по-настоящему — на тестовом хосте или в отдельной виртуалке. Бэкап, который никогда не разворачивали, с одинаковой вероятностью может оказаться и рабочим, и битым, и ты узнаешь это только в момент, когда цена ошибки максимальна.
Отдельно проверь отказоустойчивость самого LDAP-звена: временно заблокируй доступ до контроллера домена правилом firewall и убедись что происходит с уже подключёнными клиентами (обычно ничего — сессия уже установлена) и с новыми попытками подключения (они закономерно упадут в таймаут). Так ты заранее знаешь чего ждать от системы при реальном падении контроллера домена, а не выясняешь это в разгар инцидента.
Автозапуск: restart: unless-stopped в compose-файле уже покрывает перезагрузку хоста. Проверь это явно после первой настройки, а не после первого падения продакшна в три ночи.
docker inspect openvpn --format='{{.HostConfig.RestartPolicy.Name}}'
Безопасность: закрой 8833 порт от внешнего мира firewall-правилом, доступ к Web UI только через сам VPN-туннель или через реверс-прокси с whitelist по IP. Заведи отдельного bind-пользователя в LDAP только с правом чтения — не используй административную учётку домена для bind-запросов OpenVPN.
| Мера | Что даёт |
|---|---|
| UFW/firewall на 8833 | Панель админки не торчит наружу |
| fail2ban на логи OpenVPN | Автобан после N неудачных попыток логина |
| Отдельный read-only bind-юзер в LDAP | Компрометация OpenVPN не даёт доступ на запись в каталог |
| SSH hardening на самом хосте | Доступ к хосту = доступ ко всему VPN, защищай его как основную точку входа |
| Ограничение по IP для Web UI | Даже с валидным паролем не зайти не из офисной сети |
Если всё же нужен доступ к панели из внешней сети — например, для удалённого админа — не открывай 8833 напрямую, поставь перед ней nginx с базовой аутентификацией и ограничением по IP. Двойной периметр вместо одного — панель без валидного клиентского сертификата на самом nginx туда даже не достучится.
server {
listen 443 ssl;
server_name vpn-admin.example.com;
ssl_certificate /etc/nginx/certs/vpn-admin.crt;
ssl_certificate_key /etc/nginx/certs/vpn-admin.key;
allow 10.10.0.0/24;
deny all;
location / {
proxy_pass http://127.0.0.1:8833;
proxy_set_header Host $host;
}
}
fail2ban на логи OpenVPN настраивается фильтром по строкам с неудачной аутентификацией — готового джейла под этот конкретный образ в официальном пакете fail2ban нет, пиши свой фильтр под формат логов контейнера. Не перепутай: банить нужно по исходному IP из лога, а не по имени пользователя — иначе один скомпрометированный пароль забанит легитимного человека на всех устройствах разом.
Обновление: перед апдейтом образа — бэкап тома, потом pull новой версии, потом up с пересозданием контейнера. Откат — просто вернуть предыдущий тег образа в compose-файле и поднять снова, том с данными не трогается.
docker compose pull openvpn
docker compose up -d
docker compose logs -f openvpn
Если после обновления LDAP перестал биндиться — откатывай тег образа до предыдущего рабочего, разбирайся в тестовом контейнере, не в проде.
Отдельная мысль про логи и приватность. Логи OpenVPN с включённой LDAP-авторизацией хранят реальные логины сотрудников, время подключений и исходные IP-адреса — это персональные данные в юридическом смысле, а не абстрактная техническая информация. Определи заранее срок хранения этих логов и кто к ним имеет доступ, особенно если компания работает под требованиями вроде 152-ФЗ или GDPR. Ротация логов на уровне Docker (max-size, max-file в конфигурации логгера) — не только про место на диске, но и про то, чтобы не копить персональные данные бессрочно без необходимости.
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
8. FAQ
Почему openvpn не работает после настройки ldap?
Чаще всего — неверный атрибут пользователя (uid вместо sAMAccountName для AD) или недоступность LDAP-порта с контейнера по сети. Проверь ldapsearch с хоста Docker до того как копать глубже в логи OpenVPN.
Как проверить что ldap авторизация работает правильно?
Смотри логи контейнера при попытке логина клиента — там видно успешный или неуспешный bind. Второй способ — файл openvpn-status.log, где при рабочей связке отображается реальный логин пользователя из каталога, а не CN сертификата.
Что делать если после включения ldap старые пользователи не могут зайти?
Это ожидаемое поведение проекта — включение LDAP отключает локальную базу для клиентского логина. Заведи всех активных пользователей в LDAP до переключения, иначе часть команды останется снаружи в момент миграции.
Чем встроенный ldap модуль отличается от скрипта auth-user-pass-verify?
Встроенный модуль настраивается через UI за пять минут, но завязан на конкретный проект и его формат конфигурации. Скрипт с ldapsearch универсален для любого OpenVPN и любого LDAP, но требует написания и поддержки кода руками — никакой магии, чистый bash и exit-коды.
Обязательно ли использовать ldaps вместо обычного ldap?
Да. Обычный ldap:// передаёт пароль пользователя в открытом виде на bind-запросе. Это не теоретическая уязвимость — пароль виден в первом же захваченном tcpdump-дампе на сетевом сегменте между VPN-сервером и контроллером домена.
Можно ли ограничить доступ к vpn только для определённой группы ldap?
Встроенной фильтрации по memberOf в этом проекте на уровне UI нет. Практичный обход — завести отдельную OU именно под VPN-пользователей и включать в base DN только её, а не весь каталог целиком. Более тонкая маршрутизация после аутентификации делается через client-config-dir независимо от способа входа.
Что делать если сертификат внутреннего CA не проходит проверку при подключении к ldaps?
Контейнер не доверяет корневому сертификату вашего внутреннего CA — типичная ситуация для Active Directory Certificate Services. Добавь корневой сертификат CA в доверенное хранилище контейнера или укажи путь до него в настройках LDAP, если поле для этого предусмотрено в интерфейсе.
Нужен ли отдельный сервер для ldap или можно использовать существующий контроллер домена?
Отдельный сервер не нужен — используй существующий контроллер домена или сервер OpenLDAP, который уже обслуживает компанию. Единственное дополнительное требование — создать под VPN отдельную сервисную учётку с правом только на чтение и, если нужна изоляция доступа, отдельную OU именно под VPN-пользователей.
9. Прогноз
Ты поднял OpenVPN с LDAP авторизацией в Docker Compose: сертификатный контур на 1194/udp, веб-панель на 8833 закрытая от внешнего мира, доступ через единый каталог компании. Уволенный сотрудник теряет VPN одновременно с потерей учётки в AD — никакого забытого .ovpn файла, который живёт своей жизнью три года после увольнения.
Дальше это просто эксплуатация: бэкап тома раз в сутки, мониторинг AUTH_FAILED, обновление образа с проверкой на тестовом контейнере перед проливкой в прод, и раз в квартал — выборочная сверка списка активных VPN-сессий со списком реально работающих сотрудников. Рождённый в legacy рефакторинга не боится — но лучше сразу строить без legacy, чем потом его героически разгребать в три часа ночи.
Что имеет смысл сделать дальше, за рамками этой статьи: вынести LDAP-бинд-учётку под ротацию пароля по расписанию, подключить централизованный сбор логов аутентификации VPN в общий SIEM компании, и через полгода эксплуатации пересмотреть список пользователей в выделенной OU — практика показывает, что туда добавляют быстро, а вычищают неохотно, и ровно эта привычка когда-то и привела к необходимости всей этой статьи.
Если после этой статьи что-то не завелось — пиши в комментарии, разберёмся.
Оставайтесь на связи
Рецепты от IT-боли. Без воды, без рекламы, без маркетинговой шелухи.
Подписаться на IT-Аптеку →


