Zone-based firewalling on Mikrotik routers

Introduction

Mikrotik produces a number of network devices, typically at a comparatively low cost but with lots of functionality. But they certainly are not the easiest to configure, and are best for people who know a bit more about networks and have the time and motivation to learn about the system. One area that can be difficult to configure is IP firewallling, and this post will describe a method to configure these devices. This will not be a configuration for copy-and-pasting, but an approach which can and should be adapted according to the requirements. Also this may not be the best approach for you.

RouterOS

Most Mikrotik products run "RouterOS", their full-featured operating systems found on all routers and most of their switches. There is also SwitchOS, then you probably don't need or want firewalling. The difference between a router and switch is not so much the functionality available, but what the device is optimized for. You can use most if not all Routing features on a switch with RouterOS, but the performance won't be great. A router can also switch, but again it may not have the best performance - or it just may be more expensive when compared to a switch of similar size.

RouterOS is based on Linux, even though the interface is very different from typical Linux distributions. However you might some of the general concepts in RouterOS to be very similar to the configuration in Linux. One such area is the firewall, which shares the concepts of chains and tables known from the Linux netfilter framework. So if you already know Linux firewalling, you should feel at home with a mikrotik device. You can also use the approach shown here in other Linux environments.

General idea of zone-based firewalling

The basic idea of zone-based firewalling is to add another layer of abstraction by defining "zones". The easiest way is to define a zone as a set of interfaces wich share a common security level. This can be your LAN, or a DMZ, or the WAN interface. Assuming that all endpoints within a zone are similar, the rules can now be defined when crossing the border between two zones. This makes is much easier to reason about the rules and keep it organized. If you need a lot of special cases within your rules, this approach may not be the best for you. But in the general case, it can help control the complexity of a ruleset.

Implementation

Warning: when implementing firewall rules, make sure to do this in a secure environment, i.e. without the router being connected directly to the internet. Also make sure you have a way to access the router management, even if you accidently lock yourself out. Read about Mikrotiks "Safe Mode" and use it, it can be very handy for this type of work.

So let's start by defining the zones. Add a number of interface lists, with each list defining a zone. Only interfaces use for IP routing should be used. Using interfaces which are part of a bridge doesn't make sense here.

Most of these interface lists will consist of just one interface. But this way it is much easier to change later on without changing the filter rules.

/interface/list/add name=WAN
/interface/list/add name=LAN
/interface/list/add name=DMZ
/interface/list/member/add list=WAN interface=ether1
/interface/list/member/add list=LAN interface=bridge
/interface/list/member/add list=DMZ interface=ether2
/interface/list/member/add list=DMZ interface=ether3

Now on to the actual filter rules. The existing rules need to be removed, this can be done by

/ip/firewall/filter
remove [ find ]

Let's start with the Router itself, which we will treat as its own zone. Because it's nice to just think about connections instead of traffic in both directions, we start with a rule to allow any connection which was allowed earlier and has already been established. Incoming traffic to the router is handled by the /input/ chain, so first we add this one:

add chain=input action=accept connection-state=established,related

You may also want to add "untracked" as a valid connection-state, depending on your needs.

Now we add one rule for traffic from each zone, by matching on the incoming interface. These rules have one job only, that is to pass that traffic to their own chain. This keeps things easy to follow.

add chain=input action=jump jump-target=zone-LAN-to-Router in-interface-list=LAN
add chain=input action=jump jump-target=zone-WAN-to-Router in-interface-list=WAN
add chain=input action=jump jump-target=zone-DMZ-to-Router in-interface-list=DMZ

And finally add a rule for "default-deny", either by dropping or rejecting all traffic instantly, or by sending it to a special chain which can handle logging and deciding what to drop and what to reject. To keep things short, we will just reject everything right else. Because we are still building the ruleset and haven't allowed any traffic, this would block access to your router. So instead start with logging instead of rejecting, until you are certain that everything else works as intended. Later change it to actually reject traffic.

add chain=input action=log comment="change to reject"

Now we can deal with each zone by itself and create specific rules. We will keep things simple here: WAN won't be allowed anything, so we keep the chain empty. LAN can access everything, and the DMZ can only access DHCP:

add chain=zone-LAN-to-Router action=accept
add chain=zone-DMZ-to-Router protocol=udp dst-port=67

Let's continue with the forwarding chain, which is the interesting part for a firewall. It's similar to the setup above, but this time we need a chain for every source/destination-pair of zones. We will use a default-deny rule at the end, so if traffic between two zones should not be allowed, we can omit the rules altogether. Here we add the to keep everything consistent:

add chain=forward action=accept connection-state=established,related 
add chain=forward action=jump jump-target=zone-LAN-to-WAN in-interface-list=LAN out-interface-list=WAN
add chain=forward action=jump jump-target=zone-LAN-to-DMZ in-interface-list=LAN out-interface-list=DMZ
add chain=forward action=jump jump-target=zone-WAN-to-LAN in-interface-list=WAN out-interface-list=LAN
add chain=forward action=jump jump-target=zone-WAN-to-DMZ in-interface-list=WAN out-interface-list=DMZ 
add chain=forward action=jump jump-target=zone-DMZ-to-LAN in-interface-list=DMZ out-interface-list=LAN
add chain=forward action=jump jump-target=zone-DMZ-to-WAN in-interface-list=DMZ out-interface-list=WAN
add chain=forward action=log comment="change to reject"

And again, we can now thing about every pair separately and thing what traffic should be allowed. LAN is trusted and cann access everything:

add chain=zone-LAN-to-WAN action=accept
add chain=zone-LAN-to-DMZ action=accept

WAN is the least trusted zone, but here we want to expose a few services in the DMZ to incoming traffic:

add chain=zone-WAN-to-DMZ protocol=tcp dst-port=80,443 action=accept
add chain=zone-WAN-to-DMZ protocol=udp dst-address=192.0.2.17 dst-port=12500 action=accept

And finally, let's imagine systems in the DMZ need to send some logs into the LAN:

add chain=zone-WAN-to-DMZ protocol=udp dst-port=514 dst-address=198.51.100.50 action=accept

Outgoing rules are rarely needed, your router should only send traffic required for the services that are configured. But if you need them, the same principles apply.

That was a lot of chains for a very simple setup. What did we gain by doing it this way? Well, it becomes much easier to find out which rules apply, and also where to add or change rules. This reduces the risk of changing a rule, and accidently allowing traffic that wasn't intended to be allowed,

IPv6

Of course the concept can also be used for IPv6 as well. By using the same zone definitions as for IPv4, both rulesets will looks fairly similar. So you may find it easier to keep both rulesets in sync.

Keep in mind that IPv6 needs at least some ICMPv6 to work at all (Neighbor and Router Discovery to work at all, some more like MTU-exceeded to work well). So even for a zone which otherwise should not be able to send traffic, the router must accept ICMPv6 (or at least some types of ICMPv6). If all zones ues IPv6 anyway, you might want to add a general accept rule directly after the connection tracking rule.

Final words

The zone-based approach can be adapted to different needs. In the example above, only a very simple ruleset was used to show the general idea. For a more complex network, the concept can and should be extend.

Additional chains should be use, for instance to group multiple actions together (such as logging and rejecting, or rate-filtering), or to handle DNS with UDP and TCP as one rule.

You can also change the meaning of zone, just be careful you have a clear understanding what it is supposed to mean. Every system should be in one zone only, to keep things easy to follow. Using interfaces is a good start, because all systems within a single network tend to have a similar level of security and don't need to be separated at the network level.