Skip to main content

Multi-vendor network config translator with a verifiable cross-vendor audit

Project description

Netcanon

CI PyPI Python versions License: MIT Container: GHCR

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.

Netcanon migrate page: a Cisco IOS-XE config translated to Junos, showing auto-detection, a green “Validation OK” banner, an amber “Tier-3 sections detected” banner listing the ACL and NAT lines that don’t translate, and the rendered Junos `set` output.

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) 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.

# 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 "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 "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_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 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/<hostname>.<ext>. 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 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 or fixture submission.
  3. The fixture lands in tests/fixtures/real/<vendor>/, 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.


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 netcanon/definitions/library/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`)
 ├── 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


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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

netcanon-0.4.15.tar.gz (3.7 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

netcanon-0.4.15-py3-none-any.whl (1.0 MB view details)

Uploaded Python 3

File details

Details for the file netcanon-0.4.15.tar.gz.

File metadata

  • Download URL: netcanon-0.4.15.tar.gz
  • Upload date:
  • Size: 3.7 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for netcanon-0.4.15.tar.gz
Algorithm Hash digest
SHA256 40451ad727cda433355e29512bba4a560f058b3793445908a4e720bc31908638
MD5 4792262b22574c6ca793b152af6de758
BLAKE2b-256 09d7330e5439ddc7ad95f18e7c56b2e3db047248e51189dce1124d39fe628090

See more details on using hashes here.

Provenance

The following attestation bundles were made for netcanon-0.4.15.tar.gz:

Publisher: pypi-publish.yml on netcanon/netcanon

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file netcanon-0.4.15-py3-none-any.whl.

File metadata

  • Download URL: netcanon-0.4.15-py3-none-any.whl
  • Upload date:
  • Size: 1.0 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for netcanon-0.4.15-py3-none-any.whl
Algorithm Hash digest
SHA256 5cd668cb24da20846888c77d1eedcc35272d1d423cea0b552250c5df43e0bc5a
MD5 e8f462c589e3ad106856b44b25e218d7
BLAKE2b-256 211db8c1592c509a34226a58b403306ab773e08b92f9c62d05dd9130031fcfa7

See more details on using hashes here.

Provenance

The following attestation bundles were made for netcanon-0.4.15-py3-none-any.whl:

Publisher: pypi-publish.yml on netcanon/netcanon

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page