Thursday, December 27, 2018

Highly-available Fileserver in a IaaS cloud

Вот есть, скажем, IaaS-облачко, а в нём виртуалка с Windows, работает файловым сервером - публикует папки по обычному протоколу SMB. Хочется, чтобы если она упадёт, файлы были доступны с какой-нибудь другой. Как этого добиться минимальными средствами?

Казалось бы, для такого совершенно стандартного желания и решение должно быть совершенно стандартным, особенно уж для такой неновой и распространённой штуки, как общие папки Windows: поставь такую-то фичу, и щёлкни раз-два-три. Ага, щас!


Вариант 1 - shared storage

Удобнее всего, конечно, чтобы у двух виртуалок был общий диск. Тогда на них можно настроить Failover Cluster, в нём запустить fileserver role с такой прекрасной фичей, как Transparent Failover, которая позволяет клиентским приложениям вообще не чувствовать, что сервер сменился. Клиенты будут обращаться по некоему floating IP, который будет переходить с сервера на сервер вместе с ролью в случае фэйловера. Скажем, в данном примере этому IP соответствует DNS-имя Filer:

Но увы! наше облачко такое не предоставляет.


Вариант 2 - репликация

Пускай у каждой из виртуалок будет свой местный диск, а мы будем файлы между ними синхронизировать.
В набор Windows Server для таких дел входит DFS Replication (DFS-R).

Достоинства:
  • фича входит в стандартный набор в Windows Server, ей сто лет в обед.
На этом достоинства заканчиваются и начинаются недостатки:
  • главный - никаких гарантий по части целостности данных. Один клиент может изменять файл на одном сервере, а другой в то же - тот же файл на другом сервере, и пожалуйста: конфликт;
  • если файл залочен клиентом, то изменения в нём копироваться не будут, пока клиент замок не снимет; пример - логи. Стало быть, его чтение с другого сервера реальное содержание не покажет;
  • очень непрозрачный и недокументированный способ работы. Пример: если файлы синхронизуются более-менее сразу, то вот изменения в NTFS permissions или ownership - со здоровенной задержкой, могущей доходить минут до 20. В чём логика, догадываюсь - такое изменение может переписать ACLs сверху вних по здоровеннейшему дереву, так что лучше подождать, пока юзер закончит их вносить, и лишь затем реплицировать. Но вот не документировано это нигде. В случае накладок, десинхронизации, конфликтов понять, что именно пошло не так, очень тяжело.
Схожий по функциям софт от прочих производителей я не пробовал, но наверняка и у них те же ограничения.

Поскольку это не кластер, и никакого floating IP между серверами нет, как нам обеспечить переключение? Тут может помочь другая фича - DFS-Namespaces или DFS-N, она позволяет распределять клиентов между двумя или более серверами. К Replication она отношения прямого не имеет - она просто позволяет замаскировать UNC-пути к общим папкам на разных серверах за одним общим путём, и неважно, каким образом файлы на этих серверах оказались. К примеру, при обращении к контроллеру домена example.org по пути \\example.org\SHARED\Folder, SMB-клиент Windows получит ссылки на два пути:
  • \\server1.example.org\SharedFolder
  • \\server2.example.org\SharedFolder
Он выберет одну из них и будет работать соответственно либо с одним сервером, либо с другим - а если тот перестанет отвечать, переключится на другой.

Фича полезная, но failover достаточно медленный - если сервер вдруг пропадает с горизонта, то клиент сперва пытается посылать пакеты по существующему TCP-соединению, затем - открыть новые на тот же сервер, и лишь затем - пойти на следующий сервер по списку, полученному от DFS namespace. Уходит на это секунд 80.

Ещё возможность: просто определить DNS-имя, указывающее на IP-адреса обоих серверов -  к примеру:
  • filer.example.org = 192.168.10.100
  • filer.example.org = 192.168.20.100
Тогда клиенты распределятся между серверами - получив от DNS оба адреса, одни выберут первый, а другие - второй. Если же выбранный адрес перестанет отвечать, SMB-клиент достаточно умён, чтобы переключиться на второй - см. ниже про failover. Занимает процесс около 30-ти секунд.

Недостаток - не будет работать протокол аутентификации Kerberos, поскольку SMB-служба на каждом сервере привязана к его личному computer account в Active Directory, а аккаунт - к его личному хостнейму. Привязать же общий хостнейм к обоим аккаунтам нельзя. Можно ли запустить SMB-службу не под локальным аккаунтом (SYSTEM), а под доменным - обычным юзерским или gMSA? Не знаю, не пробовал.

Впрочем, всегда есть в запасе старый добрый NTLM.


Вариант 3 - виртуальный shared storage

Если облако не предоставляет shared storage, давайте построим его сами!
Тут, конечно, напрашивается воткнуть ещё одну виртуалку, которая будет предоставлять диски для наших файлсерверов по протоколу iSCSI - и тогда мы можем построить красивенький Failover Cluster и радоваться. Но это будет, естественно, самообман, потому как тогда наш iSCSI-сервер станет единой точкой отказа - если он рухнет, то всему хана.

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


Можно попытаться функции совместить: а пускай наши файлсервера заодно и работают как virtual SAN - синхронизируют между собой некий виртуальный диск и к нему же и подключаются как iSCSI-клиенты (то, что называется модным словом hyper-converged).
Но:
  • это повышает риск - надо полагаться на то, что при поломке, падении, перезагрузке не только Failover Cluster отработает как надо, но и virtual SAN.
  • вариантов virtual SAN очень много, но большинство из них под Линукс. Стало быть, на наши виртуалочки их не воткнёшь.
  • вероятно, что количество виртуалок всё равно придётся повышать до трёх, чтобы избежать возможности split brain.
На практике я опробовал два подхода к этому снаряду:

1. Windows S2D - первый кандидат, поскольку готовая фича в Windows Server 2016. Очень хотелочь бы опробовать, но увы. У неё суровые требования к локальным дискам, которые без хаканья на уровне VMware обойти нельзя. А облачко подобных игр не позволяет.

2. StarWind vSAN - привлёк внимание тем, что написан для Windows и интересной бизнес-моделью: разница между платной и бесплатной версиями лишь в отсутствии в бесплатном варианте техподдержки и графической консоли - управление идёт через PowerShell. Освоить ихний PowerShell совсем несложно, но:
  • требует он скрупулёзной внимательности и аккуратности, особенно если создаёшь много дисков; цена опечатки может быть слишком высока.
  • увы, багов слишком много. Диски могут рассинхронизироваться навсегда от простой перезагрузки; одни и те же команды могут выполняться раз выполняться, а раз - выдавать совершенно невразумительные ошибки; траблшутить всё это одному - не легче, чем DFS, а то и тяжелее. И привести эти баги могут к очень тяжёлым последствиям.
Основательно намучившись, решил, что в продакшене у меня никакого Старвинда не будет. Возможно, что при работе через платный GUI вдруг та же система проявляет чудеса надёжности, но мне это что-то сомнительно.


Конечный вариант - слоноптица

В конце остановился на совмещении вариантов 1 и 2: дублировать файлы с помощью DFS Replication, но при этом использовать плавающий адрес Failover Cluster для доступа к ним.


Иными словами, если у нас есть \\server1.example.org и \\server2.example.org, то для доступа к файлам мы будем использовать  некое третье имя - скажем, \\filer.example.org - и оно будет привязано к кластерному IP-адресу, переходящему от одного сервера к другому в случае падения. Или же IP-адресам, в случае если наши сервера сидят в разных сетях.

Строится это следующим образом:
  1. Определяем DFS-R между двумя машинами.
  2. Добавляем обе в Failover Cluster. Поскольку shared storage у нас нету, используем какой-нибудь удалённый shared folder как quorum witness - или Azure как cloud witness.
  3. Создаём в кластере empty role, даём этой роли некое имя, привязываем к IP-адресу, прописываем в DNS. Computer account в Active Directory для этой роли не создаётся - незачем.
  4. Определяем на каждой машине те же shared folders на основе дерева, синхронизируемого посредством DFS-R.
Это позволяет, с одной стороны, использовать локальные диски. а с другой, избежать недостатков DFS:
  • все клиенты юзают тот же сервер, который будет предоставлять замок на запись в конкретный файл лишь одному из них. Чтение изменений в файле доступны всем сразу.
  • соответственно, нет конфликтов между серверами, все изменения идут в одном направлении.
  • очень быстрый graceful failover, если оба сервера в той же сети (не graceful - не очень). См. ниже.
Но стоит учитывать:
  • мы лишаемся возможности распределять нагрузку между серверами (нам это особенно и не нужно - она и так мала).
  • если файл был открыт на запись, были внесены изменения, записаны на диск, но сервер рухнул перед тем, как замок был снят - значит, реплицированы эти изменения не были. Если рухнувший сервер подымется перед тем, как тот же файл был изменён на втором - репликация произойдёт. Если после - то реплицирована будет версия файла со второго сервера, поскольку у неё будет метка времени посвежее.

Дополнения и уточнения
Свидетель из Фрязино

Тонкий момент - если мы просто подымем два сервера в рамках Failover Cluster и будем ждать, что они будут друг друга взаимно подстраховывать, нас ждёт облом. В тот момент, что один из них отключится, роль файлсервера остановится так же и на втором, прекратит работать наш кластер.

Почему? Для избежания неприятной ситуации под названием "split brain", когда оба сервера считают себя главными и могут, в отсутствии координации друг с другом, искорёжить данные.

Как этого избежать? Есть два варианта:
  • Добавить третий сервер в кластер - и заодно в replication group DFS-R, чтобы копия файлов была на всех трёх.
  • Добавить так называемого "свидетеля" (witness).
Свидетель этот является не процессом, как сервер, а просто-напросто неким "журналом", куда оба сервера могут писать, отмечаясь, что они живы и указывая, кто из них в активной роли. 
  • Если сервер обнаруживает, что не слышит второй, но "свидетель" доступен - он продолжает работать.
  • Если он обнаруживает, что не может связаться ни со вторым, ни со "свидетелем" - он останавливается.
  • Если оба сервера общаются нормально, но "свидетель" отвалился - они продолжают работать как обычно.

В Windows 2016 есть три варианта для witness:
  1. Либо это shared disk, общий для всех узлов кластера - но мы начали с того, что наше облако таких не предоставляет. Если предоставляет - нам не нужна эта морока с DFS-R, стоит выстроить просто обычный Failover Cluster по рецептам Microsoft.
  2. Либо эта shared folder на каком-то третьем SMB-сервере - но тогда надо иметь этот сервер. Его не надо подключать в кластер, достаточно, чтобы он был доступен с обоих кластерных узлов. Это может быть контроллер домена, к примеру.
  3. Либо (новая фишка Win2016) это может быть blob в микрософтовском облаке - в Azure storage account. Но тогда надо располагать подпиской на Azure.

Немного технических потрохов про failover:

Скорость переключения очень зависит от того, происходит ли оно в результате того, что бывший активный сервер сам сдаёт полномочия (graceful failover - по команде админа или из-за "мягкой" перезагрузки) - или же в результате резкого падения сервера или полной потери связи с ним. Если graceful может занять до 5 секунд, то hard failover - до 20-ти. Разница происходит от того, что оставшемуся серверу надо во втором случае установить, что ранее активный признаков жизни не подаёт - ни напрямую, не через witness (см. ниже) - а в первом тот может сообщить и сам.

После того, как второй сервер решает тянуть одеяло на себя, события могут развиваться по следующим сценариям:

Внутри одной сети (VLAN, subnet):

Перенос кластерного IP - очень быстрая операция, прозрачная для клиента: сервер, забирающий его к себе, отсылает ряд пакетов Gratuitous ARP (GARP), прописывающих новый MAC-адрес для этого IP в ARP-таблице подключённых к той же сети раутеров или клиентов. Заодно и свитчи, физические или виртуальные, прописывают у себя, на каком порту сидит этот MAC. Всё, трафик продолжает течь как тёк.
Естественно, TCP-подключения при этом слетят - поскольку новый сервер вдруг получит какие-то TCP-пакеты без SYN-флага. Но поскольку при этом он отошлёт RST, клиент возобновит их сразу же, без попыток повторных передач.

Между разными сетями:

Естественно, перекинуть floating IP из сети в сеть нельзя, да и незачем. Решается эта задача вот как. Наш Failover Cluster в курсе о существовании двух сетей - по каждой на сервер. Мы можем привязать нашу кластерную "роль" к двум IP-адресам - по одному на сеть - и прописать между ними "OR dependancy". Иными словами, для того, чтобы "роль" запустилась на хосте, достаточно, чтобы один из этих адресов был доступен.

Затем мы прописываем оба адреса в DNS на одно и то же имя - к примеру:
  • filer.example.org = 192.168.10.100
  • filer.example.org = 192.168.20.100
SMB-клиенты при запросе получают из DNS оба адреса и пытаются параллельно подключиться на оба. Лишь один из них ответит (в этом разница со случаем, если бы мы просто приписали к этому имени частные IP-адреса server1 и server2, о чём см. выше).

Если посреди работы с одним из адресов он перестаёт отвечать - Windows пытается в течении 15 секунд повторять (retransmit) запрос на уровне того же TCP-соединения - а затем посылает RST и снова пытается подключиться на оба одновременно. И если наш Failover Cluster отработал как положено - то соединение на второй адрес будет установлено благополучно.

В обоих вариантах, ещё пара секунд уйдёт на аутентификацию клиента против нового сервера - по Kerberos или NTLM.

Какой вариант быстрее? Это, как сказано выше, зависит от того, насколько быстро передадут друг другу роль наши сервера.
В случае graceful failover - переключить IP-адреса между двумя MAC-адресами в той же сети берёт намного меньше времени, чем дождаться, пока SMB-клиент начнёт стучаться по запасному адресу.
В случае же hard failover - наибольшую задержку обеспечит сам кластер.

Но и даже в наихудшем случае оба варианта всё же куда быстрее, чем переключение между двумя UNC-путями, полученными от DFS-N.


Ещё свидетель. Но совсем другой. И сугубо опциональный.

Есть некий способ сократить время, которое SMB-клиент тратит на переподключение. Идея в следующем: подключившись к кластеру по floating IP, клиент получает адреса всех узлов кластера. Затем он устанавливает соединение на один из неактивных узлов, и просит тот сообщить ему по дружески, если активный вдруг отвалится  - предполагается, что узлы кластера узнают об этом событии раньше, чем клиенты. Если клиент получит такую подсказку - то начнёт переподключение немедленно, не ожидая, пока существующее TCP-соединение вылетит по таймауту.

Называется этот узел-подсказчик опять таки witness - и он, хотя имеет прямое отношение к Failover Cluster и к shared folders, не имеет ничего общего со "свидетелем" для координации кластера, о котором рассказано выше. Спасибо Микрософту за этот гнусный бардак в терминологии.

Несмотря на то, что звучит эта идея интересно, у себя я это включать большого смысла не вижу, и вот почему:
  • в случае переключения между узлами кластера в той же сети IP-адрес остаётся тем же, и клиент будет пытаться переподключиться на него же. Если новый активный узел этот адрес ещё не подобрал - неважно, продолжает ли клиент стучаться по тому же TCP-соединению, или пытается установить новое, один чёрт он в ответ ничего не получит. Если же подобрал - то на попытку retransmission по старому соединению он моментально ответит сегментом RST, после чего клиент тут же откроет новое.
  • В случае hard failover между узлами в разных сетях, новому активному узлу возьмёт даже больше времени принять на себя роль, чем клиенту - переподключиться по запасному адресу.
Таким образом, единственный случай, где есть смысл в этом протоколе - это graceful failover в кластере, разнесённом по двум разным сетям. Но дело ещё и в том, что он не очень-то дружелюбен к файерволлам. Для того, чтобы он работал, нам надо дополнительно к SMB на floating IP позволить также RPC на него же, а так же RPC на все индивидуальные адреса кластерных узлов. Поскольку открывать при этом клиентам доступ на личные SMB-службы этих узлов не хочется, эта бодяга требует двух правил вместо одного:
  1. SMB на адрес fileserver role
  2. RPС на адрес fileserver role + адреса серверов
Во-первых, это усложняет конфигурацию, а во-вторых, открытие доступа к RPC само по себе является дополнительным риском. Поэтому овчинка, на мой взгляд, выделки не стоит.

Впрочем, насколько я понимаю, придуман witness protocol не столько для кластерного файлсервера общего назначения, о котором этот пост, сколько для Scale-Out File Server. А в рамках Scale-Out не существует floating IP - соединения устанавливаются на адреса самих серверов. В этом случае заранее знать, что такой-то адрес отвалился и стучаться к нему незачем, действительно имеет большой смысл. Впрочем, Scale-Out я тут не рассматриваю.

Tuesday, December 11, 2018

VXLAN-over-IPsec tunnel between two FortiGates

The aim:
Sometimes we need to take two LANs located at different places - maybe offices, maybe cloud regions - and turn them to a single LAN, to one broadcast domain.

With FortiGates, it's possible to achieve by building a VXLAN tunnel between FortiGate in one LAN to FortiGate in another. If encryption is not necessary, we can just use native VXLAN protocol. If it's required - let's say, there's Internet between the LANs - then we can use VXLAN-over-IPsec.

The architecture:

  • Each side can be or not to be behind NAT.
  • The private subnet between FortiGate and NAT router can be the same on both sides - in fact, even the specific addresses may be the same.
  • The LAN-facing port on each FortiGates will not have IP addresses at all - we'll join it together with VXLAN tunnel interface into a software switch, and assign IP address to this switch. Of course, this IP should be different on each FortiGate, as they will belong to the same broadcast domain.
  • IPsec authentication in this example is based on pre-shared keys. For certificate-based example, see this post.

Configuration of upper FortiGate:


Basics:
config system interface
    edit wan1
        set vdom root
        set type physical
        set ip 172.16.11.1 255.255.255.0
    next
    edit port2
        set vdom root
        set type physical
    next
end
config router static
    edit 1
        set gateway 172.16.11.254
        set device wan1
    next
end

  • No IP address on LAN-facing port
  • We will be reaching the peer FortiGate via the default route.

Now we add the IPsec tunnel with VXLAN encapsulation:
config vpn ipsec phase1-interface
    edit VXLAN-on-IPsec
        set interface wan1
        set peertype any
        set proposal aes256-sha256
        set dpd on-idle
        set local-gw 172.16.11.1
        set remote-gw 5.6.7.8
        set psksecret pre_shared_key
        set encapsulation vxlan
        set encapsulation-address ipv4
        set encap-local-gw4 172.16.11.1
        set encap-remote-gw4 172.16.22.1
    next
end
config vpn ipsec phase2-interface
    edit VXLAN-on-IPsec
        set phase1name VXLAN-on-IPsec
        set proposal aes256-sha256
    next
end

  • set local-gw isn't actually required, it's here for clarity.
  • note that the peer addresses for IPsec are different from peer addreses for VXLAN:
    • for IPsec, we specify the remote public address, actually belonging to remote NAT router,
    • for VXLAN, we specify the actual private addresses of both FortiGates. Interestingly enough, they can be equal, if the WAN subnets / addresses are the same, this doesn't lead to any collision:
      • set encap-local-gw4  172.16.11.1
      • set encap-remote-gw4 172.16.11.1


Now we join it to the software switch:
config system switch-interface
    edit LAN-Soft-Switch
        set vdom root
        set member port2 VXLAN-on-IPsec
    next
end

And configure an IP address on it. Now the interfaces configuration looks like this:
config system interface
    edit wan1
        set vdom root
        set type physical
        set ip 172.16.11.1 255.255.255.0
    next
    edit port2
        set vdom root
        set type physical
    next
    edit VXLAN-on-IPsec
        set vdom root
        set interface wan1
        set type tunnel
    next
    edit LAN-Soft-Switch
        set vdom root
        set ip 10.10.10.1 255.255.255.0
        set allowaccess ping
        set type switch
    next
end


Configuration of lower FortiGate:
It's symmetrical, so I'll just list all relevant sections:
config vpn ipsec phase1-interface
    edit VXLAN-on-IPsec
        set interface wan1
        set peertype any
        set proposal aes256-sha256
        set dpd on-idle
        set local-gw 172.16.22.1
        set remote-gw 1.2.3.4
        set psksecret ENC encrypted_pre_shared_key
        set encapsulation vxlan
        set encapsulation-address ipv4
        set encap-local-gw4 172.16.22.1
        set encap-remote-gw4 172.16.11.1
    next
end
config vpn ipsec phase2-interface
    edit VXLAN-on-IPsec
        set phase1name VXLAN-on-IPsec
        set proposal aes256-sha256
    next
end
config system switch-interface
    edit LAN-Soft-Switch
        set vdom root
        set member port2 VXLAN-on-IPsec
    next
end
config system interface
    edit wan1
        set vdom root
        set type physical
        set ip 172.16.22.1 255.255.255.0
    next
    edit port2
        set vdom root
        set type physical
    next
    edit VXLAN-on-IPsec
        set vdom root
        set interface wan1
        set type tunnel
    next
    edit LAN-Soft-Switch
        set vdom root
        set ip 10.10.10.2 255.255.255.0
        set allowaccess ping
        set type switch
    next
end
config router static
    edit 1
        set gateway 172.16.22.254
        set device wan1
    next
end


The packets flowing over Internet between the NAT routers will look, after all levels of encapsulation, more or less like this:

Of course, all these headers add significant overhead to our packets. So if FortiGates' physical interfaces have a standard MTU of 1500 bytes, then the MTU of VXLAN interface (and thus of the software switch) will be only 1374 bytes:

FortiGate # fnsysctl ifconfig VXLAN-on-IPsec
VXLAN-over-IPsec Link encap:Ethernet  HWaddr 12:35:08:F7:AC:52
        UP BROADCAST RUNNING MULTICAST  MTU:1374  Metric:1
        RX packets:2656 errors:0 dropped:0 overruns:0 frame:0
        TX packets:857 errors:2 dropped:0 overruns:0 carrier:0
        collisions:0 txqueuelen:0
        RX bytes:467584 (456.6 KB)  TX bytes:55392 (54.1 KB)

FortiGate # fnsysctl ifconfig LAN-Soft-Switch​
LAN-Soft-Switch​ Link encap:Ethernet  HWaddr 00:50:56:9C:2E:8E
        inet addr:10.10.10.1  Bcast:10.10.10.255  Mask:255.255.255.0
        UP BROADCAST RUNNING MULTICAST  MTU:1374  Metric:1
        RX packets:85801 errors:0 dropped:0 overruns:0 frame:0
        TX packets:179544 errors:0 dropped:0 overruns:0 carrier:0
        collisions:0 txqueuelen:1000

        RX bytes:7299089 (6.10 MB)  TX bytes:233154091 (222.4 MB)


Useful links:
Possible alternative way of configuring VXLAN over IPsec:
  1. add a loopback address on each FortiGate
  2. set up regular IPsec tunnel between the FortiGates, allowed to usage between loopbacks only.
  3. set up native VXLAN tunnel between these loopbacks.
But I didn't try it.

Wednesday, December 5, 2018

IPsec tunnel from Windows RRAS to FortiGate

Dial-up IPsec tunnel from Windows RRAS to FortiGate


The aim:

Encrypted, mutually authenticated VPN tunnel, initiated by Windows Routing & Remote Access Service and terminated by FortiGate firewall.

Both sides of the tunnel are equipped with private IP addresses and are placed behind NAT routers.


The method:

  • We utilize IKEv2 protocol supported by both sides.
  • Each side authenticates one another by certificate. It checks validity of certificate (dates, signature of a CA) and that its name matches a predefined value. CAs that issued certificates for both side can be different or be the same one.
  • NAT traversal is achieved automatically, by encapsulating IPsec packets in UDP datagrams sent between source & destination ports 4500 (except initial IKE exchange, which is between src & dst port 500). We don't need to configure anything special about it.

Certificates pre-requisites:
  • Windows should have a certificate in the computer Personal store (can be managed by certlm.msc):
    • its Subject should be something like "CN = windows2016". This string will be used by FortiGate to authenticate the peer.
    • It should be signed by CA known to FortiGate. Let's say it's called OurRespectedCA. It should be imported to the "Trusted Root Certification Authorities" or "Intermediate Certification Authorities" store on Windows, and to the FortiGate.
  • FortiGate should also have a certificate:
    • It should list a name in "Subject" or "Subject Alternative Name" field matching the hostname or IP address that RRAS will use to access it.
    • It should be signed by CA trusted by Windows. It may be the same CA as the one that issued certificate for Windows, or may be not.
Windows RRAS configuration:



  • Right-click "Network Interfaces" and select "New Demand-dial Interface".


  • Enter name for the logical interface representing the tunnel. We're going to use IKEv2 protocol, so here I'm setting the name to "IKEv2".


  • Select "Connect using virtual private networking (VPN)":


  • Select the IKEv2 protocol:
  • Enter DNS name or IP address of the FortiGate. This name must appear in the Subject or SAN field of a certificate presented by FortiGate. In case of a mismatch RRAS won't connect.

  • Select "Route IP packets on this interface" and press Next:

  • Define remote subnets sitting behind the FortiGate (10.20.20.0/24) and on the FortiGate itself (192.168.0.1/32). These will appear in the Windows routing table (shown by "route print" command) as static routes, when the IKEv2 interface is up:


  • Leave all "Dial-out credentials" empty, just press Next:


  • Press Finish:


  • You'll see a new interface in the "Network Interfaces" list - IKEv2. Right-click it and open Properties:
  • On Options tab, set type of connection to "Persistent" and redial attempts number and intervals to something making sense:

  • On Security tab, verify that:
    • Type of VPN: IKEv2
    • Data encryption is set to "Maximum strength"
    • The "Use machine certificates" option selected, and "Verify the Name and Usage attributes of the server's certificate" flag is marked.

  • On Networking tab, disable IPv6. Any changes in IPv4 settings have no effect, for some unclear reason, so we cannot set up IPv4 address manually - it will be received from FortiGate:

  • Close Properties, right-click the IKEv2 interface and select "Connect". 


You should be connected.


FortiGate configuration:

Upload certificate & its private key for the FortiGate itself. Import it via WebUI (System --> Certificates --> Import):



Upload certificate of the CA that signed the certificate of the Windows peer. The best way to do it is via CLI, because it allows you to assign custom name to the CA, instead of  some vague CA_Cert_X:

config vpn certificate ca
    edit OurRespectedCA
        set ca "-----BEGIN CERTIFICATE-----
MIIDLjCCAhagAwIBAgIQNGhB9lMumqdIrn3YFI+EWDANBgkqhkiG9w0BAQsFADAX
...
1tAuWASUPRvwqDzvt7ZtUXDxaGFqh3i3YpasbHRdnlmeFCVG82eIS18ajDBSVrDe
lHU=
-----END CERTIFICATE-----"
        set range global
        set source user
        set trusted enable
        set scep-url ''
        set source-ip 0.0.0.0
    next
end

Define a PKI user account for the remote peer, referencing the CA certificate that we've uploaded:
config user peer
    edit Windows2016
        set ca OurRespectedCA
        set subject Windows2016
        set cn Windows2016
    next
end

This means that this PKI user must have valid certificate issued by OurRespectedCA, with "CN = Windows2016" in Subject.

Now let's define our tunnel:
config vpn ipsec phase1-interface
    edit IKEv2
        set type dynamic
        set interface wan1
        set ike-version 2
        set authmethod signature
        set mode-cfg enable
        set proposal aes256-sha256
        set dpd disable
        set dhgrp 2
        set certificate fortigate.dns.name
        set peer Windows2016
        set ipv4-start-ip 192.18.0.2
        set ipv4-end-ip 192.18.0.2
    next
end
config vpn ipsec phase2-interface
    edit IKEv2
        set phase1name IKEv2
        set proposal aes256-sha1
        set pfs disable
    next
end

Notes:
  • "set type dynamic" means that connection from any IP address will be accepted. We need this, because otherwise we cannot assign the tunnel address (192.168.0.2) to Windows ("mode-cfg enable" will not be available). If we want to limit our peer to specific IP or subnet, we should use another firewall before FortiGate.
  • "set authmethod signature" means that certificates are used for authentication of both sides.
  • "set certificate" specifies certificate & private key used by FortiGate.
  • "set peer" references the PKI user account representing the Windows machine.
  • "set proposal aes256-sha256" & "set pfs disable" mean security algorithms to use (AES256 for encryption, SHA256 for integrity, no Perfect Forward Secrety (Diffie-Hellman key exchange). These algorithms match the "Maximum strength encryption" setting on RRAS interface, as we know from "VPN Interoperability guide for Windows Server 2012 R2".
  • "set mode-cfg enable", "set ipv4-start-ip" and "set ipv4-end-ip" are used to assign tunnel address to our peer.

So now we have a logical interface named IKEv2. It now can be joined to security zones or used directly in firewall policies (although I always prefer wrapping them by zones).

But first let's configure it, to allow FortiGate and Windows to reach each other by tunnel addresses:
config system interface
    edit IKEv2
        set vdom root
        set ip 192.18.0.1 255.255.255.255
        set allowaccess ping
        set type tunnel
        set remote-ip 192.18.0.2 255.255.255.255
        set interface wan1
    next
end
Note that remote-ip matches the address that we assigned to Windows by the "phase1-interface" configuration.

We can also define static routes towards networks behind Windows:
config router static
    edit 0
        set dst 10.10.10.0 255.255.255.0
        set device IKEv2
    next
end

That's it. Mischief managed. :-)


Limitations:

1. As said above, if we want to limit FortiGate's peer to specific addresses, we should use another firewall before FortiGate  (or maybe "local-in-policy", I didn't check).


2. Assigning static address to the dial-up interface doesn't work in RRAS. Any settings are just ignored.


3. Unfortunately, Windows doesn't allow to specify explicitly the CAs, certificates issued by which will be accepted. This means that it will accept any peer certificate as long as it has fortigate.dns.name in Subject or SAN and chains up to any CA in the "Trusted Root Certification Authorities" list. So theoretically an adversary, which is able to direct traffic to fortigate.dns.name towards its own server, can get a certificate from another trusted CA, such as LetsEncrypt or Comodo, and impersonate the FortiGate.
  • The only way around this is to leave only our CA as trusted and delete any else from the list. But it means that the server can experience problems in other areas (such as browsing), and that Windows Update must be turned off (otherwise it will populate the list again).
  • The adversary still won't be able to perform full man-in-the-middle, as he cannot impersonate Windows machine to FortiGate (FortiGate does limit accepted certs to specific CAs).
  • The native FortiGate client software (FortiClient) has exactly the same problem.

4. Using pre-shared keys instead of certificates looks as natural solution for this, but unfortunately, it didn't work for me. Sporadically, the tunnel got established, but mostly it failed with authentication errors. I've found no logical explanation why. So certificates were the only option left.