Fortigate Local-in policy configuration examples for VPN IPSec, VPN SSL, BGP and more


Introduction

Local-in policy is the policy guarding/protecting the Fortigate itself, i.e. it filters/restricts access when the destination is one of the Fortigate interfaces and its IPs. Below you will find example configurations, but before jumping in, you have to know few important facts about Local-in policy:

  • It is visible in the GUI by default starting with FortiOS 7.x, but in older versions you have to go to System → Feature Visibility → Local-in Policy to make it so.

  • The Local-in policy can only be configured in CLI, the GUI display is read-only.

  • Additionally, the GUI displays only default rules, created automatically by the Fortigate when you enable appropriate services. GUI will not show any rules you configure on CLI, and thus may confuse you into thinking CLI-configured rules do not work. My advice: forget about GUI, work on CLI from the beginning.

  • You have separate, ipv4 and ipv6, local-in policies.

  • The default action in rules is deny, so when you see no action in the show output, it means the action is to deny.

  • You cannot disable/delete/manipulate the auto-created by Fortigate rules any other way but by disabling/deleting services that opened them up. The custom rules we create on CLI override (go above) the default rules, but do not remove them. This means you have to take them into account. E.g., once you configure BGP on the Fortigate, this will open port 179 TCP to ALL, so to restrict BGP port to specific IPs, you will need to create 2 rules: 1st with action accept and use those specific IPs, then 2nd rule below, that denies ALL to port 179 TCP of BGP. This way, the default auto-created rule port 179 TCP - allow ALL will not be reached when matching the traffic.

  • When configuring on CLI, you must specify: incoming interface to protect, source and destination address (you can use all), schedule, and service (you can use specific or ALL).

  • In newer FortiOS versions, if I remember correctly, 6.4.9 or newer, we can set as a source address the Geography (Geolocation) object, allowing/blocking this way access by the country.

  • Local-in policy does NOT control NAT/port-forwarded rules, aka Virtual IPs (VIPs). This means, for example, if you configured a port-forwarding VIP allowing some specific port or a one-to-one NAT in Security Rules, no matter what you do in Local-in policy for the same IPs, the Fortigate will only look at Security Rules, ignoring Local-in. In short - VIPs override Local-in policies.

  • By default, Local-in policy hits are not logged, you have to set in Log Settings → Log All for denied packets to be logged. The logs are in Local Traffic section.

  • You can use Workspace Mode to prevent mistakenly locking out yourself when changing Local-in policy, see Resources at the end of the post.

Allow VPN IPSec port 500, 4500, and protocol ESP access to specific IP addresses only

Task: We set up VPN site to site with the remote peer of 13.13.13.13 and this opened port 500 (IKE), port 4500 (NAT-T), and protocol ESP to all IPs on the Internet. Let’s limit it to the 13.13.13.13 only.

  • Create a firewall address object (if not already) for the remote peer:

config firewall address
    edit "VPNpeer1"
        set comment "Remote peer for VPN"
        set subnet 13.13.13.13 255.255.255.255
    next
end
  • Create rule to allow IKE and ESP from this peer on port1 (WAN interface):

config firewall local-in-policy
    edit 0
        set intf "port1"
        set srcaddr "VPNpeer1"
        set dstaddr "all"
        set action accept
        set service "IKE" "ESP" //"IKE" service includes ports 500 and 4500
        set schedule "always"
    next
end
  • Create rule below to deny IKE and ESP protocols to everyone else:

config firewall local-in-policy
    edit 0
        set intf "port1"
        set srcaddr "all"
        set dstaddr "all"
        set service "IKE" "ESP"
        set schedule "always"
    next
end

Done. Now, this Fortigate will only answer to this peer (13.13.13.13) on port 500 UDP (for IKE), port 4500 for NAT Traversal, and to protocol ESP (Phase 2 VPN).

Allow only to specific BGP peers to connect to the port 179 TCP

Strictly speaking, by BGP protocol standard, it is enough for just one peer to listen for incoming BGP connections on port 179 TCP. So, even if we block incoming port 179 altogether, the BGP session would still be established if the remote BGP peer has port 179 open and listening. But let’s not go overboard.

Task: After creating BGP configuration between Fortigate and the remote peer of 12.12.12.12, you noticed that port 179 TCP on the Fortigate answers to connections from any IP. Not good, let’s limit port 179 just to the BGP peer.

  • Create firewall address object for the remote BGP peer (if not already):

config firewall address
        edit BGPpeer12.12.12.12
        set subnet 12.12.12.12/32
        set comment "Remote BGP peer address"
end
  • Create Local-in rule to allow this peer connection to our port 179:

config firewall local-in-policy
edit 0
        set intf "port1"
        set srcaddr "BGPpeer12.12.12.12"
        set dstaddr "all"
        set action accept
        set service "BGP"
        set schedule "always"
    next
end
  • Create a rule below, that block all IPs to port 179 on the Fortigate:

config firewall local-in-policy
    edit 0
        set intf "port1"
        set srcaddr "all"
        set dstaddr "all"
        set service "BGP"
        set schedule "always"
    next
end

SSL VPN - limit access to the port 10443 to a specific country, Israel in this example

Fortigates have suffered a bunch of remotely exploitable vulnerabilities in their SSL VPN service. And while not securing against that, restricting access to VPN SSL to the country where the Fortigate and VPN clients are located will set up another hurdle on the attackers' path.

Note
Starting with Fortios 7.2 it is no longer necessary to use Local-in policy for that because VPN SSL Settings accept Geo object as source address to limit the access. For versions before 7.2, it is still doable only in Local-in policy.
  • Create Geo address representing Israel IPs:

config firewall address
    edit "ILgeoIPs"
        set type geography
        set comment "All Israel IPs"
        set country "IL"
    next
end
  • Use it in a Local-in policy for port 10443 (or any other set for VPN SSL)

config firewall local-in-policy
edit 0
    set intf "port1"
    set srcaddr "ILgeoIPs"
    set dstaddr "all"
    set service "Custom_10443" <-- Custom service, created earlier, not shown
    set schedule "always"
    set action accept
next
end
  • Finally, deny access to all the rest:

config firewall local-in-policy
edit 0
    set intf "port1"
    set srcaddr all
    set dstaddr "all"
    set service "Custom_10443" <-- Custom service, created earlier, not shown
    set schedule "always"
next
end

Deny all services from all IP addresses

Warning
Do this only after you have identified all services you need to be exposed to the outside on the given interface, especially management if any, and created rules in Local in Policy allowing them.
config firewall local-in-policy
    edit 0
        set intf "port1"
        set srcaddr "all"
        set dstaddr "all"
        set action deny
        set service "ALL"
        set schedule "always"
    next

Limit management port access to specific IPs

Here, I actually would recommend against using Local-in policy for that.

Few reasons to mention:
  • Fortigate already has a built-feature trustedhost for that.

  • The risk is great - Local-in rules are not visible in GUI, IP addresses change frequently, and it is easy to forget to change such a rule with the result being locked out of the Fortigate altogether. The chance of having to use console to get access back is substantial.

  • You can create a Loopback interface and enable management protocols just there. This way, you will have to create an explicit Security rule that will be prominent, which will also log all management access by default.

  • If you still decide to do so - configure rule as in other cases. The Local-in policy overrides the Trusted Host settings for admin users.

Verification and Debug

Show configured Local-in policies: show firewall local-in-policy

show firewall local-in-policy

config firewall local-in-policy
    edit 1
        set uuid 140e2800-ea2d-51ec-838f-8ecf16d58d4e
        set intf "port1"
        set srcaddr "VPNpeer1"
        set dstaddr "all"
        set action accept
        set service "IKE" "ESP"
        set schedule "always"
    next
    edit 2
        set uuid cfcaa5f0-ea2d-51ec-bc4a-56cacfb950b4
        set intf "port1"
        set srcaddr "all"
        set dstaddr "all"
        set service "IKE" "ESP"
        set schedule "always"
    next
    edit 3
        set uuid d89f54b6-ea30-51ec-9646-909a1610e650
        set intf "port1"
        set srcaddr "BGPpeer12.12.12.12"
        set dstaddr "all"
        set action accept
        set service "BGP"
        set schedule "always"
    next
    edit 4
        set uuid 0f9275d4-ea31-51ec-5c9b-271138ae6f3d
        set intf "port1"
        set srcaddr "all"
        set dstaddr "all"
        set service "BGP"
        set schedule "always"
    next
end

Show policy hit count: diag firewall iprope show 00100001 policy-id

Let’s see how many times incoming BGP connections were blocked on rule 4 above:

diag firewall iprope show 00100001 4

idx:4
pkts:30 (30 0 0 0 0 0 0 0)
bytes:1800 (1800 0 0 0 0 0 0 0)
asic_pkts:0 (0 0 0 0 0 0 0 0)
asic_bytes:0 (0 0 0 0 0 0 0 0)
flag:0x0
hit count:30 (30 0 0 0 0 0 0 0)
    first hit:2022-06-12 04:21:01 last hit:2022-06-12 04:25:35

We can see that 30 packets have been blocked incoming on port 179 so far. Note: hit count statistics on Local-in rules are available starting with 7.0 only.

Note
The key here is to use 00100001 as the table index for Local-in policy.

Usual and proven: diagnose debug flow

Let’s see how the Fortigate blocks BGP incoming connection in real-time.

dia debug flow filter port 179
dia debug flow show function
diagnose debug enable
dia debug flow trace start


# id=65308 trace_id=1 func=print_pkt_detail line=5895 msg="vd-root:0 received a
 packet(proto=6, 10.10.10.218:23538->10.10.10.111:179) tun_id=0.0.0.0 from
 port1. flag [S], seq 2606637155, ack 0, win 65535"
id=65308 trace_id=1 func=init_ip_session_common line=6076 msg="allocate a new
session-00003239, tun_id=0.0.0.0"
id=65308 trace_id=1 func=vf_ip_route_input_common line=2605 msg="find a route:
flag=84000000 gw-10.10.10.111 via root"
id=65308 trace_id=1 func=fw_local_in_handler line=522 msg="iprope_in_check()
check failed on policy 4, drop"