Firewall
The Firewall module is FreeSDN’s unified network-security surface. It handles firewall rule CRUD and reorder, NAT, VPN tunnels with live stats, and IDS/IPS for supported adapters. It also absorbs the former Gateway module, giving you gateway orchestration: designate one firewall as the site brain, assign network controllers as limbs, define canonical VLANs once, and distribute them everywhere.
You interact with the Firewall module through two separate worlds that are distinct by design:
- FreeSDN’s own normalized tables - rules, NAT rules, VPN tunnels, and IDS alerts stored in the
firewallschema. These are portable, org-scoped, appear in config backups, and are managed through the Pending Changes UI. - Live device reads and writes - proxied through vendor adapters via
/api/v1/firewall/gateways/{id}/.... These talk directly to the connected OPNsense, pfSense, MikroTik, or OpenWRT device.
Adapters
Section titled “Adapters”| Adapter | Tier | Notes |
|---|---|---|
| OPNsense | Production | ; 13 feature domains including OSPF/BGP routing; gold-standard contract verified; deepest coverage |
| pfSense | Production | Shares OPNsense client plumbing; firewall, NAT, OpenVPN, IPsec, WireGuard |
| MikroTik (RouterOS v7) | Production | API-only access (no SSH/WinBox); 13 feature domains; 13 UI tabs; IPsec and L2TP/PPTP/SSTP |
| OpenWRT | Preview / unaudited | ubus/UCI client exists; the gold-standard contract (dual-gate, secret redaction, SSRF guard) has not been audited; do not deploy against production fleets |
All Production adapters enforce:
- Dual-gate writes (
ADAPTER_READ_ONLY=false+force=true) - Tagged circuit-breaker
redact_secretson every read (camelCase-aware; ~90 sensitive keys)- Tenant scoping + SSRF guard
- Role gates on destructive operations
Connecting a firewall
Section titled “Connecting a firewall”Gateway connections live at Firewall > Gateways in the UI, or via the API at /api/v1/firewall/gateways. Credentials are Fernet-encrypted at rest and never returned in plaintext; the detail endpoint returns a has_credentials: bool flag rather than the stored secret.
OPNsense
Section titled “OPNsense”OPNsense uses API key + API secret. Generate a key in OPNsense at System > Access > Users, create a user with restricted privileges, then add the key here.
# 1. Create the gateway connectioncurl -s -X POST https://<freesdn-host>/api/v1/firewall/gateways \ -H "Cookie: freesdn_access=<token>" \ -H "X-CSRF-Token: <csrf>" \ -H "Content-Type: application/json" \ -d '{ "name": "opn-prod", "vendor": "opnsense", "host": "opn.lan", "port": 443, "verify_ssl": false, "site_id": "<site-uuid>", "api_key": "<opnsense-key>", "api_secret": "<opnsense-secret>" }'
# 2. Test the saved connectioncurl -s -X POST https://<freesdn-host>/api/v1/firewall/gateways/<gateway-id>/test \ -H "Cookie: freesdn_access=<token>" \ -H "X-CSRF-Token: <csrf>"
# 3. Trigger initial synccurl -s -X POST https://<freesdn-host>/api/v1/firewall/gateways/<gateway-id>/sync \ -H "Cookie: freesdn_access=<token>" \ -H "X-CSRF-Token: <csrf>" \ -H "Content-Type: application/json" \ -d '{"full_sync": true}'pfSense
Section titled “pfSense”pfSense uses the same API key/secret credential shape as OPNsense with "vendor": "pfsense".
MikroTik
Section titled “MikroTik”MikroTik uses username/password with "vendor": "mikrotik". The adapter communicates over the MikroTik REST API (RouterOS v7+ required - no SSH, no WinBox API).
curl -s -X POST https://<freesdn-host>/api/v1/firewall/gateways \ -H "Cookie: freesdn_access=<token>" \ -H "X-CSRF-Token: <csrf>" \ -H "Content-Type: application/json" \ -d '{ "name": "mt-core", "vendor": "mikrotik", "host": "mt.lan", "port": 443, "verify_ssl": false, "site_id": "<site-uuid>", "username": "freesdn-api", "password": "<password>" }'Test before saving
Section titled “Test before saving”You can test connectivity without persisting a connection:
# Requires: firewall.manage_rulesPOST /api/v1/firewall/gateways/test{ "vendor": "opnsense", "host": "opn.lan", "api_key": "...", "api_secret": "..." }The saved-connection test endpoint (POST /gateways/{id}/test) also accepts an optional override body so the edit dialog can test edited-but-unsaved host/port/verify_ssl against stored credentials.
Write model and staging
Section titled “Write model and staging”Every adapter-facing write goes through the staged dual-gate:
ADAPTER_READ_ONLYmust befalsein your env file. New installations default totrue.- Each mutation must include
force=true.
Until both gates pass, writes are staged to the local database and the live device is not touched. Review pending changes in the UI before applying.
SSRF protection is enforced on the gateway host field and every diagnostics target: loopback, link-local, cloud-metadata ranges (127.0.0.0/8, 169.254.0.0/16, ::1, fe80::/10, 0.0.0.0/8) and names (localhost, metadata.google.internal) are rejected at schema validation time, before any adapter call.
Firewall rules (FreeSDN tables)
Section titled “Firewall rules (FreeSDN tables)”These endpoints manage FreeSDN’s own normalized rule rows in the firewall.rules table. They do not push to a live device automatically - use the gateway live-write endpoints or the distribution engine for that.
Key endpoints
Section titled “Key endpoints”| Method | Path | Purpose | Permission |
|---|---|---|---|
| GET | /api/v1/firewall/rules | List rules - filter by device_id, action, is_enabled, site_id | firewall.view |
| GET | /api/v1/firewall/rules/{id} | Get one rule | firewall.view |
| POST | /api/v1/firewall/rules | Create rule | firewall.manage_rules |
| PATCH | /api/v1/firewall/rules/{id} | Update rule (partial) | firewall.manage_rules |
| DELETE | /api/v1/firewall/rules/{id} | Soft-delete rule | firewall.manage_rules |
| POST | /api/v1/firewall/rules/reorder | Reorder rules for a device | firewall.manage_rules |
Rule fields
Section titled “Rule fields”A rule record includes: source_address, source_port, source_zone, dest_address, dest_port, dest_zone, protocol (any/tcp/udp/icmp/…), action (allow / deny / reject / log), log_enabled, is_enabled, and rule_order. Note: schedule_id is not currently settable or returned via the REST API.
Reordering rules
Section titled “Reordering rules”Supply the target device’s UUID and a complete ordered list of rule UUIDs:
POST /api/v1/firewall/rules/reorder?device_id=<device-uuid>["<rule-uuid-1>", "<rule-uuid-2>", "<rule-uuid-3>"]The service sets rule_order = 1, 2, 3, … across only the matching non-deleted rules for that device. Rules not in the list are not touched.
PATCH field allowlist
Section titled “PATCH field allowlist”PATCH only applies whitelisted mutable fields. Non-whitelisted keys (such as device_id or hit_count) are silently ignored, not rejected.
NAT rules (FreeSDN tables)
Section titled “NAT rules (FreeSDN tables)”NAT rules live in firewall.nat_rules. Types: snat, dnat, masquerade, redirect.
| Method | Path | Purpose | Permission |
|---|---|---|---|
| GET | /api/v1/firewall/nat | List NAT rules | firewall.view |
| GET | /api/v1/firewall/nat/{id} | Get one NAT rule | firewall.view |
| POST | /api/v1/firewall/nat | Create NAT rule | firewall.manage_nat |
| PATCH | /api/v1/firewall/nat/{id} | Update NAT rule | firewall.manage_nat |
| DELETE | /api/v1/firewall/nat/{id} | Soft-delete NAT rule | firewall.manage_nat |
For live port-forward and source-NAT operations directly on a connected device, use the gateway live-write endpoints (/gateways/{id}/port-forwards, /gateways/{id}/source-nat).
VPN tunnels (FreeSDN tables)
Section titled “VPN tunnels (FreeSDN tables)”VPN tunnels live in firewall.vpn_tunnels. Types: ipsec, openvpn, wireguard, l2tp.
| Method | Path | Purpose | Permission |
|---|---|---|---|
| GET | /api/v1/firewall/vpn | List tunnels - filter by vpn_type, vpn_status | firewall.view |
| GET | /api/v1/firewall/vpn/stats | Aggregate stats: total / up / down / error | firewall.view |
| GET | /api/v1/firewall/vpn/{id} | Get one tunnel | firewall.view |
| POST | /api/v1/firewall/vpn | Create tunnel | firewall.manage_vpn |
| PATCH | /api/v1/firewall/vpn/{id} | Update tunnel | firewall.manage_vpn |
| DELETE | /api/v1/firewall/vpn/{id} | Soft-delete tunnel | firewall.manage_vpn |
VPN support by vendor
Section titled “VPN support by vendor”| Type | OPNsense | pfSense | MikroTik |
|---|---|---|---|
| IPsec (IKEv1/v2) | Yes | Yes | Yes |
| OpenVPN | Yes | Yes | - |
| WireGuard | Yes | Yes | Staged (no UI tab yet) |
| L2TP / PPTP / SSTP | - | - | Yes |
For live IPsec connect/disconnect, WireGuard server/peer management, and OpenVPN session management, use the gateway live-write endpoints under /api/v1/firewall/gateways/{id}/ipsec/, /wireguard/, and /openvpn/.
IDS/IPS
Section titled “IDS/IPS”FreeSDN tracks IDS alerts in firewall.ids_alerts (FreeSDN’s own table, populated by sync). Live IDS management goes through the gateway adapter.
FreeSDN IDS alert endpoints
Section titled “FreeSDN IDS alert endpoints”| Method | Path | Purpose | Permission |
|---|---|---|---|
| GET | /api/v1/firewall/ids/alerts | Search alerts - filter by severity, time range, acknowledged, site_id | firewall.view |
| GET | /api/v1/firewall/ids/alerts/stats | Alert counts by severity + unacknowledged count | firewall.view |
| POST | /api/v1/firewall/ids/alerts/{id}/acknowledge | Acknowledge an alert | firewall.manage_ids |
The ids tab in the UI shows a badge with the unacknowledged count.
Live IDS management (OPNsense)
Section titled “Live IDS management (OPNsense)”| Method | Path | Purpose | Permission |
|---|---|---|---|
| GET | /api/v1/firewall/gateways/{id}/ids/settings | Current IDS settings | firewall.view |
| PUT | /api/v1/firewall/gateways/{id}/ids/settings | Update settings | firewall.manage_rules |
| GET | /api/v1/firewall/gateways/{id}/ids/alerts | Live alerts (up to 5,000) | firewall.view |
| DELETE | /api/v1/firewall/gateways/{id}/ids/alerts | Clear alert log | firewall.manage_rules |
| GET | /api/v1/firewall/gateways/{id}/ids/rulesets | Rulesets (ET/Open, etc.) | firewall.view |
| POST | /api/v1/firewall/gateways/{id}/ids/rules/{sid}/toggle | Toggle a rule by SID | firewall.manage_rules |
| POST | /api/v1/firewall/gateways/{id}/ids/control | Start / stop / restart / update-rules | firewall.manage_rules |
OPNsense supports Suricata. When a critical signature fires, the health monitor emits a firewall.event.ids_critical Fabric event.
IDS mode is configurable per org in module settings: ids_mode = detect (log only) or prevent (block). Default is detect.
Firewall logs
Section titled “Firewall logs”Traffic logs are stored in firewall.logs and scoped per device.
| Method | Path | Purpose | Permission |
|---|---|---|---|
| GET | /api/v1/firewall/logs | Search logs - filter by action, source_ip, dest_ip, time range | firewall.view_logs |
For a live streaming log from a connected device, use GET /api/v1/firewall/gateways/{id}/logs/stream (SSE StreamingResponse - long-lived connection).
Gateway connections - live device management
Section titled “Gateway connections - live device management”The /api/v1/firewall/gateways/ family proxies directly to vendor adapters. These are the endpoints behind the GatewayDetailPage tabs in the UI (18 tabs for OPNsense/pfSense, 13 for MikroTik).
Connection lifecycle
Section titled “Connection lifecycle”| Method | Path | Purpose | Permission |
|---|---|---|---|
| GET | /api/v1/firewall/gateways | List connections | firewall.view |
| GET | /api/v1/firewall/gateways/summary | Aggregate online/offline/sync state counts | firewall.view |
| GET | /api/v1/firewall/gateways/{id} | Get one connection (has_credentials bool, no raw cred) | firewall.view |
| POST | /api/v1/firewall/gateways | Create connection | firewall.manage_rules |
| PATCH | /api/v1/firewall/gateways/{id} | Update connection (evicts cached adapter) | firewall.manage_rules |
| DELETE | /api/v1/firewall/gateways/{id} | Soft-delete (removes device-registry row) | firewall.manage_rules |
Live reads - selected endpoints
Section titled “Live reads - selected endpoints”| Method | Path | Purpose |
|---|---|---|
| GET | /gateways/{id}/status | Live system status; updates is_online / last_seen |
| GET | /gateways/{id}/firewall-rules | Live firewall rule list from device |
| GET | /gateways/{id}/nat-rules | Live NAT rules |
| GET | /gateways/{id}/vpn | Live VPN status (secrets redacted) |
| GET | /gateways/{id}/interfaces | Live interface list + statistics |
| GET | /gateways/{id}/dhcp | Live DHCP leases |
| GET | /gateways/{id}/arp | ARP table |
| GET | /gateways/{id}/routes/table | Kernel routing table |
| GET | /gateways/{id}/health-check | Multi-subsystem deep health (healthy bool) |
| GET | /gateways/{id}/ha-status | CARP / HA sync status |
All paths above are relative to /api/v1/firewall. All require firewall.view.
Live writes - selected endpoints
Section titled “Live writes - selected endpoints”| Method | Path | Purpose | Permission |
|---|---|---|---|
| POST | /gateways/{id}/firewall-rules | Push rule to device | firewall.manage_rules |
| PUT | /gateways/{id}/firewall-rules/{vendor_rule_id} | Update vendor rule | firewall.manage_rules |
| POST | /gateways/{id}/firewall-rules/{vendor_rule_id}/toggle?enabled=<bool> | Enable (?enabled=true, default) or disable (?enabled=false) a rule | firewall.manage_rules |
| POST | /gateways/{id}/port-forwards | Create port-forward (DNAT) | firewall.manage_rules |
| POST | /gateways/{id}/source-nat | Create SNAT rule | firewall.manage_rules |
| POST | /gateways/{id}/wireguard/servers | Create WireGuard server | firewall.manage_rules |
| POST | /gateways/{id}/wireguard/peers | Add WireGuard peer | firewall.manage_rules |
| POST | /gateways/{id}/openvpn/instances | Create OpenVPN instance | firewall.manage_rules |
| POST | /gateways/{id}/ipsec/{vendor_id}/connect | Bring IPsec tunnel up | firewall.manage_rules |
| POST | /gateways/{id}/ipsec/{vendor_id}/disconnect | Tear IPsec tunnel down | firewall.manage_rules |
| POST | /gateways/{id}/routes/static | Create static route | firewall.manage_rules |
| POST | /gateways/{id}/aliases | Create alias (firewall object) | firewall.manage_rules |
| POST | /gateways/{id}/dns/overrides | Create DNS host override | firewall.manage_rules |
| POST | /gateways/{id}/dhcp/static-mappings | Create DHCP static mapping | firewall.manage_rules |
| POST | /gateways/{id}/services/{name}/control | Start / stop / restart service | firewall.manage_rules |
| POST | /gateways/{id}/reboot | Reboot device | firewall.admin |
| POST | /gateways/{id}/halt | Halt device | firewall.admin |
| POST | /gateways/{id}/firmware/update | Apply firmware update | firewall.admin |
| GET | /gateways/{id}/config/download | Download running config | controller:write |
Diagnostics
Section titled “Diagnostics”All diagnostics run on the remote device and require firewall.view:
| Method | Path | Purpose |
|---|---|---|
| POST | /gateways/{id}/diagnostics/ping | Ping (1-20 packets) |
| POST | /gateways/{id}/diagnostics/traceroute | Traceroute |
| POST | /gateways/{id}/diagnostics/dns-lookup | DNS lookup |
| GET | /gateways/{id}/diagnostics/connections | Active PF state connections |
| GET | /gateways/{id}/diagnostics/pf-info | PF filter info |
Diagnostics host fields are validated against the same SSRF allowlist as the gateway host field.
OPNsense - feature domains
Section titled “OPNsense - feature domains”OPNsense is the most complete adapter with 13 independent domains, each accessible from the GatewayDetailPage under its own tab:
| Domain | UI tab | Example endpoint prefix |
|---|---|---|
| Firewall rules | rules | /gateways/{id}/firewall-rules |
| NAT + port forwards | nat | /gateways/{id}/nat-rules, /port-forwards, /source-nat, /nat/onetoone |
| VPN (OpenVPN + IPsec + WireGuard) | vpn | /gateways/{id}/openvpn, /ipsec, /wireguard |
| Interfaces + VIPs + LAGGs | interfaces | /gateways/{id}/interfaces, /vlans, /virtual-ips, /laggs |
| DHCP (Kea DHCPv4/v6 + relay + static maps) | dhcp | /gateways/{id}/dhcp, /kea/dhcpv4, /kea/dhcpv6, /dhcp-relay |
| DNS (Unbound host/domain overrides) | dns | /gateways/{id}/dns/overrides, /dns/domain-overrides |
| Aliases (address + port groups) | aliases | /gateways/{id}/aliases |
| Routing (static + OSPF + BGP + ARP + NDP) | routing | /gateways/{id}/routes/static, /routes/table, /arp, /ndp |
| IDS/IPS (Suricata) | ids | /gateways/{id}/ids/... |
| Traffic shaper (pipes + queues + rules) | shaper | /gateways/{id}/shaper/pipes, /shaper/queues, /shaper/rules |
| Services (start/stop/restart) | services | /gateways/{id}/services |
| Backups + config diff | backups | /gateways/{id}/backups, /config/diff |
| System + firmware + monitoring | system / monitoring | /gateways/{id}/firmware, /monitoring/temperature, /monitoring/traffic |
Additional read surfaces: HAProxy (/haproxy, /haproxy/servers, /haproxy/backends, /haproxy/frontends), ACME certificates (/acme, /acme/certificates), Tailscale (/tailscale), CrowdSec (/crowdsec), captive portal (/captive-portal), Telegraf, Monit, NetFlow, DynDNS, Syslog.
MikroTik - feature surface
Section titled “MikroTik - feature surface”MikroTik requires RouterOS v7 and REST API access. The adapter communicates only over the RouterOS REST API - no SSH, no WinBox API.
| Status | Domains |
|---|---|
| UI tab available | System, Interfaces, IP, DHCP, Firewall, DNS, VPN (L2TP/PPTP/SSTP), Hotspot, Queues, Firmware, Backup, Topology/Neighbors, SNMP |
| API-only (no UI tab yet) | CAPsMAN, PPP/PPPoE, BGP/OSPF, IPsec/Security |
Firmware lifecycle (channel check, download, install), config backup/restore, topology/neighbor discovery (/ip/neighbor with LLDP/CDP/MNDP dedup), and SNMPv3 user management each have a dedicated UI tab.
Gateway orchestration
Section titled “Gateway orchestration”Orchestration is the multi-controller capability. You designate one gateway per site as the brain (the authoritative routing and VLAN authority) and one or more switch/AP controllers as limbs (Layer 2 devices that receive config from the brain). FreeSDN holds a canonical desired-state model and distributes it.
Architecture layers
Section titled “Architecture layers”| Layer | Description |
|---|---|
| Layer 0 | Controller-direct; always works; single-controller config push |
| Layer 1 | Multi-controller VLAN visibility; alignment score; per-cell copy |
| Layer 2 | Gateway orchestration; brain/limb roles; read-only brain; canonical VLAN distribution |
| Layer 3 | Full write-to-brain orchestration is not available |
The brain adapter is read-only by default in the orchestration context: FreeSDN reads config from it to populate canonical state but does not push arbitrary mutations back. Changes to the brain’s running config go through the gateway live-write endpoints, not orchestration.
Site role map
Section titled “Site role map”The role map records which device is the brain and which are limbs for a site. Exactly one brain is required for orchestration to function; the validator enforces this.
| Method | Path | Purpose | Permission |
|---|---|---|---|
| GET | /api/v1/firewall/topology/{site_id} | Get role map (brain/limb assignments + authority_map) | gateway.view |
| PUT | /api/v1/firewall/topology/{site_id} | Upsert role map | gateway.manage_topology |
| DELETE | /api/v1/firewall/topology/{site_id} | Remove role map | gateway.manage_topology |
| POST | /api/v1/firewall/topology/{site_id}/validate | Validate (exactly one brain, capability checks) | gateway.manage_topology |
The authority_map JSONB in the role map records per-resource authority defaults: vlan_interface, dhcp, dns, firewall_rule, nat, vpn default to brain; vlan_l2 defaults to FreeSDN; port_profile, ssid, poe default to limb.
Canonical VLANs
Section titled “Canonical VLANs”A canonical VLAN is the site-wide desired state: one definition, independent of any specific controller. It lives in gateway.gw_canonical_vlans - separate from the per-controller VLAN records in network.vlans. You define it once, then distribute to any device that needs it.
| Method | Path | Purpose | Permission |
|---|---|---|---|
| GET | /api/v1/firewall/vlans | List canonical VLANs | gateway.view |
| GET | /api/v1/firewall/vlans/{id} | VLAN detail (+ DHCP scope + reservations) | gateway.view |
| POST | /api/v1/firewall/vlans | Create canonical VLAN | gateway.manage_vlans |
| PATCH | /api/v1/firewall/vlans/{id} | Update VLAN | gateway.manage_vlans |
| DELETE | /api/v1/firewall/vlans/{id} | Delete VLAN | gateway.manage_vlans |
Canonical DHCP scopes and reservations for a VLAN:
| Method | Path | Permission |
|---|---|---|
| GET | /api/v1/firewall/dhcp/scopes | gateway.view |
| POST | /api/v1/firewall/dhcp/scopes | gateway.manage_dhcp |
| POST | /api/v1/firewall/dhcp/reservations | gateway.manage_dhcp |
| DELETE | /api/v1/firewall/dhcp/reservations/{id} | gateway.manage_dhcp |
Canonical DNS overrides:
| Method | Path | Permission |
|---|---|---|
| GET | /api/v1/firewall/dns/records | gateway.view |
| POST | /api/v1/firewall/dns/records | gateway.manage_dns |
| PATCH | /api/v1/firewall/dns/records/{id} | gateway.manage_dns |
| DELETE | /api/v1/firewall/dns/records/{id} | gateway.manage_dns |
VLAN templates
Section titled “VLAN templates”Templates are org-level blueprints for standard VLAN configurations (IoT network, guest VLAN, management VLAN, etc.). Apply a template to a site to instantiate a canonical VLAN from it.
| Method | Path | Purpose | Permission |
|---|---|---|---|
| GET | /api/v1/firewall/templates | List templates | gateway.view |
| POST | /api/v1/firewall/templates | Create template | gateway.manage_vlans |
| PATCH | /api/v1/firewall/templates/{id} | Update template | gateway.manage_vlans |
| DELETE | /api/v1/firewall/templates/{id} | Delete template | gateway.manage_vlans |
| POST | /api/v1/firewall/templates/{id}/apply/{site_id} | Instantiate template at site | gateway.manage_vlans |
Distribution engine
Section titled “Distribution engine”The distribution engine pushes canonical VLANs to one or more devices at a site. It follows a Saga pattern: per-tier compensating rollback is available if a distribution fails partway through.
One distribution runs per site at a time - a DistributionLock row in the database prevents concurrent distributions. The lock auto-expires after ~5 minutes to recover from worker crashes.
Plan structure:
- Tier 0 prerequisites - brain + each limb verified reachable
- Tier 1 - L3 VLAN config pushed to brain
- Limb tiers - L2 VLAN config pushed to each limb in turn
| Method | Path | Purpose | Permission |
|---|---|---|---|
| GET | /api/v1/firewall/distribution | List distribution records | gateway.view |
| GET | /api/v1/firewall/distribution/{id} | Detail (plan + per-step results) | gateway.view |
| POST | /api/v1/firewall/distribution/trigger | Trigger distribution (vlan_id + site_id) | gateway.distribute |
| POST | /api/v1/firewall/distribution/{id}/retry | Retry a failed distribution | gateway.distribute |
| POST | /api/v1/firewall/distribution/{id}/rollback | Saga rollback | gateway.distribute |
Drift detection
Section titled “Drift detection”FreeSDN polls connected brain devices every 15 minutes (configurable via drift_check_interval_minutes) and compares running config against canonical desired state. When a divergence is found it creates a DriftEvent.
Drift types: resource_missing, resource_modified, resource_added, suppression_violated, tag_removed. Severities: critical, warning, info.
| Method | Path | Purpose | Permission |
|---|---|---|---|
| GET | /api/v1/firewall/drift/events | List drift events (paginated) | gateway.drift |
| GET | /api/v1/firewall/drift/summary | Counts by severity + pending/resolved | gateway.drift |
| POST | /api/v1/firewall/drift/check/{site_id} | Run a drift check now (enqueues task) | gateway.drift |
| POST | /api/v1/firewall/drift/events/{id}/resolve | Resolve: reapply, accept, or ignore | gateway.drift |
| GET | /api/v1/firewall/drift/suppressions | List suppression rules | gateway.drift |
| POST | /api/v1/firewall/drift/suppressions | Create suppression rule | gateway.drift |
| DELETE | /api/v1/firewall/drift/suppressions/{id} | Deactivate suppression rule | gateway.drift |
Celery beat runs gateway.check_all_sites_drift every 15 minutes and gateway.sync_all_gateways every 5 minutes (both on the sync queue).
Brownfield import
Section titled “Brownfield import”If you are adding an existing site to FreeSDN that already has config running on devices, use the import wizard. It reads the current running state from the brain and limbs and populates canonical objects from it.
The wizard runs as a 6-step session:
| Step | Name | What happens |
|---|---|---|
| 1 | Discover | Auto-discover devices at the site |
| 2 | Assign roles | Submit { gateway_id: "brain" | "limb" } for each discovered device |
| 3 | Scan | Full config pull from brain and all limbs |
| 4 | Reconcile | Submit decisions per resource: adopt, skip, merge, or ignore |
| 5 | Distribute | Push adopted resources to canonical state + devices |
| 6 | Verify | Confirm actual device state matches canonical |
Steps 2 and 4 require a POST to /import/{session_id}/step with a payload. Steps 1, 3, 5, and 6 are triggered automatically when the session advances.
| Method | Path | Purpose | Permission |
|---|---|---|---|
| POST | /api/v1/firewall/import/start | Start import session | gateway.import |
| GET | /api/v1/firewall/import/{id} | Session state (current step + per-step payloads) | gateway.import |
| POST | /api/v1/firewall/import/{id}/step | Advance wizard step | gateway.import |
| POST | /api/v1/firewall/import/{id}/cancel | Cancel session | gateway.import |
Reconciliation (direct, no wizard)
Section titled “Reconciliation (direct, no wizard)”For ongoing operations - after the initial import - you can re-import from the brain, check alignment, and distribute to limbs without using the wizard:
| Method | Path | Purpose | Permission |
|---|---|---|---|
| POST | /api/v1/firewall/reconciliation/{site_id}/import | Import VLAN interfaces from brain → create/update canonical (flags orphans) | gateway.import |
| GET | /api/v1/firewall/reconciliation/{site_id}/alignment | Compare canonical vs actual device state | gateway.view |
| POST | /api/v1/firewall/reconciliation/{site_id}/distribute | Push L2 VLAN config to limb devices | gateway.distribute |
The alignment endpoint returns per-resource status: aligned, missing, modified, extra, or error.
Orchestration dashboard
Section titled “Orchestration dashboard”GET /api/v1/firewall/dashboard/overview returns a rollup: gateway counts, sites with orchestration, canonical VLAN count, and drift summary. The dashboard also exposes read-only imported snapshots of firewall rules, NAT rules, VPN tunnels, IDS events, interfaces, and DHCP leases from brain devices - these are cached snapshots for visibility, not live reads.
Module settings
Section titled “Module settings”Settings are per-org, stored in JSONB, and configurable through the UI at Firewall > Settings.
| Setting | Type | Default | Description |
|---|---|---|---|
default_policy | allow | deny | deny | Default packet policy for new rules |
log_blocked | bool | true | Log blocked connections to firewall.logs (primary PostgreSQL) |
ids_enabled | bool | true | Enable IDS globally |
ids_mode | detect | prevent | detect | Detect (log) or prevent (block) |
log_retention_days | int 7-365 | 30 | Firewall log retention |
sync_interval_minutes | int 1-1440 | 5 | Gateway sync frequency |
drift_check_interval_minutes | int 5-1440 | 15 | Drift check frequency |
distribution_lock_ttl_seconds | int 60-3600 | 300 | Max time a distribution lock is held |
auto_remediate_drift | bool | false | Automatically push fixes for detected drift |
import_retention_days | int 1-365 | 90 | Import session retention |
Permissions
Section titled “Permissions”| Permission code | Purpose | Notes |
|---|---|---|
firewall.view | View rules, NAT, VPN, IDS alerts, logs, gateway status | Gate for almost all GET routes |
firewall.manage_rules | Create/edit/delete rules; also gates gateway CRUD and most live writes | Required for all write operations on gateway connections |
firewall.manage_nat | NAT rule CRUD (FreeSDN tables) | |
firewall.manage_vpn | VPN tunnel CRUD (FreeSDN tables) | |
firewall.manage_ids | Acknowledge IDS alerts (FreeSDN tables) | |
firewall.view_logs | View traffic logs | |
firewall.manage_gateways | Declared in manifest; not used by any route - grant firewall.manage_rules instead | |
firewall.admin | Reboot / halt device; apply firmware update | Not declared in the module manifest; must be in the global role catalog |
controller:write | Download running config | Core-level permission; not in the module manifest |
gateway.view | View orchestration topology, canonical resources, drift | |
gateway.manage_topology | Edit brain/limb role maps | |
gateway.manage_vlans | Canonical VLAN + template CRUD | |
gateway.manage_dhcp | DHCP scope and reservation CRUD | |
gateway.manage_dns | DNS override CRUD | |
gateway.distribute | Trigger distribution, retry, rollback | |
gateway.import | Run brownfield import wizard and reconciliation import | |
gateway.drift | View, resolve, and suppress drift events | |
gateway.diagnostics | Run orchestration-tier diagnostics (ping, traceroute, backup, restart-service) |
Site scoping
Section titled “Site scoping”Both FirewallService and GatewayService enforce per-user site grants. When a user has site-limited access, the service filters by accessible_site_ids so a site-limited operator cannot see or modify firewall devices or gateway connections outside their assigned sites.
Fabric integration
Section titled “Fabric integration”The Firewall module participates in the Fabric (universal app-interconnect) as both a source of operations and an emitter of events.
Operation
Section titled “Operation”| ID | Tier | Description | Permission |
|---|---|---|---|
firewall.search_alerts | Native | Recent IDS/IPS + security alerts (1-200 results, org-scoped) | firewall.view_logs |
Emitted events
Section titled “Emitted events”| Event | Trigger |
|---|---|
gateway.sync.completed | Gateway sync finishes successfully |
gateway.brain.offline | Brain device stops responding |
firewall.event.ids_critical | Critical IDS signature fires |
firewall.event.wan_down | WAN gateway goes down |
firewall.event.wan_up | WAN gateway comes back up |
firewall.event.gateway_unreachable | Gateway stops responding |
firewall.event.gateway_online | Gateway becomes reachable again |
Events are emitted on state transitions only - the health monitor (firewall.poll_health, every 2 minutes) diffs each gateway’s current state against the stored fabric_health snapshot and fires an event only when the state changes.
Background tasks
Section titled “Background tasks”| Task | Schedule | Queue | Purpose |
|---|---|---|---|
gateway.sync_all_gateways | Every 5 min | sync | Sync all enabled gateway connections (rate-limited 2/min) |
gateway.check_all_sites_drift | Every 15 min | sync | Drift check all sites with orchestration |
gateway.cleanup_distribution_locks | Every 10 min | default | Clean up expired distribution locks |
firewall.poll_health | Every 2 min | metrics | Health poll; emit Fabric events on transitions |
Configuration backup scope
Section titled “Configuration backup scope”The Firewall module contributes to the Configuration Backup portable snapshot (.fsdn archive):
Included: FirewallDevice, FirewallRule, NATRule, VPNTunnel, GatewayConnection (credentials stripped).
Excluded: firewall.ids_alerts, firewall.logs (traffic logs), firewall.gateway_sync_logs, and GatewayConnection credentials.
Restore order: FirewallDevice → rules / NAT / VPN → GatewayConnection.
Key honesty caveats
Section titled “Key honesty caveats”Next steps
Section titled “Next steps”- OPNsense adapter reference - detailed coverage of all 13 OPNsense domains, API key setup, and known limitations
- pfSense adapter reference - pfSense-specific credential model and supported operations
- MikroTik adapter reference - RouterOS v7 REST API requirements, API user setup, and tab coverage
- OpenWRT adapter reference - preview-tier notes and current limitations
- Network Management - switch ports, VLANs, APs, topology (Omada/UniFi)
- Roles and permissions - full 7-tier role hierarchy and permission grant model
- Secrets and credentials - how credentials are stored, encrypted, and redacted
- Fabric connections - build automations that trigger on Firewall events
- Configuration Backup - portable snapshot scope and restore order