CDP Audit
Deep Dive: CDP Network Audit ToolΒΆ
"Production-Ready Python Automation, Built for Scale."ΒΆ
A modular, enterprise-grade network discovery utility that crawls Cisco network topologies via Cisco Discovery Protocol (CDP). It connects (optionally through an SSH jump/bastion host), collects show cdp neighbors detail and show version, parses outputs with TextFSM, enriches with DNS resolution, and writes structured Excel reports from pre-formatted templates.
What's New: The tool has undergone a complete architectural restructure β migrated from a monolithic script to a modular Python package with YAML-based configuration, dedicated modules for each concern, and enhanced maintainability for enterprise deployments.
β¨ HighlightsΒΆ
- Modular Python package β Clean separation of concerns with
cdp_audit/package structure - Package-based execution β Run as
python -m cdp_auditfor proper module resolution - YAML configuration β Human-readable
config.yamlfor easy customization without touching code - Parallel discovery with a worker pool (configurable via environment variables or YAML config)
- Two-tier authentication β Primary user first, then customizable fallback user if primary fails
- Jump server / bastion support β Paramiko channel + Netmiko sock for secure proxy connections
- DNS enrichment β Parallel DNS resolution for discovered hostnames
- Professional Excel reporting β Template-driven reports with multiple sheets and metadata stamping
- Comprehensive validation β Pre-flight checks for templates, Excel files, and configuration
- Dedicated modules β Each module has a single, well-defined responsibility
π― The Nautomation Prime Philosophy in ActionΒΆ
Before diving into the code, understand how every design decision reflects our three core principles:
Principle 1: Modular, Maintainable ArchitectureΒΆ
Every component has a single, clear responsibility. Credentials are handled by credentials.py, discovery by discovery.py, validation by validators.py. This isn't just good practice β it's essential for team collaboration and long-term maintenance. When a bug appears in credential handling, you know exactly where to look.
The entire application is now a proper Python package (cdp_audit/) with:
- Clean imports (
from cdp_audit.credentials import CredentialManager) - Package-level execution (
python -m cdp_audit) - Isolated concerns (each module can be tested independently)
Principle 2: Configuration as Code (the Right Way)ΒΆ
Configuration migrated from hardcoded Python to human-readable YAML. Network engineers can customize worker threads, timeouts, and credential targets without touching Python code. Version control tracks configuration changes. Rollbacks are simple: git revert.
The config_loader.py module provides a clean interface between YAML configuration and Python code, with type safety and validation built in.
Principle 3: Production-Hardened DesignΒΆ
You'll notice patterns like thread locks, exception handling, retry logic, comprehensive validation, and graceful cleanup. These aren't "nice to have" β they're essential for running automation on critical infrastructure without surprises at 3 AM.
The new modular structure makes these patterns easier to implement and maintain:
- Validators run before discovery starts (fail-fast)
- Logging is centralized in
logging_setup.py - Configuration access is centralized in
app_config.py - Each module handles its own error scenarios
Principle 4: Vendor-Neutral FoundationΒΆ
Built on industry-standard libraries: Netmiko (SSH connection handling), Paramiko (SSH tunnelling), Pandas & OpenPyXL (Excel reporting), TextFSM (parsing), PyYAML (configuration). Your skills remain portable.
π§± Repository Architecture (Modular Package Structure)ΒΆ
Key Architectural Change: Migrated from monolithic
main.pyto a modular package structure (cdp_audit/) with separation of concerns, enabling easier testing, maintenance, and team collaboration.Package Execution: The tool now runs as
python -m cdp_audit, which invokescdp_audit/__main__.py, ensuring proper module resolution and import paths.
π¦ RequirementsΒΆ
- Python: 3.8+
- Python packages:
pandas,openpyxl,textfsm,paramiko,netmiko,pyyaml - (Windows only, optional)
pywin32for Windows Credential Manager integration
Install in one go:
Tested DevicesΒΆ
This tool has been tested and verified on the following Cisco IOS and IOS-XE platforms:
- Catalyst 9200 Series
- Catalyst 3650 Series
- Catalyst 3650C
- Catalyst 3650CG
- Catalyst 3650CX
- Catalyst 2960X Series
- Catalyst 2960 Series
Note: The tool should work with any Cisco IOS/IOS-XE device that supports CDP and the required show commands. The devices listed above have been explicitly tested and validated.
Required Support FilesΒΆ
- TextFSM templates:
ProgramFiles/textfsm/cisco_ios_show_cdp_neighbors_detail.textfsmProgramFiles/textfsm/cisco_ios_show_version.textfsm- Excel template:
ProgramFiles/config_files/1 - CDP Network Audit _ Template.xlsx
The validators.py module validates presence of these files at startup and exits if any are missing.
βοΈ Configuration SystemΒΆ
The tool uses a modern, layered configuration system with three configuration sources (in priority order):
Layer 1: config.yaml (Primary Configuration)ΒΆ
All default settings are defined in a human-readable YAML file at the project root.
The config.yaml file provides version-control-friendly configuration that non-programmers can edit. Settings are loaded by ProgramFiles/config_files/config_loader.py, which provides the Config class with type-safe property accessors and validation.
Network Connection Settings:
Credential Settings:
File Paths (Auto-Resolved from Project Root):
Excel Report Customization:
Why YAML Configuration?
YAML Benefits
- Human-Readable: Edit settings without Python knowledge
- Version Control Friendly: Plain text, works seamlessly with Git
- Hierarchical Structure: Logical grouping (network/credentials/paths)
- Comment Support: Inline documentation stays with config
- Type Safety: Config loader validates and provides defaults
- Data-Only: No code execution (safer than Python config files)
- Team Collaboration: Network engineers can customize without developer involvement
Layer 2: Environment Variables (Runtime Overrides)ΒΆ
Environment variables override config.yaml settings at runtime:
| Variable | Description | config.yaml Default |
|---|---|---|
CDP_LIMIT |
Max concurrent worker threads | 10 |
CDP_TIMEOUT |
SSH/auth/read timeouts (seconds) | 10 |
CDP_JUMP_SERVER |
Jump host (IP/hostname). Empty = direct | "" |
CDP_PRIMARY_CRED_TARGET |
CredMan target for primary creds | MyApp/ADM |
CDP_ANSWER_CRED_TARGET |
CredMan target for fallback creds | MyApp/Answer |
LOGGING_CONFIG |
Path to INI logging config | ProgramFiles/config_files/logging.conf |
Example (Windows PowerShell):
When to Use Environment Variables:
- Testing: Temporary overrides without modifying config.yaml
- Multi-Environment Deployments: Different settings for dev/staging/prod
- CI/CD Pipelines: Dynamic configuration from build systems
- Per-Instance Customization: Running multiple instances with different settings
Layer 3: Config Loader (config_loader.py)ΒΆ
The Config class handles all configuration access with validation:
Key Features:
- Automatic Search: Looks for
config.yamlin project root, module directory, and current directory - Environment Variable Priority: Env vars override YAML settings
- Type Conversion: Automatically converts strings to int/bool/Path
- Default Values: Provides sensible defaults if YAML keys missing
- Validation: Fails fast if config file is malformed or missing required files
- Property-Based Access: Use
config.JUMP_HOSTinstead of dict lookups
Example Usage in Code:
Best Practice: Configuration Priority
Use config.yaml for persistent organizational defaults that should be version-controlled. Use environment variables for runtime-specific overrides (testing, different environments) or secrets that shouldn't be committed to Git.
ποΈ Technical ArchitectureΒΆ
The tool operates as a modular Python package with eight primary modules, each with clear responsibilities:
| Module | Responsibility | Why It Matters |
|---|---|---|
__main__.py |
Package entry point for python -m cdp_audit |
Enables proper package execution and import resolution |
cli.py |
Command-line interface and orchestration | Clean separation of CLI logic from business logic |
credentials.py |
Secure credential collection and OS integration | Passwords stay out of code and config files |
discovery.py |
Multi-threaded topology crawling via CDP | Discovers 50+ devices in seconds, not minutes |
excel_reporter.py |
Professional, templated report generation | Maintains business branding and formatting |
validators.py |
Pre-flight checks for templates and configuration | Catches problems early; prevents mid-run failures |
logging_setup.py |
Logging configuration bootstrap | Consistent logging across all modules |
app_config.py |
Centralized configuration access point | Single source of truth for config values |
Additional Support Modules:
| Module | Responsibility | Location |
|---|---|---|
config_loader.py |
YAML configuration loading and validation | ProgramFiles/config_files/ |
config.py |
Backward compatibility wrapper | ProgramFiles/config_files/ |
Architectural PrinciplesΒΆ
Design Philosophy
Separation of Concerns: Each module has a single, well-defined responsibility. Changes to Excel formatting don't affect discovery logic.
Package-Based Organization: The cdp_audit/ package structure enables clean imports, testability, and proper Python packaging.
Dependency Injection: Modules receive configuration objects rather than reading global state. Easier to test and reason about.
Fail-Fast Validation: The validators.py module checks all prerequisites before discovery starts. No more failures after 10 minutes of crawling.
Testability: Each module can be imported and tested independently. Mock the config, test credential logic in isolation.
Module Interaction FlowΒΆ
graph LR
Main[__main__.py] --> CLI[cli.py]
CLI --> Config[app_config.py]
CLI --> Validators[validators.py]
CLI --> Credentials[credentials.py]
CLI --> Discovery[discovery.py]
CLI --> Reporter[excel_reporter.py]
Config --> Loader[config_loader.py]
Loader --> YAML[config.yaml]
Loader --> ENV[Environment Variables]
Validators --> Config
Credentials --> Config
Discovery --> Config
Reporter --> Config
Discovery --> Netmiko[Netmiko/Paramiko]
Reporter --> Excel[openpyxl/pandas]
style Main fill:#e1f5ff
style CLI fill:#e1f5ff
style Config fill:#fff4e6
style Discovery fill:#c8e6c9
style Reporter fill:#f3e5f5
Execution Flow:
__main__.pyis invoked bypython -m cdp_audit__main__.pycallscli.main()to start the applicationcli.pyloads configuration viaapp_config.pyvalidators.pyperforms pre-flight checks (templates exist, Excel valid)credentials.pycollects and validates authentication credentialsdiscovery.pyexecutes threaded network crawlingexcel_reporter.pygenerates professional report from templatecli.pydisplays summary and exit status
Package Entry PointsΒΆ
The tool is executed as a Python package:
1. Package Execution (Recommended):
Uses cdp_audit/__main__.py as the entry point, which calls cli.main().
2. Via Windows Launcher (Easiest for Non-Developers):
Validates environment, activates portable venv, runs the package via python -m cdp_audit.
3. Direct CLI Module Execution (Development/Debug):
Directly calls the CLI module (useful for debugging).
Why Package Execution?
- Proper Import Resolution: Python resolves relative imports correctly
- Standard Practice: Follows Python packaging best practices
- Installable: Can be installed with
pip install -e .for development - Distribution: Can be packaged and distributed via PyPI or private repos
π Credentials Model (credentials.py)ΒΆ
This module handles all credential management with secure OS integration.
The Two-Credential StrategyΒΆ
The tool supports a primary credential and a fully customizable fallback credential:
- Primary credentials (used for jump and device): Read from Windows Credential Manager if present (default target
MyApp/ADM), else prompted. You can optionally save what you type back to Credential Manager. - Fallback credentials (device hop only, jump still uses primary): Username is fully customizable via config.yaml (default:
answer). Password is read from Credential Manager (default targetMyApp/Answer) or prompted; you may choose to save it.
Note: On non-Windows platforms, prompts are used (no Credential Manager).
Customization: Change the fallback username in
config.yamlby settingcdp_fallback_usernameunder thecredentialssection to match your environment (e.g.,localadmin,backup,netops,svc_network).
Why Credential Management MattersΒΆ
The Problem: Network automation requires credentials. Storing them in plaintext files or hardcoding them in scripts is a security nightmare. Even prompting users every time is error-prone and doesn't scale to 10+ discovery jobs daily.
The Solution: Leverage native OS credential stores. Windows has Credential Manager, macOS has Keychain, Linux has pass. These are designed for exactly this use case and integrate with enterprise SSO/password managers.
User Experience: When you run CDP Network Audit for the first time, the script checks Windows Credential Manager for stored credentials. If none are found, it prompts you to enter your username and password. Once you provide them, the script saves them to Windows Credential Manager and uses them for the discovery process. On subsequent runs, the script retrieves the stored credentials automatically without prompting you again.
Credentials Module ArchitectureΒΆ
The credentials.py module exports a single class: CredentialManager.
Key Methods:
| Method | Purpose | Returns |
|---|---|---|
__init__() |
Initialize with config values | N/A |
_read_win_cred(target) |
Read from Windows Credential Manager | (username, password) or (None, None) |
_write_win_cred(target, user, pass) |
Write to Windows Credential Manager | bool |
get_secret_with_fallback(...) |
Orchestrate credential retrieval | (username, password) |
collect_all_credentials() |
Collect both primary and fallback creds | dict |
CredentialManager.__init__()ΒΆ
Line-by-Line:
- Import centralized config from
app_config.py(single source of truth) - Environment variables override YAML config if set (runtime flexibility)
- Three configurable values: primary target, fallback target, and fallback username
- Logger uses
__name__for proper module-level logging
Why This Matters:
- Centralized config access: Uses
app_config.pyinstead of direct Config() instantiation - config.yaml: Human-readable, persistent, version-controlled settings that match your organization's standards
- Environment variables: Runtime overrides for different environments (dev/prod) or testing
- Fallback username: No longer hardcodedβcustomize in config.yaml to match your local accounts
- Module-level logging: Each module logs with its own name for easy filtering
_read_win_cred(target_name: str) -> Tuple[str, str]ΒΆ
Reads encrypted credentials from Windows Credential Manager. Returns (username, password) tuple or (None, None) if not found.
Key Points:
- Only imports
win32credif available (Windows only) - Handles both bytes and string returns for compatibility
- Decodes password from UTF-16LE (Windows internal format)
- Gracefully fails and returns None instead of crashing
- Uses module-level logger for consistent logging
Why This Approach:
- No plaintext storage: Credentials are encrypted by Windows
- Cross-platform: Non-Windows systems skip this and use prompts
- Version-agnostic: Works with multiple pywin32 versions
- Graceful degradation: Fails quietly and falls back to prompts
_write_win_cred(target: str, username: str, password: str) -> boolΒΆ
Writes credentials to Windows Credential Manager for future reuse.
Key Points:
- Password is encoded to UTF-16LE before storage (Windows requirement)
CRED_PERSIST_LOCAL_MACHINEmeans credentials persist across sessions- Failures are logged at DEBUG level (not alarming)
- Returns
Trueon success,Falseon failure
Why This Matters:
- Users can avoid re-prompting on subsequent runs
- Credentials are encrypted and protected by Windows
- Optional save means users control persistence
- Professional metadata (Comment field) for audit trails
get_secret_with_fallback(...) -> Tuple[str, str]ΒΆ
The credential retrieval orchestrator with multi-step fallback:
Two-Credential Model:
- Primary: Your main automation account (flexible username, likely AD-backed)
- Fallback: A secondary user on each device (username customizable in config.yaml, typically a local account)
Why This Design:
- Zero installation friction - first run prompts, subsequent runs use saved credentials
- Two credentials maximize success: primary fails β retry with fallback
- Jump host always uses primary (tighter control)
- Device can fall back to secondary user (local account)
- Fully customizable to match your organization's account naming standards
collect_all_credentials() -> dictΒΆ
High-level method to collect all required credentials:
Returns a clean dictionary that discovery modules can consume without understanding credential logic.
π Validators Module (validators.py)ΒΆ
The validators.py module performs pre-flight checks before discovery begins.
Why Pre-Flight Validation MattersΒΆ
The Problem: Discovering 50+ devices over 10 minutes, then failing because an Excel template is missing or corrupt is frustrating and wastes time.
The Solution: Validate everything before discovery starts. Fail fast with helpful error messages.
Module FunctionsΒΆ
When Used:
The cli.py module calls validate_all() before proceeding with credential collection and discovery:
Benefits:
- Catches configuration errors immediately
- Provides clear, actionable error messages
- Prevents wasted time on doomed discovery runs
- Professional UX with checkmarks and crosses
π Discovery Module (discovery.py)ΒΆ
The heart of the tool: multi-threaded network discovery via CDP.
Why Parallel Discovery is EssentialΒΆ
The Problem: Discovering 50+ switches serially takes 10+ minutes. Each SSH connection is a round-trip: connect, execute, disconnect.
The Solution: Thread pool with 10 concurrent workers = 5x faster. 10 simultaneous connections instead of waiting for each one.
Discovery Module ArchitectureΒΆ
The discovery.py module exports a single class: NetworkDiscoverer.
Key Components:
- Thread-safe data structures (locks protect shared state)
- Worker thread pool for parallel device connections
- CDP parsing and intelligent queueing heuristics
- Jump server support (Paramiko + Netmiko)
- Retry logic for transient failures
- DNS resolution in parallel
Thread-Safe Data AccumulatorsΒΆ
Why Two Locks?
- If we used one lock for everything, threads would block each other constantly
- Granular locks allow more independent work
visited_lockfor quick "is this already being processed?" checksdata_lockfor appending results (slower operations)
Data Structure Choices:
- Lists for ordered results (
cdp_neighbor_details) - Sets for deduplication (
visited,hostnames,authentication_errors) - Dicts for key-value lookups (
connection_errors,dns_ip)
Jump Server SupportΒΆ
The discovery module supports both direct and jump-mediated connections.
_paramiko_jump_client(...) -> paramiko.SSHClientΒΆ
Creates a secure SSH connection to a jump/bastion host.
Key Design Choices:
WarningPolicy()- Log warnings for unknown hosts (safer than AutoAddPolicy)- Explicit password auth only - No SSH keys or agent (easier to audit)
- Consistent timeouts - All operations respect
DEFAULT_TIMEOUTsetting - Re-raise auth failures - Let caller handle credential issues
Why WarningPolicy?
- Accepts unknown hosts but logs warnings
- Catches potential man-in-the-middle attacks without crashing
- Production-ready security posture
- Better than
AutoAddPolicy(too permissive) orRejectPolicy(too strict for dynamic environments)
_netmiko_via_jump(...) -> ConnectHandlerΒΆ
The core connection function. Handles both direct and jump-host connections.
Credential Logic:
Why This Two-Credential Model:
- Jump host always uses primary (tightest control)
- Device can use fallback if primary fails (username customizable in config.yaml)
- Resilience: if your primary account is locked, fallback account can still work
- Flexibility: adapt to your organization's local account naming conventions
Direct Connection: Simply pass device IP to Netmiko.
Jump-Mediated Connection:
- Open Paramiko SSH to jump host
- Create
direct-tcpipchannel (SSH tunnel) through jump to target - Wrap channel as socket
- Pass socket to Netmiko for SSH auth
Why direct-tcpip?
- No need to open a listener on the jump host
- No port forwarding configuration required
- All traffic is inside the already-authenticated SSH session
- Secure and clean
- Standard SSH tunneling mechanism
Device Command ExecutionΒΆ
run_device_commands(...) -> Tuple[str, str]ΒΆ
Executes CDP and version commands on target device with fallback credentials.
Strategy:
- Try with primary credentials
- On auth failure, catch and retry with fallback (customizable fallback user on device)
- Don't retry auth failures (credentials won't change between attempts)
- Do retry transient errors (timeouts, SSH glitches) up to 3 times
- Always disconnect in finally block (prevent socket leaks)
Why This Approach:
- Maximizes success rate with two-credential strategy
- Transient timeouts are retried (network glitches happen)
- Auth failures fail-fast (no point retrying)
- Finally block ensures resource cleanup (even on exceptions)
- Separate tracking for auth vs. connection errors
Parsing and Intelligent QueueingΒΆ
parse_outputs_and_enqueue_neighbors(...)ΒΆ
This is the intelligence of the discovery engine. It decides which devices to audit next.
Three-Step Process:
Step 1: Parse Device ContextΒΆ
- Extract hostname, serial, uptime from
show version - Fall back to IP if parsing fails
- This becomes the "LOCAL_HOST" in CDP entries
Step 2: Parse CDP NeighborsΒΆ
- Extract each neighbor's details (ports, capabilities, management IP)
- Store in thread-safe list
- Add hostnames to DNS resolution set
Step 3: Apply Queueing HeuristicΒΆ
Only enqueue if ALL three conditions are true:
Why "Switch" in caps?
- CDP capability strings like "Switch Router" identify infrastructure
- We only want to audit infrastructure nodes, not endpoints
Why "Host" not in caps?
- IP phones, printers, cameras also show up in CDP
- Their capability includes "Host" but we can't/shouldn't manage them
Why mgmt_ip?
- If a device doesn't advertise a management IP, we have no way to SSH to it
- Queueing it would just cause failures
Example:
Deduplication:
- Check both IP and hostname against
visitedset - Prevents discovering same device multiple times
- Thread-safe with
visited_lock
Worker Thread PoolΒΆ
discover_worker() -> NoneΒΆ
The worker thread function. Multiple instances run concurrently.
Loop:
- Get next host from queue (timeout=1.0 prevents hangs)
- Recognize sentinel (None = shutdown signal)
- Check if already visited (prevent duplicate work)
- Execute discovery with up to 3 retries
- Parse outputs and enqueue new neighbors
- Always call
task_done()or queue.join() will hang
Why Sentinel Pattern?
- None signals worker to exit gracefully
- Main thread sends one sentinel per worker
- Coordinated shutdown without races
- Clean alternative to threading.Event
Why Check If Already Visited?
- Concurrent workers might both process same IP
- Prevent duplicate discovery work
- Track with hostname and IP
- Race condition: device added to visited but not yet processed
Why task_done() Is Critical:
Without task_done(), queue.join() waits forever on main thread. This is a common source of hangs in multi-threaded code!
Discovery OrchestrationΒΆ
discover_from_seeds(seeds: List[str]) -> NoneΒΆ
Main orchestration function that kicks off discovery.
Flow:
- Add seeds to queue and mark as visited
- Start worker threads (count from config)
- Wait for queue to empty (
queue.join()) - Send shutdown signals (one None per worker)
- Wait for workers to finish (
thread.join())
Why This Approach:
- Main thread waits for all work to complete
- Graceful shutdown with sentinels
- Workers clean up naturally
- No orphaned threads
DNS ResolutionΒΆ
resolve_dns_parallel() -> NoneΒΆ
After discovery, resolve all discovered hostnames to IPs in parallel.
Design:
- ThreadPoolExecutor with 4-32 workers (based on CDP_LIMIT)
- Submit all resolutions concurrently
- Collect results as they complete (don't wait for slowest)
- Best-effort - failures are logged but don't block
Why Separate from Discovery?
- DNS lookups are independent
- Can run in smaller thread pool (4-32 vs. 10)
- Doesn't block discovery if DNS is slow
- Runs after discovery completes (no interference)
Why ThreadPoolExecutor?
- Higher-level API than threading.Thread
- Automatic worker management
- Clean future-based result collection
- Built-in exception handling
π Excel Reporter Module (excel_reporter.py)ΒΆ
Professional, template-driven Excel report generation.
Why Professional Reporting MattersΒΆ
The Problem: Raw CSV or unformatted Excel is not useful for business. Reports need context, formatting, branding.
The Solution: Use a pre-formatted Excel template. Write data into it while preserving all formatting, charts, filters, and branding.
Excel Reporter Module ArchitectureΒΆ
The excel_reporter.py module exports a single class: ExcelReporter.
Template-Driven ApproachΒΆ
Step 1: Copy TemplateΒΆ
Preserve metadata (timestamps, permissions) with copy2.
Step 2: Stamp MetadataΒΆ
Fill cells B4-B8 with audit metadata (configured in YAML).
Step 3: Append DataΒΆ
Use if_sheet_exists="overlay" mode to append without destroying template.
Why This Approach:
- Template-driven: Business controls formatting without touching code
- Non-destructive: Data is appended, template is preserved
- Professional: Charts, filters, styling all maintained
- Automated: No manual Excel editing required
- Configurable: Cell locations and sheet names in YAML
Key Parameters:
startrow: Where to start writing data (after headers/metadata)header=False: Don't write column headers (template has them)index=False: Don't write row numbersmode='a': Append modeif_sheet_exists='overlay': Write over existing cells without deleting sheet
ποΈ CLI Module (cli.py)ΒΆ
The command-line interface and main application orchestrator.
Module ResponsibilitiesΒΆ
- Parse command-line arguments
- Orchestrate the discovery workflow
- Display progress and results
- Handle user interrupts gracefully
Main WorkflowΒΆ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | |
Execution Flow:
- Set up logging (
logging_setup.py) - Run validators (
validators.py) - Collect user inputs (site name, seeds)
- Collect credentials (
credentials.py) - Determine jump host (from config or prompt)
- Execute discovery (
discovery.py) - Resolve DNS in parallel
- Generate Excel report (
excel_reporter.py) - Display summary and exit
Error Handling:
- Keyboard interrupt (Ctrl+C) returns exit code 130
- Validation failures return exit code 1
- Exceptions are logged with full traceback
β‘ Package Entry Point (__main__.py)ΒΆ
The package entry point enables python -m cdp_audit execution.
Why This File?
- Enables package-based execution
- Python looks for
__main__.pywhen you runpython -m <package> - Clean separation:
cli.pyhas the logic,__main__.pyis just the entry point - Follows Python packaging best practices
π οΈ Configuration Access (app_config.py)ΒΆ
Centralized configuration access point for all modules.
Why This Module?
- Single source of truth: All modules import from here
- Lazy initialization: Config is only loaded once
- Easy mocking: Tests can replace this module to inject test config
- Clean imports:
from cdp_audit.app_config import configinstead of creating Config() everywhere
π Logging Setup (logging_setup.py)ΒΆ
Bootstrap logging for the entire application.
Features:
- Tries to load
logging.confif it exists - Falls back to basic console logging
- Respects
LOGGING_CONFIGenvironment variable - Non-blocking: failures don't crash the app
π Quick Start: Using the Launcher (Recommended)ΒΆ
The repository includes a professional Windows batch launcher (run.bat) that provides the easiest way to run the tool.
Why Use the Launcher?ΒΆ
- Zero configuration required - Just double-click or run from command line
- Automatic validation - Checks for Python environment and required files before execution
- Helpful diagnostics - Clear error messages if something is missing
- Professional interface - Clean output with status indicators and progress messages
- Safe execution - Validates environment before running the script
Using run.batΒΆ
Option 1: Double-clickΒΆ
Simply double-click run.bat in Windows Explorer to launch the tool with default behavior.
Option 2: Command LineΒΆ
This runs the CDP Network Audit with all default settings from config.yaml.
What the Launcher DoesΒΆ
1 Validates the environment:
- Checks that the portable_env virtual environment exists
- Verifies Python executable is present
- Confirms cdp_audit package exists
- Validates required TextFSM templates are present
- Validates Excel template exists
2. Provides clear feedback:
- Shows [OK] for successful checks
- Shows [WARNING] for missing optional files
- Shows [ERROR] for critical missing components
- Displays helpful troubleshooting tips on failure
3. Runs the tool:
- Activates the virtual environment
- Executes python -m cdp_audit
- Captures and displays the exit code
- Provides common troubleshooting tips if errors occur
Example OutputΒΆ
π How to Run (Interactive Flow)ΒΆ
- Ensure templates and Excel file exist under
ProgramFiles/...(see above). - (Optional) Customize
config.yamlwith your organization's defaults. - (Optional) Set environment variables as needed for runtime overrides.
- Run:
- Follow prompts:
- Site name (used in the output filename)
- Seed devices (comma-separated IPv4 / resolvable hostnames)
- Primary credentials (reads from CredMan if present; else prompts; optional save)
- Fallback password (username from
config.yaml; reads from CredMan if present; else prompts; optional save) - Jump server (from
config.yaml, env var, or prompt; blank = direct)
The tool validates/normalizes seeds to IP addresses, de-duplicates them, then starts the threaded discovery.
π§ͺ What Gets CollectedΒΆ
For each visited device the tool attempts to collect:
show version(hostname, serials, uptime) β for local context.show cdp neighbors detailβ parsed into structured rows.- DNS resolution for all discovered hostnames (best-effort), in parallel.
Discovery HeuristicsΒΆ
- Only Switch-capable CDP entries (and not hosts) with a management IP are queued as crawl candidates.
- Deduplication is performed by hostname and IP to reduce churn.
- Each target is retried up to 3 times for transient connectivity issues.
π Jump Server BehaviorΒΆ
- Set
jump_hostin thenetworksection ofconfig.yamlto specify a default jump host. - Alternatively, use the
CDP_JUMP_SERVERenvironment variable to override at runtime. - If empty, you will be prompted during runtime; leaving it blank uses direct device connections.
- The jump is created with Paramiko and a
direct-tcpipchannel; Netmiko is then bound to that channel (no local listener required).
Note: Host key policy defaults to a warning (accepts unknown keys but logs a warning). For production environments, prefer strict host key checking via
known_hostsmanagement.Tip: Configure your jump server in
config.yaml(jump_host: "192.0.2.10") for permanent use, or leave it empty to be prompted each time for flexibility.
π Excel Report OutputΒΆ
An output file named <site_name>_CDP_Network_Audit_<timestamp>.xlsx is created by copying the template.
SheetsΒΆ
- Audit β Main CDP dataset. Also stamped with metadata:
B4: Site nameB5: DateB6: TimeB7: Primary seedB8: Secondary seed (or "N/A")- DNS Resolved β Two columns:
Hostname,IP Address - Authentication Errors β One column:
Authentication Errors(IP list) - Connection Errors β Two columns:
IP Address,Error
Columns in Audit (Data Rows)ΒΆ
LOCAL_HOST, LOCAL_IP, LOCAL_PORT, LOCAL_SERIAL, LOCAL_UPTIME, DESTINATION_HOST, REMOTE_PORT, MANAGEMENT_IP, PLATFORM.
Note: The template governs formatting/filters/charts (if any). The writer appends data starting at the appropriate row offsets to preserve the layout.
π Key Design PatternsΒΆ
Pattern 1: Modular Package StructureΒΆ
Each module has a single responsibility. Changes to one don't affect others.
Pattern 2: Centralized ConfigurationΒΆ
Configuration is centralized and consistent across all modules.
Pattern 3: Thread-Safe Data AccumulationΒΆ
Only one thread updates shared data at a time.
Pattern 4: Graceful Worker ShutdownΒΆ
Coordinated shutdown without race conditions.
Pattern 5: Retry with Fallback CredentialsΒΆ
Maximizes success rate with two-credential strategy.
Pattern 6: Resource Cleanup in FinallyΒΆ
Prevents resource leaks even on exceptions.
Pattern 7: Template-Driven ReportingΒΆ
Copy β stamp metadata β append data using overlay mode.
Pattern 8: Fail-Fast ValidationΒΆ
Catch problems before expensive operations begin.
π§° LoggingΒΆ
- Logging is configured via
logging_setup.pymodule - If a
logging.conffile is present, logging is configured vialogging.config.fileConfig() - Otherwise, a basic console logger is configured at INFO with timestamps
- You can set
LOGGING_CONFIGenvironment variable to point to an INI file anywhere - Each module logs with its own name for easy filtering
π§― Errors & Retry BehaviorΒΆ
- Authentication failures: the host is recorded under Authentication Errors
- Connectivity/timeouts: the host is recorded under Connection Errors with the last error tag (e.g.,
NetmikoTimeoutException,SSHException,socket.timeout) - Retries: up to 3 attempts for each device before recording a connection error
- Graceful exit: workers always
task_done()to avoid queue hangs
π PerformanceΒΆ
- Worker threads =
CDP_LIMIT(default 10) - DNS resolution runs in a small parallel pool after discovery
- Use a conservative limit on older/CPU-bound platforms; increase on fast links
π Security ConsiderationsΒΆ
- Prefer Credential Manager (Windows) or other secret stores instead of plaintext
- Ensure jump host is hardened; consider strict host key verification
- Output workbooks can contain sensitive topology data β share on a need-to-know basis
β Exit CodesΒΆ
- 0 β Success
- 1 β Required TextFSM or Excel template missing / invalid / fatal error
- 130 β Interrupted by user (Ctrl+C)
β Example SessionΒΆ
π οΈ Customization PointsΒΆ
- User settings: Edit
config.yamlto customize worker threads, timeouts, jump server, credential targets, and fallback username - Template paths: Adjust in
config.yamlunder thefile_pathssection - Queueing heuristics (which neighbors to crawl): Modify
parse_outputs_and_enqueue_neighbors()indiscovery.py - Retry counts / timeouts: Configure in
config.yamlundernetworkor override via environment variables - Logging: Provide a
logging.confthat matches your standards (path configurable viaLOGGING_CONFIGenv var) - Fallback account: Set
cdp_fallback_usernameinconfig.yaml(undercredentials) to match your local admin account naming - Excel formatting: Customize cell locations and sheet names in
config.yamlunderexcel
Example config.yaml Customization:
π Learning OutcomesΒΆ
After studying this code, you should understand:
β
Modular Python packaging β How to structure a package with __main__.py and proper imports
β
Concurrent programming β How thread pools and locks prevent race conditions
β
SSH tunneling β How direct-tcpip channels work and why they're safer
β
Credential management β OS-level credential stores vs. plaintext files
β
TextFSM parsing β How to extract structured data from CLI output
β
Error handling β Retry strategies and graceful degradation
β
Excel automation β Template-driven reporting with data overlay
β
Network discovery β CDP heuristics and neighbor crawling logic
β
Configuration management β YAML config with environment variable overrides
β
Separation of concerns β Single-responsibility modules for maintainability
π Distribution & ExecutionΒΆ
Consistent with the Nautomation Prime delivery model, this tool is available in multiple formats:
-
Zero-Install Portable Bundle: A self-contained package including the Python interpreter and all libraries (Netmiko, Pandas, TextFSM) for use on restricted Windows jump boxes.
-
Scheduled Docker Appliance: A pre-built container designed for autonomous execution and periodic auditing.
-
Installable Package: Can be installed with
pip install -e .for development or packaged for PyPI distribution.
π Repository & DownloadsΒΆ
Ready to audit your own network? Access the hardened source code and pre-configured templates below.
- View Full Repository: Access the code, TextFSM templates, and Excel master.
- Download Latest Release: Get a clean ZIP of the production-ready files.
π Performance TuningΒΆ
| Scenario | Configuration | Rationale |
|---|---|---|
| Fast LAN, many devices | CDP_LIMIT=20, CDP_TIMEOUT=5 |
High concurrency, short timeouts work |
| Slow WAN link | CDP_LIMIT=5, CDP_TIMEOUT=30 |
Fewer threads prevent overwhelming network; higher timeout for round-trip delay |
| Mixed (some LAN, some WAN) | CDP_LIMIT=10, CDP_TIMEOUT=10 |
Balanced defaults |
| Device with high CPU | CDP_LIMIT=3-5 |
Fewer threads prevent overwhelming device |
π¬ Next StepsΒΆ
- Clone the repository:
git clone https://github.com/Nautomation-Prime/Cisco_CDP_Network_Audit - Install dependencies:
pip install -r requirements.txt - Customize config.yaml to match your environment:
- Set
jump_hostundernetworksection - Customize
cdp_fallback_usernameundercredentialssection - Adjust
default_limitanddefault_timeoutundernetworksection - Configure credential targets if different from defaults
- Read the README for installation and configuration details
- Set up credentials in Windows Credential Manager (or let the script prompt you on first run)
- Run your first discovery:
python -m cdp_audit - Review the Excel output to understand the report format
Once comfortable, customize the discovery heuristics and template for your specific topology.
Example config.yaml for Enterprise Use:
οΏ½ Related ResourcesΒΆ
Get Started Now:
- π Script Library β Find the CDP Network Audit tool and other automation scripts
- GitHub Repository β Source code, issues, and contributions
Learn More:
- π οΈ Nornir Fundamentals β Multi-device automation patterns complementary to threading
- π PRIME Framework β Understand the methodology behind this tool
Explore Similar Topics:
- Access Switch Port Audit Deep Dive β Another production tool focused on interface health and PoE
οΏ½π LicenseΒΆ
GNU General Public License v3.0
π€ AuthorΒΆ
Christopher Davies
Mission: To empower network engineers through the PRIME Frameworkβdelivering automation with measurable ROI, production-grade quality, and sustainable team capability built on the PRIME Philosophy of transparency, measurability, ownership, safety, and empowerment.