Nautobot SSoT integration for Fortinet (FortiGate firewall + FortiAP wireless, bidirectional sync).
Project description
nautobot-ssot-fortinet
Bidirectional Nautobot ↔ FortiGate sync for firewall, wireless, and live runtime state — built on the Nautobot SSoT framework.
What this does
Four Nautobot Jobs, each handling one direction of the FortiGate↔Nautobot relationship:
| # | Job | Direction | Object kinds |
|---|---|---|---|
| 1 | FortiGate → Nautobot (firewall) | Pull | Addresses, address groups, services, service groups, policies + rules, NAT (VIPs) |
| 2 | FortiGate → Nautobot (wireless) | Pull | WirelessNetworks (SSIDs), RadioProfiles (radios), optional AP Devices |
| 3 | FortiGate Live Status | Observability | Real-time wifi clients, DHCP leases, ARP table — joined by MAC |
| 4 | Nautobot → FortiGate (firewall) | Push | Address objects (all 4 types), address groups, services, service groups |
All Jobs are idempotent (re-running produces zero diffs when nothing changed) and additive-only by default (destructive deletes opt-in via a per-Job BooleanVar).
How it works
┌────────────┐
│ Nautobot │
│ │
└─────┬──────┘
↑ │
pull (2) │ ▼ push (1)
│
┌────────────┐ monitor/* (live state)
│ FortiGate │ ────────────────────────→ Job 3
└────────────┘
- Pull Jobs read
cmdb/*endpoints and write to Nautobot ORM (firewall-models + core wireless). State on the FortiGate is the source of truth; Nautobot mirrors it. - Push Job reads from Nautobot ORM and writes to FortiGate
cmdb/*endpoints. Operators edit firewall objects in Nautobot's UI; the push Job propagates them to the device. - Live status Job queries
monitor/*endpoints for real-time observed state (connected clients, leases, ARP) and renders them in the Job result page + downloadable JSON snapshot.
Quick start
# 1. Bring up the dev stack
cp development/.env.example development/.env
# edit development/.env — set NAUTOBOT_SECRET_KEY + FortiGate credentials
make -C development up
# 2. Wait ~60s, then seed the ExternalIntegration + SecretsGroup
make -C development seed
# 3. Open https://ssot-fortinet-dev.local/ , enable the 4 Jobs in
# /extras/jobs/ , then run them from /plugins/ssot/
For full installation steps including production deployment, see
docs/admin/install.md.
Live-validated against real hardware
This integration was validated end-to-end against a FortiWiFi-61E
during development. The live e2e harness (make e2e-live-firewall and
make e2e-live-wireless) connects to a real FortiGate, syncs all object
kinds, and asserts the second run produces zero diffs (full idempotency).
Real-world quirks discovered & handled:
| FortiOS reality | Translation |
|---|---|
interface-subnet address type |
Treated as ipmask (FortiOS already resolves to CIDR) |
Space-separated multi-port ("88 464" Kerberos) |
Normalized to comma ("88,464") for firewall-models' validator |
ICMP6 protocol |
Mapped to IPv6-ICMP (IANA name used by firewall-models) |
'513:512-1023' src-port qualifier (RLOGIN/RSH) |
Source-port stripped, destination kept |
protocol: "ALL" pseudo-protocol (webproxy) |
Skipped — no Nautobot equivalent |
protocol: "IP" + protocol-number: N |
Mapped to named IANA protocol (e.g. 89 → OSPFIGP) |
| FortiOS WTP-profile mode vs Nautobot WirelessNetwork mode | Most-common platform-mode per VAP wins |
FortiOS radio band strings (802.11ax-5G) |
Pattern-matched to 2.4GHz/5GHz/6GHz enum |
FortiOS VAP security: "wep128" |
Lossy mapping → Open with annotation in description |
See docs/user/external_interactions.md for the complete
field-by-field reference.
What's required
- Nautobot 3.1+ (for the wireless models)
- nautobot-ssot 4.2+ (SSoT framework)
- nautobot-firewall-models 3.0+ (firewall object target)
- fortigate-api 2.0+ (REST client; verified against FortiOS 6.4–7.x)
- Python 3.10–3.13
See docs/admin/compatibility_matrix.md for the
detailed compatibility matrix and version pinning rationale.
What's not in scope (yet)
- Policy/NAT push — pull works; push for those is a future iteration (the M2M dependency graph is more complex than addresses/services)
- Source NAT (FortiOS
ippool) — pull only does Destination NAT (VIPs) - IPv6 addresses (
firewall/address6) — IPv4 only for v1 - Multi-VDOM aware UI — Jobs scope by VDOM, but the per-VDOM dropdown in the SSoT dashboard is a Nautobot-side enhancement, not in this app
Project layout
src/nautobot_ssot_fortinet/
├── clients/fortigate.py ExternalIntegration → FortiGateAPI factory
├── diffsync/
│ ├── models/
│ │ ├── firewall.py Vendor-neutral DiffSync models
│ │ ├── wireless.py Vendor-neutral DiffSync models
│ │ ├── nautobot_firewall.py Nautobot-side CRUD (pull target)
│ │ ├── nautobot_wireless.py Nautobot-side CRUD (pull target)
│ │ └── fortigate_target_firewall.py FortiGate-side CRUD (push target)
│ └── adapters/
│ ├── fortigate_firewall.py Pull source
│ ├── fortigate_wireless.py Pull source
│ ├── fortigate_firewall_target.py Push target (read state + write CRUD)
│ ├── nautobot_firewall.py Pull target (= push source)
│ └── nautobot_wireless.py Pull target
├── jobs.py 4 Job classes
└── utils/fortios.py Translation helpers (subnet→CIDR, security→auth, etc.)
Testing
$ pytest -q
174 passed in 0.57s
Unit tests use a MagicMock-stubbed Django (per tests/conftest.py) so they
run in milliseconds without bootstrapping Nautobot. Integration coverage
is provided by the live e2e harnesses in development/scripts/ — those
require a real FortiGate.
Status
v0.x — bidirectional sync working against real hardware. Ready for
controlled production use against a single FortiGate; multi-device
deployments should validate carefully (the name-mangling convention
handles cross-device uniqueness, but multi-VDOM hostnames with __
characters need attention).
Roadmap: see CHANGELOG.md.
License
Apache-2.0.
Author
Ryan Malloy — ryan@supported.systems
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nautobot_ssot_fortinet-2026.5.19.2.tar.gz.
File metadata
- Download URL: nautobot_ssot_fortinet-2026.5.19.2.tar.gz
- Upload date:
- Size: 350.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"EndeavourOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0e23cdb8ad428ebab82ba939d7debecb7f42f978b4df0ab40cd851a793130dfa
|
|
| MD5 |
98fe1d9174282498387ba546258c51c6
|
|
| BLAKE2b-256 |
36e39b5b0dc2894cfeed1dd117e5832120d1becad3104c0b9484cf4ec293b3b6
|
File details
Details for the file nautobot_ssot_fortinet-2026.5.19.2-py3-none-any.whl.
File metadata
- Download URL: nautobot_ssot_fortinet-2026.5.19.2-py3-none-any.whl
- Upload date:
- Size: 100.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"EndeavourOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c446b4c0e703ed2a89251db2e94ecbc7030babe09ee0d7f6ec9bf2dbb8e2b621
|
|
| MD5 |
0c7d939d1e054a8bd7a17e5da2c3e46e
|
|
| BLAKE2b-256 |
8a66621dcffe81f9fd517124be695e61db7ca2b7862ca87c22b2cb9bba6cca03
|