Cisco ACL Test Scripts
W.T.F.
It has bothered me for a while that there seem to be no good tools for testing Cisco Access Control Lists (Cisco ACL Testers if you will). There are a couple of tools to build access lists interactively, even one that will run a simulation of a packet hitting the entries of a list which would be useful if you could enter an ACL by copy/pasting or importing from a Cisco config-like file, but you can't. This particular lack of tooling presents a significant problem when reviewing ACLs during troubleshooting or updating for new requirements, as soon as the ACL grows beyond 10 or so lines. This happens more frequently than a sane man can handle in my day-to-day job, and so I've drawn the line. Here are some tools I've hacked together to help me: if you do a job like mine, you might find them useful.
acltest.pl - Cisco ACL Tester
(Download: acltest.pl v0.0.0.11 - Updated March 2011)
(Sample HTML output: 'sources' search; 'both individual address elements' search using host elements; 'both individual address elements' search using network elements)
Description
acltest is a Cisco ACL Tester which identifies IP address matches from test input (1 or 2 IP addresses or network addresses) and an ACL in a format consistent with Cisco ACLs used in CatOS, IOS, NX-OS and on the ASA platform. The intent is to speed up the search for lines which a particular search address would match against by doing the leg-work of checking for overlap with address elements used in the ACL supplied while avoiding number-blindness when manually reviewing ACLs.
Dependencies
None.
Limitations
- It's all IPv4, baby. Low priority to update this to IPv6.
- At the moment it reads from a static test IP variable and an array that you need to populate in the .pl, or you can enter interactively by calling acltest.pl with argument '-i', but shortly that will be updated to read from a file and/or args.
- There is no concept of protocol, port, service or flag (including 'established') matching. This is very low on my list of priorities because I find it trivial to review these aspects once I've identified whether the address matches. I MAY add it in the future, no promises.
- ASA-style ACEs are automatically determined (see Regexps below), but there is an ambiguous case of standard ASA ACLs and numbered/standard IOS ACLs (which don't specify standard - it's valid if it's declared with 'access-list x' instead of 'ip access-list x'). In this case, IOS wins. This is only an issue with network/masked address elements, e.g. 172.16.0.0 0.0.255.255 (or 255.255.0.0) where the mask will be incorrectly interpreted as wildcard/IOS style if the standard ACL is actually ASA. I can't see a viable fix.
What it DOES do
- Iterates over the lines of the ACL (statically configured in the .pl, or specified interactively by running with '-i') looking for standard syntax (e.g. "permit ip any any", "permit icmp 10.0.0.0 0.0.255.255 any" etc.) and does some binary logic operations to test for overlap between a test IP and the source/destination address components. If there are any matches the ACL entry is output to stdout.
- It deals with 'complex' masks, where a 'complex' mask doesn't obey 'normal' mask rules (1s must be contiguous in the bit representation: 0.0.7.255 obeys 'normal' mask rules, but 0.0.8.255 doesn't [convert to binary to see why, if it isn't immediately obvious]). If the test IP has a mask it is ignored because I haven't yet worked out how it should behave here.
- Test address elements can be specified to be in ASA-style and the script converts them to IOS-style if necessary (if the appropriate variable is set).
- If the ACL is deemed to be ASA-style (see Regexps below), it doesn't invert any masks from the supplied ACL as they're already in 'subnet' style rather than wildcard.
- I've fixed a logic error that resulted in non-ACE lines (i.e. not a permit or deny statement but perhaps the name of the access-list or a remark) were always shown as matching the test.
- You can specify a source and destination element pair to test against lines of an ACL by setting the $test_component variable to '2' (as opposed to 'd' to test 1 address element against destinations, 's' to test 1 address element against sources, or the empty string to test 1 address element against both sources and destinations in the ACL). Interactively, a prompt for the second address is only triggered by entering '2' when prompted for this choice. In this mode, the first specified address element will be the source, and the second will be the destination.
- It now understands NX-OS' CIDR-notation in ACLs. NX-OS has new ACL syntax which permits specifying address elements with CIDR notation (and appears to have done away with 'complex' non-contiguous wildcard masks) and explicitly numbers ACEs. Rule numbering and the name format change aren't a problem (all ACLs in NX-OS are implicitly named extended ACLs - standard and numbered ACLs are gone, though you can use a number as a name...), but CIDR was until I wrote some code to turn CIDR into a wildcard mask.
- Updated to include the code to make it a viable CGI, accepting variables from GETs or POSTs. When outputting matches in HTML format, matched 'permit' lines go green, 'deny' lines go red, and if something somehow matches but doesn't start with permit or deny it ends up pink. Source and destination matches are highlighted (yellow/orange for sources, blue for destinations) with a darker highlight for an exact match (an exact, 1:1 logical match, where normalised test and normalised ACE address component strings match with 'eq'). The whole ACL is output in HTML mode, and the entire output is in monospaced font. There's a javascript to toggle visibility of unmatched lines.
Extensions
I've extended this script into a CGI which can be called with arguments in either GET or POST requests (GET requests are slightly limited due to maximum argument lengths). I'm calling it from a C# webapp which interrogates our Solarwinds NCMv6 database and then POSTs the various parameters. The webapp itself is fairly simple: just some database access code, a SQL query to return the most recent configuration for every device in the inventory, and a couple of Regexps to pull out ACLs and their names from a Cisco IOS/NXOS config which I've reproduced below. On our IIS installation, I simply changed the extension of the script to .plex and it executed happily.
Regular Expressions
Complete 'IP Access-List': ^(ip access-list\s+(extended|standard)?\s*([\w\d_-]+).*\n\s+.*)
Complete 'Access-List': ^(access-list\s+([\w\d_-]+)\s*.*(\naccess-list\s+\2.*)*)
'IP Access-List' names (may as well use the above one and pull out $3, or use this one and pull $2): ^ip access-list\s+(extended|standard)?\s*([\w\d_-]+).*(\n\s+.*)*
'Access-List' names (and comments if the 1st line is a remark - pull out $1 for name and if $2 is not blank, pull $3 for the comment): ^access-list\s+([\w\d_-]+)\s*(remark\s+(.*))?.*(\naccess-list\s+\1.*)*
ASA-style ACE: ^access-list\s+(?:[\w\d_-]+)\s+.*?(?:\bextended)?\s+)?(permit|deny)\s+
acl2filter.pl - Cisco ACL to TCPDump Filter
(Download: acl2filter.pl v0.0.0.3 - Updated June 2011)
Description
acl2filter is a Cisco ACL to TCPDump filter converter which reads an ACL in a format consistent with Cisco ACLs used in CatOS, IOS, NX-OS and on the ASA platform and outputs an equivalent string that can be used as a TCPDump filter. Intended use is for network administrators to test the effect of a Cisco ACL on a packet capture, either taken live from the wire (from a SPAN port, for instance) or from a pre-captured pcap.
ACLs can contain a 'log' keyword on individual lines to generate a message whenever those lines are hit. This is useful in many circumstances, not least when you need to (for instance) add an ACL to a VLAN which has been passing traffic uninterrupted for some time, and despite your best efforts you have no idea if you're going to block something that 'has always worked' (so you finish the ACL with 'permit ip any any log[-input]' and Robert's your father's brother: a log of all the stuff you haven't explicitly dealt with). Well, imagine if you will that some inconsiderate oik decided that performance trumps the ability to inspect processing operation and released a product that misleadingly allows the 'log' keyword to be used but won't actually report any matches. Well, such is the case with L2 ACLs on a physical interface. So how the shit do you deal with this? Well, kind sir, I offer a potential solution. Convert your ACL (minus the permit ip any any...) into a tcpdump filter, and use it to exclude all of the traffic it matches from packets processed by tcpdump. Initially I'll be applying this against a few days' worth of historical data to generate a PCAP of all unmatched (would be dropped if we turned the ACL 'on') traffic for review. In the future I'll probably use it to filter packets seen on a SPAN to do live-reviewing in Wireshark or tshark.
Dependencies
None.
Limitations
- It's all IPv4, baby. Low priority to update this to IPv6.
- At the moment it reads from a static test IP variable and an array that you need to populate in the .pl, or you can enter interactively by calling acl2filter.pl with argument '-i', but shortly that will be updated to read from a file and/or args.
- There is no flag (excluding 'established' - that one's handled as it's so common) matching. I MAY add it in the future, no promises.
- ASA-style ACEs are automatically determined (see Regexps above: they're the same in both scripts), but there is an ambiguous case of standard ASA ACLs and numbered/standard IOS ACLs (which don't specify standard - it's valid if it's declared with 'access-list x' instead of 'ip access-list x'). In this case, IOS wins. This is only an issue with network/masked address elements, e.g. 172.16.0.0 0.0.255.255 (or 255.255.0.0) where the mask will be incorrectly interpreted as wildcard/IOS style if the standard ACL is actually ASA. I can't see a viable fix short of using the context of the style of the surrounding lines.
- Slight problem with 'complex' masks... :-) TCPDump only supports 'proper' masks and so something like 0.1.0.255 becomes 255.254.255.0 which is invalid. The fix would be to expand the wildcard mask, but this could get really messy so I've chosen not to bother at the moment. What happens at the moment is a filter is produced that yields a syntax error when parsed by tcpdump. This is probably better than fudging the original mask (because then it's not really going to do what you want it to) and it indicates something's gone wrong.
What it DOES do
- Iterates over the lines of the ACL (statically configured in the .pl, or specified interactively by running with '-i') looking for standard syntax (e.g. "permit ip any any", "permit icmp 10.0.0.0 0.0.255.255 any" etc.) and translates it to a TCPDump filter string. If the ACL entry is a permit or deny line, the translation is structured into a series of appropriate AND/OR'd filter strings and output to stdout.
- If the ACL is deemed to be ASA-style (see Regexps above), it doesn't invert any masks from the supplied ACL as they're already in 'subnet' style rather than wildcard.
- It understands CIDR-notation in ACLs (valid in NX-OS).
- It is a viable CGI, accepting variables from GETs or POSTs.
Extensions
I've extended this script into a CGI which can be called with arguments in either GET or POST requests (GET requests are slightly limited due to maximum argument lengths). I'm calling it from a C# webapp which interrogates our Solarwinds NCMv6 database and then POSTs the various parameters. The webapp itself is fairly simple: just some database access code, a SQL query to return the most recent configuration for every device in the inventory, and a couple of Regexps to pull out ACLs and their names from a Cisco IOS/NXOS config which I've reproduced above. On our IIS installation, I simply changed the extension of the script to .plex and it executed happily.
There is a privacy policy in place for compliance with Google Analytics terms of service, which this site uses to optimise search engine placement by permitting periodical review of search terms associated with hits.