Showing posts with label fortinet. Show all posts
Showing posts with label fortinet. Show all posts

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.

Saturday, November 26, 2016

Adventures with FortiGate LB

Столкнулся на днях с довольно загадочной ситуацией.

Преамбула
Дано: есть сайт об осьми веб-серверах. Есть перед ними load balancer в виде файерволла FortiGate v5.4.1 - распределяет между ними нагрузку, а заодно переводит HTTPS-трафик в HTTP и инспектирует его. А перед ним находится сеть CDN, которая в свою очередь также шерстит трафик на предмет всяких там SQL-иньекций, блокирует DDoS-атаки, кэширует картинки и прочую статику - в общем, полезная вещь.

Когда юзер открывает сайт, DNS направляет его браузер на прокси CDN, браузер устанавливает с прокси SSL-соединение и посылает ему HTTP-запрос. Если запрос прокси нравится - пересылает его по другому SSL-соединению на LB. Тот прогоняет свои проверки - IPS, антивирус, - и перекидывает запрос на один из восьми серверов. Ответ сервера пересылается обратным путём.

Как видим, всё несложно, но есть дополнительное требование: sessions persistence, так же известное как sticky sessions. Сиречь, начав работать с каким-то сервером, юзер так и должен продолжать с ним работать - все запросы от браузера должны приходить на тот же сервер, разве что тот загнётся.
За это у нас отвечает FortiGate - он этого может добиваться одним из двух способов:

  • HTTP cookie - когда приходит запрос от нового клиента, FortiGate добавляет в ответ новый cookie с названием FGTServer и длинным псевдорандомальным значением. Браузер запоминает этот cookie и добавляет в каждый последующий запрос. По его значению Фортигейт знает, на какой из серверов эти запросы перекидывать.
  • SSL Session ID - когда клиент с сервером устанавливает SSL-соединение, сервер сочиняет некий session identifier и посылает его клиенту. Если TCP-connection, на котором SSL-соединение основано, сломалось, клиент может быстро восстановить общение с сервером, открыв новую TCP-трубу и выслав этот identifier - избежав таким образом необходимости в полном обмене ключами. Это ускоряет SSL и расцепляет его с TCP - одно SSL-соединение может использовать множество TCP-соединений. Подробности можно узнать здесь.
    В данном случае "сервер" - это не веб-сервер, а сам FortiGate. Он запоминает, на какой настоящий сервер он перебросил запрос, пришедший по соединению с новым session ID, и последующие запросы направляет соответственно.
Мы используем первый способ.

А теперь амбула
Выплыла проблема: юзерская сессия ломается. Вот залогинился он на сайт, и всё вроде отлично, но вдруг сайт ему и говорит: а ты кто такой, мил человек? Знать тебя не знаю.

Стало быть, не фурычит наша sessions persistence - попадают запросы от юзера вдруг на новый сервер, а не обслуживаются всё тем же. Открываем браузер, жмём F12, смотрим, что происходит - и впрямь: браузер получает в начале печеньку FGTServer, честно отсылает её - зато Фортигейт в какой-то момент от неё отмахивается, будто её и нету, перекидывает запрос на другой сервак и пришивает к ответу новое значение FGTServer.

Казалось бы, дело ясное - FortiGate виновен, на помойку. Но вот в чём закавыка: происходит это только при обращении через CDN - если браузер общается с Фортигейтом напрямую, то всё прекрасно, cookie при всех запросах сохраняется тот же, сервер - тот же, работает наш persistence!

Что за лабуда, думаю - может, CDN наши cookies срезает каким-то образом? Запускаю сниффер на Фортигейте, смотрю - ничего подобного! Вот он наш FGTServer в запросе, и значение правильное - да Фортигейт запрос на другой сервак перекидывает!

От отсутвия идей перешёл на второй способ, на SSL Session ID - не помогло ровно ничем. Напрямую - работает чудесно, через CDN - ломается. Что за притча?

Может, сервер health check проваливает и LB его помечает как дохлый? Может, мы максимум соединений на сервер перешли? Нет, вроде ничем не подтверждается - да и как объяснить тогда, что напрямую всё летает?


Развязка
А ларчик открылся вот как: оказывается, чёртов CDN запросы от множества разных клиентов мультиплексирует на уровне TCP. То есть приходят к ихнему прокси запросы от сотни юзеров - а тот поддерживает с нашим LB какой-то пул TCP-соединений - скажем, пять - и в каждое пихает запросы от разных юзеров.

Это performance, конечно, улучшает - не нужно для каждого нового юзера новый TCP-handshake устанавливать и отдельный сокет держать: бери да пихай его запросы в свободную TCP-трубу. Да только наш load balancer это с ума сводит - видать, не подумали программисты Fortinet, что такая ситуация возможна. При прямом-то обращении такого не бывает - по каждому TCP-коннекшену запросы только от одного клиента идут, с тем же самым FGTServer cookie - а тут он вдруг раз и меняется.

И уж понятно, почему SSL Session ID не помогает - соответствует-то он не клиенту, а CDN-прокси. Решил прокси пихнуть один запрос от клиента в один сокет, а следующий - в другой, и привет: session IDs разные, LB их отсылает на разные сервера.

Отключили мультиплексирование, заставили прокси на каждого клиента новую TCP-трубу открывать - и всё залетало. Вот такой вот хэппи энд, граждане.

P.S.
Была, впрочем, и альтернатива - почётную функцию load balancing на сам CDN возложить. Но его жадные хозяева хотели за это дополнительную денежку, а нас жаба задавила. Нам грубияны не нужны, мы сами грубияны.