Without an explicit listen address, os-net-snmp on OPNsense may not
respond on non-loopback interfaces. The fix sets
listen = {self.hostname: {"selected": 1}} which is always the
management IP used to reach this device in netOrk.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
napalm-opnsense
NAPALM community driver for OPNsense firewalls (read-only, via REST API).
Tested devices
| Model | OPNsense Version | Tested |
|---|---|---|
| OPNsense (virtual/bare metal) | 24.7 | ✅ |
Additional OPNsense versions should work — contributions welcome.
Requirements
| Dependency | Minimum version |
|---|---|
| Python | 3.9 |
| NAPALM | 4.0 |
| requests | 2.28 |
Installation
pip install napalm-opnsense
Or from source:
git clone https://github.com/napalm-automation-community/napalm-opnsense
cd napalm-opnsense
pip install -e .
Quick start
from napalm import get_network_driver
driver = get_network_driver("opnsense")
with driver(
"192.168.1.1",
"my_api_key",
"my_api_secret",
optional_args={"verify": False},
) as device:
facts = device.get_facts()
print(facts)
Authentication
OPNsense uses API key/secret pairs instead of username/password. Generate a key pair in the OPNsense GUI under System → Access → Users → edit user → API keys.
Pass the credentials via optional_args:
optional_args={
"api_key": "your-api-key",
"api_secret": "your-api-secret",
"verify": False, # set to a CA bundle path or True in production
}
Alternatively, pass the key/secret as the positional username/password
arguments.
Implemented getters
| Getter | Status | OPNsense API Endpoint |
|---|---|---|
get_facts |
✅ | GET /api/core/system/status |
get_interfaces |
✅ | GET /api/interfaces/overview/export |
get_interfaces_ip |
✅ | GET /api/interfaces/addresses/export |
get_interfaces_counters |
✅ | GET /api/diagnostics/interface/get_interface_statistics |
get_arp_table |
✅ | GET /api/diagnostics/interface/get_arp |
get_ipv6_neighbors_table |
✅ | GET /api/diagnostics/interface/get_ndp |
get_route_to |
✅ | GET /api/diagnostics/interface/get_routes |
get_environment |
✅ | GET /api/diagnostics/system/system_resources + system_temperature |
get_lldp_neighbors |
✅ ¹ | GET /api/lldpd/service/neighbor |
get_lldp_neighbors_detail |
✅ ¹ | GET /api/lldpd/service/neighbor |
get_ntp_servers |
✅ | GET /api/ntpd/service/status |
get_config |
✅ | GET /api/core/backup/download/this (XML) |
is_alive |
✅ | TCP socket check |
get_bgp_neighbors |
✅ ² | GET /api/quagga/bgp/get + GET /api/quagga/diagnostics/bgpneighbors |
get_vlans |
✅ | GET /api/interfaces/vlan_settings/search_item |
get_mac_address_table |
❌ | Not applicable (firewall, no L2 switching) |
¹ Requires the
os-lldpdplugin. Returns empty dict if the plugin is not installed. ² Requires theos-frr(FRR/Quagga) plugin. Returns empty dict if the plugin is not installed or FRR is not running.
Config management
OPNsense does not expose a single generic "push config" endpoint. Config is
managed per-module via separate API controllers. This driver implements config
management for static routes via /api/routes/routes/.
Why routes? Routes are the most common network-automation target on a firewall, and the OPNsense routes API provides full CRUD operations.
Supported methods
| Method | OPNsense API |
|---|---|
load_merge_candidate(config=...) |
stages routes in memory |
compare_config() |
diffs against GET /api/routes/routes/searchroute |
commit_config() |
snapshots backup → POST /api/routes/routes/addroute × n → reconfigure |
discard_config() |
clears staged candidate |
rollback() |
POST /api/core/backup/revert_backup/{id} — restores the config.xml snapshot taken before the last commit |
Config format
The load_merge_candidate config parameter must be a JSON array of route
objects, each with network and gateway keys. gateway must be the
name of an existing OPNsense gateway (as configured under
System → Gateways → Configuration), not an IP address.
[
{
"network": "10.0.0.0/8",
"gateway": "WAN_GW",
"descr": "Corporate internal",
"disabled": "0"
},
{
"network": "0.0.0.0/0",
"gateway": "WAN_GW",
"descr": "Default route"
}
]
Example
import json
driver = get_network_driver("opnsense")
d = driver("192.168.1.1", "user", "pass", optional_args={"api_key": "k", "api_secret": "s"})
d.open()
routes = json.dumps([{"network": "10.0.0.0/8", "gateway": "WAN_GW"}])
d.load_merge_candidate(config=routes)
print(d.compare_config()) # unified diff
d.commit_config() # applies routes and calls reconfigure
d.rollback() # removes the routes just added
d.close()
Limitations
load_replace_candidateis not supported (no XML upload endpoint in the JSON API).- Non-route config (firewall rules, DHCP, DNS, interfaces, …) must be managed via OPNsense module-specific controllers — outside the scope of this driver.
rollback()reverts the entireconfig.xmlto the pre-commit state (not just the routes). OPNsense's backup API has no partial-restore capability.- Rollback uses the backup snapshot taken at
commit_config()time. If no commit was made in the current session, the most recent available backup is used as a fallback.
Optional arguments
| Argument | Default | Description |
|---|---|---|
api_key |
username |
OPNsense API key |
api_secret |
password |
OPNsense API secret |
base_url |
https://<hostname> |
Override the base URL |
verify |
True |
TLS certificate verification (path or bool) |
Development
pip install -e ".[dev]"
pytest tests/
CI
This project includes a GitHub Actions workflow that:
- Runs unit tests across Python 3.9–3.12
- Builds sdist and wheel
- Uploads build artifacts
License
Apache 2.0 — see LICENSE.