OpenVPN LDAP Docker Compose: настройка сервера за час
<p><!-- ============================================================ SEO SEMANTIC CORE Основной ключ: openvpn ldap docker compose Частотность: точных данных Яндекс.Вордстат нет - live-доступа к сервису у меня нет, цифры не выдумываю. По косвенным признакам (объём обсуждений на форумах, число issue на GitHub-проектах с LDAP-модулем) - запрос среднечастотный, нишевый B2B/sysadmin. LSI ключи: openvpn ldap авторизация, openvpn active directory, openvpn web ui docker, vpn сервер с ldap, openvpn ldap плагин, openvpn auth ldap, docker compose vpn сервер, openvpn веб интерфейс, ldap аутентификация vpn, openvpn самбо ad Long-tail запросы: как настроить openvpn с ldap авторизацией, openvpn не пускает по ldap паролю, openvpn web ui docker compose пример, openvpn ldap active directory настройка, почему openvpn не видит ldap сервер, docker compose openvpn ldap готовый пример Интент: setup / troubleshooting (смешанный, с уклоном в setup) Конкуренты в топ-10 Яндекса по основному ключу: точный список без доступа к живой выдаче Яндекса дать не могу - это тоже флагирую честно, а не подставляю случайные домены. Ориентировочно в нише конкурируют профильные хабы (habr.com), документация проектов на GitHub и англоязычные ServerFault/askubuntu-переводы. ============================================================ --></p>
"Быстрый
<br />
Рабочая связка — OpenVPN сервер на базе образа yyxx/openvpn (проект GavinTan/openvpn) плюс встроенный Web UI на порту 8833. LDAP включается одной галкой в разделе "Системные настройки" после того как в docker-compose.yml поднят единственный контейнер. Для OpenLDAP атрибут пользователя — uid, для <a class="wpil_keyword_link" href="https://it-apteka.com/category/windows-server/" target="_blank" rel="noopener" title="Windows Server" data-wpil-keyword-link="linked" data-wpil-monitor-id="3428">Windows</a> AD — sAMAccountName. Без встроенного LDAP-модуля используешь связку OpenVPN + <a class="wpil_keyword_link" href="https://it-apteka.com/category/scripts/" target="_blank" rel="noopener" title="Скрипты" data-wpil-keyword-link="linked" data-wpil-monitor-id="3433">скрипт</a> auth-user-pass-verify с ldapsearch — дольше, но работает с любым сервером и любым UI. Ниже — рабочий compose-файл, LDAP-параметры и весь путь от нуля до подключённого клиента.<br />
<h1>Настройка OpenVPN сервера с LDAP авторизацией в Docker Compose</h1>
<h2>1. Диагноз</h2>
<p>Поднял OpenVPN. Сертификаты нарезал, .ovpn раздал. Через месяц отдел кадров привёл трёх новых сотрудников и попросил доступ «как у всех». Ты либо генеришь сертификат руками на каждого, либо вспоминаешь что где-то читал про LDAP.</p>
<p>Знакомо? Задача <strong>настройка openvpn </strong><a title="Docker Compose для домашнего сервера: структура, reverse proxy, секреты и обновления" href="https://it-apteka.com/docker-compose-dlja-domashnego-servera-struktura-reverse-proxy-sekrety-i-obnovlenija/" target="_blank" rel="noopener" data-wpil-monitor-id="3408">сервера с ldap авторизацией в docker compose</a> возникает ровно в этот момент — когда сертификатов становится больше пяти и увольнение сотрудника превращается в квест «а где мы храним отозванные crl.</p>
<p>Что получишь на выходе: VPN-сервер, который спрашивает логин-пароль из вашего LDAP или Active Directory, а не выдаёт доступ каждому у кого завалялся .ovpn файл трёхлетней давности. Уволили человека в AD — он тут же не может подключиться к VPN. Никаких лишних телодвижений.</p>
<p>Из жизни: однажды поднимал VPN у клиента после аудита безопасности, который нашёл 47 активных клиентских сертификатов при штате в 22 человека. Половина принадлежала людям, уволенным за два-три года до этого. Причина банальна — никто не вёл реестр выданных сертификатов, а CRL обновляли раз в квартал по остаточному принципу. LDAP такую ситуацию структурно исключает: нет отдельного пользователя в VPN, есть только отражение того, что уже есть в каталоге компании.</p>
<p>Время на весь цикл: час, если LDAP уже поднят и ты знаешь base DN. Три часа, если приходится разбираться в структуре чужого каталога методом тыка.</p>
<p>Нужно: <a title="Команды Docker: шпаргалка с примерами для сервера и продакшена" href="https://it-apteka.com/docker-shpargalka-i-primery/" target="_blank" rel="noopener" data-wpil-monitor-id="3412">сервер с Docker</a> и Docker Compose, доступ к LDAP или AD (адрес, порт, bind DN, base DN), открытый порт 1194/udp снаружи и порт под веб-интерфейс.</p>
<p>В статье пройдём: архитектуру связки, установку OpenVPN с готовым Web UI, включение LDAP авторизации, проверку что всё работает, <a title="Резервное копирование MikroTik RouterOS 7 в Telegram: рабочий скрипт и разбор ошибок" href="https://it-apteka.com/rezervnoe-kopirovanie-mikrotik-routeros-7-v-telegram-avtomatizacija-za-20-minut/" target="_blank" rel="noopener" data-wpil-monitor-id="3409">разбор типичных ошибок</a>, альтернативный вариант без готового UI через auth-user-pass-verify, и профилактику — что бэкапить и как обновляться не роняя продакшн.</p>
<p>Отдельно проговорю то, что обычно упускают в статьях про VPN плюс LDAP: разницу между авторизацией на уровне подключения и авторизацией на уровне ресурса. LDAP-бинд отвечает только на вопрос «существует ли такой пользователь с таким паролем в каталоге». Он не отвечает на вопрос «имеет ли этот пользователь право видеть подсеть бухгалтерии». Второе делается отдельно — через группы в CCD (client-config-dir) или через маршрутизацию на файрволе. Смешивать эти два уровня — частая ошибка, из-за которой потом кто-то из отдела продаж внезапно видит шару финансового отдела.</p>
<p>Второй момент, который стоит понимать заранее: сертификат клиента и LDAP-логин — это два разных фактора, а не один и тот же механизм под разными именами. TLS-сертификат подтверждает что устройство доверенное (первый фактор), LDAP-бинд подтверждает что человек за устройством — тот, за кого себя выдаёт (второй фактор). Убрать сертификат и оставить только логин-пароль можно, но тогда <a title="Почему VPN не работает: DPI, блокировки, утечки DNS и что реально помогает в 2025-2026" href="https://it-apteka.com/pochemu-vpn-ne-rabotaet-dpi-blokirovki-utechki-dns-i-chto-realno-pomogaet-v-2025-2026/" target="_blank" rel="noopener" data-wpil-monitor-id="3415">VPN</a> становится доступен с любого устройства при утечке пароля — для корпоративного контура это обычно неприемлемо.</p>
<p>Третий момент — откуда вообще берётся выбор конкретного инструмента для этой задачи. Готовых Web UI для OpenVPN на GitHub десятки, но реально живых, с поддержкой LDAP из коробки и обновлениями хотя бы раз в несколько месяцев — единицы. В статье используется именно тот вариант, где LDAP не приколочен сбоку отдельным патчем от энтузиаста три года назад, а поддерживается как часть основного функционала проекта с активной историей релизов. Это не единственно верный выбор, но осознанный — критерии подбора описаны отдельно в разделе про альтернативы.</p>
<pre class="mermaid">%%{init: {
'theme': 'base',
'themeVariables': {
'primaryColor': '#ffffff',
'primaryTextColor': '#1e293b',
'primaryBorderColor': '#94a3b8',
'lineColor': '#64748b',
'fontSize': '15px',
'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
},
'flowchart': {'curve': 'linear', 'nodeSpacing': 50, 'rankSpacing': 50}
}}%%
flowchart TD
A["Клиент OpenVPN"] --> B["OpenVPN сервер порт 1194 udp"]
B --> C["Проверка сертификата"]
C --> D["Запрос логин пароль"]
D --> E["LDAP или Active Directory"]
E --> F["Bind успешен"]
F --> G["Доступ выдан"]
style A fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
style E fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#c2410c
style G fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
</pre>
<h2>2. Причины</h2>
<p>Почему именно LDAP, а не просто сертификаты — и что ломается если делать по-другому.</p>
<table>
<tbody>
<tr>
<th>Причина держать VPN на сертификатах без LDAP</th>
<th>Почему это ломается на масштабе</th>
</tr>
<tr>
<td>Каждому клиенту — свой .crt и .key</td>
<td>Уволенного сотрудника нужно вручную отозвать через CRL, забыл — доступ живёт вечно</td>
</tr>
<tr>
<td>Пароль общий на всех</td>
<td>Утечка одного пароля — утечка доступа для всей компании, ротация превращается в рассылку по всем</td>
</tr>
<tr>
<td>Локальная база пользователей на самом VPN</td>
<td>Двойной источник правды — AD отдельно, VPN отдельно, рассинхрон гарантирован через месяц</td>
</tr>
<tr>
<td>Ручное управление доступом в Excel-табличке</td>
<td>Никто не помнит зачем у Иванова доступ с 2019 года, аудит невозможен</td>
</tr>
<tr>
<td>Один админ помнит все пароли наизусть</td>
<td>Админ в отпуске — доступ никто не даёт и не отзывает</td>
</tr>
</tbody>
</table>
<p>LDAP или AD решает это одним центром правды. Уволил в AD — через минуту (или сразу, если TTL сессии короткий) человек не может залогиниться в VPN. Никакого дублирования учёток.</p>
<p>Есть ещё практический довод в пользу LDAP, который обычно всплывает уже после внедрения, а не до: онбординг нового сотрудника перестаёт требовать участия сетевого инженера. HR или IT-support заводит учётку в AD по стандартному чек-листу приёма на работу, и человек в тот же день получает доступ к VPN без отдельного тикета «выпустите мне сертификат». До внедрения LDAP этот процесс почти всегда завязан на конкретного человека, который умеет генерировать сертификаты — а если этот человек в отпуске, новый сотрудник сидит без доступа до его возвращения.</p>
<p>Есть и обратная сторона, про которую молчат маркетинговые страницы готовых VPN-решений: LDAP добавляет вашему VPN точку отказа. Если контроллер домена лёг — лежит и авторизация в VPN, даже если сам туннель технически жив. Это не повод отказываться от связки, но повод держать резервный контроллер домена или хотя бы кэширующий LDAP-прокси рядом. Планируй отказоустойчивость каталога отдельно от планирования отказоустойчивости самого VPN — это разные системы с разными точками отказа.</p>
<p>Проверка отказоустойчивости LDAP на этапе планирования дешевле, чем разбор инцидента, когда весь офис не может подключиться в понедельник утром потому что единственный контроллер домена ушёл на плановую перезагрузку без предупреждения. Заложи хотя бы второй контроллер домена в качестве резервного LDAP_URL, если такая возможность есть в конфигурации UI, или держи под рукой процедуру быстрого переключения на резервный адрес вручную.</p>
<p>Ещё одна причина, которую сисадмины обнаруживают только когда уже поздно: аудит доступа. Регулятор, служба безопасности или просто здравый смысл рано или поздно спрашивают «кто и когда заходил в VPN за последний квартал». С локальной базой пользователей на самом сервере VPN этот вопрос решается ковырянием в логах вручную. С LDAP — централизованный лог событий аутентификации плюс сопоставление логина с реальным сотрудником из каталога, а не с абстрактным client047, который сгенерировали два года назад и забыли на кого.</p>
<h2>3. Рецепт</h2>
<h3>Подготовка</h3>
<p>Проверь среду перед стартом — три вещи, без них можно не начинать.</p>
<pre><code class="language-bash">
docker --version
docker compose version
</code></pre>
"Проверка
<br />
Прежде чем городить контейнеры, убедись что твой сервер вообще видит LDAP на сетевом уровне. Используй ldapsearch с хоста — если он не отвечает отсюда, из контейнера тоже не ответит.<br />
<pre><code class="language-bash">
ldapsearch -x -H ldap://ldap.example.local:389 -D "cn=binduser,dc=example,dc=local" -w 'пароль' -b "dc=example,dc=local" "(uid=testuser)"
</code></pre>
<p>Результат: должна вернуться запись пользователя testuser. Если получаешь «Can’t contact LDAP server» — проблема на <a class="wpil_keyword_link" href="https://it-apteka.com/category/networks/" target="_blank" rel="noopener" title="Сети" data-wpil-keyword-link="linked" data-wpil-monitor-id="3431">сети</a> или в firewall, дальше идти рано.</p>
<h3>Системные требования</h3>
<table>
<tbody>
<tr>
<th>Компонент</th>
<th>Версия на момент публикации</th>
</tr>
<tr>
<td><a class="wpil_keyword_link" href="https://it-apteka.com/tag/docker/" target="_blank" rel="noopener" title="Docker" data-wpil-keyword-link="linked" data-wpil-monitor-id="3429">Docker</a> Engine</td>
<td>27.x</td>
</tr>
<tr>
<td>Docker Compose</td>
<td>v2 (плагин compose, не отдельный docker-compose 1.x)</td>
</tr>
<tr>
<td>Образ OpenVPN + Web UI</td>
<td>yyxx/openvpn (проект GavinTan/openvpn), актуальный релиз v2.5.2</td>
</tr>
<tr>
<td>ОС хоста</td>
<td><a title="Настройка статического IP в Ubuntu 22.04-24.04 и Debian 12-13: Новый мир Netplan" href="https://it-apteka.com/nastrojka-staticheskogo-ip-v-ubuntu-22-04-24-04-i-debian-12-13-nov/" target="_blank" rel="noopener" data-wpil-monitor-id="3413">Debian 12 / Ubuntu</a> 22.04+ (любой Linux с поддержкой TUN)</td>
</tr>
<tr>
<td>LDAP-сервер</td>
<td>OpenLDAP 2.5+ либо Windows Server AD DS 2016+</td>
</tr>
</tbody>
</table>
<p>На момент публикации актуальна версия v2.5.2. Перед установкой проверь свежие релизы на странице проекта — LDAP-модуль в нём допиливают, баги правят регулярно.</p>
<h3>Таблица портов</h3>
<table>
<tbody>
<tr>
<th>Порт</th>
<th>Протокол</th>
<th>Назначение</th>
<th>Доступен снаружи</th>
</tr>
<tr>
<td>1194</td>
<td>UDP</td>
<td>Туннель OpenVPN</td>
<td>Да</td>
</tr>
<tr>
<td>8833</td>
<td>TCP</td>
<td>Web UI администрирования</td>
<td>Нет — только через VPN или за реверс-прокси с ограничением по IP</td>
</tr>
<tr>
<td>389 / 636</td>
<td>TCP</td>
<td>LDAP / LDAPS до вашего каталога</td>
<td>Нет — внутренняя сеть или VPN-туннель до контроллера домена</td>
</tr>
</tbody>
</table>
<h3>Шаг 1. Каталог и docker-compose.yml</h3>
<p>Создай рабочую директорию и файл compose. Том ./data хранит всё — конфиги, сертификаты, базу пользователей веб-интерфейса.</p>
<pre><code class="language-bash">
mkdir -p /opt/openvpn && cd /opt/openvpn
</code></pre>
<pre><code class="language-text">
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
</code></pre>
<p>Результат: файл готов, никаких опечаток в отступах YAML — это первое место где всё падает молча.</p>
<p>Обрати внимание на том <code>./data</code> — это не мелочь для галочки. Внутри него после первого запуска появится вся PKI-инфраструктура (CA, серверный сертификат, CRL), база пользователей веб-панели, конфиги клиентов и сама LDAP-конфигурация после того как её сохранишь через UI. По факту это единственное состояние, которое отличает твой инстанс от чистого образа. Потерял том — потерял всё, придётся заново генерировать PKI и рассылать новые .ovpn всем клиентам одновременно, а это письмо всей компании и день поддержки.</p>
<p>Если <a title="Перенос VPS сервера без потери данных: пошаговая инструкция" href="https://it-apteka.com/perenos-vps-servera-bez-poteri-dannyh-poshagovaja-instrukcija/" target="_blank" rel="noopener" data-wpil-monitor-id="3416">сервер</a> стоит за NAT или ты арендуешь VPS у облачного провайдера — заранее пробрось 1194/udp на уровне провайдерского firewall или Security Group, а не только внутри iptables хоста. Забытое правило на уровне облака — самая частая причина «контейнер поднялся, а клиенты не коннектятся» в первые полчаса после разворачивания.</p>
<h3>Шаг 2. Первый запуск</h3>
<pre><code class="language-bash">
docker compose up -d
docker compose logs -f openvpn
</code></pre>
<p>Жди строку о старте веб-сервера на 8833 и OpenVPN-демона на 1194. Если контейнер перезапускается по кругу — смотри логи, в 90% случаев это конфликт портов с уже висящим на хосте OpenVPN.</p>
<h3>Шаг 3. Вход в Web UI и базовая настройка</h3>
<p>Открой http://<IP-сервера>:8833. Логин и пароль по умолчанию — admin/admin. Первым делом меняешь пароль, это не опция.</p>
"Смена
<br />
Пароль admin/admin боты сканируют по всему интернету за считанные часы после того как порт торчит наружу. Раздел "Системные настройки" — меняй пароль сразу после первого входа, до любых других шагов.<br />
<p>В разделе «Управление → Клиенты» сгенерируй тестовый .ovpn — убедись что базовый сертификатный контур работает ещё до включения LDAP. Так проще отделить проблемы туннеля от проблем авторизации.</p>
<p>Вот тут важно не спешить. Подключись этим тестовым конфигом с любого устройства и проверь что туннель поднимается, пинги идут, интернет через VPN работает — если включена соответствующая опция. Только после этого имеет смысл трогать LDAP. Если начинаешь сразу с LDAP и что-то не работает — непонятно, ломается сертификатный контур или сама авторизация, и диагностика превращается в гадание вместо последовательной проверки.</p>
<p>Заодно проверь раздел «Системные настройки → OpenVPN» — там задаются базовые параметры сервера: подсеть для клиентов, <a class="wpil_keyword_link" href="https://it-apteka.com/tag/dns/" target="_blank" rel="noopener" title="DNS" data-wpil-keyword-link="linked" data-wpil-monitor-id="3432">DNS</a>-серверы которые будут проталкиваться клиентам, режим полного тоннелирования трафика или только доступа к внутренним ресурсам. По умолчанию проект работает в режиме split-tunnel (доступ только к внутренней сети), full-tunnel (весь трафик клиента через VPN) включается отдельной галкой. Для корпоративного доступа к ресурсам обычно достаточно split-tunnel — он не грузит канал сервера чужим интернет-трафиком.</p>
<h3>Шаг 4. Настройка LDAP-подключения</h3>
<p>В разделе системных настроек включаешь LDAP-аутентификацию. После включения локальные VPN-аккаунты веб-интерфейса перестают работать для клиентского логина — учти это до того как отключишь себе доступ.</p>
<table>
<tbody>
<tr>
<th>Параметр</th>
<th>Значение для OpenLDAP</th>
<th>Значение для Windows AD</th>
</tr>
<tr>
<td>LDAP_URL</td>
<td>ldaps://ldap.example.local:636</td>
<td>ldaps://dc01.example.local:636</td>
</tr>
<tr>
<td>LDAP_USER_ATTRIBUTE</td>
<td>uid</td>
<td>sAMAccountName</td>
</tr>
<tr>
<td>Base DN</td>
<td>ou=users,dc=example,dc=local</td>
<td>ou=vpn-users,dc=example,dc=local</td>
</tr>
<tr>
<td>Bind DN</td>
<td>cn=vpnbind,dc=example,dc=local</td>
<td>vpnbind@example.local</td>
</tr>
</tbody>
</table>
<p>LDAP_USER_ATTR_IPADDR_NAME и LDAP_USER_ATTR_CONFIG_NAME — опциональные поля. Если хочешь закрепить статический IP за пользователем через атрибут каталога, используй свободное текстовое поле вроде mobile или homePhone — большинство схем LDAP такие поля не занимают ничем важным.</p>
<p>Используй ldaps:// (TLS), а не plain ldap://. Пароли пользователей летят по сети в открытом виде на bind-запросе, если TLS не включён — это не гипотетический риск, а буквально пароль в tcpdump.</p>
<p>По факту разница между 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 — выясни это заранее, а не после того как раздал доступ всей компании вместо одного отдела.</p>
<p>Не перепутай base DN с bind DN — это классическая ошибка новичков в LDAP. Base DN — это точка входа в дерево каталога, откуда начинается поиск пользователя. Bind DN — это учётка, от имени которой сервис делает сам запрос на поиск. Перепутал местами — получишь либо «пользователь не найден» при живом пользователе, либо ошибку аутентификации самого сервиса.</p>
<pre class="mermaid">%%{init: {
'theme': 'base',
'themeVariables': {
'primaryColor': '#f8fafc',
'primaryTextColor': '#1e293b',
'primaryBorderColor': '#94a3b8',
'noteBkgColor': '#fefce8',
'noteTextColor': '#713f12',
'noteBorderColor': '#fbbf24',
'actorBkg': '#f8fafc',
'actorBorder': '#94a3b8',
'actorTextColor': '#1e293b',
'fontSize': '15px',
'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
},
'sequence': {
'mirrorActors': false,
'messageAlign': 'center',
'actorMargin': 120,
'width': 160,
'noteMargin': 12
}
}}%%
sequenceDiagram
participant K as Клиент
participant V as OpenVPN сервер
participant L as LDAP сервер
K->>V: Сертификат плюс логин пароль
V->>L: Bind запрос по LDAPS
L->>V: Успех или отказ
V->>K: Доступ разрешён либо отклонён
</pre>
<h3>Шаг 5. Генерация клиентского конфига под LDAP-аккаунт</h3>
<p>После включения LDAP клиенты логинятся своей учёткой из каталога, а не по сертификату-с-паролем. Сертификат остаётся первым фактором (TLS), логин-пароль — вторым. Итого два фактора без всякого MFA — уже неплохо для старта.</p>
<pre><code class="language-bash">
docker compose exec openvpn cat /data/client.ovpn
</code></pre>
<p>Скачай файл, раздай пользователю, попроси ввести свой AD/LDAP логин при подключении. Пароль от VPN теперь — это пароль от домена. Второй пароль запоминать никому не нужно, и это единственная причина по которой пользователи это одобряют.</p>
<h3>Шаг 5.1. Шифрование и параметры туннеля</h3>
<p>По умолчанию проект генерирует конфиг с современным набором — AES-256-GCM для канала данных и TLS 1.2+ для управляющего канала. Трогать эти параметры без веской причины не стоит: устаревшие шифры вроде BF-CBC оставлены только для совместимости с древними клиентами и тянут за собой уязвимости, о которых сообщество OpenVPN писало ещё лет десять назад.</p>
<p>Раздельно стоит проверить длину ключа Diffie-Hellman — для новых установок это 2048 или 3072 бита, генерируется автоматически при первом запуске. Если мигрируешь с древней инсталляции, где DH-параметры на 1024 бита — пересоздай их, это не тот случай где «работает — не трогай» уместен.</p>
<pre><code class="language-bash">
docker compose exec openvpn openssl dhparam -out /data/pki/dh.pem 2048
</code></pre>
<p>Что касается full-tunnel против split-tunnel — выбор зависит от модели угроз, а не от удобства. Full-tunnel (весь трафик клиента через VPN) даёт контроль над тем что происходит с рабочим ноутбуком в публичном Wi-Fi аэропорта, но увеличивает нагрузку на канал <a title="VPN на MikroTik: полный гайд 2026 — WireGuard, L2TP/IPsec, IKEv2, настройка сервера и клиента" href="https://it-apteka.com/vpn-na-mikrotik-polnyj-gajd-2026-wireguard-l2tp-ipsec-ikev2-nastrojka-servera-i-klienta/" target="_blank" rel="noopener" data-wpil-monitor-id="3410">сервера и требует настройки</a> DNS-утечек отдельно. Split-tunnel (только внутренние ресурсы через VPN) легче для инфраструктуры, но оставляет остальной трафик пользователя вне вашего контроля. Для доступа к внутренним сервисам компании обычно достаточно split-tunnel; full-tunnel имеет смысл только если явно нужна <a class="wpil_keyword_link" href="https://it-apteka.com/category/security/" target="_blank" rel="noopener" title="Безопасность" data-wpil-keyword-link="linked" data-wpil-monitor-id="3426">защита</a> трафика в недоверенных сетях.</p>
<h3>Шаг 6. MFA и ограничение доступа по группам</h3>
<p>Проект поддерживает MFA поверх LDAP — второй фактор настраивается пользователем самостоятельно через личную страницу после логина под своей учёткой. Включаешь опцию MFA для конкретного клиента в разделе «Клиенты», пользователь при следующем логине на пользовательской странице сканирует QR-код в приложении-аутентификаторе (Google Authenticator, Authy, любой TOTP-совместимый) и с этого момента при скачивании конфига в него подставляется актуальный TOTP-параметр.</p>
<p>Ограничение доступа по группам LDAP из коробки в этом проекте не такое гибкое как хотелось бы — на уровне UI фильтрации по memberOf нет. Рабочий обходной путь — завести отдельный base DN или отдельную OU именно под VPN-пользователей и добавлять туда только тех, кому доступ разрешён. Управление доступом идёт через членство в этой OU, а не через сложную фильтрацию атрибутов на лету. Это грубее чем полноценный RBAC, зато предсказуемо и не ломается при следующем обновлении образа.</p>
<p>Если нужна более тонкая маршрутизация — оставшийся контроль делай на уровне CCD (client-config-dir): для каждого клиента можно прописать индивидуальные push-маршруты и ограничения подсети, независимо от того как он аутентифицировался.</p>
<h2>4. Проверка</h2>
<p>Три команды, которые показывают что связка живая от начала до конца.</p>
<pre><code class="language-bash">
docker compose ps
</code></pre>
<p>Результат: контейнер openvpn в статусе Up, без Restarting.</p>
<pre><code class="language-bash">
docker compose logs --tail=50 openvpn | grep -i ldap
</code></pre>
<p>Результат: строки о успешном bind к LDAP-серверу при попытке логина, без ошибок invalid credentials на валидном пароле.</p>
<pre><code class="language-bash">
docker compose exec openvpn cat /data/openvpn-status.log
</code></pre>
<p>Результат: список активных клиентских подключений с реальным именем пользователя из LDAP, а не CN сертификата — если видишь именно логин пользователя, а не общий client01, авторизация встроена в цепочку правильно.</p>
<p>Четвёртая проверка, которую часто пропускают — реальный тест с невалидным паролем. Подключись тестовым клиентом и осознанно введи неправильный пароль. Сервер обязан отклонить подключение с чётким сообщением об ошибке аутентификации, а не молча пустить внутрь. Звучит очевидно, но именно эта проверка ловит ситуации когда LDAP включён в UI, но фактически не применяется из-за опечатки в пути к скрипту проверки или из-за того что старый сертификатный контур продолжает пускать всех подряд параллельно с новым LDAP-контуром.</p>
<pre><code class="language-bash">
docker compose logs --tail=20 openvpn | grep -i "denied\|invalid"
</code></pre>
<p>Пятая проверка — время отклика LDAP-бинда. Если аутентификация занимает больше двух-трёх секунд на одного пользователя, при массовом подключении в начале рабочего дня (все заходят в 9:00 одновременно) сервер захлебнётся очередью bind-запросов. Замерь время одного логина секундомером или через time при ручном ldapsearch — это дешевле чем разбираться с жалобами всего отдела в понедельник утром.</p>
<pre><code class="language-bash">
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)"
</code></pre>
<h2>5. Осложнения</h2>
<table>
<tbody>
<tr>
<th>Ошибка</th>
<th>Причина</th>
<th>Решение</th>
<th>Команда</th>
</tr>
<tr>
<td>AUTH_FAILED сразу после ввода пароля</td>
<td>Неверный LDAP_USER_ATTRIBUTE — для AD часто по ошибке ставят uid вместо sAMAccountName</td>
<td>Смени атрибут на sAMAccountName для AD, перезапусти сервис</td>
<td>
<pre><code class="language-bash">docker compose restart openvpn</code></pre>
</td>
</tr>
<tr>
<td>Can’t contact LDAP server в логах</td>
<td>Контейнер не видит LDAP-хост по сети — чаще всего firewall блокирует 636 порт между Docker-хостом и контроллером домена</td>
<td>Проверь доступность порта с хоста, открой правило firewall именно для исходящего 636/tcp</td>
<td>
<pre><code class="language-bash">nc -zv dc01.example.local 636</code></pre>
</td>
</tr>
<tr>
<td>Web UI не открывается после смены пароля</td>
<td>Пароль сброшен, но сессия в браузере закэширована старым токеном</td>
<td>Очисти куки для домена/IP или открой в приватном окне</td>
<td>—</td>
</tr>
<tr>
<td>Клиент подключается, но интернета нет</td>
<td>Не настроен NAT/маскарадинг на хосте, или забыт redirect-gateway</td>
<td>Добавь iptables MASQUERADE правило для интерфейса tun, проверь push redirect-gateway в конфиге</td>
<td>
<pre><code class="language-bash">iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE</code></pre>
</td>
</tr>
<tr>
<td>Сертификат протух через год без предупреждения</td>
<td>Дефолтный срок жизни клиентских сертификатов — 1 год, без EASYRSA_CERT_EXPIRE это тихо ломается</td>
<td>Задай переменную окружения с нужным сроком до генерации сертификатов</td>
<td>
<pre><code class="language-bash">EASYRSA_CERT_EXPIRE=3650</code></pre>
</td>
</tr>
<tr>
<td>После включения LDAP старые локальные VPN-аккаунты не логинятся</td>
<td>Это ожидаемое поведение — LDAP полностью заменяет локальную базу для клиентского логина</td>
<td>Заведи всех активных пользователей в LDAP заранее, до переключения флага</td>
<td>—</td>
</tr>
<tr>
<td>Клиент дропается ровно раз в час без видимой причины</td>
<td>Истёк tls-crypt/tls-auth ключ пересогласования, либо не совпадает reneg-sec на <a title="Настройка NTP MikroTik: клиент, сервер и всё что ломается без него" href="https://it-apteka.com/nastrojka-ntp-na-mikrotik-klient-i-server-shpargalka-dlja-ros-6-i-7/" target="_blank" rel="noopener" data-wpil-monitor-id="3411">клиенте и сервере</a></td>
<td>Синхронизируй параметр reneg-sec в обоих конфигах, перегенерируй клиентский профиль</td>
<td>
<pre><code class="language-bash">grep reneg-sec /data/server.conf</code></pre>
</td>
</tr>
<tr>
<td>Web UI отвечает медленно при большом числе клиентов в LDAP</td>
<td>Base DN указывает на весь каталог целиком, а не на конкретную OU — каждый поиск перебирает тысячи записей</td>
<td>Сузь base DN до конкретной организационной единицы с VPN-пользователями</td>
<td>—</td>
</tr>
</tbody>
</table>
<p>Ну и запросы у вас — сказала база LDAP и повисла на bind-таймауте в 30 секунд, потому что кто-то забыл индекс на атрибуте поиска. Проверяй индексацию базового DN заранее, а не когда триста клиентов одновременно ловят таймаут в девять утра понедельника.</p>
<p>Отдельно про сертификат сервера при LDAPS. Если контроллер домена использует самоподписанный или внутренний корпоративный CA (частая ситуация для AD CS), контейнер OpenVPN может не доверять этому CA по умолчанию и рвать TLS-соединение до LDAP ещё на этапе установки сессии. Решение — положить корневой сертификат внутреннего CA в системное хранилище доверенных сертификатов внутри контейнера или указать путь до него явно в настройках LDAP, если UI это поддерживает. Диагностируется это одной командой openssl s_client с указанием LDAPS-хоста — если рукопожатие TLS падает с verify error, дело именно в доверии к CA, а не в самой LDAP-логике.</p>
<pre><code class="language-bash">
openssl s_client -connect dc01.example.local:636 -showcerts
</code></pre>
<p>Ещё одна практическая мелочь: если после смены пароля в AD пользователь продолжает логиниться старым паролем ещё какое-то время — это не баг проекта, а особенность LDAP-кэширования на стороне контроллера домена или прокси между OpenVPN и AD. Если такое поведение критично, проверь TTL кэша на промежуточных LDAP-прокси, если они есть в цепочке.</p>
<p>И последнее, что регулярно ловят на проде: время на хосте Docker расходится с реальным больше чем на пять минут, из-за чего Kerberos-часть проверки (если она вообще используется в цепочке до LDAP) начинает валиться с ошибками времени, которые выглядят как проблема авторизации, а на деле — забытый или сломанный NTP-клиент на хосте.</p>
<pre><code class="language-bash">
timedatectl status
</code></pre>
<h2>6. Альтернативы</h2>
<p>Если готовый Web UI с LDAP-модулем не подходит — три рабочих пути в обход.</p>
<table>
<tbody>
<tr>
<th>Вариант</th>
<th>Когда выбирать</th>
</tr>
<tr>
<td>OpenVPN + скрипт auth-user-pass-verify с ldapsearch</td>
<td>Нужен полный контроль над логикой проверки, готов писать и поддерживать <a class="wpil_keyword_link" href="https://it-apteka.com/tag/bash/" target="_blank" rel="noopener" title="Bash" data-wpil-keyword-link="linked" data-wpil-monitor-id="3430">bash</a>-скрипт руками</td>
</tr>
<tr>
<td>OpenVPN Access Server (официальный, платный после 2 клиентов)</td>
<td>Нужна поддержка вендора и готовый LDAP/SAML коннектор без самостоятельной сборки</td>
</tr>
<tr>
<td>WireGuard + внешний identity-провайдер (headscale, Authelia)</td>
<td>Готов отказаться от OpenVPN ради меньшего оверхеда, LDAP тут не нативный, но прикручивается через прокси-слой аутентификации</td>
</tr>
</tbody>
</table>
<p>Почему в статье выбран именно yyxx/openvpn: LDAP там встроен в UI без танцев со скриптами, MFA идёт из коробки, проект живой — последний релиз вышел в июне 2026 года. Для связки «поднять за час и не читать исходники на Go» это самый короткий путь.</p>
<p>По деньгам разница ощутимая. OpenVPN Access Server — официальный продукт компании OpenVPN Inc, бесплатен на двух одновременных клиентов, дальше идёт по подписке за клиентское место. Для команды из десяти-двадцати человек это уже заметная строка в бюджете ежемесячно, при том что LDAP-модуль там по сути делает то же самое, что и открытый проект — проверяет логин-пароль об LDAP-сервер. WireGuard с внешним identity-провайдером обычно легче по нагрузке на канал и быстрее по установке соединения, но LDAP там не встроен нативно в сам протокол — потребуется отдельный слой вроде Authelia или oauth2-proxy перед точкой входа, что увеличивает число движущихся частей в системе и, соответственно, число мест где что-то может сломаться.</p>
<p>Если LDAP-модуль конкретного проекта не подходит под твою схему каталога (нестандартные атрибуты, кастомные OU), путь через auth-user-pass-verify скрипт остаётся универсальным запасным вариантом — он работает с любым LDAP независимо от того, что умеет конкретный Web UI.</p>
<pre><code class="language-bash">
#!/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 $?
</code></pre>
<p>В server.conf такой скрипт подключается двумя строками, без которых он просто не вызовется — security 2 обязателен, без него OpenVPN откажется запускать внешние скрипты по умолчанию.</p>
<pre><code class="language-text">
script-security 2
auth-user-pass-verify /opt/app/checkpsw.sh via-file
verify-client-cert require
username-as-common-name
</code></pre>
<p>Плюс этого пути — полная независимость от конкретного проекта и его темпов разработки. Минус — весь UI придётся либо писать самому, либо смириться с командной строкой и файлами логов вместо кнопок. Для команды из трёх админов, которые и так живут в терминале, это не минус вообще.</p>
<p>Ещё один вариант, который стоит упомянуть отдельно — PAM-модуль pam_ldap в связке с OpenVPN через plugin openvpn-auth-pam.so. Он даёт более стандартизированный путь интеграции через системный PAM-стек, знакомый любому кто настраивал SSH с LDAP-авторизацией. Компромисс тот же — никакого готового веб-интерфейса из коробки, вся логика в конфигах /etc/pam.d.</p>
<h2>7. Профилактика</h2>
<p>Мониторинг: следи за логами контейнера на предмет повторяющихся AUTH_FAILED с одного IP — это либо забытый скрипт с устаревшим паролем, либо перебор.</p>
<pre><code class="language-bash">
docker compose logs -f openvpn | grep -i "AUTH_FAILED"
</code></pre>
<p>Бэкап: том ./data целиком — в нём и PKI, и база пользователей веб-интерфейса, и конфиги клиентов. Без него восстановление после смерти хоста — это генерация всей PKI заново и рассылка новых .ovpn всем клиентам разом.</p>
<pre><code class="language-bash">
tar -czf openvpn-backup-$(date +%F).tar.gz /opt/openvpn/data
</code></pre>
<p>Периодичность — раз в сутки cron-задачей, хранить минимум 14 копий за пределами самого хоста. Капля никотина убивает лошадь, одна необъявленная перезапись тома при docker <a title="Бэкап Docker Compose: автоматизация через Bash и Crontab" href="https://it-apteka.com/311/" target="_blank" rel="noopener" data-wpil-monitor-id="3414">compose</a> down -v убивает весь PKI без единого бэкапа под рукой.</p>
<pre><code class="language-text">
0 3 * * * tar -czf /backup/openvpn-$(date +\%F).tar.gz /opt/openvpn/data
</code></pre>
<p>Восстановление из бэкапа — три команды, если под рукой чистый хост с тем же Docker Compose файлом. Останавливаешь контейнер, разворачиваешь архив на место тома, поднимаешь контейнер заново.</p>
<pre><code class="language-bash">
docker compose down
rm -rf /opt/openvpn/data
tar -xzf openvpn-backup-2026-08-20.tar.gz -C /
docker compose up -d
</code></pre>
<p>Проверь это восстановление хотя бы раз до того как оно понадобится по-настоящему — на тестовом хосте или в отдельной виртуалке. Бэкап, который никогда не разворачивали, с одинаковой вероятностью может оказаться и рабочим, и битым, и ты узнаешь это только в момент, когда цена ошибки максимальна.</p>
<p>Отдельно проверь отказоустойчивость самого LDAP-звена: временно заблокируй доступ до контроллера домена правилом firewall и убедись что происходит с уже подключёнными клиентами (обычно ничего — сессия уже установлена) и с новыми попытками подключения (они закономерно упадут в таймаут). Так ты заранее знаешь чего ждать от системы при реальном падении контроллера домена, а не выясняешь это в разгар инцидента.</p>
<p>Автозапуск: restart: unless-stopped в compose-файле уже покрывает перезагрузку хоста. Проверь это явно после первой настройки, а не после первого падения продакшна в три ночи.</p>
<pre><code class="language-bash">
docker inspect openvpn --format='{{.HostConfig.RestartPolicy.Name}}'
</code></pre>
<p>Безопасность: закрой 8833 порт от внешнего мира firewall-правилом, доступ к Web UI только через сам VPN-туннель или через реверс-прокси с whitelist по IP. Заведи отдельного bind-пользователя в LDAP только с правом чтения — не используй административную учётку домена для bind-запросов OpenVPN.</p>
<table>
<tbody>
<tr>
<th>Мера</th>
<th>Что даёт</th>
</tr>
<tr>
<td>UFW/firewall на 8833</td>
<td>Панель админки не торчит наружу</td>
</tr>
<tr>
<td>fail2ban на логи OpenVPN</td>
<td>Автобан после N неудачных попыток логина</td>
</tr>
<tr>
<td>Отдельный read-only bind-юзер в LDAP</td>
<td>Компрометация OpenVPN не даёт доступ на запись в каталог</td>
</tr>
<tr>
<td>SSH hardening на самом хосте</td>
<td>Доступ к хосту = доступ ко всему VPN, защищай его как основную точку входа</td>
</tr>
<tr>
<td>Ограничение по IP для Web UI</td>
<td>Даже с валидным паролем не зайти не из офисной сети</td>
</tr>
</tbody>
</table>
<p>Если всё же нужен доступ к панели из внешней сети — например, для удалённого админа — не открывай 8833 напрямую, поставь перед ней nginx с базовой аутентификацией и ограничением по IP. Двойной периметр вместо одного — панель без валидного клиентского сертификата на самом nginx туда даже не достучится.</p>
<pre><code class="language-text">
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;
}
}
</code></pre>
<p>fail2ban на логи OpenVPN настраивается фильтром по строкам с неудачной аутентификацией — готового джейла под этот конкретный образ в официальном пакете fail2ban нет, пиши свой фильтр под формат логов контейнера. Не перепутай: банить нужно по исходному IP из лога, а не по имени пользователя — иначе один скомпрометированный пароль забанит легитимного человека на всех устройствах разом.</p>
<p>Обновление: перед апдейтом образа — бэкап тома, потом pull новой версии, потом up с пересозданием контейнера. Откат — просто вернуть предыдущий тег образа в compose-файле и поднять снова, том с данными не трогается.</p>
<pre><code class="language-bash">
docker compose pull openvpn
docker compose up -d
docker compose logs -f openvpn
</code></pre>
<p>Если после обновления LDAP перестал биндиться — откатывай тег образа до предыдущего рабочего, разбирайся в тестовом контейнере, не в проде.</p>
<p>Отдельная мысль про логи и приватность. Логи OpenVPN с включённой LDAP-авторизацией хранят реальные логины сотрудников, время подключений и исходные IP-адреса — это персональные данные в юридическом смысле, а не абстрактная техническая информация. Определи заранее срок хранения этих логов и кто к ним имеет доступ, особенно если компания работает под требованиями вроде 152-ФЗ или GDPR. Ротация логов на уровне Docker (max-size, max-file в конфигурации логгера) — не только про место на диске, но и про то, чтобы не копить персональные данные бессрочно без необходимости.</p>
<pre><code class="language-text">
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
</code></pre>
<h2>8. FAQ</h2>
<p><strong>Почему openvpn не работает после настройки ldap?</strong><br />
Чаще всего — неверный атрибут пользователя (uid вместо sAMAccountName для AD) или недоступность LDAP-порта с контейнера по сети. Проверь ldapsearch с хоста Docker до того как копать глубже в логи OpenVPN.</p>
<p><strong>Как проверить что ldap авторизация работает правильно?</strong><br />
Смотри логи контейнера при попытке логина клиента — там видно успешный или неуспешный bind. Второй способ — файл openvpn-status.log, где при рабочей связке отображается реальный логин пользователя из каталога, а не CN сертификата.</p>
<p><strong>Что делать если после включения ldap старые пользователи не могут зайти?</strong><br />
Это ожидаемое поведение проекта — включение LDAP отключает локальную базу для клиентского логина. Заведи всех активных пользователей в LDAP до переключения, иначе часть команды останется снаружи в момент миграции.</p>
<p><strong>Чем встроенный ldap модуль отличается от скрипта auth-user-pass-verify?</strong><br />
Встроенный модуль настраивается через UI за пять минут, но завязан на конкретный проект и его формат конфигурации. Скрипт с ldapsearch универсален для любого OpenVPN и любого LDAP, но требует написания и поддержки кода руками — никакой магии, чистый bash и exit-коды.</p>
<p><strong>Обязательно ли использовать ldaps вместо обычного ldap?</strong><br />
Да. Обычный ldap:// передаёт пароль пользователя в открытом виде на bind-запросе. Это не теоретическая уязвимость — пароль виден в первом же захваченном tcpdump-дампе на сетевом сегменте между VPN-сервером и контроллером домена.</p>
<p><strong>Можно ли ограничить доступ к vpn только для определённой группы ldap?</strong><br />
Встроенной фильтрации по memberOf в этом проекте на уровне UI нет. Практичный обход — завести отдельную OU именно под VPN-пользователей и включать в base DN только её, а не весь каталог целиком. Более тонкая маршрутизация после аутентификации делается через client-config-dir независимо от способа входа.</p>
<p><strong>Что делать если сертификат внутреннего CA не проходит проверку при подключении к ldaps?</strong><br />
Контейнер не доверяет корневому сертификату вашего внутреннего CA — типичная ситуация для Active Directory Certificate Services. Добавь корневой сертификат CA в доверенное хранилище контейнера или укажи путь до него в настройках LDAP, если поле для этого предусмотрено в интерфейсе.</p>
<p><strong>Нужен ли отдельный сервер для ldap или можно использовать существующий контроллер домена?</strong><br />
Отдельный сервер не нужен — используй существующий контроллер домена или сервер OpenLDAP, который уже обслуживает компанию. Единственное дополнительное требование — создать под VPN отдельную сервисную учётку с правом только на чтение и, если нужна изоляция доступа, отдельную OU именно под VPN-пользователей.</p>
<h2>9. Прогноз</h2>
<p>Ты поднял OpenVPN с LDAP авторизацией в Docker Compose: сертификатный контур на 1194/udp, веб-панель на 8833 закрытая от внешнего мира, доступ через единый каталог компании. Уволенный сотрудник теряет VPN одновременно с потерей учётки в AD — никакого забытого .ovpn файла, который живёт своей жизнью три года после увольнения.</p>
<p>Дальше это просто эксплуатация: бэкап тома раз в сутки, <a class="wpil_keyword_link" href="https://it-apteka.com/category/monitoring/" target="_blank" rel="noopener" title="Мониторинг" data-wpil-keyword-link="linked" data-wpil-monitor-id="3427">мониторинг</a> AUTH_FAILED, обновление образа с проверкой на тестовом контейнере перед проливкой в прод, и раз в квартал — выборочная сверка списка активных VPN-сессий со списком реально работающих сотрудников. Рождённый в legacy рефакторинга не боится — но лучше сразу строить без legacy, чем потом его героически разгребать в три часа ночи.</p>
<p>Что имеет смысл сделать дальше, за рамками этой статьи: вынести LDAP-бинд-учётку под ротацию пароля по расписанию, подключить централизованный сбор логов аутентификации VPN в общий SIEM компании, и через полгода эксплуатации пересмотреть список пользователей в выделенной OU — практика показывает, что туда добавляют быстро, а вычищают неохотно, и ровно эта привычка когда-то и привела к необходимости всей этой статьи.</p>
<p>Если после этой статьи что-то не завелось — пиши в комментарии, разберёмся.</p>
Быстрый ответ
Рабочая связка — OpenVPN сервер на базе образа yyxx/openvpn (проект GavinTan/openvpn) плюс встроенный Web UI на порту 8833. LDAP включается одной галкой в разделе «Системные настройки» после того как в docker-compose.yml поднят единственный контейнер. Для OpenLDAP атрибут пользователя — uid, для Windows AD — sAMAccountName. Без встроенного LDAP-модуля используешь связку OpenVPN + скрипт auth-user-pass-verify с ldapsearch — дольше, но работает с любым сервером и любым UI. Ниже — рабочий compose-файл, LDAP-параметры и весь путь от нуля до подключённого клиента.
Настройка 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
Проверка доступа к LDAP
Прежде чем городить контейнеры, убедись что твой сервер вообще видит LDAP на сетевом уровне. Используй ldapsearch с хоста — если он не отвечает отсюда, из контейнера тоже не ответит.
Результат: должна вернуться запись пользователя testuser. Если получаешь «Can’t contact LDAP server» — проблема на сети или в firewall, дальше идти рано.
На момент публикации актуальна версия 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 хранит всё — конфиги, сертификаты, базу пользователей веб-интерфейса.
Результат: файл готов, никаких опечаток в отступах 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. Первым делом меняешь пароль, это не опция.
Смена пароля по умолчанию
Пароль 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 бита — пересоздай их, это не тот случай где «работает — не трогай» уместен.
Что касается 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.
Результат: список активных клиентских подключений с реальным именем пользователя из LDAP, а не CN сертификата — если видишь именно логин пользователя, а не общий client01, авторизация встроена в цепочку правильно.
Четвёртая проверка, которую часто пропускают — реальный тест с невалидным паролем. Подключись тестовым клиентом и осознанно введи неправильный пароль. Сервер обязан отклонить подключение с чётким сообщением об ошибке аутентификации, а не молча пустить внутрь. Звучит очевидно, но именно эта проверка ловит ситуации когда LDAP включён в UI, но фактически не применяется из-за опечатки в пути к скрипту проверки или из-за того что старый сертификатный контур продолжает пускать всех подряд параллельно с новым LDAP-контуром.
Пятая проверка — время отклика LDAP-бинда. Если аутентификация занимает больше двух-трёх секунд на одного пользователя, при массовом подключении в начале рабочего дня (все заходят в 9:00 одновременно) сервер захлебнётся очередью bind-запросов. Замерь время одного логина секундомером или через time при ручном ldapsearch — это дешевле чем разбираться с жалобами всего отдела в понедельник утром.
Дефолтный срок жизни клиентских сертификатов — 1 год, без EASYRSA_CERT_EXPIRE это тихо ломается
Задай переменную окружения с нужным сроком до генерации сертификатов
EASYRSA_CERT_EXPIRE=3650
После включения LDAP старые локальные VPN-аккаунты не логинятся
Это ожидаемое поведение — LDAP полностью заменяет локальную базу для клиентского логина
Заведи всех активных пользователей в LDAP заранее, до переключения флага
—
Клиент дропается ровно раз в час без видимой причины
Истёк tls-crypt/tls-auth ключ пересогласования, либо не совпадает reneg-sec на клиенте и сервере
Синхронизируй параметр reneg-sec в обоих конфигах, перегенерируй клиентский профиль
grep reneg-sec /data/server.conf
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-логике.
Ещё одна практическая мелочь: если после смены пароля в 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 коннектор без самостоятельной сборки
Готов отказаться от 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.
В server.conf такой скрипт подключается двумя строками, без которых он просто не вызовется — security 2 обязателен, без него OpenVPN откажется запускать внешние скрипты по умолчанию.
Плюс этого пути — полная независимость от конкретного проекта и его темпов разработки. Минус — весь UI придётся либо писать самому, либо смириться с командной строкой и файлами логов вместо кнопок. Для команды из трёх админов, которые и так живут в терминале, это не минус вообще.
Ещё один вариант, который стоит упомянуть отдельно — PAM-модуль pam_ldap в связке с OpenVPN через plugin openvpn-auth-pam.so. Он даёт более стандартизированный путь интеграции через системный PAM-стек, знакомый любому кто настраивал SSH с LDAP-авторизацией. Компромисс тот же — никакого готового веб-интерфейса из коробки, вся логика в конфигах /etc/pam.d.
7. Профилактика
Мониторинг: следи за логами контейнера на предмет повторяющихся AUTH_FAILED с одного IP — это либо забытый скрипт с устаревшим паролем, либо перебор.
Бэкап: том ./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-файле уже покрывает перезагрузку хоста. Проверь это явно после первой настройки, а не после первого падения продакшна в три ночи.
Безопасность: закрой 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 туда даже не достучится.
fail2ban на логи OpenVPN настраивается фильтром по строкам с неудачной аутентификацией — готового джейла под этот конкретный образ в официальном пакете fail2ban нет, пиши свой фильтр под формат логов контейнера. Не перепутай: банить нужно по исходному IP из лога, а не по имени пользователя — иначе один скомпрометированный пароль забанит легитимного человека на всех устройствах разом.
Обновление: перед апдейтом образа — бэкап тома, потом pull новой версии, потом up с пересозданием контейнера. Откат — просто вернуть предыдущий тег образа в compose-файле и поднять снова, том с данными не трогается.
Если после обновления LDAP перестал биндиться — откатывай тег образа до предыдущего рабочего, разбирайся в тестовом контейнере, не в проде.
Отдельная мысль про логи и приватность. Логи OpenVPN с включённой LDAP-авторизацией хранят реальные логины сотрудников, время подключений и исходные IP-адреса — это персональные данные в юридическом смысле, а не абстрактная техническая информация. Определи заранее срок хранения этих логов и кто к ним имеет доступ, особенно если компания работает под требованиями вроде 152-ФЗ или GDPR. Ротация логов на уровне Docker (max-size, max-file в конфигурации логгера) — не только про место на диске, но и про то, чтобы не копить персональные данные бессрочно без необходимости.
Почему 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 — практика показывает, что туда добавляют быстро, а вычищают неохотно, и ровно эта привычка когда-то и привела к необходимости всей этой статьи.
Если после этой статьи что-то не завелось — пиши в комментарии, разберёмся.
Руководитель ИТ / Кризис-менеджер 25 лет в IT: от инженера в МегаФоне до руководителя отдела. Знаю, как выглядит бардак: нестабильные сети, устаревшая инфраструктура, конфликты в команде, раздутые сроки. Помогаю бизнесу выходить из кризиса: навожу порядок в легаси, стабилизирую то, что разваливается, выстраиваю прогнозируемые процессы. Не раз возвращал к жизни ИТ-структуры — знаю цену хаосу. 📍 Ищу проект для полной реорганизации / стабилизации. 📬 Telegram: @over_dude ✉️ mail@it-apteka.com
Оставайтесь на связи
Рецепты от IT-боли. Без воды, без рекламы, без маркетинговой шелухи.