Analyzing ACLs and Firewall RulesOctober 20, 2019
Hello and welcome to our getting to know batfish series where we showcase key capabilities of batfish and how they can be used as part of network automation. My name is Ratul and I will show you how you can use batfish to analyze access control lists or ACLs and firewall rules. Network and security engineers must ensure that ACLs and firewall rules are permitting and denying traffic as intended. This task usually involves manually checking these rules or loading them onto a lab device in order to test behaviors on example packets of interest.
These methods are not only time consuming but also error-prone. It is just too easy to make mistakes but with batfish analyzing ACLs and firewall rules, which we generally call filters, is easy. Batfish has three questions that assist in this task. First, using testfilters you can see what a filter will do with a given packet right down to the line of the filter that matches the packet. Second, using searchfilters you can guarantee how filter treats a large space of flows. Third, using filterLineReachability you can sanity check a filter to ensure that every line in it will match at least some packets. In this video, I will show you how to use each of these questions. I will do so on a simple network with two devices one a router with ACLs and another a firewall. The configuration files for both of these devices are available on github. The link is shown in the description of this video. The first question I will demo is testfilters. This question shows what filters do with a particular flow and why.
It takes as input the details of the flow and a set of filters to test. The answer provides a detailed view of how the flow is treated by each filter in the set. Let’s take an example. Suppose we want to test if a filter permits hosts in a subnet to reach a DNS server. This test can be easily expressed using the pybatfish snippet shown here. We first define the flow of interest. In this case we’ve picked the source IP of one of the representative hosts in the subnet, the destination IP of the DNS server, and the application is DNS. We provide this flow as input to the test filter question along with the filter that we are interested in testing. When we run this question, the answer we get back is as follows. The first two columns are showing the filter that we tested. This column is showing the flow that we tested. The remaining three columns are showing what happens to this flow when it goes through this filter.
The action is permit. This is the line that matches and the packet gets to this line because of this trace that is shown here. In this case, the trace is not deep but it can be quite deep for filters that reference other structures such as policy maps and object groups. Let’s take another example. Suppose we want to test that our firewall blocks HTTP flows from one zone to another. This query can be expressed using this pybatfish snippet. As before, we define the flow of interest using the source IP, destination IP, and application, and give that as an input to the headers parameter of testfilters question.
The startLocation parameter is specifying that we want to test what happens to the flow when it enters a firewall via GigabitEthernet0/0 – this is the interface in the first zone. And we are saying that the filter of interest is the output filter on GigabitEthernet0/0/3 which is the interface in the second zone. In the earlier query, we did not use the startLocation parameter because our ACL was not sensitive to how the flow entered the device but our firewall filter is sensitive to where the packets enter it. When we run this query, we get back this result. What we see here, in a format that is identical to the first example, is that the flow is indeed denied and it is denied because it does not match the outgoing ACL on zone3, which was the second zone of interest.
I’ll now talk about the searchfilters question. While testfilters reasons about individual flows, searchfilters reasons about entire spaces of flows and provides comprehensive guarantees for all flows in the space. It takes as input the space of flows, specified using possible header field values, and a match condition that is permit or deny. It outputs flows in the space that match the condition. If no flow is outputted, that essentially means that we have the strong guarantee that no flow exists in the entire space that matches the condition.
Let’s take an example. Earlier, we use testfilters to test that a representative host in the subnet could reach the DNS server. Now we’ll use searchfilters to get the comprehensive guarantee that all hosts in the subnet can reach the DNS server. This task can be performed using this pybatfish snippet. We are specifying the entire space of flows using source IPSs, destination IPs, and applications. Note that we have specified the entire subnet of interest as source IPs and not just a representative host.
We provide the space of flows as input to search filters and we specify the action as deny. Note that the action is deny because we want to look for flows in this space that are denied. Our policy is that all flow should be permitted, so we specify the opposite to look for flows that are denied. If the snippet returns no answers, we know that our policy is met.
Otherwise, we’ll get a flow for which this policy is actually denied. Let’s run this query and see what happens. As it happens, we do get an answer. That is, a flow for which the policy is violated. The columns here are the same as testfilters, and what we see here is that there is a flow, namely one with source IP 10.10.for which the action is deny.
And we see the line in the filter that actually leads to the denial and the trace that led to the denial. So basically what has happened here is that there is a sneaky little line sitting here that is denying a specific host in the entire subnet. Tools like penetration testing which test with individual flows cannot find such violations, while the comprehensive guarantees of searchfilters enables us to find these things. Now let’s take another example. In the second example of testfilters, we tested that an HTTP flow could not go from one zone to another. Now suppose you wanted a much stricter condition, namely that the only TCP traffic that can go between the zones is NFS traffic. This is easy to check using searchfilters as well.
As shown in this snippet, first we are defining the space of flows of interest. We are defining them using the entire source subnet the destination subnet and all non-NFS TCP flows. So we’d specify IP protocol as TCP and in the range of destination ports. We skip the NFS port to describe all non-NFS flows. We provide this space as input to searchfilters along with the startLocation and filters field as before, and we specify the action as permit. As in the previous example, the action is permit because we want to look for flows that are permitted that is flows that are in violation of our policy that we intend. Let’s run this query and see what we get back. Now we actually got back no flows. That’s good news because this means that in this entire space of flows there is not a single flow that is permitted. In other words, all flows are denied. Such strong guarantees are almost impossible to get using any other tool today. I will now demo the filter line reachability question. When debugging filters, it can be useful to confirm that every line is reachable.
That is, it matches at least some packets that are not matched by any line before it. Unreachable filter lines are often symptomatic of bugs. Even if they are not bugs, they represent lines that can be safely removed to reduce the length of the filter without changing its behavior. This snippet runs the filterLineReachability question on a router with ACL in our example network. When we run it we get back two rows, each of which corresponds to an unreachable line in the filter.
Let’s look at the first row. It shows that the unreachable line exists on router with ACL, and the name of the filter is acl_in. The unreachable line is 670 permit IP with this IP address, the action is permit, and the blocking lines are these two lines that are shown here. The different action column shows whether the blocking lines have a different action than the unreachable line. When the actions are different, it’s a more worrisome situation compared to the case when the actions are same. The reason column shows why the line is unreachable. In this case it’s unreachable because it has blocking lines.
There can be other reasons for a line being unreachable, such as circular references. When that happens additional info column provides more information about the nature of unreachability. To summarize, in this demo, I showed you how easy it is to analyze filters using three capabilities of batfish. First using testfilters you can test how filters in the network treat a given flow. Second, using searchfilters you can verify how filters treat a large space of flows and get comprehensive guarantees on all flows within the space. The final one is filterLineReachability that helps you identify all lines in your filters that will never match a packet.
If you found these capabilities useful, you may also want to check out our demo on how to safely change your filters. Alright, thanks for watching! And don’t forget to join our community on slack or github, where we’d love to help you get started with batfish and answer any questions. Good bye and take care. .
As found on Youtube