How to automate BGP prefix-list imports from NetBox into Cisco routers for datacenter networking using Ansible and Python

How to automate BGP prefix-list imports from NetBox into Cisco routers for datacenter networking using Ansible and Python

Automating BGP prefix-lists from netbox turns “tribal knowledge + spreadsheets” into a controlled, auditable workflow. In datacenters, prefix-lists are effectively policy: what can be advertised, what can be accepted, and what must never leak. This tutorial shows a safe, advanced workflow to source prefixes from NetBox, validate them with Python, transform them into Cisco prefix-lists with Ansible, and deploy with rollback and verification.


Why centralizing prefix data in NetBox matters for datacenter BGP (data model and ownership patterns)

What to store in NetBox

At minimum, you need a source of truth for:

  • Prefix inventory: ipam/prefixes representing aggregate blocks and sub-allocations.
  • Ownership/intent:
  • Tenant (e.g., platform team vs. customer)
  • Site/region (DC1, DC2)
  • VRF (prod vs. infra; or EVPN L3VNI VRFs)
  • Role (e.g., “bgp-advertise”, “mgmt”, “loopbacks”, “p2p”)
  • Environment safety:
  • Tags like prod, stage, lab
  • Status (active, reserved, deprecated)
  • Custom fields for export control (e.g., export_to_internet: false)

NetBox’s model works well when you treat prefixes as intent rather than “what’s currently in the router config”.

Ownership patterns that scale

A practical ownership pattern:

  • IPAM team owns the prefix objects (creation, uniqueness, VRF correctness).
  • Network team owns export policy selection (tags/roles that indicate advertisement/acceptance intent).
  • Automation owns rendering and deployment, but never invents prefixes.

Concrete guidance:

  • Use tags to choose what is exported:
  • bgp-advertise for outbound announcements
  • bgp-accept for inbound allow-lists (less common; often you want route-policy/AS-path based controls too)
  • Use VRFs to avoid accidentally mixing tenant spaces.
  • Prefer aggregates for eBGP announcements; let IGP/EVPN carry specifics internally.

Designing a safe automation workflow: idempotence, change windows, and dry-run validation

A reliable workflow should answer: “What will change? Is it safe? Can we undo it quickly?”

Workflow checklist (recommended)

  1. Extract relevant prefixes from NetBox (filtered by VRF, status, tags, site).
  2. Validate (Python):
  3. Schema/required fields
  4. Overlaps and duplicates
  5. Environment tagging (no prod prefixes in lab)
  6. Sanity constraints (no RFC1918 exported to internet, no default route unless explicitly allowed)
  7. Render candidate configuration (Ansible template).
  8. Pre-check/diff against device running config.
  9. Change window gate:
  10. Only deploy in approved windows unless emergency override.
  11. Deploy with rollback hooks:
  12. Use device checkpoint / replace / rollback where possible.
  13. For IOS XR and NX-OS, leverage commit-confirm or checkpoint features (platform dependent).
  14. Post-deploy verification:
  15. BGP advertised-routes / received-routes checks
  16. Route-server validation (if present)
  17. Monitoring alerts (prefix count thresholds)

Idempotence expectations

Your automation should produce the same intended prefix-list every run:

  • Sort prefixes deterministically (e.g., by prefix length then network address).
  • Generate numbered seq entries or use deterministic ordering with a consistent seq increment.
  • Remove entries that are no longer present in NetBox (authoritative sync), but only for lists under automation control.

Tip: keep generated lists clearly named and scoped, e.g. PL-NB-PROD-DC1-OUT.

Dry-run validation options

  • Run Python validator in CI and in “operator dry-run” mode locally.
  • Use Ansible --check plus platform diff capabilities where available.
  • Render configs to artifacts and require human approval for “large blast radius” changes (e.g., >N prefixes changed).

Python validation scripts: schema checks, overlapping prefixes, and tagging for environments

A small Python tool can pull from NetBox and enforce policy before Ansible ever touches a router. This reduces risk and makes failures actionable.

Example: validate NetBox prefixes for export

Assumptions:

  • Prefixes must be active
  • Must have tag bgp-advertise
  • Must belong to allowed VRFs (or global table if you explicitly permit it)
  • Must not overlap within the export set
  • Must match environment tags (e.g., prod only)

Notes:
- NetBox API responses vary by version/customization; adjust fields accordingly.
- For large datasets, consider paging and caching.

python
#!/usr/bin/env python3
import os
import sys
import ipaddress
import requests

NETBOX_URL = os.environ.get("NETBOX_URL", "https://netbox.example/api/")
NETBOX_TOKEN = os.environ["NETBOX_TOKEN"]

ALLOWED_ENVS = {"prod"}          # gate deployments by environment
REQUIRED_TAG = "bgp-advertise"
DISALLOW_DEFAULT = True

session = requests.Session()
session.headers.update({
    "Authorization": f"Token {NETBOX_TOKEN}",
    "Accept": "application/json",
})

def nb_get(path, params=None):
    url = NETBOX_URL.rstrip("/") + "/" + path.lstrip("/")
    out = []
    while url:
        r = session.get(url, params=params)
        r.raise_for_status()
        data = r.json()
        out.extend(data.get("results", []))
        url = data.get("next")
        params = None
    return out

def tags_set(obj):
    # NetBox can represent tags as list of objects or strings depending on config/version
    t = obj.get("tags", [])
    if not t:
        return set()
    if isinstance(t[0], dict):
        return {x.get("name") for x in t}
    return set(t)

def validate(prefixes):
    errs = []

    exported = []
    for p in prefixes:
        prefix = p.get("prefix")
        status = p.get("status", {}).get("value") if isinstance(p.get("status"), dict) else p.get("status")
        tset = tags_set(p)

        if status != "active":
            continue
        if REQUIRED_TAG not in tset:
            continue

        env = next((t for t in tset if t in {"prod", "stage", "lab"}), None)
        if env not in ALLOWED_ENVS:
            errs.append(f"{prefix}: environment tag {env} not in allowed {sorted(ALLOWED_ENVS)}")
            continue

        net = ipaddress.ip_network(prefix, strict=False)
        if DISALLOW_DEFAULT and (net.prefixlen == 0):
            errs.append("0.0.0.0/0 or ::/0 not allowed unless explicitly approved")
            continue

        exported.append((net, p))

    # Overlap check (O(n^2) is fine for typical prefix-list sizes; optimize if huge)
    exported_sorted = sorted(exported, key=lambda x: (x[0].version, x[0].prefixlen, int(x[0].network_address)))
    for i in range(len(exported_sorted)):
        net_i, _ = exported_sorted[i]
        for j in range(i + 1, len(exported_sorted)):
            net_j, _ = exported_sorted[j]
            if net_i.version != net_j.version:
                continue
            if net_j.network_address > net_i.broadcast_address:
                break
            if net_i.overlaps(net_j):
                # Overlaps are not always wrong, but in prefix-lists they often imply ambiguity.
                errs.append(f"Overlap detected: {net_i} overlaps {net_j}")

    return errs, [str(n) for n, _ in exported_sorted]

def main():
    # Example filters: tag=bgp-advertise, status=active, site=dc1, vrf_id=...
    # Keep API-side filters for performance, but still enforce in validator.
    prefixes = nb_get("ipam/prefixes/", params={
        "tag": REQUIRED_TAG,
        "status": "active",
        "limit": 2000,
    })

    errs, export_list = validate(prefixes)

    if errs:
        print("VALIDATION FAILED:")
        for e in errs:
            print(f" - {e}")
        sys.exit(2)

    # Emit a simple newline-delimited list (or JSON/YAML if preferred)
    for p in export_list:
        print(p)

if __name__ == "__main__":
    main()

Tagging approach for environments

In practice, enforce a simple rule: every exported prefix must have exactly one environment tag (prod/stage/lab). This prevents “oops, prod prefix leaked into lab router templates” and makes CI gating straightforward.

If you need finer control, add tags like:

  • export-edge vs. export-interdc
  • no-export (explicit deny)
  • customer vs. internal

Then implement precedence rules in the validator (e.g., no-export always wins).


Ansible playbook patterns to transform NetBox output into Cisco prefix-lists and commit with rollback hooks

High-level design

  • Use Ansible to:
  • Query NetBox (or read validated artifacts produced by the Python script/CI)
  • Render prefix-list configuration (Jinja2)
  • Push to devices with platform modules
  • Verify and (if supported) rollback on failure

There are two common patterns:

  1. Artifact-first (recommended): CI runs Python validation, produces prefixes_prod_dc1.txt (or YAML), then Ansible only deploys that exact artifact.
  2. Direct query: Ansible queries NetBox at runtime. Faster iteration, but riskier if NetBox changes between validation and deploy.

Example: Jinja2 template for IOS / IOS XE prefix-list

This renders deterministic sequence numbers. Adjust name, seq spacing, and permit/deny logic as needed.

templates/prefix-list-ios.j2:

jinja2
{% set seq = 10 %}
ip prefix-list {{ prefix_list_name }} description Managed by Ansible from NetBox
{% for p in prefixes %}
ip prefix-list {{ prefix_list_name }} seq {{ seq }} permit {{ p }}
{% set seq = seq + 10 %}
{% endfor %}
!

For NX-OS, syntax is similar (ip prefix-list NAME seq ...). For IOS XR, you’ll typically use prefix-set under route-policy instead of classic prefix-lists; the same concept applies but with different rendering.

Example: Ansible playbook (artifact-first) with diff + basic rollback hook

This example assumes:
- Python validation runs earlier and produces a file with one prefix per line.
- You deploy to IOS XE devices using cisco.ios.ios_config.
- For rollback, you capture “before” config and provide an operator-controlled rollback play (practical and reliable across platforms).

playbooks/deploy_prefixlists.yml:

yaml
---
- name: Deploy NetBox-managed BGP prefix-list to Cisco IOS/IOS XE
  hosts: dc_edge_routers
  gather_facts: false
  vars:
    prefix_list_name: "PL-NB-PROD-EDGE-OUT"
    prefixes_artifact: "artifacts/prefixes_prod.txt"

  pre_tasks:
    - name: Read validated prefixes artifact
      ansible.builtin.slurp:
        src: "{{ prefixes_artifact }}"
      register: prefixes_file
      delegate_to: localhost
      run_once: true

    - name: Parse prefixes into a list
      ansible.builtin.set_fact:
        prefixes: >-
          {{
            (prefixes_file.content | b64decode).splitlines()
            | map('trim')
            | reject('equalto','')
            | list
          }}

    - name: Ensure deterministic ordering
      ansible.builtin.set_fact:
        prefixes: "{{ prefixes | sort }}"

  tasks:
    - name: Backup running-config (local)
      cisco.ios.ios_command:
        commands:
          - show running-config
      register: running
      changed_when: false

    - name: Save pre-change running-config
      ansible.builtin.copy:
        content: "{{ running.stdout[0] }}"
        dest: "backups/{{ inventory_hostname }}-prechange.cfg"
      delegate_to: localhost

    - name: Render prefix-list candidate config
      ansible.builtin.template:
        src: "templates/prefix-list-ios.j2"
        dest: "rendered/{{ inventory_hostname }}-{{ prefix_list_name }}.cfg"
      delegate_to: localhost
      vars:
        prefixes: "{{ prefixes }}"

    - name: Deploy prefix-list config (with diff)
      cisco.ios.ios_config:
        src: "rendered/{{ inventory_hostname }}-{{ prefix_list_name }}.cfg"
        match: line
        diff_against: running
      register: deploy_result

    - name: Post-check: show prefix-list
      cisco.ios.ios_command:
        commands:
          - "show ip prefix-list {{ prefix_list_name }}"
      register: pl_out
      changed_when: false

  post_tasks:
    - name: Print resulting prefix-list
      ansible.builtin.debug:
        var: pl_out.stdout[0]

Notes on rollback hooks (real-world options)

Rollback is platform-specific:

  • IOS XR: commit confirmed (ideal) and rollback features.
  • NX-OS: checkpoints and rollback (checkpoint / rollback running-config checkpoint), depending on version/features.
  • IOS/IOS XE: no universal transactional commit; practical rollback patterns:
  • Maintain a pre-change backup and provide an automated “restore” procedure.
  • Use EEM/apply-revert patterns cautiously (complex and failure-prone).
  • Limit scope: only touch a dedicated prefix-list and verify immediately.

A pragmatic approach is:
- Deploy only to a canary router first.
- Verify BGP advertisements/acceptance.
- Then roll across the fleet.

Transforming NetBox output into multiple lists

Datacenters often need multiple policy views:

  • Per site: PL-NB-PROD-DC1-OUT, PL-NB-PROD-DC2-OUT
  • Per tenant/VRF: PL-NB-PROD-TENANT-A-OUT
  • IPv4 vs IPv6: separate lists

Prefer generating these from filters/tags in validation stage and emitting separate artifacts, e.g.:

  • prefixes_prod_dc1_v4.txt
  • prefixes_prod_dc1_v6.txt

Operational runbook: testing with route servers, CI/CD gating, monitoring, and post-deploy verification

1) Testing strategy (canary + route servers)

If you have route servers (common in EVPN fabrics or internet-edge designs), use them to validate policy safely:

  • Apply new prefix-list to a single edge router or a test VRF first.
  • Validate on route server:
  • Expected prefixes are present
  • No unexpected more-specifics
  • No default route unless intended
  • Confirm eBGP neighbors see the correct advertisements.

2) CI/CD gating

A practical pipeline:

  • Step A: Pull NetBox data (read-only API token)
  • Step B: Validate (Python script)
  • Fail build on overlaps, missing environment tags, forbidden exports
  • Step C: Render configs (Ansible templates locally)
  • Save artifacts
  • Produce a diff summary (prefix count changes)
  • Step D: Approval gate
  • Require manual approval if change magnitude exceeds threshold
  • Step E: Deploy
  • Canary first
  • Then batch rollout

Suggested gates:
- “Prefix count delta > 20%” requires approval
- Any overlap requires approval or explicit allow-list
- Any prefix outside allowed aggregates fails

3) Monitoring

Monitor both intent and effect:

  • Intent: NetBox changes to tagged prefixes (audit trail)
  • Effect:
  • BGP advertised prefix count per neighbor
  • Route-map/prefix-list hit counters (where supported)
  • External monitoring (looking glass / upstream route analytics)

Set alerts:
- “Advertised routes dropped to 0”
- “Advertised routes increased by > X”
- “Unexpected RFC1918 on internet-facing peer”

4) Post-deploy verification commands (examples)

On IOS/IOS XE (edge verification):

text
show ip prefix-list PL-NB-PROD-EDGE-OUT
show ip bgp neighbors <peer-ip> advertised-routes
show ip bgp neighbors <peer-ip> routes
show ip bgp summary

If using route-maps with prefix-lists, also validate attachment points:

text
show running-config | section route-map
show running-config | include prefix-list|route-map|neighbor

Key takeaways

  • netbox is effective as the authoritative source of prefix intent when prefixes are consistently tagged (role + environment) and owned with clear responsibilities.
  • Build a validation-first workflow: Python checks prevent bad data from becoming network outages.
  • Favor artifact-based deployments for repeatability: validate once, deploy exactly what was validated.
  • Ensure idempotence with deterministic ordering and controlled scope (only manage dedicated prefix-lists).
  • Add operational safety: canary deployments, diff checks, monitoring, and a realistic rollback plan per Cisco platform.

Common pitfalls

  • Overlaps and duplicates: easy to introduce in NetBox, dangerous in policy. Detect overlaps in validation and require explicit exceptions.
  • Missing environment tags: leads to prod/lab contamination. Enforce exactly one env tag on exported prefixes.
  • Uncontrolled scope: managing “everything in running-config” is risky. Only manage clearly named, automation-owned lists/policy blocks.
  • Assuming rollback works the same on all Cisco platforms: IOS XE vs NX-OS vs IOS XR have different capabilities. Design rollback per platform, and keep changes small.
  • No post-deploy verification: pushing config without checking BGP outcomes is incomplete automation. Always validate advertised/received routes and prefix counts.
  • NetBox query drift: querying NetBox at deploy time can yield different data than what was reviewed. Prefer validated artifacts or pin to a change identifier (e.g., NetBox change log timestamp) when possible.