[ci skip] Add pfSense firewall management skill
This commit is contained in:
parent
ca43b97fa0
commit
c473663b98
2 changed files with 626 additions and 0 deletions
432
.claude/pfsense.py
Normal file
432
.claude/pfsense.py
Normal file
|
|
@ -0,0 +1,432 @@
|
|||
#!/usr/bin/env python3
|
||||
"""pfSense CLI tool for managing the firewall via SSH.
|
||||
|
||||
Usage:
|
||||
python pfsense.py <command> [options]
|
||||
|
||||
Commands:
|
||||
status System status overview
|
||||
interfaces List interfaces with IPs and status
|
||||
gateways Show gateway status
|
||||
rules [iface] List firewall rules (optional: filter by interface)
|
||||
nat List NAT/port forward rules
|
||||
aliases List firewall aliases
|
||||
alias <name> Show alias details (members)
|
||||
states Show state table summary
|
||||
states-top [n] Top N connections by state count (default 10)
|
||||
dhcp-leases [iface] Show DHCP leases (optional: filter by interface)
|
||||
arp Show ARP table
|
||||
routes Show routing table
|
||||
services List services and status
|
||||
service <action> <name> Start/stop/restart a service
|
||||
logs [n] Show last N log lines (default 50)
|
||||
logs-filter <text> Search logs for text
|
||||
pfctl <args> Run arbitrary pfctl command
|
||||
php <code> Run PHP code on pfSense shell
|
||||
diag <host> Ping diagnostic to host
|
||||
backup Download config backup to stdout (XML)
|
||||
uptime Show system uptime
|
||||
cpu Show CPU usage
|
||||
memory Show memory usage
|
||||
disk Show disk usage
|
||||
temp Show CPU temperature
|
||||
pkg-list List installed packages
|
||||
dns-resolve <host> Resolve hostname via pfSense DNS
|
||||
wireguard Show WireGuard status
|
||||
bgp Show BGP summary (FRR)
|
||||
ospf Show OSPF neighbors (FRR)
|
||||
tailscale Show Tailscale status
|
||||
snort Show Snort status
|
||||
raw <command> Run arbitrary shell command
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
PFSENSE_HOST = "admin@10.0.20.1"
|
||||
SSH_OPTS = ["-o", "ConnectTimeout=10", "-o", "StrictHostKeyChecking=no"]
|
||||
|
||||
|
||||
def ssh(cmd: str, timeout: int = 30) -> str:
|
||||
"""Execute a command on pfSense via SSH."""
|
||||
result = subprocess.run(
|
||||
["ssh"] + SSH_OPTS + [PFSENSE_HOST, cmd],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout,
|
||||
)
|
||||
if result.returncode != 0 and result.stderr:
|
||||
print(f"Error: {result.stderr.strip()}", file=sys.stderr)
|
||||
return result.stdout.strip()
|
||||
|
||||
|
||||
def cmd_status(_args):
|
||||
print(ssh("""
|
||||
echo "=== System ==="
|
||||
uname -sr
|
||||
echo "Version: $(cat /etc/version)"
|
||||
uptime
|
||||
echo ""
|
||||
echo "=== CPU ==="
|
||||
sysctl -n hw.model
|
||||
echo "Load: $(sysctl -n vm.loadavg)"
|
||||
echo ""
|
||||
echo "=== Memory ==="
|
||||
php -r '
|
||||
$mem = @file_get_contents("/proc/meminfo") ?: "";
|
||||
$total = (int)shell_exec("sysctl -n hw.physmem") / 1024 / 1024;
|
||||
$free_pages = (int)shell_exec("sysctl -n vm.stats.vm.v_free_count");
|
||||
$page_size = (int)shell_exec("sysctl -n hw.pagesize");
|
||||
$free = $free_pages * $page_size / 1024 / 1024;
|
||||
printf("Total: %.0f MB, Free: %.0f MB, Used: %.0f MB (%.1f%%)\n",
|
||||
$total, $free, $total - $free, ($total - $free) / $total * 100);
|
||||
'
|
||||
echo ""
|
||||
echo "=== Disk ==="
|
||||
df -h / /var /tmp 2>/dev/null | grep -v "^Filesystem" | awk '{print $6 ": " $3 "/" $1 " (" $5 " used)"}'
|
||||
echo ""
|
||||
echo "=== States ==="
|
||||
pfctl -si 2>/dev/null | grep "current entries"
|
||||
echo ""
|
||||
echo "=== Temperature ==="
|
||||
sysctl -a 2>/dev/null | grep temperature | head -5
|
||||
"""))
|
||||
|
||||
|
||||
def cmd_interfaces(_args):
|
||||
print(ssh("""
|
||||
php -r '
|
||||
require_once("config.inc");
|
||||
require_once("interfaces.inc");
|
||||
$cfg = parse_config(true);
|
||||
foreach($cfg["interfaces"] as $k => $v) {
|
||||
$if = $v["if"] ?? "?";
|
||||
$descr = $v["descr"] ?? $k;
|
||||
$ip = $v["ipaddr"] ?? "dhcp";
|
||||
$subnet = $v["subnet"] ?? "";
|
||||
$enabled = isset($v["enable"]) || $k == "wan" || $k == "lan" ? "UP" : "DOWN";
|
||||
$gw = $v["gateway"] ?? "-";
|
||||
printf("%-8s %-20s %-10s %-18s gw:%-10s %s\n", $k, $descr, $if, $ip . ($subnet ? "/" . $subnet : ""), $gw, $enabled);
|
||||
}
|
||||
'
|
||||
"""))
|
||||
|
||||
|
||||
def cmd_gateways(_args):
|
||||
print(ssh("pfSsh.php playback gatewaystatus"))
|
||||
|
||||
|
||||
def cmd_rules(args):
|
||||
iface_filter = args.interface if hasattr(args, 'interface') and args.interface else ""
|
||||
if iface_filter:
|
||||
print(ssh(f"pfctl -sr 2>/dev/null | grep -i '{iface_filter}'"))
|
||||
else:
|
||||
print(ssh("pfctl -sr 2>/dev/null"))
|
||||
|
||||
|
||||
def cmd_nat(_args):
|
||||
print(ssh("pfctl -sn 2>/dev/null"))
|
||||
|
||||
|
||||
def cmd_aliases(_args):
|
||||
print(ssh("pfctl -sT 2>/dev/null"))
|
||||
|
||||
|
||||
def cmd_alias(args):
|
||||
print(ssh(f"pfctl -t {args.name} -T show 2>/dev/null"))
|
||||
|
||||
|
||||
def cmd_states(_args):
|
||||
print(ssh("pfctl -si 2>/dev/null"))
|
||||
|
||||
|
||||
def cmd_states_top(args):
|
||||
n = args.n if hasattr(args, 'n') and args.n else 10
|
||||
print(ssh(f"pfctl -ss 2>/dev/null | awk '{{print $3}}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -{n}"))
|
||||
|
||||
|
||||
def cmd_dhcp_leases(args):
|
||||
iface = args.interface if hasattr(args, 'interface') and args.interface else ""
|
||||
filter_clause = f'if($l["if"] == "{iface}")' if iface else ""
|
||||
print(ssh(f"""
|
||||
php -r '
|
||||
require_once("config.inc");
|
||||
require_once("interfaces.inc");
|
||||
$leases = system_get_dhcpleases();
|
||||
foreach($leases["lease"] as $l) {{
|
||||
{filter_clause}
|
||||
printf("%-16s %-18s %-8s %-15s %-10s %s\n",
|
||||
$l["ip"], $l["mac"] ?? "-", $l["act"] ?? "-",
|
||||
$l["hostname"] ?? "-", $l["if"] ?? "-",
|
||||
$l["online"] ?? "-");
|
||||
}}
|
||||
'
|
||||
"""))
|
||||
|
||||
|
||||
def cmd_arp(_args):
|
||||
print(ssh("arp -an"))
|
||||
|
||||
|
||||
def cmd_routes(_args):
|
||||
print(ssh("netstat -rn"))
|
||||
|
||||
|
||||
def cmd_services(_args):
|
||||
print(ssh("""
|
||||
php -r '
|
||||
require_once("config.inc");
|
||||
require_once("service-utils.inc");
|
||||
$svcs = get_services();
|
||||
foreach($svcs as $s) {
|
||||
$status = get_service_status($s) ? "RUNNING" : "STOPPED";
|
||||
printf("%-30s %s\n", $s["name"], $status);
|
||||
}
|
||||
'
|
||||
"""))
|
||||
|
||||
|
||||
def cmd_service(args):
|
||||
action = args.action
|
||||
name = args.name
|
||||
if action not in ("start", "stop", "restart"):
|
||||
print(f"Invalid action: {action}. Use start/stop/restart.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
print(ssh(f"pfSsh.php playback svc {action} {name}"))
|
||||
|
||||
|
||||
def cmd_logs(args):
|
||||
n = args.n if hasattr(args, 'n') and args.n else 50
|
||||
print(ssh(f"clog -f /var/log/filter.log 2>/dev/null | tail -{n}"))
|
||||
|
||||
|
||||
def cmd_logs_filter(args):
|
||||
print(ssh(f"clog -f /var/log/filter.log 2>/dev/null | grep -i '{args.text}'"))
|
||||
|
||||
|
||||
def cmd_pfctl(args):
|
||||
print(ssh(f"pfctl {args.args}"))
|
||||
|
||||
|
||||
def cmd_php(args):
|
||||
print(ssh(f"php -r '{args.code}'"))
|
||||
|
||||
|
||||
def cmd_diag(args):
|
||||
print(ssh(f"ping -c 4 {args.host}"))
|
||||
|
||||
|
||||
def cmd_backup(_args):
|
||||
print(ssh("cat /cf/conf/config.xml"))
|
||||
|
||||
|
||||
def cmd_uptime(_args):
|
||||
print(ssh("uptime"))
|
||||
|
||||
|
||||
def cmd_cpu(_args):
|
||||
print(ssh("""
|
||||
echo "Load: $(sysctl -n vm.loadavg)"
|
||||
echo "Model: $(sysctl -n hw.model)"
|
||||
echo "Cores: $(sysctl -n hw.ncpu)"
|
||||
top -b -d1 2>/dev/null | head -5 || vmstat 1 2 | tail -1
|
||||
"""))
|
||||
|
||||
|
||||
def cmd_memory(_args):
|
||||
print(ssh("""
|
||||
php -r '
|
||||
$total = (int)shell_exec("sysctl -n hw.physmem") / 1024 / 1024;
|
||||
$free_pages = (int)shell_exec("sysctl -n vm.stats.vm.v_free_count");
|
||||
$inactive_pages = (int)shell_exec("sysctl -n vm.stats.vm.v_inactive_count");
|
||||
$cache_pages = (int)shell_exec("sysctl -n vm.stats.vm.v_cache_count");
|
||||
$page_size = (int)shell_exec("sysctl -n hw.pagesize");
|
||||
$free = $free_pages * $page_size / 1024 / 1024;
|
||||
$inactive = $inactive_pages * $page_size / 1024 / 1024;
|
||||
$cache = $cache_pages * $page_size / 1024 / 1024;
|
||||
$used = $total - $free - $inactive - $cache;
|
||||
printf("Total: %.0f MB\n", $total);
|
||||
printf("Used: %.0f MB (%.1f%%)\n", $used, $used / $total * 100);
|
||||
printf("Free: %.0f MB\n", $free);
|
||||
printf("Inactive: %.0f MB\n", $inactive);
|
||||
printf("Cache: %.0f MB\n", $cache);
|
||||
'
|
||||
"""))
|
||||
|
||||
|
||||
def cmd_disk(_args):
|
||||
print(ssh("df -h"))
|
||||
|
||||
|
||||
def cmd_temp(_args):
|
||||
print(ssh("sysctl -a 2>/dev/null | grep -i temp"))
|
||||
|
||||
|
||||
def cmd_pkg_list(_args):
|
||||
print(ssh("pfSsh.php playback listpkg"))
|
||||
|
||||
|
||||
def cmd_dns_resolve(args):
|
||||
print(ssh(f"drill {args.host} @127.0.0.1 2>/dev/null || host {args.host} 127.0.0.1 2>/dev/null || nslookup {args.host} 127.0.0.1"))
|
||||
|
||||
|
||||
def cmd_wireguard(_args):
|
||||
print(ssh("wg show 2>/dev/null || echo 'WireGuard not active or wg command not found'"))
|
||||
|
||||
|
||||
def cmd_bgp(_args):
|
||||
print(ssh("/usr/local/bin/vtysh -c 'show bgp summary' 2>/dev/null || echo 'FRR/BGP not available'"))
|
||||
|
||||
|
||||
def cmd_ospf(_args):
|
||||
print(ssh("/usr/local/bin/vtysh -c 'show ip ospf neighbor' 2>/dev/null || echo 'FRR/OSPF not available'"))
|
||||
|
||||
|
||||
def cmd_tailscale(_args):
|
||||
print(ssh("tailscale status 2>/dev/null || echo 'Tailscale not available'"))
|
||||
|
||||
|
||||
def cmd_snort(_args):
|
||||
print(ssh("""
|
||||
php -r '
|
||||
require_once("config.inc");
|
||||
require_once("service-utils.inc");
|
||||
$svcs = get_services();
|
||||
foreach($svcs as $s) {
|
||||
if(stripos($s["name"], "snort") !== false) {
|
||||
$status = get_service_status($s) ? "RUNNING" : "STOPPED";
|
||||
printf("%-30s %s\n", $s["name"], $status);
|
||||
}
|
||||
}
|
||||
'
|
||||
echo "---Alerts (last 20)---"
|
||||
cat /var/log/snort/snort_*/alert 2>/dev/null | tail -20 || echo "No alert logs found"
|
||||
"""))
|
||||
|
||||
|
||||
def cmd_raw(args):
|
||||
print(ssh(args.command))
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="pfSense management via SSH")
|
||||
sub = parser.add_subparsers(dest="command", help="Command to run")
|
||||
|
||||
sub.add_parser("status", help="System status overview")
|
||||
sub.add_parser("interfaces", help="List interfaces")
|
||||
sub.add_parser("gateways", help="Show gateway status")
|
||||
|
||||
p = sub.add_parser("rules", help="List firewall rules")
|
||||
p.add_argument("interface", nargs="?", default="", help="Filter by interface")
|
||||
|
||||
sub.add_parser("nat", help="List NAT rules")
|
||||
sub.add_parser("aliases", help="List aliases")
|
||||
|
||||
p = sub.add_parser("alias", help="Show alias members")
|
||||
p.add_argument("name", help="Alias name")
|
||||
|
||||
sub.add_parser("states", help="State table summary")
|
||||
|
||||
p = sub.add_parser("states-top", help="Top connections by state count")
|
||||
p.add_argument("n", nargs="?", type=int, default=10)
|
||||
|
||||
p = sub.add_parser("dhcp-leases", help="Show DHCP leases")
|
||||
p.add_argument("interface", nargs="?", default="", help="Filter by interface")
|
||||
|
||||
sub.add_parser("arp", help="ARP table")
|
||||
sub.add_parser("routes", help="Routing table")
|
||||
sub.add_parser("services", help="List services")
|
||||
|
||||
p = sub.add_parser("service", help="Control a service")
|
||||
p.add_argument("action", choices=["start", "stop", "restart"])
|
||||
p.add_argument("name", help="Service name")
|
||||
|
||||
p = sub.add_parser("logs", help="Show firewall logs")
|
||||
p.add_argument("n", nargs="?", type=int, default=50)
|
||||
|
||||
p = sub.add_parser("logs-filter", help="Search logs")
|
||||
p.add_argument("text", help="Text to search for")
|
||||
|
||||
p = sub.add_parser("pfctl", help="Run pfctl command")
|
||||
p.add_argument("args", help="pfctl arguments")
|
||||
|
||||
p = sub.add_parser("php", help="Run PHP code")
|
||||
p.add_argument("code", help="PHP code to execute")
|
||||
|
||||
p = sub.add_parser("diag", help="Ping diagnostic")
|
||||
p.add_argument("host", help="Host to ping")
|
||||
|
||||
sub.add_parser("backup", help="Download config backup (XML)")
|
||||
sub.add_parser("uptime", help="System uptime")
|
||||
sub.add_parser("cpu", help="CPU usage")
|
||||
sub.add_parser("memory", help="Memory usage")
|
||||
sub.add_parser("disk", help="Disk usage")
|
||||
sub.add_parser("temp", help="CPU temperature")
|
||||
sub.add_parser("pkg-list", help="List packages")
|
||||
|
||||
p = sub.add_parser("dns-resolve", help="Resolve hostname")
|
||||
p.add_argument("host", help="Hostname to resolve")
|
||||
|
||||
sub.add_parser("wireguard", help="WireGuard status")
|
||||
sub.add_parser("bgp", help="BGP summary")
|
||||
sub.add_parser("ospf", help="OSPF neighbors")
|
||||
sub.add_parser("tailscale", help="Tailscale status")
|
||||
sub.add_parser("snort", help="Snort status")
|
||||
|
||||
p = sub.add_parser("raw", help="Run arbitrary command")
|
||||
p.add_argument("command", help="Command to run")
|
||||
|
||||
args = parser.parse_args()
|
||||
if not args.command:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
cmd_map = {
|
||||
"status": cmd_status,
|
||||
"interfaces": cmd_interfaces,
|
||||
"gateways": cmd_gateways,
|
||||
"rules": cmd_rules,
|
||||
"nat": cmd_nat,
|
||||
"aliases": cmd_aliases,
|
||||
"alias": cmd_alias,
|
||||
"states": cmd_states,
|
||||
"states-top": cmd_states_top,
|
||||
"dhcp-leases": cmd_dhcp_leases,
|
||||
"arp": cmd_arp,
|
||||
"routes": cmd_routes,
|
||||
"services": cmd_services,
|
||||
"service": cmd_service,
|
||||
"logs": cmd_logs,
|
||||
"logs-filter": cmd_logs_filter,
|
||||
"pfctl": cmd_pfctl,
|
||||
"php": cmd_php,
|
||||
"diag": cmd_diag,
|
||||
"backup": cmd_backup,
|
||||
"uptime": cmd_uptime,
|
||||
"cpu": cmd_cpu,
|
||||
"memory": cmd_memory,
|
||||
"disk": cmd_disk,
|
||||
"temp": cmd_temp,
|
||||
"pkg-list": cmd_pkg_list,
|
||||
"dns-resolve": cmd_dns_resolve,
|
||||
"wireguard": cmd_wireguard,
|
||||
"bgp": cmd_bgp,
|
||||
"ospf": cmd_ospf,
|
||||
"tailscale": cmd_tailscale,
|
||||
"snort": cmd_snort,
|
||||
"raw": cmd_raw,
|
||||
}
|
||||
|
||||
func = cmd_map.get(args.command)
|
||||
if func:
|
||||
func(args)
|
||||
else:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
194
.claude/skills/pfsense/SKILL.md
Normal file
194
.claude/skills/pfsense/SKILL.md
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
---
|
||||
name: pfsense
|
||||
description: |
|
||||
Manage the pfSense firewall at 10.0.20.1 via SSH. Use when:
|
||||
(1) User asks about firewall rules, NAT, port forwarding,
|
||||
(2) User asks about network diagnostics (ARP, routing, DNS, ping),
|
||||
(3) User asks about DHCP leases or static mappings,
|
||||
(4) User asks about VPN status (WireGuard, Tailscale),
|
||||
(5) User asks about pfSense services (Snort, FRR/BGP/OSPF, etc.),
|
||||
(6) User asks about firewall states, connections, or traffic,
|
||||
(7) User mentions "pfsense", "firewall", "gateway", or network troubleshooting,
|
||||
(8) User wants to check system health (CPU, memory, disk, temp) of pfSense.
|
||||
pfSense CE 2.7.2 on FreeBSD 14.0, VMID 101 on Proxmox.
|
||||
author: Claude Code
|
||||
version: 1.0.0
|
||||
date: 2026-02-14
|
||||
---
|
||||
|
||||
# pfSense Firewall Management
|
||||
|
||||
## Overview
|
||||
- **Host**: `10.0.20.1` (Kubernetes VLAN gateway)
|
||||
- **SSH**: `ssh admin@10.0.20.1`
|
||||
- **Version**: pfSense CE 2.7.2, FreeBSD 14.0
|
||||
- **Proxmox VMID**: 101 (8 CPU, 16GB RAM, 32G disk)
|
||||
- **Web UI**: `https://pfsense.viktorbarzin.me` (via reverse proxy) or `https://10.0.20.1`
|
||||
- **Installed packages**: FRR (BGP/OSPF), Tailscale, Snort, WireGuard, REST API, FreeRADIUS
|
||||
|
||||
## Interfaces
|
||||
|
||||
| Name | Description | Physical | IP | Network |
|
||||
|------|-------------|----------|-----|---------|
|
||||
| wan | WAN | vtnet0 | 192.168.1.2/24 | Physical network |
|
||||
| lan | Management VMs | vtnet1 | 10.0.10.1/24 | VLAN 10 |
|
||||
| opt1 | Kubernetes | vtnet2 | 10.0.20.1/24 | VLAN 20 |
|
||||
| opt2 | WireGuard | tun_wg0 | 10.3.2.1/24 | VPN tunnel |
|
||||
| tailscale0 | Tailscale | tailscale0 | 100.64.0.x | Headscale mesh |
|
||||
|
||||
## CLI Script
|
||||
|
||||
**Script**: `.claude/pfsense.py`
|
||||
|
||||
### Execution Pattern
|
||||
```bash
|
||||
cd ~/code/infra && python3 .claude/pfsense.py <command> [options]
|
||||
```
|
||||
|
||||
### Available Commands
|
||||
|
||||
#### System Information
|
||||
```bash
|
||||
python3 .claude/pfsense.py status # Full system overview
|
||||
python3 .claude/pfsense.py uptime # Uptime
|
||||
python3 .claude/pfsense.py cpu # CPU info and load
|
||||
python3 .claude/pfsense.py memory # Memory breakdown
|
||||
python3 .claude/pfsense.py disk # Disk usage
|
||||
python3 .claude/pfsense.py temp # CPU temperature
|
||||
python3 .claude/pfsense.py pkg-list # Installed packages
|
||||
```
|
||||
|
||||
#### Network & Interfaces
|
||||
```bash
|
||||
python3 .claude/pfsense.py interfaces # Interface list with IPs
|
||||
python3 .claude/pfsense.py gateways # Gateway status
|
||||
python3 .claude/pfsense.py arp # ARP table
|
||||
python3 .claude/pfsense.py routes # Routing table
|
||||
python3 .claude/pfsense.py dns-resolve <host> # DNS lookup via pfSense
|
||||
python3 .claude/pfsense.py diag <host> # Ping test
|
||||
```
|
||||
|
||||
#### Firewall
|
||||
```bash
|
||||
python3 .claude/pfsense.py rules # All firewall rules
|
||||
python3 .claude/pfsense.py rules opt1 # Rules for Kubernetes interface
|
||||
python3 .claude/pfsense.py nat # NAT / port forwarding rules
|
||||
python3 .claude/pfsense.py aliases # List all aliases
|
||||
python3 .claude/pfsense.py alias <name> # Show alias members
|
||||
python3 .claude/pfsense.py states # State table summary
|
||||
python3 .claude/pfsense.py states-top 20 # Top 20 IPs by connection count
|
||||
```
|
||||
|
||||
#### DHCP
|
||||
```bash
|
||||
python3 .claude/pfsense.py dhcp-leases # All DHCP leases
|
||||
python3 .claude/pfsense.py dhcp-leases opt1 # Kubernetes network leases only
|
||||
```
|
||||
|
||||
#### Services
|
||||
```bash
|
||||
python3 .claude/pfsense.py services # List all services + status
|
||||
python3 .claude/pfsense.py service restart snort # Restart a service
|
||||
python3 .claude/pfsense.py service stop wireguard # Stop a service
|
||||
python3 .claude/pfsense.py service start wireguard # Start a service
|
||||
```
|
||||
|
||||
#### VPN & Routing
|
||||
```bash
|
||||
python3 .claude/pfsense.py wireguard # WireGuard tunnel status
|
||||
python3 .claude/pfsense.py tailscale # Tailscale/Headscale status
|
||||
python3 .claude/pfsense.py bgp # BGP summary (FRR)
|
||||
python3 .claude/pfsense.py ospf # OSPF neighbors (FRR)
|
||||
```
|
||||
|
||||
#### Security
|
||||
```bash
|
||||
python3 .claude/pfsense.py snort # Snort IDS status + recent alerts
|
||||
python3 .claude/pfsense.py logs # Last 50 firewall log entries
|
||||
python3 .claude/pfsense.py logs 200 # Last 200 entries
|
||||
python3 .claude/pfsense.py logs-filter "blocked" # Search logs
|
||||
```
|
||||
|
||||
#### Advanced
|
||||
```bash
|
||||
python3 .claude/pfsense.py pfctl "-sr" # Raw pfctl command
|
||||
python3 .claude/pfsense.py php "echo phpversion();" # Run PHP on pfSense
|
||||
python3 .claude/pfsense.py raw "ls /tmp" # Run arbitrary shell command
|
||||
python3 .claude/pfsense.py backup # Dump config.xml to stdout
|
||||
```
|
||||
|
||||
## Direct SSH Access
|
||||
|
||||
For tasks not covered by the script, SSH directly:
|
||||
```bash
|
||||
ssh admin@10.0.20.1 "<command>"
|
||||
```
|
||||
|
||||
### Useful Direct Commands
|
||||
```bash
|
||||
# pfSense PHP shell (interactive config access)
|
||||
ssh admin@10.0.20.1 "php -r 'require_once(\"config.inc\"); \$cfg = parse_config(true); echo json_encode(\$cfg[\"nat\"], JSON_PRETTY_PRINT);'"
|
||||
|
||||
# pfSsh.php playback commands
|
||||
ssh admin@10.0.20.1 "pfSsh.php playback gatewaystatus"
|
||||
ssh admin@10.0.20.1 "pfSsh.php playback svc restart snort"
|
||||
ssh admin@10.0.20.1 "pfSsh.php playback listpkg"
|
||||
|
||||
# Config sections via PHP
|
||||
ssh admin@10.0.20.1 "php -r 'require_once(\"config.inc\"); \$cfg = parse_config(true); print_r(\$cfg[\"filter\"][\"rule\"][0]);'"
|
||||
|
||||
# FRR/vtysh for routing
|
||||
ssh admin@10.0.20.1 "/usr/local/bin/vtysh -c 'show ip route'"
|
||||
ssh admin@10.0.20.1 "/usr/local/bin/vtysh -c 'show bgp ipv4 unicast'"
|
||||
```
|
||||
|
||||
## REST API (pfSense-pkg-RESTAPI v2.2)
|
||||
|
||||
The REST API package is installed but **no API keys are configured**. To use it:
|
||||
1. Create an API key in pfSense Web UI: System > REST API > Settings > Keys
|
||||
2. Use Bearer token auth: `curl -sk https://10.0.20.1/api/v2/status/system -H 'Authorization: Bearer <key>'`
|
||||
|
||||
Until API keys are set up, use SSH for all operations.
|
||||
|
||||
## Key Services
|
||||
|
||||
| Service | Status | Notes |
|
||||
|---------|--------|-------|
|
||||
| FRR (BGP/OSPF) | Running | Routing daemon |
|
||||
| Snort | Running | IDS/IPS |
|
||||
| WireGuard | Running | VPN tunnel (10.3.2.0/24) |
|
||||
| Tailscale | Running | Mesh VPN via Headscale |
|
||||
| FreeRADIUS | Running | RADIUS auth |
|
||||
| DHCP (Kea) | Running | kea-dhcp4 |
|
||||
| SSH | Running | Admin access |
|
||||
| NTP | Running | Time sync |
|
||||
|
||||
## Firewall Stats
|
||||
- **167 firewall rules** (pfctl -sr)
|
||||
- **154 NAT rules** (pfctl -sn)
|
||||
- **~784 active states** (varies)
|
||||
- **10 aliases** (LAN, OPT1, OPT2, WAN networks + custom)
|
||||
|
||||
## NFS Backup
|
||||
Config backups stored at NFS: `/mnt/main/pfsense-backup`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Command |
|
||||
|-------|---------|
|
||||
| Can't reach internet from K8s | `python3 .claude/pfsense.py gateways` + `python3 .claude/pfsense.py diag 8.8.8.8` |
|
||||
| K8s pod can't reach external | `python3 .claude/pfsense.py rules opt1` + check NAT |
|
||||
| DHCP not working | `python3 .claude/pfsense.py dhcp-leases opt1` + `python3 .claude/pfsense.py service restart kea-dhcp4` |
|
||||
| High connection count | `python3 .claude/pfsense.py states-top 20` |
|
||||
| Snort blocking traffic | `python3 .claude/pfsense.py snort` + check alerts |
|
||||
| DNS resolution failing | `python3 .claude/pfsense.py dns-resolve <host>` |
|
||||
| BGP/OSPF routes missing | `python3 .claude/pfsense.py bgp` or `python3 .claude/pfsense.py ospf` |
|
||||
| WireGuard tunnel down | `python3 .claude/pfsense.py wireguard` |
|
||||
|
||||
## Notes
|
||||
1. **FreeBSD-based**: Commands differ from Linux (no `ip`, use `ifconfig`, `netstat`, `arp`)
|
||||
2. **pfctl is the firewall**: Rules loaded from config.xml via PHP, managed by pfctl
|
||||
3. **Config file**: `/cf/conf/config.xml` — all pfSense config in one XML file
|
||||
4. **PHP shell**: pfSense uses PHP for all config management; `config.inc` loads the config
|
||||
5. **Do NOT edit config.xml directly** — use the Web UI or PHP functions that properly reload services
|
||||
6. **Logs**: Binary circular logs, read with `clog -f /var/log/<logfile>`
|
||||
Loading…
Add table
Add a link
Reference in a new issue