{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Safely refactoring ACLs and firewall rules" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Changing ACLs or firewall rules (or *filters*) is one of the riskiest updates to a network. Even a small error can block connectivity for a large set of critical services or open up sensitive resources to the world at large. Earlier notebooks showed how to [analyze filters for what they do and do not allow](https://github.com/batfish/pybatfish/blob/master/jupyter_notebooks/Analyzing%20ACLs%20and%20Firewall%20Rules.ipynb) and how to [make specific changes in a provably safe manner](https://github.com/batfish/pybatfish/blob/master/jupyter_notebooks/Provably%20Safe%20ACL%20and%20Firewall%20Changes.ipynb).\n", "\n", "This notebook shows how to refactor complex filters in a way that the full impact of refactoring can be understood and analyzed for correctness *before* refactored filters are pushed to the network. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Original ACL\n", "\n", "We will use the following ACL as a running example in this notebook. The ACL can be read as a few separate sections:\n", "\n", "* Line 10: Deny ICMP redirects\n", "* Lines 20, 23: Permit BFD traffic on certain blocks\n", "* Lines 40-80: Permit BGP traffic\n", "* Lines 90-100: Permit DNS traffic a /24 subnet while denying it from a /32 within that\n", "* Lines 110-500: Permit or deny IP traffic from certain subnets\n", "* Line 510: Permit ICMP echo reply\n", "* Lines 520-840: Deny IP traffic to certain subnets\n", "* Lines 850-880: Deny all other types of traffic\n", "\n", "(The IP address space in the ACL appears all over the place because it has been anonymized via [Netconan](https://github.com/intentionet/netconan). Netconan preserves the super- and sub-prefix relationships when anonymizing IP addresses and prefixes.)" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# The ACL before refactoring\n", "original_acl = \"\"\"\n", "ip access-list acl\n", " 10 deny icmp any any redirect\n", " 20 permit udp 117.186.185.0/24 range 49152 65535 117.186.185.0/24 eq 3784\n", " 30 permit udp 117.186.185.0/24 range 49152 65535 117.186.185.0/24 eq 3785\n", " 40 permit tcp 11.36.216.170/32 11.36.216.169/32 eq bgp\n", " 50 permit tcp 11.36.216.176/32 11.36.216.179/32 eq bgp\n", " 60 permit tcp 204.150.33.175/32 204.150.33.83/32 eq bgp\n", " 70 permit tcp 205.248.59.64/32 205.248.59.67/32 eq bgp\n", " 80 permit tcp 205.248.58.190/32 205.248.58.188/32 eq bgp\n", " 90 deny udp 10.10.10.42/32 218.8.104.58/32 eq domain\n", " 100 permit udp 10.10.10.0/24 218.8.104.58/32 eq domain\n", " 110 deny ip 54.0.0.0/8 any\n", " 120 deny ip 163.157.0.0/16 any\n", " 130 deny ip 166.144.0.0/12 any\n", " 140 deny ip 198.170.50.0/24 any\n", " 150 deny ip 198.120.0.0/16 any\n", " 160 deny ip 11.36.192.0/19 any\n", " 170 deny ip 11.125.64.0/19 any\n", " 180 permit ip 166.146.58.184/32 any\n", " 190 deny ip 218.66.57.0/24 any\n", " 200 deny ip 218.66.56.0/24 any\n", " 210 deny ip 218.67.71.0/24 any\n", " 220 deny ip 218.67.72.0/24 any\n", " 230 deny ip 218.67.96.0/22 any\n", " 240 deny ip 8.89.120.0/22 any\n", " 250 deny ip 54.203.159.1/32 any\n", " 260 permit ip 218.8.104.0/25 any\n", " 270 permit ip 218.8.104.128/25 any\n", " 280 permit ip 218.8.103.0/24 any\n", " 290 deny ip 144.49.45.40/32 any\n", " 300 deny ip 163.255.18.63/32 any\n", " 310 deny ip 202.45.130.141/32 any\n", " 320 deny ip 212.26.132.18/32 any\n", " 330 deny ip 218.111.16.132/32 any\n", " 340 deny ip 218.246.165.90/32 any\n", " 350 deny ip 29.228.179.210/32 any\n", " 360 deny ip 194.181.135.214/32 any\n", " 370 deny ip 10.64.90.249/32 any\n", " 380 deny ip 207.70.46.217/32 any\n", " 390 deny ip 219.185.241.117/32 any\n", " 400 deny ip 2.80.3.219/32 any\n", " 410 deny ip 27.212.145.150/32 any\n", " 420 deny ip 131.159.53.215/32 any\n", " 430 deny ip 214.220.213.107/32 any\n", " 440 deny ip 196.64.84.239/32 any\n", " 450 deny ip 28.69.250.136/32 any\n", " 460 deny ip 200.45.87.238/32 any\n", " 470 deny ip any 11.125.89.32/30\n", " 480 deny ip any 11.125.89.36/30\n", " 490 deny ip any 11.125.89.40/30\n", " 500 deny ip any 11.125.89.44/30\n", " 510 permit icmp any any echo-reply\n", " 520 deny ip any 11.36.199.216/30\n", " 530 deny ip any 11.36.199.36/30\n", " 540 deny ip any 11.36.199.2/30\n", " 550 deny ip any 11.36.199.52/30\n", " 560 deny ip any 11.36.199.20/30\n", " 570 deny ip any 11.125.82.216/30\n", " 580 deny ip any 11.125.82.220/32\n", " 590 deny ip any 11.125.82.36/30\n", " 600 deny ip any 11.125.82.12/30\n", " 610 deny ip any 11.125.80.136/30\n", " 620 deny ip any 11.125.80.141/32\n", " 630 deny ip any 11.125.87.48/30\n", " 640 deny ip any 11.125.87.168/30\n", " 650 deny ip any 11.125.87.173/32\n", " 660 deny ip any 11.125.90.56/30\n", " 670 deny ip any 11.125.90.240/30\n", " 680 deny ip any 11.125.74.224/30\n", " 690 deny ip any 11.125.91.132/30\n", " 700 deny ip any 11.125.89.132/30\n", " 710 deny ip any 11.125.89.12/30\n", " 720 deny ip any 11.125.92.108/30\n", " 730 deny ip any 11.125.92.104/32\n", " 740 deny ip any 11.125.92.28/30\n", " 750 deny ip any 11.125.92.27/32\n", " 760 deny ip any 11.125.92.160/30\n", " 770 deny ip any 11.125.92.164/32\n", " 780 deny ip any 11.125.92.204/30\n", " 790 deny ip any 11.125.92.202/32\n", " 800 deny ip any 11.125.93.192/29\n", " 810 deny ip any 11.125.95.204/30\n", " 820 deny ip any 11.125.95.224/30\n", " 830 deny ip any 11.125.95.180/30\n", " 840 deny ip any 11.125.95.156/30\n", " 850 deny tcp any any\n", " 860 deny icmp any any\n", " 870 deny udp any any\n", " 880 deny ip any any\n", "\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Compressed ACL\n", "\n", "Now, assume that we want to compress this ACL to make it more manageable. We do the following operations:\n", "\n", "* Merge the two BFD permit statements on lines 20-30 into one statement using the range directive.\n", "* Remove the BGP session on line 80 because it has been decommissioned\n", "* Remove lines 180 and 250 because they are shadowed by earlier lines and will never match a packet. Such lines can be found via the `filterLineReachability` question, as shown [here](https://github.com/batfish/pybatfish/blob/master/jupyter_notebooks/Analyzing%20ACLs%20and%20Firewall%20Rules.ipynb#filterLineReachability:-Analyzing-reachability-of-filter-lines).\n", "* Merge pairs of lines (190, 200), (210, 220), and (260, 270) by combining their prefixes into a less specific prefix.\n", "* Remove all deny statements on lines 520-870. They are not needed given the final deny on line 880.\n", "\n", "The result of these actions, which halve the ACL size, is shown below. To enable easy observation of changes, we have preserved the line numbers. " ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "compressed_acl = \"\"\"\n", "ip access-list acl\n", " 10 deny icmp any any redirect\n", " 20 permit udp 117.186.185.0/24 range 49152 65535 117.186.185.0/24 range 3784 3785\n", "! 30 MERGED WITH LINE ABOVE \n", " 40 permit tcp 11.36.216.170/32 11.36.216.169/32 eq bgp\n", " 50 permit tcp 11.36.216.176/32 11.36.216.179/32 eq bgp\n", " 60 permit tcp 204.150.33.175/32 204.150.33.83/32 eq bgp\n", " 70 permit tcp 205.248.59.64/32 205.248.59.67/32 eq bgp\n", "! 80 DECOMMISSIONED BGP SESSION\n", " 90 deny udp 10.10.10.42/32 218.8.104.58/32 eq domain\n", " 100 permit udp 10.10.10.0/24 218.8.104.58/32 eq domain\n", " 110 deny ip 54.0.0.0/8 any\n", " 120 deny ip 163.157.0.0/16 any\n", " 130 deny ip 166.144.0.0/12 any\n", " 140 deny ip 198.170.50.0/24 any\n", " 150 deny ip 198.120.0.0/16 any\n", " 160 deny ip 11.36.192.0/19 any\n", " 170 deny ip 11.125.64.0/19 any\n", "! 180 REMOVED UNREACHABLE LINE\n", " 190 deny ip 218.66.56.0/23 any\n", "! 200 MERGED WITH LINE ABOVE \n", " 210 deny ip 218.67.71.0/23 any\n", "! 220 MERGED WITH LINE ABOVE \n", " 230 deny ip 218.67.96.0/22 any\n", " 240 deny ip 8.89.120.0/22 any\n", "! 250 REMOVED UNREACHABLE LINE\n", " 260 permit ip 218.8.104.0/24 any\n", "! 270 MERGED WITH LINE ABOVE\n", " 280 permit ip 218.8.103.0/24 any\n", " 290 deny ip 144.49.45.40/32 any\n", " 300 deny ip 163.255.18.63/32 any\n", " 310 deny ip 202.45.130.141/32 any\n", " 320 deny ip 212.26.132.18/32 any\n", " 330 deny ip 218.111.16.132/32 any\n", " 340 deny ip 218.246.165.90/32 any\n", " 350 deny ip 29.228.179.210/32 any\n", " 360 deny ip 194.181.135.214/32 any\n", " 370 deny ip 10.64.90.249/32 any\n", " 380 deny ip 207.70.46.217/32 any\n", " 390 deny ip 219.185.241.117/32 any\n", " 400 deny ip 2.80.3.219/32 any\n", " 410 deny ip 27.212.145.150/32 any\n", " 420 deny ip 131.159.53.215/32 any\n", " 430 deny ip 214.220.213.107/32 any\n", " 440 deny ip 196.64.84.239/32 any\n", " 450 deny ip 28.69.250.136/32 any\n", " 460 deny ip 200.45.87.238/32 any\n", " 470 deny ip any 11.125.89.32/28\n", " 510 permit icmp any any echo-reply\n", "! 520-870 REMOVED UNNECESSARY DENIES\n", " 880 deny ip any any\n", "\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The challenge for us is to find out if and how this compressed ACL differs from the original. That is, is there is traffic that is treated differently by the two ACLs, and if so, which lines are responsible for the difference.\n", "\n", "This task is difficult to get right through manual reasoning alone, which is why we developed the `compareFilters` question in Batfish." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Comparing filters\n", "\n", "We can compare the two ACLs above as follows. To initialize snapshots, we will use Batfish's `init_snapshot_from_text` function which creates a snapshot with a single device who configuration is the provided text. The analysis shown below can be done even when the filters are embedded within bigger device configurations. " ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
 NodeFilter_NameLine_IndexLine_ContentLine_ActionReference_Line_IndexReference_Line_Content
0configacl16210 deny ip 218.67.71.0/23 anyDENY50510 permit icmp any any echo-reply
1configacl40510 permit icmp any any echo-replyPERMIT21220 deny ip 218.67.72.0/24 any
2configacl41880 deny ip any anyDENY780 permit tcp 205.248.58.190/32 205.248.58.188/32 eq bgp
\n" ], "text/plain": [ " Node Filter_Name Line_Index Line_Content \\\n", "0 config acl 16 210 deny ip 218.67.71.0/23 any \n", "1 config acl 40 510 permit icmp any any echo-reply \n", "2 config acl 41 880 deny ip any any \n", "\n", " Line_Action Reference_Line_Index \\\n", "0 DENY 50 \n", "1 PERMIT 21 \n", "2 DENY 7 \n", "\n", " Reference_Line_Content \n", "0 510 permit icmp any any echo-reply \n", "1 220 deny ip 218.67.72.0/24 any \n", "2 80 permit tcp 205.248.58.190/32 205.248.58.188/32 eq bgp " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Import packages \n", "%run startup.py\n", "bf = Session(host=\"localhost\")\n", "\n", "# Initialize a snapshot with the original ACL\n", "original_snapshot = bf.init_snapshot_from_text(\n", " original_acl, \n", " platform=\"cisco-nx\", \n", " snapshot_name=\"original\", \n", " overwrite=True)\n", "\n", "# Initialize a snapshot with the compressed ACL\n", "compressed_snapshot = bf.init_snapshot_from_text(\n", " compressed_acl, \n", " platform=\"cisco-nx\", \n", " snapshot_name=\"compressed\", \n", " overwrite=True)\n", "\n", "# Now, compare the two ACLs in the two snapshots\n", "answer = bf.q.compareFilters().answer(snapshot=compressed_snapshot, reference_snapshot=original_snapshot)\n", "show(answer.frame())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `compareFilters` question compares two filters and returns pairs of lines, one from each filter, that match the same flow(s) but treat them differently. If it reports no output, the filters are guaranteed to be identical. The analysis is exhaustive and considers *all possible* flows.\n", "\n", "As we can see from the output above, our compressed ACL is not the same as the original one. In particular, line 210 of the compressed ACL will deny some flows that were being permitted by line 510 of the original; and line 510 of the compressed ACL will permit some flows that were being denied by line 220 of the original ACL. Because the permit statements correspond to ICMP traffic, we can tell that the traffic treated by the two filters is ICMP. To narrow learn specific source and destination IPs that are impacted, one may run the `searchFilters` question, as shown [here](https://github.com/batfish/pybatfish/blob/master/jupyter_notebooks/Provably%20Safe%20ACL%20and%20Firewall%20Changes.ipynb#Step-3:-Ensure-that-no-collateral-damage-has-occurred). \n", "\n", "By looking at the output above, we can immediately understand the difference: \n", "\n", "* The first line is showing that the compressed ACL is denying some traffic on line 210 (with index 16) that the original ACL was permitting via line 510, and the compressed ACL is permitting some traffic on line 510 that the original ACL was denying via line 220. \n", "\n", " It turns out that the address space merger we did for lines 210 and 220 in the original ACL, where we combined 218.67.72.0/24 and 218.67.71.0/24 into 218.67.71.0/23, was not correct. The other similar mergers of 218.66.57.0/24 and 218.66.56.0/24 into 218.66.56.0/23 and of 218.8.104.0/25 and 218.8.104.128/25 into 218.8.104.0/24 were correct.\n", "\n", "\n", "* The third line is showing that the compressed ACL is denying some traffic at the end of the ACL that the original ACL was permitting via line 80. This is an expected change of decommissioning the BGP session on line 80. \n", "\n", " It is not always the case that refactoring is semantics preserving. Where `compareFilters` helps is succinctly enumerating *all* differences. Engineers can look at the differences and decide if the refactored filter meets their intent." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Splitting ACLs\n", "\n", "Compressing large ACLs is one type of refactoring engineers do; another one is splitting a large ACL into multiple smaller ACLs and composing them on the same device or spreading across multiple devices in the network. Smaller ACLs are easier to maintain and evolve. However, the split operation is risky. We may forget to include in the smaller ACLs some protections that exist in the original ACL. We show how such splits can be safely done using Batfish.\n", "\n", "Suppose we want to split the compressed ACL above into multiple smaller ACLs that handle different concerns. So, we should have different ACLs for different types of traffic and different ACLs for different logical groups of nodes in the network. The result of such splitting is shown below. For ease of exposition, we have retained the line numbers from the original ACL and mimic a scenario in which all ACLs live on the same device." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "smaller_acls = \"\"\"\n", "ip access-list deny-icmp-redirect\n", " 10 deny icmp any any redirect\n", "\n", "ip access-list permit-bfd\n", " 20 permit udp 117.186.185.0/24 range 49152 65535 117.186.185.0/24 range 3784 3785\n", "\n", "ip access-list permit-bgp-session\n", " 40 permit tcp 11.36.216.170/32 11.36.216.169/32 eq bgp\n", " 50 permit tcp 11.36.216.176/32 11.36.216.179/32 eq bgp\n", " 60 permit tcp 204.150.33.175/32 204.150.33.83/32 eq bgp\n", " 70 permit tcp 205.248.59.64/32 205.248.59.67/32 eq bgp\n", "\n", "ip access-list acl-dns\n", " 90 deny udp 10.10.10.42/32 218.8.104.58/32 eq domain\n", " 100 permit udp 10.10.10.0/24 218.8.104.58/32 eq domain\n", "\n", "ip access-list deny-untrusted-sources-group1\n", " 110 deny ip 54.0.0.0/8 any\n", " 120 deny ip 163.157.0.0/16 any\n", " 130 deny ip 166.144.0.0/12 any\n", " 140 deny ip 198.170.50.0/24 any\n", " 150 deny ip 198.120.0.0/16 any\n", " 160 deny ip 11.36.192.0/19 any\n", "\n", "ip access-list deny-untrusted-sources-group2\n", " 160 deny ip 11.36.192.0/20 any\n", " 190 deny ip 218.66.56.0/23 any\n", " 210 deny ip 218.67.71.0/23 any\n", " 230 deny ip 218.67.96.0/22 any\n", " 240 deny ip 8.89.120.0/22 any\n", " \n", "ip access-list permit-trusted-sources\n", " 260 permit ip 218.8.104.0/24 any\n", " 280 permit ip 218.8.103.0/24 any\n", "\n", "ip access-list deny-untrusted-sources-group3\n", " 290 deny ip 144.49.45.40/32 any\n", " 300 deny ip 163.255.18.63/32 any\n", " 310 deny ip 202.45.130.141/32 any\n", " 320 deny ip 212.26.132.18/32 any\n", " 300 deny ip 218.111.16.132/32 any\n", " 340 deny ip 218.246.165.90/32 any\n", " 350 deny ip 29.228.179.210/32 any\n", " 360 deny ip 194.181.135.214/32 any\n", " 370 deny ip 10.64.90.249/32 any\n", " 380 deny ip 207.70.46.217/32 any\n", " 390 deny ip 219.185.241.117/32 any\n", " \n", "ip access-list deny-untrusted-sources-group4\n", " 400 deny ip 2.80.3.219/32 any\n", " 410 deny ip 27.212.145.150/32 any\n", " 420 deny ip 131.159.53.215/32 any\n", " 430 deny ip 214.220.213.107/32 any\n", " 440 deny ip 196.64.84.239/32 any\n", " 450 deny ip 28.69.250.136/32 any\n", " 460 deny ip 200.45.87.238/32 any\n", "\n", "ip access-list acl-tail\n", " 470 deny ip any 11.125.89.32/28\n", " 510 permit icmp any any echo-reply\n", " 880 deny ip any any\n", "\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Given the split ACLs above, one analysis may be to figure out if each untrusted source subnet was included in a smaller ACL. Otherwise, we have lost protection that was present in the original ACL. We can accomplish this analysis via the `findMatchingFilterLines` question, as shown below. \n", "\n", "Once we are satisfied with analysis of filters, for an end-to-end safety guarantee, we should also analyze if there are new flows that the network will allow (or disallow) after the change. Such an analysis can be done via the `differentialReachability` question, as shown [here](https://github.com/batfish/pybatfish/blob/master/jupyter_notebooks/Introduction%20to%20Forwarding%20Change%20Validation.ipynb#Change-Scenario-2:-Validating-the-end-to-end-impact-of-an-ACL-change). " ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "54.0.0.0/8 .... OK\n", "163.157.0.0/16 .... OK\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "166.144.0.0/12 .... OK\n", "198.170.50.0/24 .... OK\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "198.120.0.0/16 .... OK\n", "11.36.192.0/19 .... Multiply present\n" ] }, { "data": { "text/html": [ "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
 NodeFilterLineLine_IndexAction
0configdeny-untrusted-sources-group1160 deny ip 11.36.192.0/19 any5DENY
1configdeny-untrusted-sources-group2160 deny ip 11.36.192.0/20 any0DENY
\n" ], "text/plain": [ " Node Filter Line \\\n", "0 config deny-untrusted-sources-group1 160 deny ip 11.36.192.0/19 any \n", "1 config deny-untrusted-sources-group2 160 deny ip 11.36.192.0/20 any \n", "\n", " Line_Index Action \n", "0 5 DENY \n", "1 0 DENY " ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "11.125.64.0/19 .... ABSENT\n", "218.66.56.0/24 .... OK\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "218.66.57.0/24 .... OK\n", "218.67.71.0/23 .... OK\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "218.67.96.0/22 .... OK\n", "8.89.120.0/22 .... OK\n" ] } ], "source": [ "# Initialize a snapshot with the smaller ACLs\n", "smaller_snapshot = bf.init_snapshot_from_text(\n", " smaller_acls, \n", " platform=\"cisco-nx\", \n", " snapshot_name=\"smaller\", \n", " overwrite=True)\n", "\n", "# All untrusted subnets\n", "untrusted_source_subnets = [\"54.0.0.0/8\", \n", " \"163.157.0.0/16\", \n", " \"166.144.0.0/12\", \n", " \"198.170.50.0/24\", \n", " \"198.120.0.0/16\", \n", " \"11.36.192.0/19\", \n", " \"11.125.64.0/19\", \n", " \"218.66.56.0/24\", \n", " \"218.66.57.0/24\", \n", " \"218.67.71.0/23\", \n", " \"218.67.96.0/22\", \n", " \"8.89.120.0/22\"\n", " ]\n", "\n", "for subnet in untrusted_source_subnets:\n", " # Find which ACLs match traffic from this source subnet\n", " answer = bf.q.findMatchingFilterLines(\n", " headers=HeaderConstraints(srcIps=subnet),\n", " filters=\"/deny-untrusted/\").answer(snapshot=smaller_snapshot)\n", "\n", " # Each source subnet should match exactly one ACL\n", " af = answer.frame()\n", " if len(af) == 1:\n", " print(\"{} .... OK\".format(subnet))\n", " elif len(af) == 0:\n", " print(\"{} .... ABSENT\".format(subnet))\n", " else:\n", " print(\"{} .... Multiply present\".format(subnet))\n", " show(af)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the code above, we first enumerate all untrusted subnets in the network. The granularity of this specification need not be the same as that in the ACL. For instance, we enumerate 218.66.56.0/24 and 218.66.57.0/24 as untrusted subnets but the ACL has a less specific prefix 218.66.56.0/23. Batfish understands such relationships and provides an accurate analysis that is not possible with simple string matching.\n", "\n", "The **for** loop above uses the `findMatchingFilterLines` question to find out which lines across all ACLs whose names contain \"deny-untrusted\" will match packets starting the the specified subnet. Our expectation is that each subnet should match exactly one line in exactly one ACL, and the output shows \"OK\" against such subnets. It shows \"Absent\" for subnets that do not match any line and shows the multiple matching lines for subnets where that happens.\n", "\n", "We see that during the split above, we ended up matching the subnet 11.36.192.0/19 twice, once as a /19 in ACL deny-untrusted-sources-group1 and then as /20 in ACL deny-untrusted-sources-group2. More dangerously, we completely forgot to match the 11.125.64.0/19, which will open a security hole in the network if these smaller ACLs were applied." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Summary\n", "\n", "In this notebook, we showed how to use the `compareFilters` and `findMatchingFilterLines` questions of Batfish to safely refactor complex filters. \n", "\n", "* `compareFilters` analyzes the original and revised filter to enumerate all cases that will treat *any* flow differently. \n", "* `findMatchingFilterLines` enumerates all lines across all specified filters that match the given space of flows.\n", "\n", "For additional ways to analyze filter using Batfish, see the [\"Analyzing ACLs and Firewall Rules\"](https://github.com/batfish/pybatfish/blob/master/jupyter_notebooks/Analyzing%20ACLs%20and%20Firewall%20Rules.ipynb) and the [\"Provably Safe ACL and Firewall Changes\"](https://github.com/batfish/pybatfish/blob/master/jupyter_notebooks/Provably%20Safe%20ACL%20and%20Firewall%20Changes.ipynb) notebooks." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "***\n", "### Get involved with the Batfish community\n", "\n", "Join our community on [Slack](https://join.slack.com/t/batfish-org/shared_invite/enQtMzA0Nzg2OTAzNzQ1LTcyYzY3M2Q0NWUyYTRhYjdlM2IzYzRhZGU1NWFlNGU2MzlhNDY3OTJmMDIyMjQzYmRlNjhkMTRjNWIwNTUwNTQ) and [GitHub](https://github.com/batfish/batfish). " ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.15" } }, "nbformat": 4, "nbformat_minor": 2 }