Files
Anthropic-Cybersecurity-Skills/skills/configuring-pfsense-firewall-rules/scripts/agent.py
T
mukul975 c47eed6a64 Production hardening: security fixes, code quality, 724 skills complete
- Fix 25 shell=True subprocess calls with list-based commands
- Fix 49 verify=False in defensive skills (env-var override)
- Add timeout to 231 HTTP/subprocess/socket calls
- Fix 6 SQL injection patterns with whitelist validation
- Replace 8 __import__() with standard imports
- Remove 701 unused imports across 442 files
- Add authorized-testing disclaimers to all offensive skills
- Complete 11 incomplete skill directories
- Expand 10 stub SKILL.md files with full content
- Fix 2 YAML parse errors in frontmatter
- Fix 5 pre-existing syntax errors
- Convert 22 hardcoded paths/ports to environment variables
- Back up 21 redundant skill pairs to .bak
- Fix 2 global declaration errors
- 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE)
- 0 compile errors across all 724 agent.py files
2026-03-19 13:26:49 +01:00

217 lines
8.4 KiB
Python

#!/usr/bin/env python3
"""pfSense Firewall Configuration Agent - Manages firewall rules via pfSense API."""
import json
import logging
import os
import argparse
from datetime import datetime
import requests
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
class PfSenseAPI:
"""Client for pfSense REST API (pfsense-api package)."""
def __init__(self, base_url, api_key, api_secret):
self.base_url = base_url.rstrip("/")
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"{api_key} {api_secret}",
"Content-Type": "application/json",
})
self.session.verify = not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true" # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
def get(self, endpoint):
resp = self.session.get(f"{self.base_url}/api/v1/{endpoint}", timeout=30)
resp.raise_for_status()
return resp.json()
def post(self, endpoint, data):
resp = self.session.post(f"{self.base_url}/api/v1/{endpoint}", json=data, timeout=30)
resp.raise_for_status()
return resp.json()
def put(self, endpoint, data):
resp = self.session.put(f"{self.base_url}/api/v1/{endpoint}", json=data, timeout=30)
resp.raise_for_status()
return resp.json()
def delete(self, endpoint, data=None):
resp = self.session.delete(f"{self.base_url}/api/v1/{endpoint}", json=data, timeout=30)
resp.raise_for_status()
return resp.json()
def get_firewall_rules(api):
"""Retrieve all firewall rules from pfSense."""
result = api.get("firewall/rule")
rules = result.get("data", [])
logger.info("Retrieved %d firewall rules", len(rules))
return rules
def create_firewall_rule(api, interface, action, protocol, source, destination, dst_port, description):
"""Create a new firewall rule on pfSense."""
rule = {
"type": action,
"interface": interface,
"ipprotocol": "inet",
"protocol": protocol,
"src": source,
"dst": destination,
"dstport": dst_port,
"descr": description,
"top": False,
}
result = api.post("firewall/rule", rule)
logger.info("Created rule: %s %s %s -> %s:%s (%s)",
action, protocol, source, destination, dst_port, description)
return result
def create_lan_to_wan_rules(api):
"""Create standard LAN-to-WAN egress rules."""
rules = [
("pass", "tcp", "LAN net", "any", "80", "Allow HTTP outbound"),
("pass", "tcp", "LAN net", "any", "443", "Allow HTTPS outbound"),
("pass", "udp", "LAN net", "any", "53", "Allow DNS outbound"),
("pass", "tcp", "LAN net", "any", "53", "Allow DNS TCP outbound"),
("block", "any", "LAN net", "any", "", "Block all other LAN egress"),
]
for action, proto, src, dst, port, desc in rules:
create_firewall_rule(api, "lan", action, proto, src, dst, port, desc)
logger.info("Created %d LAN-to-WAN rules", len(rules))
def create_dmz_rules(api, dmz_interface="opt1"):
"""Create DMZ isolation rules allowing only inbound web traffic."""
rules = [
("pass", "tcp", "any", "DMZ net", "80", "Allow HTTP to DMZ web servers"),
("pass", "tcp", "any", "DMZ net", "443", "Allow HTTPS to DMZ web servers"),
("block", "any", "DMZ net", "LAN net", "", "Block DMZ to LAN"),
("pass", "tcp", "DMZ net", "any", "80", "Allow DMZ HTTP outbound for updates"),
("pass", "tcp", "DMZ net", "any", "443", "Allow DMZ HTTPS outbound"),
("pass", "udp", "DMZ net", "any", "53", "Allow DMZ DNS"),
]
for action, proto, src, dst, port, desc in rules:
create_firewall_rule(api, dmz_interface, action, proto, src, dst, port, desc)
logger.info("Created %d DMZ rules", len(rules))
def create_guest_isolation_rules(api, guest_interface="opt2"):
"""Create guest network isolation rules - internet only, no internal access."""
rules = [
("block", "any", "GUEST net", "LAN net", "", "Block Guest to LAN"),
("block", "any", "GUEST net", "DMZ net", "", "Block Guest to DMZ"),
("block", "any", "GUEST net", "192.168.0.0/16", "", "Block Guest to RFC1918"),
("block", "any", "GUEST net", "10.0.0.0/8", "", "Block Guest to RFC1918"),
("block", "any", "GUEST net", "172.16.0.0/12", "", "Block Guest to RFC1918"),
("pass", "any", "GUEST net", "any", "", "Allow Guest to Internet"),
]
for action, proto, src, dst, port, desc in rules:
create_firewall_rule(api, guest_interface, action, proto, src, dst, port, desc)
logger.info("Created %d Guest isolation rules", len(rules))
def configure_nat_port_forward(api, interface, external_port, target_ip, target_port, protocol="tcp"):
"""Create a NAT port forward rule."""
nat_rule = {
"interface": interface,
"protocol": protocol,
"src": "any",
"dst": "wanip",
"dstport": external_port,
"target": target_ip,
"local-port": target_port,
"descr": f"Port forward {external_port} -> {target_ip}:{target_port}",
}
result = api.post("firewall/nat/port_forward", nat_rule)
logger.info("Created NAT: %s:%s -> %s:%s", interface, external_port, target_ip, target_port)
return result
def audit_firewall_rules(rules):
"""Audit firewall rules for common security issues."""
findings = []
for i, rule in enumerate(rules):
if rule.get("src") == "any" and rule.get("dst") == "any" and rule.get("type") == "pass":
findings.append({
"rule_index": i,
"finding": "Overly permissive rule: any-to-any pass",
"severity": "High",
"rule": rule.get("descr", "No description"),
})
if not rule.get("descr"):
findings.append({
"rule_index": i,
"finding": "Rule without description",
"severity": "Low",
"rule": f"Rule #{i}",
})
if rule.get("disabled"):
findings.append({
"rule_index": i,
"finding": "Disabled rule should be reviewed or removed",
"severity": "Info",
"rule": rule.get("descr", "No description"),
})
logger.info("Audit: %d findings across %d rules", len(findings), len(rules))
return findings
def get_firewall_logs(api, limit=100):
"""Retrieve recent firewall log entries."""
result = api.get(f"diagnostics/system_log/firewall?limit={limit}")
return result.get("data", [])
def generate_report(rules, audit_findings, nat_rules=None):
"""Generate pfSense firewall configuration report."""
report = {
"timestamp": datetime.utcnow().isoformat(),
"total_rules": len(rules),
"audit_findings": audit_findings,
"rules_summary": [
{"interface": r.get("interface", ""), "type": r.get("type", ""),
"description": r.get("descr", "")}
for r in rules
],
}
print(f"PFSENSE FIREWALL REPORT: {len(rules)} rules, {len(audit_findings)} findings")
return report
def main():
parser = argparse.ArgumentParser(description="pfSense Firewall Configuration Agent")
parser.add_argument("--url", required=True, help="pfSense base URL (https://192.168.1.1)")
parser.add_argument("--api-key", required=True, help="pfSense API key")
parser.add_argument("--api-secret", required=True, help="pfSense API secret")
parser.add_argument("--audit-only", action="store_true", help="Audit rules without changes")
parser.add_argument("--setup-dmz", action="store_true", help="Create DMZ rules")
parser.add_argument("--setup-guest", action="store_true", help="Create guest isolation rules")
parser.add_argument("--output", default="pfsense_report.json")
args = parser.parse_args()
api = PfSenseAPI(args.url, args.api_key, args.api_secret)
rules = get_firewall_rules(api)
findings = audit_firewall_rules(rules)
if not args.audit_only:
if args.setup_dmz:
create_dmz_rules(api)
if args.setup_guest:
create_guest_isolation_rules(api)
report = generate_report(rules, findings)
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
logger.info("Report saved to %s", args.output)
if __name__ == "__main__":
main()