https://github.com/netcanon/netcanon
Multi-vendor network config translator — Cisco / Juniper / Fortinet / Aruba / Arista / MikroTik / OPNsense. Cross-mesh audit catches silent translation errors before they ship.
https://github.com/netcanon/netcanon
arista aruba cisco config-migration fastapi fortinet juniper mikrotik network-automation network-configuration opnsense python vendor-translation
Last synced: about 10 hours ago
JSON representation
Multi-vendor network config translator — Cisco / Juniper / Fortinet / Aruba / Arista / MikroTik / OPNsense. Cross-mesh audit catches silent translation errors before they ship.
- Host: GitHub
- URL: https://github.com/netcanon/netcanon
- Owner: netcanon
- License: mit
- Created: 2026-05-13T06:20:23.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-06-20T07:01:02.000Z (6 days ago)
- Last Synced: 2026-06-20T07:13:52.176Z (6 days ago)
- Topics: arista, aruba, cisco, config-migration, fastapi, fortinet, juniper, mikrotik, network-automation, network-configuration, opnsense, python, vendor-translation
- Language: Python
- Homepage: https://pypi.org/project/netcanon/
- Size: 21.5 MB
- Stars: 6
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Codeowners: .github/CODEOWNERS
- Security: SECURITY.md
- Agents: AGENTS.md
Awesome Lists containing this project
README
# Netcanon
[](https://github.com/netcanon/netcanon/actions/workflows/ci.yml)
[](https://pypi.org/project/netcanon/)
[](https://pypi.org/project/netcanon/)
[](LICENSE)
[](https://github.com/netcanon/netcanon/pkgs/container/netcanon)
**Multi-vendor network config translator with a verifiable cross-vendor audit.**
Translates running-config across twelve codecs spanning Cisco (IOS-XE,
NX-OS, IOS-XR), Juniper Junos, Arista EOS, Aruba (AOS-S, AOS-CX),
Fortinet FortiGate, MikroTik RouterOS, OPNsense, and VyOS — see
[`docs/CAPABILITIES.md`](docs/CAPABILITIES.md) for the full per-codec list.
You point Netcanon at a config from one vendor and it renders the
equivalent config for another — through a shared canonical model, with
every translatable field declared as supported, lossy, or unsupported.
What sets it apart is the audit underneath. Every supported vendor
pair × every field gets classified into one of eight variance classes
(`ALIGNED` / `CODEC_BUG` / `EXPECTED_LOSSY` / `EXPECTED_UNSUPPORTED` /
`METHODOLOGY_ISSUE_under` / `METHODOLOGY_ISSUE_over` / `STRUCTURAL_ONLY`
/ `TRIVIAL_EMPTY`). The cross-mesh audit catches silent translation
errors — the kind that produce output that *looks* valid but quietly
drops or transforms a field — before they ship.
The browser UI translating Cisco IOS-XE → Junos — interface names and L2 membership mapped across vendors, every field declared supported/lossy/unsupported, and Tier-3 sections (ACLs, NAT, …) surfaced rather than silently dropped.
---
## See it in 10 seconds
```bash
docker run --rm --entrypoint netcanon ghcr.io/netcanon/netcanon:latest demo --pair cisco__junos
```
(Installed via pip instead? Just `netcanon demo --pair cisco__junos`.)
The `demo` command above translates a built-in sample. To translate your
own, start the server (see [Install](#install)) and paste a config like
this into the browser migrate page:
```
hostname access-sw-01
!
vlan 10
name DATA
!
interface GigabitEthernet1/0/1
description Server-A
switchport mode access
switchport access vlan 10
!
snmp-server community public RO
!
ip route 0.0.0.0 0.0.0.0 192.168.1.1
```
Get this:
```
set system host-name access-sw-01
set interfaces ge-1/0/1 description "Server-A"
set interfaces ge-1/0/1 unit 0 family ethernet-switching interface-mode access
set interfaces ge-1/0/1 unit 0 family ethernet-switching vlan members DATA
set vlans DATA vlan-id 10
set routing-options static route 0.0.0.0/0 next-hop 192.168.1.1
set snmp community public authorization read-only
```
Notice ``GigabitEthernet1/0/1`` became ``ge-1/0/1`` and the access-VLAN
membership rendered into Junos `ethernet-switching` form — Netcanon
translates interface names and L2 membership across vendor conventions,
not just the surrounding scalar config.
Same canonical pipeline drives the HTTP API and the browser UI. Run
`netcanon demo --list` (or `python tools/demo.py --list` from a source
checkout) to see all four embedded scenarios (Cisco→Junos,
FortiGate→MikroTik, Aruba→Arista, OPNsense→Junos).
---
## The trust signal — and the invitation
Across every vendor pair we ship a fixture **and** a cross-vendor
expectation for — 8 of the 12 codecs today; `aruba_aoscx`, `cisco_iosxr`,
`cisco_nxos`, and `vyos` have fixtures but not yet cross-vendor
expectations — the cross-mesh audit tracks `CODEC_BUG` drift cell by
cell. The live reconciliation (`tests/fixtures/real/PHASE4_RECONCILIATION.md`
for the roll-up, `tests/fixtures/real/phase4_findings_residuals.md` for
the per-cell triage) currently reports a small number of residual
high-severity cells (5 at last run) — each triaged as a benign
modelling/structural artifact (4 on real fixtures, 1 synthetic) rather
than a translation error, and every one is enumerated. That's not "we
think it works"; that's every covered cell, checked by automated test
against vendor-doc-grounded expectations, with nothing swept under the rug.
The honest follow-up: **the audit only covers cells we have fixtures
for.** Real-world configs exercise paths the synthetic fixtures
haven't reached — and that's where you come in. If you have a
running-config that translates wrong (or doesn't translate at all),
that's the highest-impact bug report this project can receive. See
[`BUG_REPORTING.md`](BUG_REPORTING.md) for the workflow — Netcanon
ships its own sanitiser (the `/sanitize` browser page, the
`netcanon sanitize` CLI, and the `POST /api/v1/sanitize` HTTP
endpoint all share one library) so you never paste real WAN IPs,
hashes, hostnames, or usernames into a public issue.
For the full audit narrative + the variance-class taxonomy, see
[`docs/HOW_WE_TEST.md`](docs/HOW_WE_TEST.md).
---
## How it compares
Arriving from "I need a Batfish / NAPALM / Capirca alternative"? Most
adjacent tools occupy a different slot — Netcanon's niche is
**bidirectional translation between vendors' native running-config
formats**, with a per-field capability matrix and a cross-mesh audit.
| Tool | What it does | Translates native config? |
|---|---|---|
| **Netcanon** | Multi-vendor config translation (parse + render) | ✅ bidirectional, 12 codecs |
| [Batfish](https://github.com/batfish/batfish) | Config analysis + routing simulation | ❌ parse-only — *complements* Netcanon |
| [Capirca](https://github.com/google/capirca) / [Aerleon](https://github.com/aerleon/aerleon) | Firewall ACL DSL → vendor syntax | ❌ render-only from a DSL — *competes* (firewall scope) |
| [NAPALM](https://github.com/napalm-automation/napalm) / [Netmiko](https://github.com/ktbyers/netmiko) | Device get/set + SSH transport | ❌ no translation — *complements* (deploy side) |
| [Oxidized](https://github.com/ytti/oxidized) / [RANCID](https://shrubbery.net/rancid/) | Multi-vendor config **backup** + diff | ❌ backup-only — overlaps Netcanon's backup half |
Netcanon **competes** with Capirca / Aerleon (but defers firewall / NAT
/ VPN / QoS to Tier-3) and **complements** Batfish (analyse what you
translated), NAPALM / Netmiko (deploy it), and NetBox / Nautobot
(desired-state vs existing-state). Full breakdown — including the
backup + sanitiser landscape — in
[`docs/COMPARISON.md`](docs/COMPARISON.md).
---
## Install
### Docker (recommended)
> [!IMPORTANT]
> **As of 0.4.0 the container fails closed on a public bind.** The
> default entrypoint (`netcanon serve`) binds `0.0.0.0`, so it now
> **refuses to start** unless you either set an API key
> (`-e NETCANON_API_KEY=...`, which gates `/api/v1`) **or** explicitly
> opt out with `-e NETCANON_ALLOW_INSECURE_BIND=1`. This is deliberate
> — it stops an accidental `docker run -p` from exposing an
> unauthenticated API to your network. Just kicking the tyres? Add
> `-e NETCANON_ALLOW_INSECURE_BIND=1`; for anything reachable by other
> hosts, set `NETCANON_API_KEY` (the full command below does). A pure
> loopback bind (`-e NETCANON_HOST=127.0.0.1`) needs neither.
```bash
# Optional but recommended for production: setting the key explicitly keeps
# it in your secret store, separate from the data volume. Skip this line
# AND the `-e` flag below and Netcanon auto-generates + persists a key in
# data/.fernet_key on first run (zero-config). This key encrypts device
# credentials at rest: loss = re-entering every saved device password;
# leak = decryptable stored credentials.
NETCANON_FERNET_KEY=$(python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())")
# Required for a network-exposed (0.0.0.0) deployment: an API key gates
# /api/v1. Without it (or NETCANON_ALLOW_INSECURE_BIND=1) the container
# refuses to start on a non-loopback bind (SEC-01 fail-closed).
NETCANON_API_KEY=$(python -c "import secrets; print(secrets.token_urlsafe(32))")
docker run --rm -p 8000:8000 \
-v $(pwd)/configs:/app/configs \
-v $(pwd)/data:/app/data \
-e NETCANON_FERNET_KEY="$NETCANON_FERNET_KEY" \
-e NETCANON_API_KEY="$NETCANON_API_KEY" \
ghcr.io/netcanon/netcanon:latest
# -> http://127.0.0.1:8000 (UI)
# -> http://127.0.0.1:8000/docs (Swagger)
# -> http://127.0.0.1:8000/health (health probe)
```
`configs/` is where backed-up running-configs land; `data/` holds
device profiles, schedules, and job state. The device-definition
library ships *inside the package* (there is no `definitions/` mount
target) — set `NETCANON_DEFINITIONS_DIR` to a bind-mounted directory
only if you maintain a custom definition set.
`NETCANON_FERNET_KEY` injects the credential-encryption key directly
(recommended for production / orchestrated deployments — the key
never touches disk). If you skip the `-e` flag, Netcanon auto-
generates a key on first run inside `data/.fernet_key` so the
container works zero-config; for the production deployment path see
[`SECURITY.md`](SECURITY.md) "Credential Storage".
`NETCANON_API_KEY` turns on a built-in bearer-token gate: when set,
every `/api/v1` request must carry `Authorization: Bearer
$NETCANON_API_KEY`. **The key gates the `/api/v1` surface only — it does
NOT cover the HTML UI.** Several UI pages are server-rendered and read
data server-side, *not* through `/api/v1`: the diff view
(`/configs/{a}/vs/{b}`) emits full config text (secrets included), and
`/configs` / `/devices` list the config + device inventory. The API key
does **not** protect those pages, so for any non-loopback exposure you
**must** front the app with a reverse proxy that authenticates the whole
surface — do not rely on the key alone to secure the UI. Because the
image binds `0.0.0.0`, `netcanon serve` refuses to start without a key
(or `NETCANON_ALLOW_INSECURE_BIND=1`), so an unauthenticated public bind
is a deliberate choice — see [`SECURITY.md`](SECURITY.md) "Threat Model".
The published image is signed via Sigstore with an SBOM attestation.
Verify against the immutable digest (`ghcr.io/netcanon/netcanon@sha256:`),
not a mutable tag — see [`SECURITY.md`](SECURITY.md) "Supply-Chain
Integrity" for the exact `cosign verify` invocation.
**Docker Hub mirror** — same image, convenience-mirrored to Docker
Hub if your tooling defaults to `docker.io`:
```bash
docker run --rm -p 8000:8000 -e NETCANON_ALLOW_INSECURE_BIND=1 netcanon/netcanon:latest
```
The Docker Hub mirror has the same image bytes but no cosign
signature or SBOM attestation — operators in regulated environments
should pull from GHCR for the attested provenance chain. See
[`SECURITY.md`](SECURITY.md) for the supply-chain story.
### Pip
```bash
pip install netcanon
uvicorn netcanon.main:app --host 127.0.0.1 --port 8000
```
`netcanon` also installs the `netcanon` CLI — `netcanon sanitize -i
my-config.txt --source-vendor cisco_iosxe_cli --dry-run` is the
typical CLI entrypoint for the bug-reporting workflow. If the
server's running, the **`/sanitize` browser page** is the easier
path (paste or pick a stored config, click Sanitize, copy the
output — see [`BUG_REPORTING.md`](BUG_REPORTING.md) for the full
workflow including what gets redacted).
### Desktop (Windows)
Download the MSI from
[Releases](https://github.com/netcanon/netcanon/releases), or from
source:
```bash
pip install -e ".[desktop]"
python -m netcanon_desktop
```
The desktop shell runs the same FastAPI app inside a PySide6 webview
with a tray icon — same UI, no command-line. See
[`netcanon_desktop/README.md`](netcanon_desktop/README.md) for the
threading model, settings, and MSI build instructions.
---
## Walkthroughs — "is this the right tool for my migration?"
Each walkthrough is paired 1:1 with a runnable demo scenario. Read
the narrative first, run `python tools/demo.py --pair ` to see
the actual translation.
| Walkthrough | Demo scenario | Frame |
|---|---|---|
| [Cisco IOS-XE → Juniper Junos](docs/walkthroughs/cisco_iosxe_to_junos.md) | `cisco__junos` | DC leaf migration: VLANs + interfaces + routes |
| [FortiGate → MikroTik RouterOS](docs/walkthroughs/fortigate_to_mikrotik.md) | `fortigate__mikrotik` | Branch-firewall consolidation: DNS + interfaces + DHCP pools |
| [Aruba AOS-S → Arista EOS](docs/walkthroughs/aruba_to_arista.md) | `aruba__arista` | Switch refresh: VLAN-centric → port-centric grammar |
| [OPNsense → Juniper Junos](docs/walkthroughs/opnsense_to_junos.md) | `opnsense__junos` | Edge-firewall migration with explicit Tier-3 boundary |
Each walkthrough ends in a manual-review checklist — what to verify
on the device after the rendered config lands, before you apply it.
---
## What translates, and what doesn't
The canonical model classifies every field by semantic stability
across vendors. Full per-codec matrix is in
[`docs/CAPABILITIES.md`](docs/CAPABILITIES.md); the short version:
* **Tier 1 — auto-translatable.** hostname, interfaces (name /
description / enabled state / IPv4 + IPv6 addresses / per-interface
VRF binding), VLANs, static routes, DNS / NTP / syslog servers,
timezone. Every shipped codec parses + renders these fully (the
experimental `cisco_iosxe` NETCONF stub excepted — it renders
interfaces only).
* **Tier 2 — translatable with caveats.** SNMP (incl. SNMPv3 USM),
LAGs, local users, RADIUS, DHCP server pools, VXLAN VNIs, EVPN
type-5 routes, routing instances / VRFs, Junos `apply-groups`.
Hashes that the target's CLI cannot consume surface as commented
review lines, never as plaintext fallback.
* **Tier 3 — opaque carry / never auto-rendered.** Firewall rules,
NAT, IPsec / OpenVPN / WireGuard, QoS, route-maps, dynamic routing
protocol stanzas, PKI. These are vendor-specific stateful policy
that doesn't translate cross-vendor cleanly — Netcanon **detects**
them, surfaces them via the migrate-page banner with a count and
section names, and deliberately doesn't auto-render. Hand-build
them natively on the target.
If your migration's primary need is firewall translation,
[`docs/COMPARISON.md`](docs/COMPARISON.md) names adjacent tools
(Capirca / Aerleon) that handle that scope. Netcanon is the right
tool for the *router* portion of a migration — and explicitly the
wrong tool to claim it does the firewall portion.
---
## Two concerns, one app
Netcanon co-hosts:
1. **Backup** — pulls `running-config` (or vendor equivalent) from
network devices over SSH / NETCONF / REST and stores it verbatim
in `configs/.`. Runs on a schedule or on demand.
2. **Migration** — translates a stored backup from one vendor's
config grammar to another through the canonical intent tree.
Same FastAPI process; same UI; same Docker image. Use whichever
half (or both). See [`ARCHITECTURE.md`](ARCHITECTURE.md) for the
four-layer design.
---
## Found a bug? Got a config that breaks it?
That's the contribution this project values most. Workflow:
1. Sanitise your config — open the `/sanitize` browser page (easiest
if the server's running), or run the `netcanon sanitize` CLI
(no server required). Both strip hostnames, usernames, IPs,
hashes, SNMP communities, etc., with a counter-per-session
stable substitution table you can audit before submission.
2. Open a [bug report](https://github.com/netcanon/netcanon/issues/new?template=bug_report.yml)
or [fixture submission](https://github.com/netcanon/netcanon/issues/new?template=fixture_submission.yml).
3. The fixture lands in `tests/fixtures/real//`, the
cross-mesh audit re-runs, and the variance class your fixture
surfaces gets a row in `tests/fixtures/real/PHASE4_RECONCILIATION.md`.
Full workflow is in [`BUG_REPORTING.md`](BUG_REPORTING.md).
---
## For contributors
| You want to… | Start here |
|---|---|
| Understand the architecture | [`ARCHITECTURE.md`](ARCHITECTURE.md) — four-layer model, canonical bridge, codec types |
| Follow the contributor rules | [`AGENTS.md`](AGENTS.md) — hard rules, parity checklist, gotchas |
| Read the slower-changing methodology | [`docs/METHODOLOGY.md`](docs/METHODOLOGY.md) — matrix-honesty discipline distilled, portable to other projects |
| Look up project jargon | [`docs/glossary.md`](docs/glossary.md) — canonical, codec, mesh, ship-before-wire, target profile |
| Read the canonical model overview | [`netcanon/migration/canonical/README.md`](netcanon/migration/canonical/README.md) |
| Add or change an HTTP route | [`netcanon/api/routes/README.md`](netcanon/api/routes/README.md) — frozen pipeline-stage signatures, endpoint inventory |
| Add a new codec | [`netcanon/migration/codecs/README.md`](netcanon/migration/codecs/README.md) |
| Add a new device definition / target profile | [`netcanon/definitions/library/README.md`](netcanon/definitions/library/README.md) |
| Add a new canonical field | [`docs/adding-a-canonical-field.md`](docs/adding-a-canonical-field.md) |
| Ship a feature across web + desktop | [`docs/feature-parity-walkthrough.md`](docs/feature-parity-walkthrough.md) |
| See what's shipped recently | [`CHANGELOG.md`](CHANGELOG.md) |
| Check codec certification tiers | [`tests/fixtures/real/RESULTS.md`](tests/fixtures/real/RESULTS.md) |
| Write tests | [`tests/README.md`](tests/README.md) |
| Review the security model | [`SECURITY.md`](SECURITY.md) |
| Community / participation norms | [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) — Contributor Covenant + enforcement contact |
### Run the test suite
```bash
pip install -e ".[dev]"
pytest # all tiers; desktop tier needs the [desktop] extra
pytest -m e2e # Playwright browser tests (slower)
```
Tests run across four layers: unit (pure functions, no I/O — the
real-capture validation harness lives here as a unit subset),
integration (TestClient + mocked SSH at the `get_collector`
factory), e2e (Playwright against a live Uvicorn), and desktop
(PySide6 + pystray mocked). CI runs the **unit + integration** tiers
on Python 3.11 / 3.12 / 3.13 / 3.14 against Ubuntu; the e2e + desktop
tiers run locally, not in CI. The CI pytest invocation uses `-x`
(stop at first failure), so a green run reports the authoritative
pass count while a red run stops early.
### Layout
```
netcanon/ FastAPI application (shared by both platforms)
├── api/routes/ HTTP endpoints
├── collectors/ SSH/NETCONF/REST fetchers — one factory,
│ one mock-point (`get_collector`)
├── definitions/ Device-definition loader + shipped YAML
│ library (`library/`, baked into the wheel)
├── migration/ Cross-vendor translation pipeline
│ ├── canonical/ CanonicalIntent model + shared transforms
│ └── codecs/ Per-vendor parse/render implementations
├── services/ Plain-function orchestrators (pipeline, detect, …)
├── storage/ FileConfigStore
├── tools/ sanitize, etc.
└── templates/ Jinja2 templates (most interactive elements
carry a data-testid — see AGENTS.md)
netcanon_desktop/ Windows tray/webview shell around the same server
tools/demo.py One-command cross-vendor translation demo
docs/walkthroughs/ Narrative migration walkthroughs (paired with demo)
docs/vendors/ Per-vendor "what works for me?" pages
tests/unit/ Pure-function tests, no I/O
tests/integration/ FastAPI TestClient tests, SSH mocked
tests/e2e/ Playwright browser tests
tests/desktop/ PySide6/pystray-mocked desktop shell tests
tests/fixtures/real/ Real-capture validation corpus (see RESULTS.md)
```
---
## See also
* [`ARCHITECTURE.md`](ARCHITECTURE.md) — the four-layer model + canonical
bridge + codec types
* [`AGENTS.md`](AGENTS.md) — contributor directives, hard rules, doc-sync
checklist
* [`tests/README.md`](tests/README.md) — test-tier layout + how to run
* [`docs/CAPABILITIES.md`](docs/CAPABILITIES.md) — per-codec capability matrix
* [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md) — operator-facing
diagnostic flowchart
* [`SECURITY.md`](SECURITY.md) — security model, sanitiser, supply-chain
integrity controls
* [`CHANGELOG.md`](CHANGELOG.md) — release log
* [`TRADEMARKS.md`](TRADEMARKS.md) — third-party trademark / nominative-use notice
---
## License
MIT. See [`LICENSE`](LICENSE). Third-party fixtures keep their
upstream licences — see [`tests/fixtures/real/NOTICE.md`](tests/fixtures/real/NOTICE.md)
for provenance.
For responsible disclosure of security issues, see
[`SECURITY.md`](SECURITY.md).