This article will show a configuration example with a Hub and spoke topology where a FortiGate acting as an SSL VPN client will establish a tunnel with the SSL VPN server in order to allow users that are LAN connected behind the Spoke(client) to be tunneled through SSL VPN and be able to reach destinations on the Hub.
FortiOS v7.0.x and newer versions.
FortiGate SSL VPN client and Server configuration
For this case, following network example will be used:
- In such environments, customers can have FortiGates acting as SSL VPN clients in remote Branch offices that connect to a main HUB FortiGate located in the Headquarters. All LAN-connected hosts in the Branch offices would then be able to access resources on the Remote network through Policies matching traffic for specific LAN destinations.
- The Spoke FortiGate will use PSK and a PKI client certificate to authenticate.
- Split routing will be used in order for only traffic matching the destination network 20.20.20.0/24 to be routed through the tunnel and the other traffic to go through the internet. Once the tunnel is established and proper policy matched we will verify with debugs the connection and see the injected route learned from the Server in the Spoke FortiGate.
- Both FortiGates used in this example are v7.0.11
1) Configuration of HUB FortiGate (Server in Headquarters).
a) Generate and Import the Server Certificate and CA (issuer) certificate in FortiGate.
To generate certificates in a Microsoft environment, follow the Kb article below which shows an example to create an EAP server certificate for FortiAuthenticator.
The same concept can be used to generate Server certificates for FortiGate or other endpoints:
In this scenario, the certificates and private keys have been generated using XCA certificate generation tool (openSSL frontend).
Server certificate (CN = client2.lab.net)
CA certificate (issuer) (CN = global.lab.net)
Go to System -> Certificates -> Create/Import.
Select 'Certificate'.
Import the Server certificate.
After it is imported the certificate will appear under 'Local Certificate'.
Go to System -> Certificates -> Create/Import.
Select 'CA Certificate'
Import CA certificate:
The certificate will appear under 'Remote CA Certificate' named as CA_Cert_1 in case this was the first imported case.
b) Configure the Local user and PKIuser
In this case, a local use called 'client2' will be used, which will represent the SSL VPN Spoke FortiGate when the tunnel will be established.
To enable certificate authentication we will need to create a PKI user. This option is not available in the GUI but it will appear after we create the first user through CLI:
config user peer
edit "pk1"
set ca "CA_Cert_1"
set subject "CN = client2.lab.net"
next
end
The user 'pk1' has been created and the issuing CA has been defined, which was previously imported and the subject parameter matching the Subject field in the Certificate 'CN = client2.lab.net'
The entry will now be available on GUI:
c) Create the SSLVPN portal and configure the SSL VPN settings
A new portal called 'testportal2' will be created and split tunneling based on Policy Destination will be enabled.
Leave other settings as default:
Configure the SSL VPN settings and add portal mapping:
Additionally, an authentication rule will be configured for the portal adding the certificate authentication requirement and defining the 'client2':
config vpn ssl settings
set servercert "client2.lab.net"
set tunnel-ip-pools "SSLVPN_TUNNEL_ADDR1"
set tunnel-ipv6-pools "SSLVPN_TUNNEL_IPv6_ADDR1"
set source-interface "port1"
set source-address "all"
set source-address6 "all"
set default-portal "full-access"
config authentication-rule
edit 1
set users "client2"
set portal "testportal2"
set client-cert enable
set user-peer "pk1"
next
end
end
d) Create the firewall policy with the destination the remote subnet 20.20.20.0/24.
CLI config:
config firewall policy
edit 1
set name "sslvpn2"
set uuid xx
set srcintf "ssl.root"
set dstintf "port2" <---- Lan interface of HUB.
set action accept
set srcaddr "all"
set dstaddr "Internal2" <----- Address object Internal LAN network of Hub.
set schedule "always"
set service "ALL"
set nat enable
set users "client2" <------ FortiGate Client.
next
end
2) Configuration of SPOKE FortiGate (Client in Branch office).
a) For the SPOKE configuration we will perform the same steps a) and b) identically with same parameters
Summarized:
- Import the Server certificate (CN = client2.lab.net).
- Import the CA certificate (issuer) (CN = global.lab.net).
- Configure Local user 'client2'.
- Configure PKI user 'pk1'.
config user peer
edit "pk1"
set ca "CA_Cert_1"
set subject "CN = client2.lab.net"
next
end
b) Configure the SSL VPN client.
In newer FOS v7.0.x there is an additional option in VPN > SSL VPN client.
Here, an SSL VPN tunnel interface has been created under the WAN(port1) of the Spoke FortiGate.
In the SSL VPN client configuration, the below settings have been created, where under the 'Serve' parameter, it will be necessary to specify the Public IP where the HUB FortiGate listens for connections.
CLI configuration:
config vpn ssl client
edit "sslclient_to_srv"
set interface "sslclient_port1"
set user "client2"
set psk ENC XXXXXX
set peer "pk1"
set server "X.X.X.X"
set port 10443
set certificate "client2.lab.net"
next
end
c) Configure the Firewall policy.
CLI configuration:
edit 8
set name "Policy_to_Server_ssl_tunnel"
set uuid XX
set srcintf "port2" <----- Lan interface Spoke.
set dstintf "sslclient_port1"
set action accept
set srcaddr "all"
set dstaddr "all"
set schedule "always"
set service "ALL"
set nat enable
next
3) Validation of the configuration.
Once the configuration of the SSL VPN client on the SPOKE is finished and the Firewall policy is in place, the attempt to automatically establish the tunnel SPOKE <->HUB will be notified
a) Enabling the following debugs on Hub to verify what is happening:
diag debug reset
diag debug app sslvpn -1
diag debug app fnbamd -1
diag debug console timestamp enable
diag debug enable
Successful connection attempt:
2023-05-08 17:10:13 [3162:root:13]User Agent: FortiSSLVPN
2023-05-08 17:10:13 [3162:root:13]rmt_logincheck_cb_handler:1283 user 'client2' has a matched local entry.
2023-05-08 17:10:13 [3162:root:13]sslvpn_auth_check_usrgroup:2978 forming user/group list from policy.
2023-05-08 17:10:13 [3162:root:13]sslvpn_auth_check_usrgroup:3024 got user (1) group (0:0).
2023-05-08 17:10:13 [3162:root:13]sslvpn_validate_user_group_list:1890 validating with SSL VPN authentication rules (1), realm ().
2023-05-08 17:10:13 [3162:root:13]sslvpn_validate_user_group_list:1975 checking rule 1 cipher.
2023-05-08 17:10:13 [3162:root:13]sslvpn_validate_user_group_list:1983 checking rule 1 realm.
2023-05-08 17:10:13 [3162:root:13]sslvpn_validate_user_group_list:1994 checking rule 1 source intf.
2023-05-08 17:10:13 [3162:root:13]sslvpn_validate_user_group_list:2033 checking rule 1 vd source intf.
2023-05-08 17:10:13 [3162:root:13]sslvpn_validate_user_group_list:2526 rule 1 done, got user (1:1) group (0:0) peer group (0).
2023-05-08 17:10:13 [3162:root:13]sslvpn_validate_user_group_list:2534 got user (1:1) group (0:0) peer group (0).
2023-05-08 17:10:13 [3162:root:13]sslvpn_validate_user_group_list:2876 got user (1:1), group (0:0) peer group (0).
2023-05-08 17:10:13 [3162:root:13]fam_cert_send_req:1109 do certificate peer check first(2).
2023-05-08 17:10:13 [3162:root:13]fam_cert_send_req:1181 doing certificate checking for 1 peer(s).
2023-05-08 17:10:13 [2341] handle_req-Rcvd auth_cert req id=691969652, len=1101, opt=0
2023-05-08 17:10:13 [974] __cert_auth_ctx_init-req_id=691969652, opt=0
2023-05-08 17:10:13 [103] __cert_chg_st- 'Init'
2023-05-08 17:10:13 [140] fnbamd_cert_load_certs_from_req-1 cert(s) in req.
2023-05-08 17:10:13 [661] __cert_init-req_id=691969652
2023-05-08 17:10:13 [710] __cert_build_chain-req_id=691969652
2023-05-08 17:10:13 [257] fnbamd_chain_build-Chain discovery, opt 0x13, cur total 1
2023-05-08 17:10:13 [273] fnbamd_chain_build-Following depth 0
2023-05-08 17:10:13 [308] fnbamd_chain_build-Extend chain by system trust store. (good: 'CA_Cert_1')
2023-05-08 17:10:13 [273] fnbamd_chain_build-Following depth 1
2023-05-08 17:10:13 [287] fnbamd_chain_build-Self-sign detected.
2023-05-08 17:10:13 [99] __cert_chg_st- 'Init' -> 'Validation'
2023-05-08 17:10:13 [831] __cert_verify-req_id=691969652
2023-05-08 17:10:13 [832] __cert_verify-Chain is complete.
2023-05-08 17:10:13 [457] fnbamd_cert_verify-Chain number:2
2023-05-08 17:10:13 [471] fnbamd_cert_verify-Following cert chain depth 0
2023-05-08 17:10:13 [533] fnbamd_cert_verify-Issuer found: CA_Cert_1 (SSL_DPI opt 1)
2023-05-08 17:10:13 [471] fnbamd_cert_verify-Following cert chain depth 1
2023-05-08 17:10:13 [675] fnbamd_cert_check_group_list-checking group with name 'pk1'
2023-05-08 17:10:13 [490] __check_add_peer-check 'pk1'
2023-05-08 17:10:13 [366] peer_subject_cn_check-Cert subject 'CN = client2.lab.net, CN = client2.lab.net'
2023-05-08 17:10:13 [294] __RDN_match-Checking 'CN' val 'client2.lab.net' -- match.
2023-05-08 17:10:13 [324] __cert_subject_RDN_compare-Total matched RDNs in cert: 1
2023-05-08 17:10:13 [391] peer_subject_cn_check-Subject is good.
2023-05-08 17:10:13 [497] __check_add_peer-'pk1' check ret:good
2023-05-08 17:10:13 [612] __peer_user_clear_unmatched-Clear all user(s) other than 'pk1'
2023-05-08 17:10:13 [631] __peer_user_clear_unmatched-
2023-05-08 17:10:13 [191] __get_default_ocsp_ctx-def_ocsp_ctx=(nil), no_ocsp_query=0, ocsp_enabled=0
2023-05-08 17:10:13 [738] fnbamd_cert_check_group_list-Peer users
2023-05-08 17:10:13 [741] fnbamd_cert_check_group_list- 'pk1' ('N/A','N/A')
2023-05-08 17:10:13 [867] __cert_verify_do_next-req_id=691969652
2023-05-08 17:10:13 [99] __cert_chg_st- 'Validation' -> 'Done'
2023-05-08 17:10:13 [912] __cert_done-req_id=691969652
2023-05-08 17:10:13 [1652] fnbamd_auth_session_done-Session done, id=691969652
2023-05-08 17:10:13 [957] __fnbamd_cert_auth_run-Exit, req_id=691969652
2023-05-08 17:10:13 [1689] create_auth_cert_session-fnbamd_cert_auth_init returns 0, id=691969652
2023-05-08 17:10:13 [1608] auth_cert_success-id=691969652
2023-05-08 17:10:13 [1059] fnbamd_cert_auth_copy_cert_status-req_id=691969652
2023-05-08 17:10:13 [1067] fnbamd_cert_auth_copy_cert_status-Matched peer user 'pk1'
2023-05-08 17:10:13 [833] fnbamd_cert_check_matched_groups-checking group with name 'pk1'
2023-05-08 17:10:13 [895] fnbamd_cert_check_matched_groups-matched
2023-05-08 17:10:13 [1098] fnbamd_cert_auth_copy_cert_status-Leaf cert status is unchecked.
2023-05-08 17:10:13 [1186] fnbamd_cert_auth_copy_cert_status-Cert st 2c0, req_id=691969652
2023-05-08 17:10:13 [216] fnbamd_comm_send_result-Sending result 0 (nid 672) for req 691969652, len=2149
2023-05-08 17:10:13 [3162:root:13]__auth_cert_cb:895 certificate check OK.
2023-05-08 17:10:13 [3162:root:13]__auth_cert_cb:912 certificate check OK, matched peer [pk1].
2023-05-08 17:10:13 2023-05-08 17:10:13 [1553] destroy_auth_cert_session-id=691969652
2023-05-08 17:10:13 [3162:root:13]sslvpn_update_user_group_list:1720 Remove user(s) and group(s) do not set matched peer [pk1].
[1032] fnbamd_cert_auth_uninit-req_id=691969652
2023-05-08 17:10:13 2023-05-08 17:10:13 [131] fnbamd_peer_ctx_free-Freeing peer ctx 'pk1'
[3162:root:13]sslvpn_update_user_group_list:1724 got user (1:1), group (0:0) peer group (0) after update.
2023-05-08 17:10:13 [3162:root:13]sslvpn_authenticate_user:183 authenticate user: [client2]
2023-05-08 17:10:13 [3162:root:13]sslvpn_authenticate_user:197 create fam state
2023-05-08 17:10:13 [3162:root:13]user 'client2' uses 2FA: ctx->peer_two_factor = 0, ctx->peer_name.peername = 1, ctx->is_two_factor = 0
2023-05-08 17:10:13 [3162:root:13]fam_auth_send_req:882 found node client2:1:pk1, valid:1
2023-05-08 17:10:13 [3162:root:13][fam_auth_send_req_internal:426] Groups sent to FNBAM:
2023-05-08 17:10:13 [3162:root:13]group_desc[0].grpname = client2
2023-05-08 17:10:13 [3162:root:13][fam_auth_send_req_internal:438] FNBAM opt = 0X301420
2023-05-08 17:10:13 local auth is done with user 'client2', ret=0
2023-05-08 17:10:13 [3162:root:13]fam_auth_send_req_internal:514 fnbam_auth return: 0
2023-05-08 17:10:13 [3162:root:13][fam_auth_send_req_internal:539] Authenticated groups (1) by FNBAM with auth_type (1):
2023-05-08 17:10:13 [3162:root:13]Received: auth_rsp_data.grp_list[0] = 16777218
2023-05-08 17:10:13 [3162:root:13][fam_auth_send_req_internal:652] The user client2 is authenticated.
2023-05-08 17:10:13 [3162:root:13]Auth successful for user [client2] with matched user-peer [pk1]
2023-05-08 17:10:13 [3162:root:13]fam_do_cb:665 fnbamd return auth success.
2023-05-08 17:10:13 [3162:root:13]SSL VPN login matched rule (1).
2023-05-08 17:10:13 [3162:root:13]User Agent: FortiSSLVPN
2023-05-08 17:10:13 [3162:root:13]rmt_web_session_create:1209 create web session, idx[0]
2023-05-08 17:10:13 [3162:root:13]login_succeeded:536 redirect to hostcheck
2023-05-08 17:10:13 [3162:root:13]User Agent: FortiSSLVPN
2023-05-08 17:10:13 [3162:root:13]deconstruct_session_id:709 decode session id ok, user=[client2], group=[],authserver=[],portal=[testportal2],host[X.X.X.Y],realm=[],csrf_token=[B0FEE0F19A14662E198D8689F019133D],idx=0,auth=1,sid=27f04e3b,login=1683558613,access=1683558613,saml_logout_url=no,pip=no,grp_
info=[ATJgjh],rmt_grp_info=[]
2023-05-08 17:10:13 [3162:root:13]deconstruct_session_id:709 decode session id ok, user=[client2], group=[],authserver=[],portal=[testportal2],host[X.X.X.Y],realm=[],csrf_token=[B0FEE0F19A14662E198D8689F019133D],idx=0,auth=1,sid=27f04e3b,login=1683558613,access=1683558613,saml_logout_url=no,pip=no,grp_
info=[ATJgjh],rmt_grp_info=[]
2023-05-08 17:10:13 [3162:root:13]deconstruct_session_id:709 decode session id ok, user=[client2], group=[],authserver=[],portal=[testportal2],host[X.X.X.Y],realm=[],csrf_token=[B0FEE0F19A14662E198D8689F019133D],idx=0,auth=1,sid=27f04e3b,login=1683558613,access=1683558613,saml_logout_url=no,pip=no,grp_
info=[ATJgjh],rmt_grp_info=[]
2023-05-08 17:10:13 [3162:root:13]req: /remote/fortisslvpn_xml?dual_stack=1
2023-05-08 17:10:13 [3162:root:13]deconstruct_session_id:709 decode session id ok, user=[client2], group=[],authserver=[],portal=[testportal2],host[X.X.X.Y],realm=[],csrf_token=[B0FEE0F19A14662E198D8689F019133D],idx=0,auth=1,sid=27f04e3b,login=1683558613,access=1683558613,saml_logout_url=no,pip=no,grp_
info=[ATJgjh],rmt_grp_info=[]
2023-05-08 17:10:13 [3162:root:13]deconstruct_session_id:709 decode session id ok, user=[client2], group=[],authserver=[],portal=[testportal2],host[X.X.X.Y],realm=[],csrf_token=[B0FEE0F19A14662E198D8689F019133D],idx=0,auth=1,sid=27f04e3b,login=1683558613,access=1683558613,saml_logout_url=no,pip=no,grp_
info=[ATJgjh],rmt_grp_info=[]
2023-05-08 17:10:13 [3162:root:13]rmt_fortisslvpn_xml_cb_handler:1139 Client requests dual stack tunnel
2023-05-08 17:10:13 [3162:root:13]sslvpn_reserve_dynip:1476 tunnel vd[root] ip[X.X.X.X] app session idx[0]
2023-05-08 17:10:13 [3162:root:13]form_ipv4_pol_split_tunnel_addr:79 Matched policy (id = 1) to add ipv4 split tunnel routing address
2023-05-08 17:10:13 [3162:root:13]req: /remote/sslvpn-tunnel2
2023-05-08 17:10:13 [3162:root:13]sslvpn_tunnel2_handler,59, Calling rmt_conn_access_ex.
2023-05-08 17:10:13 [3162:root:13]deconstruct_session_id:709 decode session id ok, user=[client2], group=[],authserver=[],portal=[testportal2],host[X.X.X.Y],realm=[],csrf_token=[B0FEE0F19A14662E198D8689F019133D],idx=0,auth=1,sid=27f04e3b,login=1683558613,access=1683558613,saml_logout_url=no,pip=no,grp_
info=[ATJgjh],rmt_grp_info=[]
2023-05-08 17:10:13 [3162:root:13]normal tunnel2 request received.
2023-05-08 17:10:13 [3162:root:13]sslvpn_tunnel2_handler,173, Calling tunnel2.
2023-05-08 17:10:13 [3162:root:13]tunnel2_enter:1289 0x7fa7f7f56500:0x7fa7f7322000 sslvpn user[client2],type 1,logintime 0 vd 0 vrf 0
2023-05-08 17:10:13 [3162:root:13]tun dev (ssl.root) opened (33)
2023-05-08 17:10:13 [3162:root:13]fsv_associate_fd_to_ipaddr:2022 associate X.X.X.X to tun (ssl.root:33)
2023-05-08 17:10:13 [3162:root:13]proxy arp: scanning 6 interfaces for IP X.X.X.X
2023-05-08 17:10:13 [3162:root:13]Cannot determine ethernet address for proxy ARP
2023-05-08 17:10:13 [3162:root:13]Will add auth policy for policy 1 for user client2:
2023-05-08 17:10:13 [3162:root:13]Add auth logon for user client2:, matched group number 1
b) Validation on HUB FortiGate.
Go to Dashboard -> Network -> SSL VPN.
It will be possible to see the SSL VPN spoke tunnel represented by Local user 'client2'.
c) Route injection validation on SPOKE FortiGate.
In the Branch office FortiGate, it is possible to verify that it has dynamically added a route to the remote subnet 20.20.20.0/24 learned from the HUB.
firewall # get router info routing-table database
Codes: K - kernel, C - connected, S - static, R - RIP, B - BGP
O - OSPF, IA - OSPF inter area
N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
E1 - OSPF external type 1, E2 - OSPF external type 2
i - IS-IS, L1 - IS-IS level-1, L2 - IS-IS level-2, ia - IS-IS inter area
> - selected route, * - FIB route, p - stale info
Routing table for VRF=0
S *> 0.0.0.0/0 [10/0] via Y.Y.Y.Y, port1, [1/0]
C *> 10.10.10.0/24 is directly connected, port3
C *> X.X.X.X/20 is directly connected, port1
C *> X.X.X.X/32 is directly connected, sslclient_port1
S *> 20.20.20.0/24 [10/0] is directly connected, sslclient_port1, [1/0]
C *> X.X.X.X/32 is directly connected, VPN
C *> 192.168.10.0/24 is directly connected, port2
In the same manner, it is possible to have multiple other branches which are possible to reference as 'client3' or 'branch3' and so on in a Hub and Spoke topology.
More details and options for this type of setup are provided in the Fortinet documentation below:
The Fortinet Security Fabric brings together the concepts of convergence and consolidation to provide comprehensive cybersecurity protection for all users, devices, and applications and across all network edges.
Copyright 2024 Fortinet, Inc. All Rights Reserved.