Skip to content
All projects

PF-09 · AWS · Python

AWS firewall request automation

A CLI that checks whether a firewall request is already open, previews changes only for what is blocked, and verifies the real traffic.

Role
Design and implementation
Period
Personal project · released Nov 2026
Cloud
AWS
Scale
3-step CLI · 4 input formats · 4 resource types
Key tech
Python · boto3 · AWS · CloudWatch Logs · Athena · Excel
GitHub
3
Steps: confirm, modify, flowlog
4
Input formats
4
Resource types identified

At a glance

Firewall requestSource · Destination · Portnetops confirmnetops modifynetops flowlogIs it already open?Preview only the blocked flowsVerify with real trafficOpenNo work neededBlockedOn to the next stepPreviewPrints affected resources andrules--apply + typed applyAdds rules onlyTraffic foundCloudWatch Logs · AthenaNothing changes by default
flowlog searches CloudWatch Logs or Athena for the actual traffic.

Problem

Firewall requests often asked for flows that were already open, which added duplicate rules, and finding the related resources and rules meant searching the console account by account.

Approach

netops confirm resolves addresses to AWS resources and evaluates security group rules and routes. Only blocked flows go to modify, which prints a plan first. flowlog searches CloudWatch Logs or Athena for the actual traffic.

Result

By default nothing changes. Adding a rule needs --apply and a typed apply, rules can only be added, and a security group without free slots is never chosen.

Up close

AddressIP · CIDR · IP range ·RDS addressResolveEC2 · LB · RDS · ENISource outboundRule checkDestination inboundRule checkRoute checkWhen the destination isoutside AWSVerdict: blocked (Route needed)Separates cases that a new rule cannot fixRules and routes are read together to avoid needless allow rules
The verdict names the cause, such as Security group rule needed or Route needed.

Rules and routes decide

What this code does: Records whether a rule or a missing route is the reason.

        if any(not c.ok for c in verdict.checks):
            verdict.ok = False
            verdict.issues.append("Security group rule needed")

        if source.resources and not target.resources:
            for resource in source.resources:
                ec2 = self.sessions.client("ec2", resource.account)
                result = route_analysis.evaluate(
                    route_analysis.find_table(ec2, resource.subnet_id, resource.vpc_id), dst
                )
                detail = (
                    f"via {route_analysis.next_hop(result.routes[0])}"
                    if result.ok
                    else f"no route in {result.table_id or 'any table'}"
                )
                verdict.checks.append(Check("route", resource.label, result.ok, detail))
                if not result.ok:
                    verdict.ok = False
                    verdict.issues.append("Route needed")
        return verdict

Apply only adds rules

What this code does: The only change is adding a rule, and a rule that already exists passes quietly.

    def apply(self, change: Change) -> str:
        ec2 = self.sessions.client("ec2", change.account)
        call = (
            ec2.authorize_security_group_ingress
            if change.direction == "ingress"
            else ec2.authorize_security_group_egress
        )
        try:
            call(GroupId=change.sg_id, IpPermissions=change.permissions)
        except ClientError as error:
            if error.response["Error"]["Code"] == "InvalidPermission.Duplicate":
                return "already present"
            raise
        return "applied"

What I learned

The safe action must be the one that needs no flag. A --dry-run option fails dangerously when someone forgets it.

What I would improve

The security group is chosen automatically, with no menu to pick another. Prefix lists are not evaluated, and flow log search has not been run against a live archive since this refactor.

Related field notes

Open to work · Cloud infrastructure and security