Multi-vendor network config translator with a verifiable cross-vendor audit
Project description
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 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
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) 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 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.
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 | Config analysis + routing simulation | ❌ parse-only — complements Netcanon |
| Capirca / Aerleon | Firewall ACL DSL → vendor syntax | ❌ render-only from a DSL — competes (firewall scope) |
| NAPALM / Netmiko | Device get/set + SSH transport | ❌ no translation — complements (deploy side) |
| Oxidized / 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.
Install
Docker (recommended)
[!IMPORTANT] As of 0.4.0 the container fails closed on a public bind. The default entrypoint (
netcanon serve) binds0.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 accidentaldocker run -pfrom exposing an unauthenticated API to your network. Just kicking the tyres? Add-e NETCANON_ALLOW_INSECURE_BIND=1; for anything reachable by other hosts, setNETCANON_API_KEY(the full command below does). A pure loopback bind (-e NETCANON_HOST=127.0.0.1) needs neither.
# 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. Don't bind-mount
definitions/ — those YAMLs are baked into the image as tracked
content; mounting an empty host directory over them will crash
startup.
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 "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. /health and the HTML UI shell stay open, but the
UI's data calls hit /api/v1, so an exposed UI should sit behind your
reverse proxy's auth. 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 "Threat Model".
The published image is signed via Sigstore with an SBOM attestation.
Verify against the immutable digest (ghcr.io/netcanon/netcanon@sha256:<digest>),
not a mutable tag — see 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:
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 for the supply-chain story.
Pip
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 for the full
workflow including what gets redacted).
Desktop (Windows)
Download the MSI from Releases, or from source:
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 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 <key> to see
the actual translation.
| Walkthrough | Demo scenario | Frame |
|---|---|---|
| Cisco IOS-XE → Juniper Junos | cisco__junos |
DC leaf migration: VLANs + interfaces + routes |
| FortiGate → MikroTik RouterOS | fortigate__mikrotik |
Branch-firewall consolidation: DNS + interfaces + DHCP pools |
| Aruba AOS-S → Arista EOS | aruba__arista |
Switch refresh: VLAN-centric → port-centric grammar |
| OPNsense → Juniper Junos | 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; 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_iosxeNETCONF 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 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:
- Backup — pulls
running-config(or vendor equivalent) from network devices over SSH / NETCONF / REST and stores it verbatim inconfigs/<hostname>.<ext>. Runs on a schedule or on demand. - 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 for the
four-layer design.
Found a bug? Got a config that breaks it?
That's the contribution this project values most. Workflow:
- Sanitise your config — open the
/sanitizebrowser page (easiest if the server's running), or run thenetcanon sanitizeCLI (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. - Open a bug report or fixture submission.
- The fixture lands in
tests/fixtures/real/<vendor>/, the cross-mesh audit re-runs, and the variance class your fixture surfaces gets a row intests/fixtures/real/PHASE4_RECONCILIATION.md.
Full workflow is in BUG_REPORTING.md.
For contributors
| You want to… | Start here |
|---|---|
| Understand the architecture | ARCHITECTURE.md — four-layer model, canonical bridge, codec types |
| Follow the contributor rules | AGENTS.md — hard rules, parity checklist, gotchas |
| Read the slower-changing methodology | docs/METHODOLOGY.md — matrix-honesty discipline distilled, portable to other projects |
| Look up project jargon | docs/glossary.md — canonical, codec, mesh, ship-before-wire, target profile |
| Read the canonical model overview | netcanon/migration/canonical/README.md |
| Add or change an HTTP route | netcanon/api/routes/README.md — frozen pipeline-stage signatures, endpoint inventory |
| Add a new codec | netcanon/migration/codecs/README.md |
| Add a new device definition / target profile | definitions/README.md |
| Add a new canonical field | docs/adding-a-canonical-field.md |
| Ship a feature across web + desktop | docs/feature-parity-walkthrough.md |
| See what's shipped recently | CHANGELOG.md |
| Check codec certification tiers | tests/fixtures/real/RESULTS.md |
| Write tests | tests/README.md |
| Review the security model | SECURITY.md |
| Community / participation norms | CODE_OF_CONDUCT.md — Contributor Covenant + enforcement contact |
Run the test suite
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`)
├── 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
definitions/ Device definition YAMLs
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— the four-layer model + canonical bridge + codec typesAGENTS.md— contributor directives, hard rules, doc-sync checklisttests/README.md— test-tier layout + how to rundocs/CAPABILITIES.md— per-codec capability matrixdocs/TROUBLESHOOTING.md— operator-facing diagnostic flowchartSECURITY.md— security model, sanitiser, supply-chain integrity controlsCHANGELOG.md— release logTRADEMARKS.md— third-party trademark / nominative-use notice
License
MIT. See LICENSE. Third-party fixtures keep their
upstream licences — see tests/fixtures/real/NOTICE.md
for provenance.
For responsible disclosure of security issues, see
SECURITY.md.
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 netcanon-0.4.2.tar.gz.
File metadata
- Download URL: netcanon-0.4.2.tar.gz
- Upload date:
- Size: 5.0 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b0cd64d32eee3c361a0773755e9085c3427b73bf19c1d9b2825db9dfa15d10ef
|
|
| MD5 |
207d9f8f20501750e0667aa0ae85a450
|
|
| BLAKE2b-256 |
843d1870c3001bffb9f2ac0624be9bee2df110159b17fd545c93fe72e790be26
|
Provenance
The following attestation bundles were made for netcanon-0.4.2.tar.gz:
Publisher:
pypi-publish.yml on netcanon/netcanon
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
netcanon-0.4.2.tar.gz -
Subject digest:
b0cd64d32eee3c361a0773755e9085c3427b73bf19c1d9b2825db9dfa15d10ef - Sigstore transparency entry: 1903889433
- Sigstore integration time:
-
Permalink:
netcanon/netcanon@4bfda2aec88bb4f114faa0cfc8545f77e21e2d11 -
Branch / Tag:
refs/tags/v0.4.2 - Owner: https://github.com/netcanon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-publish.yml@4bfda2aec88bb4f114faa0cfc8545f77e21e2d11 -
Trigger Event:
push
-
Statement type:
File details
Details for the file netcanon-0.4.2-py3-none-any.whl.
File metadata
- Download URL: netcanon-0.4.2-py3-none-any.whl
- Upload date:
- Size: 884.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8972a192c299b0868eb692efa949f41a9d9fa0bb12b32ef526e350a30ec0755c
|
|
| MD5 |
5e1379423f5289b8670a63b609c3fba3
|
|
| BLAKE2b-256 |
4d7b169a3124bd3872d66ddc45f2f9092bf0614b960df6d7ac837dd54e9cef17
|
Provenance
The following attestation bundles were made for netcanon-0.4.2-py3-none-any.whl:
Publisher:
pypi-publish.yml on netcanon/netcanon
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
netcanon-0.4.2-py3-none-any.whl -
Subject digest:
8972a192c299b0868eb692efa949f41a9d9fa0bb12b32ef526e350a30ec0755c - Sigstore transparency entry: 1903889564
- Sigstore integration time:
-
Permalink:
netcanon/netcanon@4bfda2aec88bb4f114faa0cfc8545f77e21e2d11 -
Branch / Tag:
refs/tags/v0.4.2 - Owner: https://github.com/netcanon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-publish.yml@4bfda2aec88bb4f114faa0cfc8545f77e21e2d11 -
Trigger Event:
push
-
Statement type: