Windows Server для самых маленьких. Часть 28: Настраиваем VPN для удалённого доступа

Все части серии «Windows Server для самых маленьких»

Как устроен IPsec, что происходит в обмене IKEv2 и чем VPN отличается от прокси, разобрано в частях 19 и 20 сетевой серии. Там туннель соединял две сети. Здесь туннель соединяет сотрудника на домашнем диване с офисом, и у этого случая свои вопросы: как шлюз узнаёт, что подключается именно сотрудник, а не тот, кто подобрал его ноутбук, какой адрес выдать, куда пустить, и как всё это связано с каталогом, NPS из части 26 и сертификатами из части 25.

Роль Windows в этой схеме — проверять пользователей. Сам туннель обычно терминирует не Windows, а то, что и так стоит на границе сети: файрвол, роутер или Linux-машина со strongSwan. У Windows Server есть собственная роль для VPN, Routing and Remote Access, но в этой части её нет. Шлюз здесь — strongSwan: он бесплатный, стоит на любом Linux, и его конфигурация показывает механику без лишних слоёв. Как его ставить и настраивать в современном формате swanctl, я писал раньше: переход от ipsec.conf к swanctl и swanctl с EAP-MSCHAPv2. Там пользователи и пароли лежали в самом strongSwan; сегодня их проверяет домен. Ещё раньше был пост IKEv2 VPN на Ubuntu 20.04 — по нему настраивать шлюз не надо: strongSwan там описан в устаревшем формате ipsec.conf, а для клиентов Windows предлагается скачать вручную собранный плагин. Как настроить шлюз правильно, разобрано ниже, в этой части.


Схема

 ноутбук дома               интернет           граница сети                   внутри
 ┌───────────┐                               ┌────────────────┐               ┌──────────┐
 │ клиент    │ ── IKEv2: UDP 500, 4500 ────► │ vpn-gw         │ ── RADIUS ──► │ nps1     │ ── AD ──► dc1, dc2
 │ Windows   │                               │ strongSwan     │  UDP 1812     │ NPS      │
 └───────────┘                               └────────────────┘               └──────────┘
   адрес 192.168.50.x из пула vpn-gw,          192.0.2.10 снаружи             проверяет пароль,
   DNS 192.168.1.10 и .11                      192.168.1.1 внутри             решает по политике

Имя vpn.example.com — в публичной зоне, указывает на внешний адрес vpn-gw 192.0.2.10. Шлюз принимает IKEv2 на UDP 500 и 4500 (4500 — IKE и ESP, завёрнутые в UDP для прохода через NAT, NAT-T из части 19), проверяет пользователя через nps1 по RADIUS, выдаёт клиенту адрес из пула 192.168.50.0/24 и адреса DNS-серверов — контроллеров домена. Внутренняя сеть должна знать, что 192.168.50.0/24 достижима через vpn-gw; если vpn-gw и есть роутер внутренней сети, как здесь, знать ничего не нужно.

Три роли, три протокола между ними:

Между Протокол Что внутри
клиент — vpn-gw IKEv2 поверх UDP рукопожатие IPsec, внутри него — EAP
vpn-gw — nps1 RADIUS, UDP 1812 те же сообщения EAP, переложенные в атрибуты RADIUS
nps1 — контроллеры LDAP и Netlogon кто такой alice, в какой она группе, верен ли ответ на вызов MS-CHAPv2

LDAP NPS читает учётную запись и группы, а ответ MS-CHAPv2 проверяет не сам: передаёт его контроллеру по Netlogon — защищённому каналу RPC между членом домена и контроллером, по которому проверяется и NTLM (часть 3).

Шлюз не знает ни одного пароля и не умеет их проверять: он только передаёт EAP между клиентом и NPS и получает ответ «пускать» или «не пускать». Точно так же, как точка доступа в части 27, — с той разницей, что EAP едет не в кадрах EAPOL, а внутри сообщений IKE_AUTH.


Как это работает

В части 19 IKE_AUTH был одним обменом: обе стороны предъявили AUTH, и SA готова. С EAP обмен растягивается:

 клиент (alice)                    vpn-gw (strongSwan)               nps1 (NPS)
   │                                    │                                 │
   ├─ IKE_SA_INIT ─────────────────────►│                                 │
   │◄─ IKE_SA_INIT ─────────────────────┤                                 │
   ├─ IKE_AUTH: IDi, CP, SA, TS ───────►│  нет AUTH = клиент хочет EAP    │
   │◄─ IKE_AUTH: IDr, CERT, AUTH,       │                                 │
   │   EAP-Request/Identity ────────────┤                                 │
   ├─ EAP-Response/Identity: alice ────►│                                 │
   │                                    ├─ Access-Request (EAP-Message) ─►│  alice есть в AD?
   │                                    │◄─ Access-Challenge (EAP) ───────┤  MS-CHAPv2 Challenge
   │◄─ EAP-Request: MS-CHAPv2 ──────────┤                                 │
   ├─ EAP-Response: MS-CHAPv2 ─────────►├─ Access-Request (EAP-Message) ─►│  проверить ответ
   │                                    │◄─ Access-Challenge (EAP) ───────┤  MS-CHAPv2 Success
   │◄─ EAP-Request: MS-CHAPv2 ──────────┤                                 │
   ├─ EAP-Response: MS-CHAPv2 ─────────►├─ Access-Request (EAP-Message) ─►│
   │                                    │◄─ Access-Accept + MS-MPPE keys ─┤  политика VPN, группа
   │◄─ EAP-Success ─────────────────────┤                                 │
   ├─ IKE_AUTH: AUTH (из ключей EAP) ──►│  ключи совпали = пароль верный  │
   │◄─ IKE_AUTH: AUTH, CP (адрес, DNS), │                                 │
   │   SA, TS ──────────────────────────┤                                 │
   │══ ESP ════════════════════════════►│                                 │

По шагам.

Клиент не предъявляет AUTH. В первом IKE_AUTH он отправляет свой идентификатор, но не доказательство: по RFC 7296 это и означает «хочу аутентифицироваться по EAP». Шлюз отвечает своим сертификатом и AUTH — то есть сначала аутентифицируется сам, и только потом спрашивает пользователя. Клиент проверяет сертификат: цепочка до доверенного корня, имя vpn.example.com в сертификате совпадает с тем, к чему подключались. Это единственная защита от поддельного шлюза, и ниже видно, почему она важна.

Сертификат шлюза едет в ответе, только если клиент о нём попросил: в первом IKE_AUTH клиент кладёт полезную нагрузку CERTREQ со списком центров, которым доверяет, а strongSwan по умолчанию (send_cert = ifasked) отправляет сертификаты только в ответ на неё. Windows перечисляет в CERTREQ все свои доверенные корни, поэтому сообщение получается большим. Большим получается и ответ шлюза: сертификат шлюза и промежуточного центра с ключами RSA 4096 бит — около 3,5 КБ, больше MTU. Такие сообщения IKEv2 режет на фрагменты сам (фрагментация IKE, RFC 7383: каждый фрагмент зашифрован и собирается получателем до разбора); Windows поддерживает её с версии 1803, strongSwan — по умолчанию. Без неё большие UDP-датаграммы разбиваются на IP-фрагменты, а их многие домашние роутеры и провайдеры отбрасывают.

EAP внутри IKE. Дальше внутри зашифрованного IKE_AUTH идут сообщения EAP — того же протокола, что в 802.1X: запрос идентификатора, ответ CORP\alice (имя в том виде, в каком его ввёл пользователь), затем метод. Шлюз каждое сообщение от клиента кладёт в атрибут EAP-Message пакета RADIUS Access-Request и отправляет NPS, а то, что NPS вернул в Access-Challenge, разворачивает обратно в EAP и отдаёт клиенту. Шлюз содержимого не понимает и понимать не должен.

Метод — EAP-MSCHAPv2. NPS предлагает клиенту MS-CHAPv2: присылает случайный вызов, клиент считает ответ из вызова и NT-хеша своего пароля, NPS передаёт вызов и ответ контроллеру домена по Netlogon и, получив подтверждение, присылает свой ответ, по которому клиент убеждается, что пароль знает и сервер; последний обмен — подтверждение успеха. На подключение получается три пары запрос — ответ RADIUS: два Access-Challenge и в конце Access-Accept. В Wi-Fi тот же метод заворачивали в PEAP — TLS-туннель с сертификатом NPS, — потому что кадры EAPOL в эфире никто не защищает. Здесь TLS не нужен: обмен уже идёт внутри IKE SA, зашифрованной ключами, о которых договорились в IKE_SA_INIT, а сторона, с которой договорились, подтвердила себя сертификатом vpn-gw. Поэтому для VPN в NPS включают EAP-MSCHAPv2 без PEAP; в списке методов NPS он называется «Microsoft: Secured password (EAP-MSCHAP v2)», и именно его Windows-клиент предлагает по умолчанию. Это не тот MS-CHAPv2 без EAP из таблицы части 26: тот передавался в PPP (Point-to-Point Protocol — протоколе модемных и PPTP-подключений) без защиты обмена, а здесь каждый байт закрыт IPsec.

Отсюда и важность проверки сертификата шлюза клиентом: если клиент согласится на любой сертификат, любой, кто перехватит соединение, получит от него ответ MS-CHAPv2 на свой вызов, а этот ответ можно подбирать офлайн. Windows по умолчанию сертификат проверяет и без доверенного корня не подключится.

Ключи EAP становятся AUTH. В Access-Accept NPS возвращает не только разрешение, но и ключевой материал — атрибуты MS-MPPE-Send-Key и MS-MPPE-Recv-Key (RFC 2548), зашифрованные общим секретом RADIUS. Из них strongSwan собирает MSK (master session key), тот же ключ выводит клиент из своего пароля и обмена MS-CHAPv2, и обе стороны используют его для финальных AUTH. Если пароль был неверный, ключи не совпадут и AUTH не сойдётся; поддельный RADIUS-сервер не сможет проверить ответ через контроллер и выдать правильные ключи. Так IKE связывает «пользователь прошёл EAP» с «этот туннель принадлежит этому пользователю».

Configuration Payload. Уже в первом IKE_AUTH клиент просит адрес и параметры (CP(CFG_REQUEST): Windows просит INTERNAL_IP4_ADDRESS, INTERNAL_IP4_DNS, INTERNAL_IP4_NBNS — сервер имён NetBIOS, — те же для IPv6 и пару служебных атрибутов), а в последнем ответе шлюз выдаёт CP(CFG_REPLY) с адресом из пула и DNS-серверами — то, что для них настроено. Селекторы трафика определяют, что пойдёт в туннель: 0.0.0.0/0 — всё (полный туннель), конкретные сети — раздельный.

Кроме пароля EAP умеет и сертификаты: метод EAP-TLS (RFC 5216, взаимная аутентификация TLS) с сертификатом пользователя из части 25 проходит через ту же цепочку RADIUS, в NPS это «Smart Card or other certificate», для strongSwan разницы нет. Ниже — вариант с паролем как самый частый; что поменять для EAP-TLS, сказано по ходу.


NPS

Шлюз регистрируется клиентом RADIUS, как в части 26 (vpn-gw, 192.168.1.1, New-NpsRadiusClient). Адрес клиента — тот, с которого приходят запросы, то есть внутренний адрес шлюза: в самих запросах strongSwan пишет NAS-IP-Address — внешний адрес, на котором принимает IKE, и NPS опознаёт клиента не по нему, а по адресу отправителя UDP.

Дальше две политики. Командлетов для политик NPS в PowerShell нет, их создают оснасткой или netsh nps; оснастка предлагает мастер «Configure VPN or Dial-Up», который создаёт обе, и вот как они выглядят на сервере, где мастер уже запускали (фрагмент вывода):

PS C:\> netsh nps show crp

Connection request policy configuration:
---------------------------------------------------------
Name             = Virtual Private Network (VPN) Connections
State            = Enabled
Processing order = 2
Policy source    = 0

Condition attributes:

Name                                    Id          Value
---------------------------------------------------------
Condition0                              0x3d        "^5$"

Profile attributes:

Name                                    Id          Value
---------------------------------------------------------
Auth-Provider-Type                      0x1025      "0x1"
Override-RAP-Auth                       0x1fb0      "FALSE"

Политика запросов на подключение решает, где проверять запросы с NAS-Port-Type = Virtual. 0x3d — атрибут NAS-Port-Type, ^5$ — его значение 5, Virtual (strongSwan ставит его в каждый запрос; точки доступа ставили 19, Wireless-802.11). 0x1025 — Auth-Provider-Type, 1 — проверять здесь, а не пересылать другому серверу RADIUS. Политика по умолчанию «Use Windows authentication for all users» делает то же для всех запросов, так что отдельная нужна, только если остальные запросы пересылаются прокси. Командой — одним add crp, условие в нём обязательно:

PS C:\> netsh nps add crp name="VPN" state="enable" processingorder="2" policysource="0" conditionid="0x3d" conditiondata="^5$" profileid="0x1025" profiledata="0x1"

Сетевая политика — кого пускать и каким методом. Мастер делает её для выбранной группы (на сервере выше — Domain Users), но правильнее завести отдельную группу VPN Users и создать политику одной командой netsh nps add np со всеми условиями и параметрами — как в части 26:

PS C:\> $sid = (Get-ADGroup 'VPN Users').SID.Value
PS C:\> netsh nps add np name="VPN Users" state="enable" processingorder="2" policysource="0" conditionid="0x3d" conditiondata="^5$" conditionid="0x1023" conditiondata="$sid" profileid="0x100f" profiledata="TRUE" profileid="0x1005" profiledata="TRUE" profileid="0x1009" profiledata="0x5" profileid="0x100a" profiledata="1A000000000000000000000000000000" profileid="0x7" profiledata="0x1" profileid="0x6" profiledata="0x2"

Что в ней:

Идентификатор Что Значение
0x3d условие NAS-Port-Type ^5$ — Virtual
0x1023 условие Windows-Groups SID группы CORP\VPN Users — именно SID, не имя
0x100f NP-Allow-Dial-in разрешить (Grant)
0x1005 Ignore-User-Dialin-Properties не смотреть флаг «Network Access Permission» в свойствах учётной записи: решает политика
0x1009 NP-Authentication-Type 0x5 — EAP, и только EAP: ни MS-CHAPv2 без EAP, ни PAP
0x100a NP-Allowed-EAP-Type первый байт — тип EAP: 1A = 26 — EAP-MSCHAPv2; для EAP-TLS добавить второе значение 0D000000000000000000000000000000 (13)
0x7, 0x6 Framed-Protocol = PPP, Service-Type = Framed атрибуты ответа, которые ставит и мастер; strongSwan их не читает

Мастер добавляет ещё атрибуты Microsoft для RRAS — MS-Link-Utilization-Threshold, MS-Link-Drop-Time-Limit, MS-MPPE-Encryption-Policy, MS-MPPE-Encryption-Types; strongSwan их тоже игнорирует. Проверить результат — netsh nps show np (параметра name у него нет, он выводит все политики).

Порядок обработки важен: политика VPN должна стоять раньше политик без условия по типу порта, иначе запрос подхватит первая подходящая. Учётные данные те же, что везде: пароль домена; учётные записи в Protected Users через MS-CHAPv2 не пройдут, потому что для них запрещён NTLM, на котором MS-CHAPv2 построен, — как в части 26. Что ещё умеет NPS в ответе: Framed-IP-Address даст пользователю фиксированный адрес (strongSwan его применит, если в pools соединения указан пул radius), Session-Timeout ограничит сессию, а Class или Filter-Id с именем группы strongSwan может считать членством в группе (параметры class_group и filter_id плагина) и по нему выбирать конфигурацию с groups.


Шлюз: strongSwan

vpn-gw — Ubuntu с двумя интерфейсами: eth0 наружу с адресом 192.0.2.10 и eth1 внутрь с 192.168.1.1. Пакеты:

apt install charon-systemd strongswan-swanctl libcharon-extra-plugins

charon-systemd — сам демон IKE и служба strongswan.service, которая после запуска выполняет swanctl --load-all; libcharon-extra-plugins — там плагин eap-radius, без него strongSwan умеет только локальные EAP-методы. Проверить, что плагин загружен, можно после запуска: swanctl --stats в строке loaded plugins перечисляет загруженные плагины, eap-radius должен быть в списке.

Сертификат шлюза

Клиенты проверяют сертификат по тем же правилам, что браузер: цепочка до доверенного корня, имя. Два пути:

  • Свой удостоверяющий центр, например из части 25. Шаблон — дубликат «Web Server» или «RAS and IAS Server» с именем из запроса, EKU Server Authentication; корневой сертификат есть на всех машинах домена, на личные устройства его нужно поставить руками. EKU IP security IKE intermediate (1.3.6.1.5.5.8.2.2) Windows не нужен, но и не мешает, а старым версиям macOS он требуется.
  • Публичный центр (Let's Encrypt через ACME — протокол, которым шлюз сам доказывает центру владение именем и получает сертификат, RFC 8555): корень уже есть везде, на личных устройствах ничего ставить не нужно, но сертификат живёт 90 дней и после продления strongSwan нужно перечитать его (swanctl --load-creds).

В обоих случаях имя vpn.example.com должно быть в сертификате — в Subject или Subject Alternative Name, надёжнее в SAN как dNSName; иначе Windows отвечает ошибкой 13801. Файлы раскладываются по каталогам swanctl:

/etc/swanctl/x509/vpn.example.com.pem      сертификат шлюза
/etc/swanctl/private/vpn.example.com.pem   его закрытый ключ, права 600
/etc/swanctl/x509ca/issuing-ca.pem         промежуточный центр, чтобы отдавать клиенту цепочку

Конфигурация

/etc/swanctl/conf.d/vpn.conf:

connections {
    ikev2-eap-radius {
        version = 2
        proposals = aes256-sha256-modp2048,aes256-sha256-modp1024
        mobike = yes
        unique = never
        rekey_time = 0
        dpd_delay = 30
        pools = pool-v4, pool-v6

        local {
            auth = pubkey
            certs = vpn.example.com.pem
            id = vpn.example.com
        }
        remote {
            auth = eap-radius
            eap_id = %any
        }
        children {
            ikev2-eap-radius-child {
                local_ts = 0.0.0.0/0, ::/0
                esp_proposals = aes256-sha256-modp2048,aes256-sha256,aes256-sha1
                rekey_time = 0
                dpd_action = clear
            }
        }
    }
}

pools {
    pool-v4 {
        addrs = 192.168.50.0/24
        dns = 192.168.1.10, 192.168.1.11
    }
    pool-v6 {
        addrs = 2001:db8:50::/64
        dns = 2001:db8:1::10, 2001:db8:1::11
    }
}

Что здесь важно:

  • local { auth = pubkey } — шлюз аутентифицируется сертификатом; id должен совпадать с именем в сертификате и с тем, что клиент вводит как адрес сервера.
  • remote { auth = eap-radius; eap_id = %any } — клиентов проверять через RADIUS, любые идентификаторы принимать (решает NPS). Идентификатор IKE у Windows-клиента — его собственный IP-адрес (за домашним роутером — внутренний, вроде 10.0.0.15), а имя пользователя приходит уже в EAP; eap_id = %any разводит эти два имени.
  • proposals — AES-256 и SHA-256 с группами Диффи — Хеллмана 2048 и 1024 бит. Windows по умолчанию предлагает для IKEv2 только modp1024, и без второго варианта в списке клиент не подключится, пока на нём не включили modp2048 (ниже, в разделе о клиенте, и отдельной заметкой). Когда все клиенты настроены, modp1024 из списка убирают.
  • esp_proposals — алгоритмы самого туннеля. Windows без своей настройки предлагает для ESP целостность только SHA-1, поэтому без aes256-sha1 такой клиент поднимет IKE SA, но не туннель. aes256-sha256-modp2048 — для клиентов, которым включили PFS (-PfsGroup ниже): при перевыпуске ключей туннеля они присылают обмен Диффи — Хеллмана, и шлюз должен его принять; для первого туннеля, который создаётся в IKE_AUTH, группа не используется.
  • mobike = yes — переезд клиента между сетями без переподключения (MOBIKE, RFC 4555): ноутбук уснул на Wi-Fi и проснулся на телефоне в режиме модема — туннель жив. Это и умолчание.
  • unique = never — один пользователь может подключиться с нескольких устройств одновременно; при умолчании no новое подключение с уведомлением INITIAL_CONTACT вытеснило бы старое.
  • rekey_time = 0 на обоих уровнях — не перевыпускать ключи по времени со стороны шлюза: Windows-клиент делает это сам по своему расписанию, а перевыпуск, начатый шлюзом, Windows принимает плохо: например, если шлюз предлагает для туннеля обмен Диффи — Хеллмана, а на клиенте PFS не настроен, клиент отвечает ошибкой 13816. dpd_delay = 30 — Dead Peer Detection: если 30 секунд от клиента не было ни одного пакета, шлюз шлёт пустое сообщение INFORMATIONAL; не ответил после всех повторных передач (по умолчанию пять, около 165 секунд) — клиент считается ушедшим. Параметр dpd_timeout для IKEv2 не действует. dpd_action = clear — закрыть туннель и ничего больше не делать; это умолчание, и swanctl --list-conns показывает его как dpd action is none.
  • local_ts = 0.0.0.0/0, ::/0 — полный туннель: шлюз готов принять весь трафик клиента. Какой трафик клиент на самом деле отправит, решает клиент своими маршрутами (ниже); чтобы шлюз пропускал только офисные сети, здесь пишут их список, например 192.168.0.0/16.
  • pools — адреса и атрибуты для Configuration Payload; dns — контроллеры домена, чтобы внутренние имена разрешались. Адрес за пользователем закрепляется: strongSwan помнит, кому что выдавал, и при переподключении отдаёт тот же. Пул IPv6 размером /64 strongSwan урезает до /97 (2³¹ адресов) и пишет об этом при загрузке: virtual IP pool too large, limiting to 2001:db8:50::/97. Туда же при желании split_include = 192.168.0.0/16 — подсказка клиентам, которые понимают атрибут раздельного туннеля (Apple, strongSwan); Windows её игнорирует.

Плагин RADIUS настраивается отдельно, /etc/strongswan.d/charon/eap-radius.conf:

eap-radius {
    load = yes
    accounting = yes
    servers {
        nps1 {
            address = 192.168.1.20
            secret = <общий секрет, тот же, что у клиента RADIUS vpn-gw в NPS>
            auth_port = 1812
            acct_port = 1813
        }
    }
}

accounting = yes (по умолчанию выключено) — отправлять NPS Accounting-Request при старте и завершении сессии: тогда в журнале NPS видно не только входы, но и длительность и объёмы. Серверов можно перечислить несколько, strongSwan переключится на следующий, если первый не отвечает; второй NPS — тема, которую стоит закрыть сразу после первого, потому что без RADIUS VPN не работает вовсе. Файл с секретом — права 600. Остальное, что strongSwan кладёт в каждый запрос, — NAS-Port-Type = 5 (Virtual), Service-Type = 2 (Framed), NAS-Port — номер IKE SA, NAS-Port-Id — имя соединения, Called-Station-Id и Calling-Station-Id — адреса шлюза и клиента с портами, NAS-Identifier = strongSwan.

Применить:

systemctl restart strongswan          # eap-radius.conf читается при старте
swanctl --list-conns

Файрвол и маршрутизация

Шлюз должен принимать IKE и ESP, пересылать расшифрованный трафик клиентов и, для полного туннеля, выпускать их в интернет под своим адресом:

sysctl -w net.ipv4.ip_forward=1
iptables -A INPUT -i eth0 -p udp -m multiport --dports 500,4500 -j ACCEPT
iptables -A INPUT -i eth0 -p esp -j ACCEPT
iptables -A FORWARD -s 192.168.50.0/24 -m policy --dir in --pol ipsec --proto esp -j ACCEPT
iptables -A FORWARD -d 192.168.50.0/24 -m policy --dir out --pol ipsec --proto esp -j ACCEPT
iptables -t nat -A POSTROUTING -o eth0 -m policy --dir out --pol ipsec -j ACCEPT
iptables -t nat -A POSTROUTING -s 192.168.50.0/24 -o eth0 -j MASQUERADE
iptables -t mangle -A FORWARD -p tcp -m policy --dir in --pol ipsec -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 -j TCPMSS --set-mss 1360

-p esp — ESP без UDP, для клиентов, которые не за NAT (с NAT-T всё идёт на 4500). -m policy --pol ipsec — правило срабатывает только для пакетов, которые пришли из туннеля или уйдут в него; так трафик клиентов VPN отличают от трафика из интернета с теми же адресами. Правило ACCEPT в nat перед MASQUERADE не даёт подменять адрес пакетам, которые уходят в другие туннели шлюза (если они есть). Последняя строка — MSS clamping: ESP и UDP съедают часть MTU, и без уменьшения MSS большие TCP-сегменты через туннель не проходят (часть 4 сетевой серии — сам приём, часть 19 — во что обходится туннель); --mss 1361:1536 переписывает только значения больше 1360. Если внутренний роутер — не vpn-gw, ему нужен маршрут 192.168.50.0/24 → 192.168.1.1.

Для EAP-TLS вместо пароля на шлюзе не меняется ничего: EAP любого типа проходит через eap-radius прозрачно; меняются политика NPS (тип 13 в разрешённых) и клиент (сертификат пользователя вместо пароля).


Клиент

Подключение на Windows создаётся командой, и в ней всё, что было настроено на шлюзе:

PS C:\> Add-VpnConnection -Name 'Corp VPN' -ServerAddress vpn.example.com -TunnelType Ikev2 -AuthenticationMethod Eap -EncryptionLevel Required -RememberCredential -DnsSuffix corp.example.com
PS C:\> Set-VpnConnectionIPsecConfiguration -ConnectionName 'Corp VPN' -EncryptionMethod AES256 -IntegrityCheckMethod SHA256 -DHGroup Group14 -PfsGroup PFS2048 -CipherTransformConstants AES256 -AuthenticationTransformConstants SHA256128 -Force

Без -AllUserConnection подключение создаётся для текущего пользователя, с ним — для всех пользователей машины (нужны права администратора, и тот же ключ — в остальных командах ниже). -AuthenticationMethod Eap без XML-профиля — EAP-MSCHAPv2, «Secured password»: клиент спросит имя и пароль домена и предложит NPS ровно тот метод, который разрешён в политике. Так выглядит профиль, созданный этой командой:

PS C:\> Get-VpnConnection -Name 'Corp VPN' | Select-Object Name, TunnelType, AuthenticationMethod, EncryptionLevel, SplitTunneling, ConnectionStatus

Name     TunnelType AuthenticationMethod EncryptionLevel SplitTunneling ConnectionStatus
----     ---------- -------------------- --------------- -------------- ----------------
Corp VPN Ikev2      {Eap}                Required                 False Disconnected

PS C:\> $x = (Get-VpnConnection -Name 'Corp VPN').EapConfigXmlStream
PS C:\> $x.EapHostConfig.EapMethod.Type.'#text'
26
PS C:\> $x.EapHostConfig.Config.Eap.EapType.UseWinLogonCredentials
false

Тип EAP 26 и UseWinLogonCredentials = false: пароль спрашивается при подключении, а не берётся от входа в Windows. Для EAP-TLS передаётся другой XML-профиль EAP через -EapConfigXmlStream, снятый с настроенной вручную машины тем же свойством EapConfigXmlStream.

Вторая команда переводит клиента на modp2048 (Group14), AES-256 и SHA-256 и для IKE, и для ESP, и включает PFS — та же политика, что в proposals и esp_proposals шлюза. Без неё Windows предложит для IKE modp1024, а для ESP — SHA-1. Есть и способ для всех подключений машины сразу — параметр реестра NegotiateDH2048_AES256 в HKLM:\SYSTEM\CurrentControlSet\Services\RasMan\Parameters (1 — предлагать AES-256 и modp2048 вместе со старым, 2 — только их); он меняет только IKE, ESP остаётся на SHA-1:

PS C:\> Get-ItemPropertyValue HKLM:\SYSTEM\CurrentControlSet\Services\RasMan\Parameters -Name NegotiateDH2048_AES256
1

Доверие к сертификату шлюза. Если сертификат от своего центра, корень должен лежать в Cert:\LocalMachine\Root. У машин домена с центром предприятия он там с части 25; на личном ноутбуке — Import-Certificate -FilePath corp-root-ca.cer -CertStoreLocation Cert:\LocalMachine\Root из консоли администратора. Без этого — ошибка 13801 ниже.

Полный или раздельный туннель. По умолчанию Windows ставит маршрут по умолчанию через VPN: весь интернет клиента идёт через офис (для этого на vpn-gw и был NAT). Раздельный туннель — флаг -SplitTunneling и маршруты руками:

PS C:\> Set-VpnConnection -Name 'Corp VPN' -SplitTunneling $true
PS C:\> Add-VpnConnectionRoute -ConnectionName 'Corp VPN' -DestinationPrefix 192.168.0.0/16

Тогда через туннель идут только офисные сети, остальное — напрямую, со всеми оговорками про утечки из части 20. С IPv6 своя деталь: адрес из пула pool-v6 Windows получает, но маршрут IPv6 через VPN не ставит даже при полном туннеле. Маршрут по умолчанию ::/0 командлет добавить не даёт, зато хватает 2000::/3 — всех глобальных адресов IPv6: Add-VpnConnectionRoute -DestinationPrefix 2000::/3.

Подключиться из командной строки — rasdial 'Corp VPN' alice * (звёздочка — спросить пароль), отключиться — rasdial 'Corp VPN' /DISCONNECT; без аргументов rasdial перечисляет активные подключения:

PS C:\> rasdial
No connections
Command completed successfully.

Такой же профиль можно раздать машинам домена групповой политикой или через Intune (облачную службу управления устройствами Microsoft) как XML для CSP VPNv2 — узла конфигурации, через который MDM настраивает VPN; в нём же включают автоподключение (Always On VPN — не отдельный протокол, а способ доставки и автозапуска того же IKEv2-профиля). macOS и iOS подключаются встроенным клиентом IKEv2 с аутентификацией «Username» — это тот же EAP-MSCHAPv2; Android — приложением strongSwan с профилем «IKEv2 EAP (Username/Password)». Linux — тем же strongSwan в роли клиента или NetworkManager с плагином.


Что ломается

Клиент Windows сообщает об ошибках номерами; они же — в событиях источника RasClient в журнале Application:

Код Текст Windows Что обычно значит
809 the remote server is not responding шлюз не ответил на UDP 500/4500: файрвол, нет проброса портов
13801 IKE authentication credentials are unacceptable сертификат шлюза не прошёл проверку: нет EKU Server Authentication, истёк, клиент не доверяет корню, имя не совпало с ServerAddress
13868 Policy match error не совпали алгоритмы: у клиента modp1024, у шлюза только modp2048, или разные шифры
691 the user name and password combination … is not recognized, or the selected authentication protocol is not permitted NPS отказал: пароль, группа, политика — смотреть 6273 на NPS
812 the authentication method used by the server … may not match the authentication method configured in your connection profile политика NPS требует метод, которого нет в профиле клиента
13806 IKE failed to find valid machine certificate у клиента нет нужного сертификата (EAP-TLS, аутентификация компьютера)

Коды придуманы для шлюза RRAS, и за чужим шлюзом клиент видит причину не всегда так точно; точная причина отказа — в журнале NPS.

На стороне шлюза journalctl -u strongswan договаривает остальное. NPS не отвечает — retransmit 1 of RADIUS Access-Request и в конце RADIUS Access-Request timed out after … attempts: так выглядят и выключенный NPS, и неверный секрет, и шлюз, заведённый в NPS не с тем адресом, — NPS такие запросы молча отбрасывает и пишет в журнал System событие 13 (незнакомый клиент), 14 или 18 (не сходится Authenticator или Message-Authenticator, то есть секрет). Отказ NPS — RADIUS authentication of 'CORP\alice' failed и EAP method … failed for peer …. Клиент не принял аутентификацию шлюза — вместо следующего сообщения EAP приходит parsed INFORMATIONAL request 2 [ N(AUTH_FAILED) ], и полуоткрытая IKE SA удаляется по таймауту. На NPS — события 6273 с кодом причины: 16 — неверные учётные данные, 48 — ни одна сетевая политика не подошла (чаще всего тип порта или порядок политик), 66 — метод не разрешён, 65 — доступ запрещён разрешением в учётной записи или в совпавшей политике.

Доступность снаружи проверяется до всего остального: nmap -sU -p 500,4500 vpn.example.com покажет open или open|filtered, а клиент, получивший 13801 или 13868, уже доказал, что IKE отвечает.


Смотрим руками

Что strongSwan загрузил — соединение, пулы и сертификат:

# swanctl --list-conns
ikev2-eap-radius: IKEv2, no reauthentication, no rekeying, dpd delay 30s
  local:  %any
  remote: %any
  local public key authentication:
    id: vpn.example.com
    certs: CN=vpn.example.com
  remote EAP_RADIUS authentication:
    eap_id: %any
  ikev2-eap-radius-child: TUNNEL, no rekeying, dpd action is none
    local:  0.0.0.0/0 ::/0
    remote: dynamic

# swanctl --list-pools
pool-v4              192.168.50.0                        0 / 0 / 254
pool-v6              2001:db8:50::                  0 / 0 / 2147483646

# swanctl --list-certs --subject "CN=vpn.example.com" | grep -vE 'serial|keyid|subjkey|authkey'
List of X.509 End Entity Certificates

  subject:  "CN=vpn.example.com"
  issuer:   "CN=Example Corp Issuing CA"
  validity:  not before Feb 02 18:39:58 2026, ok
             not after  Feb 02 18:39:58 2031, ok (expires in 1556 days)
  altNames:  vpn.example.com
  flags:     serverAuth ikeIntermediate
  CRL URIs:  http://pki.example.com/issuing-ca.crl
  pubkey:    RSA 4096 bits, has private key

no rekeying — это rekey_time = 0, remote: dynamic — селектор клиента станет его виртуальным адресом. В пулах три числа — подключённые сейчас (online), отключившиеся, но с закреплённым адресом (offline), и размер; размер пула IPv6 — те самые 2³¹ − 2. У сертификата оба EKU и has private key — ключ найден в /etc/swanctl/private.

Активные подключения показывает swanctl --list-sas: у подключения удалённого доступа к строке remote с идентификатором и адресом клиента добавляются EAP: '<имя пользователя>' и в квадратных скобках виртуальный адрес, а режим туннеля за NAT — TUNNEL-in-UDP.

Журнал одного подключения Windows-клиента, без строк с идентификаторами производителей, запросов сертификатов неизвестных центров, фрагментов и отправки пакетов:

# journalctl -u strongswan -o cat --since '2026-10-26 20:22:40' --until '2026-10-26 20:22:55' | grep -vE 'vendor ID|unknown ca with|endpoint changed|received fragment|EF\(|\[NET\]'
11[ENC] parsed IKE_SA_INIT request 0 [ SA KE No N(FRAG_SUP) N(NATD_S_IP) N(NATD_D_IP) V V V V ]
11[IKE] 203.0.113.77 is initiating an IKE_SA
11[IKE] IKE_SA (unnamed)[201] state change: CREATED => CONNECTING
11[CFG] selected proposal: IKE:AES_CBC_256/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/MODP_2048
11[IKE] remote host is behind NAT
11[IKE] sending cert request for "CN=Example Corp Root CA"
11[IKE] sending cert request for "CN=Example Corp Issuing CA"
11[ENC] generating IKE_SA_INIT response 0 [ SA KE No N(NATD_S_IP) N(NATD_D_IP) CERTREQ N(FRAG_SUP) N(CHDLESS_SUP) N(MULT_AUTH) ]
05[ENC] parsed IKE_AUTH request 1 [ IDi CERTREQ N(MOBIKE_SUP) CPRQ(ADDR DNS NBNS SRV ADDR6 DNS6 SRV6) SA TSi TSr ]
05[IKE] received cert request for "CN=Example Corp Root CA"
05[IKE] received 62 cert requests for an unknown ca
05[CFG] looking for peer configs matching 192.0.2.10[%any]...203.0.113.77[10.0.0.15]
05[CFG] selected peer config 'ikev2-eap-radius'
05[IKE] initiating EAP_IDENTITY method (id 0x00)
05[IKE] processing INTERNAL_IP4_ADDRESS attribute
05[IKE] processing INTERNAL_IP4_DNS attribute
05[IKE] processing INTERNAL_IP4_NBNS attribute
05[IKE] processing INTERNAL_IP4_SERVER attribute
05[IKE] processing INTERNAL_IP6_ADDRESS attribute
05[IKE] processing INTERNAL_IP6_DNS attribute
05[IKE] processing INTERNAL_IP6_SERVER attribute
05[IKE] peer supports MOBIKE
05[IKE] authentication of 'vpn.example.com' (myself) with RSA signature successful
05[IKE] sending end entity cert "CN=vpn.example.com"
05[IKE] sending issuer cert "CN=Example Corp Issuing CA"
05[ENC] generating IKE_AUTH response 1 [ IDr CERT CERT AUTH EAP/REQ/ID ]
05[ENC] splitting IKE message (3488 bytes) into 3 fragments
06[ENC] parsed IKE_AUTH request 2 [ EAP/RES/ID ]
06[IKE] received EAP identity 'CORP\alice'
06[CFG] sending RADIUS Access-Request to server 'nps1'
06[CFG] received RADIUS Access-Challenge from server 'nps1'
06[IKE] initiating EAP_MSCHAPV2 method (id 0x01)
06[ENC] generating IKE_AUTH response 2 [ EAP/REQ/MSCHAPV2 ]
16[ENC] parsed IKE_AUTH request 3 [ EAP/RES/MSCHAPV2 ]
16[CFG] sending RADIUS Access-Request to server 'nps1'
16[CFG] received RADIUS Access-Challenge from server 'nps1'
16[ENC] generating IKE_AUTH response 3 [ EAP/REQ/MSCHAPV2 ]
09[ENC] parsed IKE_AUTH request 4 [ EAP/RES/MSCHAPV2 ]
09[CFG] sending RADIUS Access-Request to server 'nps1'
09[CFG] received RADIUS Access-Accept from server 'nps1'
09[IKE] RADIUS authentication of 'CORP\alice' successful
09[IKE] EAP method EAP_MSCHAPV2 succeeded, MSK established
09[ENC] generating IKE_AUTH response 4 [ EAP/SUCC ]
15[ENC] parsed IKE_AUTH request 5 [ AUTH ]
15[IKE] authentication of '10.0.0.15' with EAP successful
15[IKE] authentication of 'vpn.example.com' (myself) with EAP
15[IKE] peer requested virtual IP %any
15[CFG] reassigning offline lease to 'CORP\alice'
15[IKE] assigning virtual IP 192.168.50.1 to peer 'CORP\alice'
15[IKE] peer requested virtual IP 2001:db8:50::2
15[CFG] reassigning offline lease to 'CORP\alice'
15[IKE] assigning virtual IP 2001:db8:50::1 to peer 'CORP\alice'
15[IKE] building INTERNAL_IP4_DNS attribute
15[IKE] building INTERNAL_IP4_DNS attribute
15[IKE] building INTERNAL_IP6_DNS attribute
15[IKE] building INTERNAL_IP6_DNS attribute
15[IKE] IKE_SA ikev2-eap-radius[201] established between 192.0.2.10[vpn.example.com]...203.0.113.77[10.0.0.15]
15[IKE] IKE_SA ikev2-eap-radius[201] state change: CONNECTING => ESTABLISHED
15[CFG] selected proposal: ESP:AES_CBC_256/HMAC_SHA1_96/NO_EXT_SEQ
15[IKE] CHILD_SA ikev2-eap-radius-child{219} established with SPIs c5f5e571_i 8151acd4_o and TS 0.0.0.0/0 ::/0 === 192.168.50.1/32 2001:db8:50::1/128
15[ENC] generating IKE_AUTH response 5 [ AUTH CPRP(ADDR ADDR6 DNS DNS DNS6 DNS6) SA TSi TSr N(MOBIKE_SUP) N(ADD_4_ADDR) N(ADD_4_ADDR) N(ADD_4_ADDR) N(ADD_6_ADDR) N(ADD_6_ADDR) N(ADD_6_ADDR) ]

Здесь видна вся схема из начала статьи. Первый IKE_AUTH клиента — IDi CERTREQ … CPRQ(…) SA TSi TSr без AUTH: просьба об EAP, запрос адреса и 63 запроса сертификатов — по одному на каждый доверенный корень Windows, из которых шлюз узнал один. Идентификатор клиента — 10.0.0.15, его адрес за домашним роутером. Шлюз отвечает IDr CERT CERT AUTH EAP/REQ/ID — свой сертификат, промежуточный центр, подпись и запрос идентификатора — и режет ответ на три фрагмента. Дальше три пары RADIUS: идентификатор → вызов MS-CHAPv2, ответ → сообщение об успехе от сервера, подтверждение → Access-Accept. MSK established — те самые ключи из Access-Accept, из которых затем считается последний AUTH. Пул выдаёт пользователю тот же адрес, что в прошлый раз (reassigning offline lease), и ответ уходит с CPRP(ADDR ADDR6 DNS DNS DNS6 DNS6): только адреса и DNS, NBNS и прочее strongSwan не заполняет. IKE договорился о MODP_2048 — на этом клиенте включён NegotiateDH2048_AES256, — а ESP о HMAC_SHA1_96: своей политики IPsec у подключения нет, и SHA-1 — единственная целостность, которую Windows предлагает для ESP. Без aes256-sha1 в esp_proposals это подключение не поднялось бы.

Тот же разговор глазами NPS — файл учёта (о его формате — в части 26), только записи политики VPN:

PS C:\> Invoke-Command -ComputerName nps1 -ScriptBlock {
    $type = @{ '1'='Access-Request'; '2'='Access-Accept'; '3'='Access-Reject'; '11'='Access-Challenge' }
    Get-Content (Get-ChildItem C:\Windows\System32\LogFiles\IN*.log | Sort-Object Name | Select-Object -Last 1).FullName |
        Where-Object { $_ -like '*Virtual Private Network*' } | Select-Object -Last 6 | ForEach-Object {
            $e = ([xml]$_).Event
            [PSCustomObject]@{ Time = $e.Timestamp.'#text'.Split(' ')[1]; Packet = $type[$e.'Packet-Type'.'#text']; Port = $e.'NAS-Port'.'#text'; Calling = $e.'Calling-Station-Id'.'#text'; Eap = $e.'EAP-Friendly-Name'.'#text' } }
} | Format-Table Time, Packet, Port, Calling, Eap -AutoSize

Time         Packet           Port Calling             Eap
----         ------           ---- -------             ---
20:22:48.502 Access-Request   201  203.0.113.77[43113]
20:22:48.502 Access-Challenge
20:22:48.546 Access-Request   201  203.0.113.77[43113]
20:22:48.546 Access-Challenge
20:22:48.576 Access-Request   201  203.0.113.77[43113] Microsoft: Secured password (EAP-MSCHAP v2)
20:22:48.576 Access-Accept                             Microsoft: Secured password (EAP-MSCHAP v2)

Три пары за 74 миллисекунды. NAS-Port 201 — тот же номер, что у ikev2-eap-radius[201] в журнале шлюза: по нему запись NPS связывается с подключением на шлюзе. Первый запрос целиком — все элементы строки:

Timestamp=10/26/2026 20:22:48.502 | Computer-Name=NPS1 | Event-Source=IAS | User-Name=CORP\alice | NAS-Port-Type=5 | Service-Type=2 | NAS-Port=201 | NAS-Port-Id=ikev2-eap-radius | NAS-IP-Address=192.0.2.10 | Called-Station-Id=192.0.2.10[4500] | Calling-Station-Id=203.0.113.77[43113] | NAS-Identifier=strongSwan | Client-IP-Address=192.168.1.1 | Client-Vendor=0 | Client-Friendly-Name=vpn-gw | Proxy-Policy-Name=Virtual Private Network (VPN) Connections | Provider-Type=1 | SAM-Account-Name=CORP\alice | Fully-Qualifed-User-Name=CORP\alice | Class=311 1 192.168.1.20 10/26/2026 10:43:14 104 | Authentication-Type=5 | NP-Policy-Name=Virtual Private Network (VPN) Connections | Packet-Type=1 | Reason-Code=0

NAS-IP-Address — внешний адрес шлюза, Client-IP-Address — внутренний, с которого пришёл пакет и по которому NPS нашёл клиента vpn-gw. Calling-Station-Id — внешний адрес пользователя с портом, вместо MAC у Wi-Fi. В Access-Challenge NPS кладёт Session-Timeout = 60 — сколько секунд ждать следующего шага EAP, — а в Access-Accept — Framed-Protocol, Service-Type, атрибуты Microsoft из политики и MS-CHAP-Domain = 01434F5250 (байт-идентификатор и CORP); ключи MS-MPPE в файл учёта не пишутся. Записей Accounting-Request нет: на этом шлюзе accounting не включён. По тем же полям в журнале Security пишется событие 6272 — с Client Friendly Name = vpn-gw и внешним адресом пользователя в Calling Station Identifier; журнал Security перезаписывается быстро, поэтому для истории подключений VPN надёжнее файл учёта.


Источники

IKEv2 и IPsec:

  • RFC 7296 — IKEv2: IKE_SA_INIT и IKE_AUTH, EAP в IKE_AUTH и MSK в финальном AUTH (раздел 2.16), CERTREQ, Configuration Payload, проверка живости;
  • RFC 7383 — фрагментация сообщений IKEv2;
  • RFC 4555 — MOBIKE;
  • RFC 3948 — ESP в UDP для прохода через NAT;
  • RFC 4303 — ESP.

EAP и RADIUS:

  • RFC 3748 и RFC 3579 — EAP и его перенос в RADIUS;
  • RFC 2759 — MS-CHAPv2;
  • RFC 5216 — EAP-TLS;
  • RFC 2548 — атрибуты Microsoft: MS-MPPE-Send-Key, MS-MPPE-Recv-Key, MS-CHAP-Domain;
  • RFC 2865 и RFC 2866 — RADIUS (NAS-Port-Type Virtual = 5, Calling-Station-Id, Class) и учёт;
  • RFC 8555 — ACME.

strongSwan:

  • swanctl.conf — параметры соединения: unique, mobike, rekey_time, dpd_delay (и dpd_timeout, не действующий в IKEv2), send_cert, eap_id, dpd_action, атрибуты пулов;
  • swanctl — --list-conns, --list-pools, --list-certs, --list-sas, --stats, --load-creds;
  • eap-radius Plugin — серверы RADIUS, accounting, class_group, filter_id, nas_identifier, атрибуты, которые плагин передаёт клиенту;
  • Virtual IP Addresses — пулы, закрепление адреса за идентификатором, пул radius для Framed-IP-Address;
  • Retransmission — повторные передачи и время до признания собеседника недоступным;
  • Windows Clients — предложения Windows по умолчанию (modp1024, SHA-1 в ESP), NegotiateDH2048_AES256, PFS и ошибка 13816, rekey_time = 0, фрагментация с 1803, маршрут IPv6 2000::/3;
  • Windows Certificate Requirements — EKU, имя в Subject или SAN, ikeIntermediate для старых macOS, ошибка 13801;
  • Forwarding and Split-Tunneling — пересылка, NAT и раздельный туннель на шлюзе;
  • iptables-extensions — модули policy и tcpmss, цель TCPMSS.

Network Policy Server:

Клиент Windows:


Блок о сертификатах и доступе к сети закончен: один каталог, один NPS, одни и те же учётные записи для Wi-Fi, провода и VPN, а шлюз может быть любым — лишь бы умел RADIUS. Следующие две части — о машинах, которые во всех предыдущих частях были только клиентами DNS и целью для ssh: о Linux-серверах, которые становятся членами домена наравне с Windows.

Дальше — часть 29: ввод Linux в домен. Что значит «член домена» для машины без Netlogon; realmd и sssd, krb5.conf, PAM и NSS; сопоставление SID в идентификаторы POSIX; короткие имена и домен по умолчанию; ограничение входа группами и sudo для доменных групп; регистрация в DNS; и каноническое имя в /etc/hosts, из-за которого Kerberos часто не работает на только что введённой машине.

Оставить комментарий могут только зарегистрированные пользователи.

Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.