Skip to main content

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.

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

Paste this:

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 supported vendor pair × every field declared as supported, the cross-mesh audit tracks CODEC_BUG drift cell by cell. The live reconciliation (tests/fixtures/real/PHASE4_RECONCILIATION.md — the authoritative count) currently reports a small number of residual high-severity cells (5 at last run), each triaged as a benign comparator artifact on a synthetic fixture rather than a translation error, and every one is enumerated in that file. That's not "we think it works"; that's every cell that should translate, 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.


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. 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 (cosign verify ghcr.io/netcanon/netcanon ...) with an SBOM attestation.

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, certs, 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 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


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.1.tar.gz (5.0 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.1-py3-none-any.whl (881.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: netcanon-0.4.1.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

Hashes for netcanon-0.4.1.tar.gz
Algorithm Hash digest
SHA256 6725c2344903a9fc21d78fef191b4b27356364408f863b4a403fb8903c7e1d75
MD5 e27264a3c04da7a48810e7e0f86b3b67
BLAKE2b-256 77409a8377ff2c19e2417e2e9d73a7c7319d00f4c74f349f8bcab5bb7d1d6071

See more details on using hashes here.

Provenance

The following attestation bundles were made for netcanon-0.4.1.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.1-py3-none-any.whl.

File metadata

  • Download URL: netcanon-0.4.1-py3-none-any.whl
  • Upload date:
  • Size: 881.5 kB
  • 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 dc5b008591d2176dbaf7139c61a9204fc23da077f071563fc4ad6d339bd35dbc
MD5 639fc58b30da9df10f6758321da55ade
BLAKE2b-256 7ba804f5a87f5b27a81878910eaa46e1050be78f3e488a4efee668afe7acb56f

See more details on using hashes here.

Provenance

The following attestation bundles were made for netcanon-0.4.1-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