Project: firewall and ACL rule audit
In short: Rule sets grow over years and collect broad, unused and duplicate entries that weaken security. This project parses access control lists into structured records and runs checks for any-to-any permits, risky services, shadowed rules, zero hit counts and missing remarks. The output is a ranked list that supports a careful, approved clean-up.
Goal and what you will build
Firewall and router rule sets rarely shrink. Temporary rules become permanent, projects end but their access stays, and nobody is sure which rule is safe to remove. Regular rule reviews are expected by standards such as ISO/IEC 27001:2022 and PCI DSS (Payment Card Industry Data Security Standard). In this project you will build an audit tool for Cisco IOS extended ACLs (Access Control Lists) that reads the configuration and the hit counters, then reports the rules that need attention.
The tool is read-only. It recommends; people decide. Any rule removal goes through your normal change process with approval and a rollback plan.
Design
Each ACL entry is parsed into a small record: ACL name, sequence, action, protocol, source, destination, port and hit count. Checks are simple functions that take a list of records and return findings, so you can add new checks without touching the parser. Severity guides the review order:
| Finding | Severity | Why |
|---|---|---|
| permit ip any any | High | No filtering at all |
| Telnet, FTP or SNMP permitted from any | High | Clear-text or sensitive management services |
| Shadowed rule | Medium | An earlier rule already matches all its traffic |
| Zero hits over a long period | Low | Probably unused; confirm before removing |
| No remark | Low | No recorded owner or business reason |
Build it step by step
- Collect
show access-lists(which includes hit counts) from lab routers with Netmiko, or save the output to text files. - Parse each line with a regular expression into a record. Start with the common formats and log any line you cannot parse rather than guessing.
- Write one function per check, returning findings with a severity.
- Print findings grouped by ACL and severity, and export them to CSV for the review meeting.
- Test each check with a small sample ACL that contains one known problem.
#!/usr/bin/env python3
import re, sys
LINE = re.compile(
r"^\s*(?P<seq>\d+)\s+(?P<action>permit|deny)\s+(?P<proto>\S+)\s+"
r"(?P<src>any|host \S+|\S+ \S+)\s+(?P<dst>any|host \S+|\S+ \S+)"
r"(?:\s+eq\s+(?P<port>\S+))?(?:.*\((?P<hits>\d+) match)?")
RISKY = {"telnet", "23", "ftp", "21", "snmp", "161"}
def parse(text):
acls, name = {}, None
for line in text.splitlines():
m = re.match(r"^(?:Extended|Standard) IP access list (\S+)", line)
if m:
name = m.group(1); acls[name] = []; continue
m = LINE.match(line)
if m and name:
r = m.groupdict(); r["hits"] = int(r["hits"] or 0)
acls[name].append(r)
return acls
def covers(a, b):
"""True if rule a matches everything rule b matches (simple version)."""
wide = lambda x, y: x == "any" or x == y
return (a["proto"] in ("ip", b["proto"]) and wide(a["src"], b["src"])
and wide(a["dst"], b["dst"]) and (a["port"] is None or a["port"] == b["port"]))
def audit(acls):
for name, rules in acls.items():
for i, r in enumerate(rules):
tag = f"{name} seq {r['seq']}"
if r["action"] == "permit" and r["src"] == "any" and r["dst"] == "any":
if r["proto"] == "ip":
yield "HIGH", tag, "permit ip any any"
elif r["port"] in RISKY:
yield "HIGH", tag, f"{r['port']} open from any"
if any(covers(prev, r) for prev in rules[:i]):
yield "MEDIUM", tag, "shadowed by an earlier rule"
if r["hits"] == 0:
yield "LOW", tag, "zero hits since counters last cleared"
if __name__ == "__main__":
for sev, where, msg in sorted(audit(parse(open(sys.argv[1]).read()))):
print(f"{sev:7} {where:28} {msg}")
Tip: zero hits only mean "not used since the counters were last cleared or the device rebooted". Record when counters started, and wait for a full business cycle, including month-end and holiday periods, before calling a rule unused.
How to extend it
- Use a mature parsing library such as
ciscoconfparse2, or the firewall vendor’s API, rather than growing your own regular expressions forever. - Expand the shadowing check to understand subnets with the
ipaddressmodule, wildcard masks and port ranges (the wildcard mask tool helps you test). - Add checks for rules without a final logged deny, and for remarks missing a ticket reference.
- Run the audit in a CI pipeline whenever ACL changes are proposed, blocking merges with high findings.
Showing it in your portfolio
Include a deliberately messy sample ACL in the repository and the audit output it produces, with an explanation of each finding. Describe the limitations honestly: the simple shadowing check does not yet handle overlapping subnets. Explain the human side too, such as review meetings, approval and rollback, because employers want engineers who improve security without causing outages. Link the project to the theory in ACL design.
Practice
What is a shadowed rule?
Why should a rule with zero hits not be deleted immediately?
Why is the audit tool read-only?
Key takeaways
- Rule sets grow over time; regular audits are expected by standards.
- Parse rules into records, then run small, testable check functions.
- Flag any-any permits, risky services, shadowed, unused and undocumented rules.
- Zero hits need context: when were counters cleared?
- The tool recommends; approved change control decides.
Original content written by Mohamed Fayas for FM TecHub. © 2026 FM TecHub. You're welcome to share a link to this page; please don't copy the text elsewhere without permission.