Skip to content

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 firewall schema. 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.

Adapter Available integration Release scope
OPNsense Broad firewall, NAT, VPN and routing integration Coverage varies by endpoint and device/API version; the prepared certificate inventory checks are described below
pfSense Firewall, NAT and VPN integration Verify the installed API package and operation compatibility; shared client code does not prove OPNsense endpoint compatibility
MikroTik (RouterOS v7) REST API integration No SSH or WinBox transport; qualify the required operations against the deployed RouterOS version
OpenWRT Preview ubus/UCI integration and configuration export Package availability and device acceptance remain operation-specific; do not treat the presence of a UI tab as a production-fleet qualification

Adapter-facing operations use the applicable tenant/site permission checks, destination restrictions and write controls. Their exact behavior is operation-specific: inspect the result and Pending Changes state, and do not treat a generic HTTP success or an empty list as proof that a device operation completed.


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 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.

Terminal window
# 1. Create the gateway connection
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": "opn-prod",
"vendor": "opnsense",
"host": "opn.lan",
"port": 443,
"verify_ssl": true,
"site_id": "<site-uuid>",
"api_key": "<opnsense-key>",
"api_secret": "<opnsense-secret>"
}'
# 2. Test the saved connection
curl -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 sync
curl -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 uses the same API key/secret credential shape as OPNsense with "vendor": "pfsense".

MikroTik uses username/password with "vendor": "mikrotik". The adapter communicates over the MikroTik REST API (RouterOS v7+ required - no SSH, no WinBox API).

Terminal window
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": true,
"site_id": "<site-uuid>",
"username": "freesdn-api",
"password": "<password>"
}'

You can test connectivity without persisting a connection:

Terminal window
# Requires: firewall.manage_rules
POST /api/v1/firewall/gateways/test
{ "vendor": "opnsense", "host": "opn.lan", "api_key": "...", "api_secret": "..." }

The saved-connection test endpoint (POST /gateways/{id}/test) accepts an optional verify_ssl override only. It uses the saved host and port so stored credentials cannot be redirected to a different destination. Test a new host with explicitly supplied credentials through the unsaved-connection endpoint; do not assume an edited host will be used by the saved test.


Certificate inventory and expiry monitoring

Section titled “Certificate inventory and expiry monitoring”

The gateway System tab reports certificate expiry from the selected gateway’s live inventory. The GET /api/v1/firewall/gateways/{id}/certificates/certs and /certificates/expiry routes require firewall.view and the applicable organization/site scope. The expiry route accepts days_threshold from 1 through 365; the UI requests 30 days.

State Meaning Operator action
Loading or refresh in progress No new inventory has been confirmed Wait for completion; an earlier healthy result is hidden during refresh
Confirmed empty inventory A supported read succeeded with zero certificates Confirm that this is expected for the device; it is not an inventory failure
Unsupported (501) The adapter does not provide this read Use the device’s own certificate inventory and record the coverage gap
Unavailable (502) The read failed, was malformed, or did not return a complete consistent inventory Check connectivity, API permissions and device/API compatibility, then use Retry
Unknown expiry At least one returned certificate has a missing or invalid expiry date Inspect it on the device; unknown dates require attention and are not counted as valid
Expired or expiring soon A parsed expiry is past or within the configured threshold Follow the device’s approved renewal procedure
Valid inventory A nonempty successful inventory contains no expired, expiring or unknown entries Continue monitoring; this is expiry status, not trust-chain or service-usage validation

A refresh failure replaces the previous healthy display with an unavailable state. Retry performs another read; it does not issue, renew, delete or install certificates. Missing dates display as unknown rather than an epoch date. OPNsense inventory checks pagination totals and certificate identities and includes only public inventory fields. Private keys and raw provider error bodies are excluded from this path.

Trust-store, CA/CRL and ACME inventory reads have a separate prepared correction described below. Expiry monitoring does not establish certificate renewal, trust-chain validation or coverage of every vendor’s API.

This monitoring feature is separate from TLS verification of the connection to the gateway. Do not disable connection verification to make an expiry indicator green. It also does not provide managed configuration-backup retention or device restore; see backups and recovery.

The trust-store overview (GET /api/v1/firewall/gateways/{id}/certificates), CA inventory (/certificates/cas), ACME overview (/acme) and ACME certificate inventory (/acme/certificates) require firewall.view and the applicable organization/site scope. A confirmed empty result means the supported read completed and returned no entries. It must not be used as a substitute for a failed or unsupported read.

Result Operator meaning
Loading or refreshing The current read has not completed; cached counts are hidden
Confirmed empty All required component reads succeeded and their validated inventories are empty
Unsupported (501) This adapter does not provide the requested operation
Unavailable (502) A component failed, returned malformed data, or returned incomplete or inconsistent inventory
Populated inventory The validated read returned entries; counts alone do not prove active services or healthy certificates

If any required trust or ACME component fails, the overview is unavailable. It does not combine failed components into a successful empty summary. A failed refresh hides previous counts and offers explicit retry. Retry performs reads only. Raw provider errors and private-key material are not displayed.

For OPNsense, the prepared adapter uses the documented ACME search endpoints and nested acmeclient.settings response. Both environment and log-level selections must be unambiguous. CRL search identifies CA rows separately from configured revocation lists: a CA without a CRL does not increase the CRL count. Certificate alternate names and last-update values come from the normalized inventory. Search output does not establish private-key presence; missing evidence remains unknown.

The upstream GET search recordset has a default limit of 9,999 rows. Returned totals and page metadata must match the retrieved records; a truncated response is unavailable, not a complete inventory. This implementation does not promise unbounded pagination. For a larger inventory, use the device’s supported management interface until full pagination is implemented and qualified.

Enabled in configuration is not issuance or renewal success. An ACME entry can be configured as enabled while validation, issuance, deployment or renewal has failed. Check the device’s own ACME job history and certificate state. The view does not perform public ACME transactions, validate certificate chains or revocation, or authorize device writes.

These corrections do not qualify every gateway aggregate, managed backup retention/restore, or hardware firmware/API version. Use controlled device-specific acceptance records before relying on them for production operations. Keep gateway connection TLS verification enabled independently of these inventory states.


The selected VPN, Tailscale, HAProxy, proxy, CrowdSec, Monit, Telegraf and NetFlow panels now distinguish loading, confirmed results and unavailable reads. The overview also reports device-summary failures explicitly. A pending refresh hides prior values; a failed refresh keeps them hidden and provides a read-only retry. An unavailable monitoring read must not be interpreted as a stopped service or zero incidents.

The twelve selected live-read routes return 501 for an unsupported adapter read and 502 for failed required reads or malformed normalized results. Existing organization, site and permission checks still apply. Responses remain bound to the admitted gateway identity. OpenWrt retains its separate device-summary API contract rather than being forced into OPNsense’s interface fields.

For OPNsense aggregates, every required component must complete successfully. Cancellation is preserved, and provider error bodies are not shown. This does not establish complete validation of successful upstream responses: some underlying parsers still interpret malformed HTTP 200 bodies as defaults. Confirm the actual firmware and installed plugin responses before relying on these statuses operationally.

Monit, Telegraf, NetFlow and CrowdSec display the normalized API fields. Configuration enablement and a reported service state do not prove working traffic forwarding, VPN connectivity, incident collection or enforcement. CrowdSec’s bounded result window is not a complete incident history. Other independent overview, firmware and monitoring queries have separate qualification limits.

VPN raw-status display additionally masks known secret fields when their values are strings, arrays or objects, and masks uninspected containers beyond its nesting limit. The API also applies its shared redactor before delivery. This key-based protection does not promise to recognize arbitrary secrets under unknown field names.

These checks use synthetic device I/O, isolated PostgreSQL/API requests and the actual dashboard. They do not restart services, write gateway configuration, install plugins or perform backup/restore. Use an isolated target to rehearse failure and recovery on the intended image and firmware. Review the candidate’s docker/GATEWAY-AGGREGATE-READS and release support boundary for scope.

Every adapter-facing write goes through the staged dual-gate:

  1. ADAPTER_READ_ONLY must be false. The shipped Docker Compose stack runs read-WRITE (ADAPTER_READ_ONLY=false) so FreeSDN manages your gear out of the box. Set ADAPTER_READ_ONLY=true in .env for a monitor-only deployment.
  2. Each mutation must include force=true. This gate is unchanged: a staged change reaches a device only when an operator explicitly applies it.

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.


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.

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

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.

Supply the target device’s UUID and a complete ordered list of rule UUIDs:

Terminal window
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 only applies whitelisted mutable fields. Non-whitelisted keys (such as device_id or hit_count) are silently ignored, not rejected.


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 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
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/.


FreeSDN tracks IDS alerts in firewall.ids_alerts (FreeSDN’s own table, populated by sync). Live IDS management goes through the gateway adapter.

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.

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.


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).

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
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.

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

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 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 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.


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.

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.

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.

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

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

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:

  1. Tier 0 prerequisites - brain + each limb verified reachable
  2. Tier 1 - L3 VLAN config pushed to brain
  3. 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

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).

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

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.

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.


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

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)

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.


The Firewall module participates in the Fabric (universal app-interconnect) as both a source of operations and an emitter of events.

ID Tier Description Permission
firewall.search_alerts Native Recent IDS/IPS + security alerts (1-200 results, org-scoped) firewall.view_logs
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.


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

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.



All product names, logos, and brands are property of their respective owners. FreeSDN is an independent project and is not affiliated with or endorsed by the vendors it integrates with. See Trademarks.