Skip to main content

nautobot-ssot-fortinet

Bidirectional Nautobot ↔ FortiGate sync for firewall, wireless, DCIM (Device + interfaces + IPs + VLANs + static routes), and live runtime state — built on the Nautobot SSoT framework.

Tests Live-validated Python Nautobot

What this does

Seven Nautobot Jobs, spanning three sync domains plus a live-status observer:

# 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 → Nautobot (device + interfaces) Pull FortiGate as dcim.Device, physical + aggregate + VLAN sub-interfaces, interface IPs, static routes
4 FortiGate Live Status Observability Real-time wifi clients, DHCP leases, ARP table (joined by MAC), plus managed APs
5 Nautobot → FortiGate (firewall) Push Full CRUD across address / group / service / service-group / policy / NAT
6 Nautobot → FortiGate (wireless) Push VAP + RadioProfile CRUD (VAP delete blocked by FortiOS REST — see docs)
7 Nautobot → FortiGate (device + interfaces) Push VLAN sub-interface CRUD (v3.3+), static route CRUD (v3.4+, opt-in via push_routes)

All sync Jobs (1, 2, 3, 5, 6, 7) are idempotent (re-running produces zero diffs when nothing changed) and additive-only by default (destructive deletes opt-in via a per-Job BooleanVar). Job 4 (Live Status) writes no Nautobot records; each run drops a timestamped JSON snapshot of observed device state.

The device + interfaces push (Job 7) is additionally scoped to protect the appliance: only virtual VLAN sub-interfaces with a valid parent, and only routes with seq_num >= 1000. Anything outside that envelope is refused before the REST call — a bad write to a live FortiGate's interface or route table can disconnect it.

How it works

                     ┌────────────┐
                     │  Nautobot  │
                     └─────┬──────┘
                        ↑  │
              3 pull    │  ▼   3 push
                        │
                     ┌────────────┐    monitor/* (live state)
                     │  FortiGate │ ─────────────────────────→ Live Status Job
                     └────────────┘
  • Pull Jobs (3) read cmdb/* endpoints and write to Nautobot ORM: firewall-models for firewall objects, core wireless for VAPs and RadioProfiles, and core dcim + IPAM + our own FortinetStaticRoute model for the device / interfaces / routes. The FortiGate is the source of truth; Nautobot mirrors it.
  • Push Jobs (3) read from Nautobot ORM and write to FortiGate cmdb/* endpoints. Operators edit objects in Nautobot's UI; the matching push Job propagates them to the device. Full CRUD across firewall (v2.x), wireless (v2.x, VAP delete excepted), and device+interface (VLAN sub-interfaces since v3.3, static routes since v3.4).
  • 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. No Nautobot writes.

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.l.warehack.ing/ (or the loopback
#    fallback http://127.0.0.1:8080/ if you don't have the wildcard DNS
#    resolving), enable the Jobs you want 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, and against a FortiGate-100F cluster in Kevin Mueller's production environment (v3.2 series). The live e2e harness in development/scripts/e2e_push_*.py connects to a real FortiGate, exercises CRUD per object kind, and asserts a second run produces zero diffs (full idempotency). Run all of them with make -C development e2e-push-all.

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; we've validated against FortiOS 7.0.14 on real hardware; the underlying library covers 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)

Real gaps as of v3.4.2. See CHANGELOG.md and the v3.x summary for what has shipped so far (Policy/NAT push, Device sync, VLAN sub-interfaces, static routes — all live-validated).

  • IPv6 firewall addresses (firewall/address6 and addrgrp6) — IPv4 only right now. IPv6 interface IPs on Devices are already synced (v3.0+), but the standalone AddressObject types aren't.
  • Source NAT (FortiOS ippool) — pull and push only cover Destination NAT (VIPs). SNAT pools have three subtype variants (overload, one-to-one, fixed-port-range) that map differently to Nautobot semantics; each needs a design pass.
  • VPN sync — Nautobot has vpn.VPN / vpn.VPNTunnel / endpoint models and FortiOS has phase1/phase2 data, but nothing bridges them yet. Tunnel interfaces are also skipped in the device sync until this lands.
  • firewall.schedule — policy push currently hardcodes schedule: "always". Time-based rules can't be authored in Nautobot yet.
  • Zone objects (system.zone) — referenced in policy interface fields as strings, but not synced as first-class Nautobot records.
  • 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.

If any of these are blocking your deployment, file an issue on GitHub — the roadmap prioritizes by real operator need.

Project layout

src/nautobot_ssot_fortinet/
├── clients/fortigate.py             ExternalIntegration → FortiGateAPI factory
├── diffsync/
│   ├── models/
│   │   ├── firewall.py              Vendor-neutral firewall DiffSync models
│   │   ├── wireless.py              Vendor-neutral wireless DiffSync models
│   │   ├── devices.py               Vendor-neutral Device + Interface + Route models (v3.0+)
│   │   ├── nautobot_firewall.py     Nautobot-side CRUD (firewall pull target)
│   │   ├── nautobot_wireless.py     Nautobot-side CRUD (wireless pull target)
│   │   ├── nautobot_devices.py      Nautobot-side CRUD (device+route pull target)
│   │   ├── fortigate_target_firewall.py   FortiGate-side CRUD (firewall push)
│   │   ├── fortigate_target_wireless.py   FortiGate-side CRUD (wireless push)
│   │   └── fortigate_target_devices.py    FortiGate-side CRUD (device+route push, v3.3+)
│   └── adapters/
│       ├── fortigate_firewall.py           Firewall pull source
│       ├── fortigate_wireless.py           Wireless pull source
│       ├── fortigate_devices.py            Device+route pull source (v3.0+)
│       ├── fortigate_firewall_target.py    Firewall push target
│       ├── fortigate_wireless_target.py    Wireless push target
│       ├── fortigate_devices_target.py     Device+route push target (v3.3+)
│       ├── nautobot_firewall.py            Nautobot-side firewall reader
│       ├── nautobot_wireless.py            Nautobot-side wireless reader
│       └── nautobot_devices.py             Nautobot-side device+route reader
├── jobs.py                          7 Job classes (see the SSoT dashboard)
├── models.py                        FortinetStaticRoute Django model (v3.1+)
├── views.py, urls.py, tables.py,    UI for FortinetStaticRoute at
├── forms.py, filters.py,            /plugins/ssot-fortinet/static-routes/
├── navigation.py, templates/
├── migrations/                      Django migrations for FortinetStaticRoute
└── utils/fortios.py                 Translation helpers (subnet→CIDR, security→auth, etc.)

Testing

$ pytest -q
302 passed in 0.28s

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. make -C development e2e-push-all runs every push CRUD e2e in sequence; make -C development e2e-jobs-lifecycle runs the Job.run() regression guard.

Status

v3.x — bidirectional sync for firewall, wireless, and DCIM (Device + Interfaces + VLAN sub-interfaces + Static Routes), live-validated against a FortiWiFi-61E and against a real FortiGate-100F cluster in production. Multi-device deployments handle cross-device uniqueness via the name-mangling convention (<host>__<vdom>__<original>); multi-VDOM hostnames with __ characters would need attention if that convention conflicts with your host naming.

Roadmap: see CHANGELOG.md and the v3.x summary.

License

Apache-2.0.

Author

Ryan Malloy — ryan@supported.systems

Metadata

Release files for nautobot-ssot-fortinet 2026.5.19.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for nautobot-ssot-fortinet 2026.5.19.4
File Size Uploaded
nautobot_ssot_fortinet-2026.5.19.4.tar.gz 359.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for nautobot-ssot-fortinet 2026.5.19.4
File Interpreter ABI Platform
nautobot_ssot_fortinet-2026.5.19.4-py3-none-any.whl Python 3 none any Details

Total release size: 460.7 kB

Release files / nautobot_ssot_fortinet-2026.5.19.4.tar.gz

Download URL nautobot_ssot_fortinet-2026.5.19.4.tar.gz
Size 359.2 kB
Tags Source
SHA-256 checksum
How to use checksums
b025aa227a5534110d089ba7b535da08f3f67c2db4ec1d4f030d2a2f57f5c9c8
BLAKE2b-256 checksum
How to use checksums
fada4f22dd3d9c1abafb4b37c2103dbdf849a44ffbe9eed424ab359453b36243
Upload date
Uploaded using Trusted Publishing?
What is 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}

Release files / nautobot_ssot_fortinet-2026.5.19.4-py3-none-any.whl

Download URL nautobot_ssot_fortinet-2026.5.19.4-py3-none-any.whl
Size 101.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ac754adfaa82b5a034217e7d2590639522654e81b9d3700ca3697c5c14b67081
BLAKE2b-256 checksum
How to use checksums
3acb71340998f6ed24a8857575d964780ee821792b1e9cc17cd3f61210a637fe
Upload date
Uploaded using Trusted Publishing?
What is 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}
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page