Free tech learning for everyone, from basics to pro

Automation ProjectsProgramming and Application SecurityAdvanced

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.

ACL audit pipelineCollectconfig + hitcountsParseone rule perrowRun checkspolicy rulesScorehigh, medium,lowReportfindings toreview
Figure 1. The audit turns rule text into structured records, runs each check against them and produces a ranked list of findings.

Design

Audit checksAny to anypermit ip any anyRisky servicestelnet, ftp, snmpShadowednever reachedUnusedzero hit countNo remarkno owner or reasonNo final denyimplicit only
Figure 2. Six practical checks catch most of the problems found in long-lived firewall and router rule sets.

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:

FindingSeverityWhy
permit ip any anyHighNo filtering at all
Telnet, FTP or SNMP permitted from anyHighClear-text or sensitive management services
Shadowed ruleMediumAn earlier rule already matches all its traffic
Zero hits over a long periodLowProbably unused; confirm before removing
No remarkLowNo recorded owner or business reason

Build it step by step

  1. Collect show access-lists (which includes hit counts) from lab routers with Netmiko, or save the output to text files.
  2. 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.
  3. Write one function per check, returning findings with a severity.
  4. Print findings grouped by ACL and severity, and export them to CSV for the review meeting.
  5. 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 ipaddress module, 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?
A rule that can never match because an earlier rule in the list already matches all of its traffic, so it is dead and often misleading.
Why should a rule with zero hits not be deleted immediately?
Counters may have been cleared recently, or the traffic may be rare but important, such as month-end jobs or disaster recovery. Confirm over a full cycle and with the owner first.
Why is the audit tool read-only?
Rule removal can cause outages, so changes must go through review, approval and rollback planning; the tool provides evidence for that process.

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.
Tracks your learning on this device only. See your progress on the learning paths.

About the author

Mohamed Fayas

Founder of FM TecHub. CCNA certified, with a BTEC HND in Network Engineering, and BEng (Hons) in Computer Networking and Cloud Security. He writes practical, plain-English guides on networking, security and cloud.

More about the author

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.

Stay up to date by email

New notes, tools and security news from FM TecHub, explained simply. Free.

We only use your email to send these updates. See our privacy policy.