Elshayib/Audnet

GitHub: Elshayib/Audnet

Audnet 是一款基于 SSH 的自动化网络设备安全合规审计工具,用于检测多供应商网络环境中的配置漂移问题。

Stars: 0 | Forks: 2

# 网络安全与合规审计工具 [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue.svg)](https://www.python.org/downloads/) [![CI](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/ccc1cabc82233902.svg)](https://github.com/Elshayib/Audnet/actions/workflows/ci.yml) [![Release](https://img.shields.io/github/v/release/Elshayib/Audnet)](https://github.com/Elshayib/Audnet/releases/latest) [![PyPI](https://img.shields.io/pypi/v/audnet.svg)](https://pypi.org/project/audnet/) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/audnet.svg)](https://pypi.org/project/audnet/) ``` ┌─────────────────────────────────────────────────────────────────┐ │ AUDNET ARCHITECTURE │ │ │ │ ┌──────────┐ ┌──────────────┐ ┌────────────────────┐ │ │ │ YAML │───▶│ Collector │───▶│ TextFSM Parser │ │ │ │ Inventory│ │ (Netmiko + │ │ (CLI → JSON) │ │ │ │ +Baseline│ │ ThreadPool)│ └────────┬───────────┘ │ │ └──────────┘ └──────┬───────┘ │ │ │ │ ▼ │ │ │ ┌────────────────────┐ │ │ │ │ Compliance Engine │ │ │ │ │ (11 Security Rules)│ │ │ │ │ │ │ ▼ ▼ │ │ ┌──────────────────┐ ┌────────────────────┐ │ │ │ DeviceSnapshot │──▶│ Report Generator │ │ │ │ (Pydantic) │ │ (Jinja2 → MD/HTML) │ │ │ └──────────────────┘ └────────────────────┘ │ │ │ │ │ ▼ │ │ ┌────────────────────┐ │ │ │ audit_report.md │ │ │ │ audit_report.html │ │ │ └────────────────────┘ │ │ │ │ Parallel SSH ──▶ 4 devices concurrently (configurable) │ │ All layers independently testable — no real hardware needed │ └─────────────────────────────────────────────────────────────────┘ ``` ## 问题描述 在生产网络中,配置漂移是不可避免的。工程师们会进行手动更改, 绕过安全基线——例如启用 SSHv1、将交换机端口保留在默认 VLAN 上, 或将 NTP/syslog 指向未经授权的服务器。传统的审计方式是手动的、 容易出错的,且无法大规模扩展。 **audnet** 通过自动执行基于 SSH 的安全基线合规审计来解决此问题。 这可以实时检测漂移,并通过执行强化策略来防止未来的漂移。 ## 解决方案 一个 Python CLI 工具,它可以: 1. **并行连接**到多个路由器/交换机,通过 SSH(Netmiko + ThreadPool,支持重试) 2. **获取实时状态** —— `show ip interface brief`、`show version`、`show running-config` 3. **将非结构化 CLI** 解析为干净的 JSON(使用 TextFSM 模板) 4. **根据基线进行审计** —— 标记 SSHv1、未授权的 VLAN、未授权的 NTP/syslog 服务器 5. **生成报告** —— 专业的 Markdown 和 HTML 报告,包含通过/失败摘要 6. **支持过滤器和 JSON**,可用于定向运行和 CI 集成 7. **跟踪历史记录** —— 基于存储的 SQLite 审计历史记录,具备漂移/回归检测功能 8. **基于 Git 的配置历史** —— 版本化的设备配置快照,支持 diff 和回滚 9. **多供应商** —— Cisco IOS/XE/NX-OS、Arista EOS、Juniper JunOS、Palo Alto PAN-OS 10. **NetBox 资产清单** —— 通过 NetBox API 实现动态设备清单 11. **Docker 就绪** —— 支持基于 cron 定时任务的容器化审计 每一层都可以通过模拟响应进行独立测试——不需要真实的网络硬件。 ## 安装 ### 快速安装(普通用户) ``` # 从 PyPI 安装(推荐用于生产环境) pip install audnet # 或者使用 uv uv tool install audnet # 从源码安装(最新开发版本) pip install git+https://github.com/Elshayib/Audnet.git # 验证 audnet --version ``` ### 开发环境设置(贡献者) ### 前置条件 - Python 3.12+ - [uv](https://docs.astral.sh/uv/) 包管理器 - Linux/macOS 环境 ### 逐步设置 ``` # 1. Clone 仓库 git clone https://github.com/Elshayib/Audnet.git cd audnet # 2. 安装依赖(使用 uv.lock 确保可复现的安装) uv venv uv pip install -e ".[dev]" # 3. 激活虚拟环境 source .venv/bin/activate # 4. 安装 pre-commit hooks pre-commit install # 5. 验证安装 python -c "import audnet; print(audnet.__version__)" # 6. 运行测试套件 pytest tests/ -v ``` `uv pip install -e ".[dev]"` 会读取已提交的 `uv.lock` 文件,以便在所有环境中安装完全相同的依赖版本。在添加新的依赖后,请使用 `uv lock`(不带参数)重新生成锁定文件。 ### 快速开始 ``` # 对示例 inventory 执行 dry run —— 不建立 SSH 连接 audnet audit --dry-run # 对您的设备执行真实的审计 audnet audit --inventory inventories/devices.yaml --baseline baselines/security_baseline.yaml ``` ### 配置设备清单 在 `inventories/devices.yaml` 中编辑你的网络设备: ``` defaults: device_type: cisco_ios port: 22 devices: - name: core-router-01 host: 192.168.1.1 username: admin password: "${AUDNET_PASSWORD}" # resolved from environment ``` 通过环境变量设置密码: ``` export AUDNET_PASSWORD="your-secure-password" ``` #### 基于 SSH 密钥的身份验证 代替密码验证,使用 SSH 密钥: ``` devices: - name: core-router-01 host: 192.168.1.1 username: admin use_keys: true key_file: ~/.ssh/id_ed25519 ``` - `use_keys: true` — 启用 SSH 密钥验证 - `key_file` — 私钥文件路径(可选;如果省略,则使用 SSH 代理或默认密钥) #### NetBox 动态清单 直接从 NetBox 获取设备,而不是使用静态 YAML 文件: ``` export NETBOX_TOKEN="your-netbox-token" audnet audit --inventory "netbox://netbox.example.com?site=dc1&role=router" ``` 有关完整详细信息,请参阅下方的 [NetBox 清单](#netbox-dynamic-inventory)部分。 ### 自定义安全基线 编辑 `baselines/security_baseline.yaml` 以匹配你所在组织的策略: ``` checks: ssh_version: severity: critical rule: ssh_v2_only inactive_ports: severity: high rule: no_open_ports allowed_vlans: [10, 20, 30] # your secure VLANs ntp_config: severity: medium rule: ntp_approved approved_servers: - 10.0.0.50 syslog_config: severity: medium rule: syslog_approved approved_servers: - 10.0.0.60 ``` ## 使用说明 ### 运行全面审计 ``` source .venv/bin/activate audnet audit \ --inventory inventories/devices.yaml \ --baseline baselines/security_baseline.yaml \ --output audit_report \ --format both \ --workers 4 ``` ### 高级用法 过滤到单个设备或特定检查项,输出 JSON 用于脚本化处理: ``` audnet audit --device core-router-01 --check ssh_v2_only,ntp_config --json ``` ### 异步模式(推荐用于 >20 台设备) 基于 asyncio 的收集器使用 `asyncssh`,以降低内存开销并提高 可扩展性。建议在涉及超过 20 台设备的审计中使用此模式。 ``` audnet audit --async ``` 与默认的同步收集器相比的权衡: | | 同步(默认) | 异步(`--async`) | |---|---|---| | 依赖项 | Netmiko | asyncssh | | 并发模型 | ThreadPool | asyncio Semaphore | | 最适合 | <20 台设备 | >20 台设备 | | 单连接内存占用 | 较高(线程栈) | 较低(协程) | ### 使用示例 以下所有示例均使用默认的清单和基线路径。请根据需要进行调整。 #### 审计单台设备 ``` audnet audit --device core-router-01 ``` #### 仅运行特定检查 在单个 `--check` 中使用逗号分隔的值: ``` audnet audit --check ssh_v2_only,ntp_config ``` 或者重复使用该标志: ``` audnet audit --check ssh_v2_only --check ntp_config ``` #### 用于 CI/CD 流水线的 JSON 输出 ``` audnet audit --json ``` 输出示例: ``` [ { "device_name": "core-router-01", "overall_pass": true, "checks": [ {"check_name": "ssh_v2_only", "passed": true, "detail": "SSHv2 configured"}, {"check_name": "ntp_approved", "passed": false, "detail": "unauthorized NTP: 10.0.0.99"} ] } ] ``` 通过管道传递给 `jq` 进行定向查询: ``` audnet audit --json | jq '.[] | select(.overall_pass == false) | .device_name' ``` #### 试运行模式 验证你的配置而无需连接设备: ``` audnet audit --dry-run ``` 与过滤器结合使用以预览定向运行: ``` audnet audit --dry-run --device core-router-01 --check ssh_v2_only ``` #### CI 的严格模式 如果任何设备包含明文密码(没有 `${ENV_VAR}` 引用),则立即失败。 检查 `password`、`secret`、`passwd` 和 `token` 字段: ``` audnet audit --strict ``` 如果不使用 `--strict`,系统将仅记录警告而不会失败。 #### 详细的调试日志 ``` audnet audit -v --dry-run ``` #### 组合使用:单台设备、特定检查、JSON、严格模式 ``` audnet audit --device core-router-01 --check ssh_v2_only --json --strict ``` #### 允许出现合规失败而不返回非零退出码 默认情况下,如果合规性检查失败,audnet 会以退出码 1 退出。使用 `--no-fail` 可始终以退出码 0 退出(当你想要获取报告但不希望破坏 CI 时非常有用): ``` audnet audit --no-fail ``` #### 审计历史记录 从 SQLite 历史数据库中查询过去的审计运行记录: ``` # 显示最近 20 次运行 audnet history # 显示特定设备的最近 5 次运行 audnet history --device core-router-01 --last 5 # 显示过去 7 天的运行记录 audnet history --since 7d # 仅显示失败的运行 audnet history --status fail # JSON 输出 audnet history --format json ``` #### 基于 Git 的配置历史 每次成功的审计都会自动将已脱敏的设备运行配置快照存入 Git 仓库 (默认路径:`~/.net-audit/git-config-history`)。配置在存储前会进行脱敏处理 —— 密码、 密钥、团体字符串以及私钥块都会被隐去。 ``` # 查看设备的 Git config 历史 audnet history-log --device core-router-01 # 显示特定时间点的 config audnet history-show --device core-router-01 --ref HEAD~3 # 两个时间点之间的 Diff audnet history-diff --device core-router-01 --from HEAD~1 --to HEAD # 预览 rollback(默认为 dry-run) audnet rollback --device core-router-01 --ref HEAD~1 # 实际执行 rollback audnet rollback --device core-router-01 --ref HEAD~1 --no-dry-run ``` 使用自定义 Git 仓库路径: ``` audnet audit --git-history-dir /path/to/config-repo ``` 推送到远程仓库以实现团队共享的集中式历史记录: ``` # 一次性配置远程仓库 cd ~/.net-audit/git-config-history git remote add origin git@github.com:yourorg/config-history.git # 然后使用 --git-push 进行审计,以在每次 snapshot 后 push audnet audit --git-push ``` 在某次运行中跳过 Git 历史记录: ``` audnet audit --no-git-history ``` #### 列出支持的供应商 列出所有已注册的供应商设备类型: ``` audnet list-vendors ``` #### 列出检查项 列出所有可用的合规规则名称: ``` audnet list-checks ``` #### 显示版本号 ``` audnet version ``` ### 输出示例 ``` $ audnet audit --inventory inventories/devices.yaml [INFO] Loaded 2 devices from inventory [INFO] Connecting in parallel (workers=4)... core-router-01: ✓ passed (4/4 checks) dist-switch-02: ✗ failed (SSHv1 enabled, Gi0/3 on unauthorized VLAN 1) Report: audit_report.md + audit_report.html generated. Summary: 1 passed, 1 with issues. ``` ### CLI 选项 #### `audit` 子命令 | 选项 | 默认值 | 描述 | |--------|---------|-------------| | `--inventory` | `inventories/devices.yaml` | 设备清单 YAML 路径,或 `netbox://` URL | | `--baseline` | `baselines/security_baseline.yaml` | 安全基线 YAML 路径 | | `--output` | `audit_report` | 输出文件前缀 | | `--format` | `both` | 输出格式:`md`、`html` 或 `both` | | `--workers` | `4` | 最大并行 SSH 连接数 | | `--device` | (全部) | 按名称过滤到单个设备 | | `--check` | (全部) | 过滤到特定检查项(可重复;支持逗号分隔) | | `--json` | `false` | 将 JSON 摘要输出到 stdout | | `--dry-run`, `-n` | `false` | 验证配置而不连接到设备 | | `--strict` | `false` | 遇到明文密码(无 `${ENV_VAR}` 引用)即失败 | | `--no-fail` | `false` | 即使合规检查失败也以代码 0 退出 | | `-v`, `--verbose` | `false` | 启用带有控制台输出的调试日志 | | `--async` | `false` | 使用 asyncio 收集器 (asyncssh) —— 推荐用于 >20 台设备 | | `--connect-timeout` | `30` | SSH 连接超时时间(以秒为单位) | | `--timeout` | (无) | 单台设备收集的实际耗时超时(以秒为单位) | | `--history-dir` | `~/.net-audit` | SQLite 历史数据库目录 | | `--no-history` | `false` | 跳过将审计结果写入历史数据库 | | `--no-drift` | `false` | 跳过审计运行之间的漂移/回归检测 | | `--git-history-dir` | `~/.net-audit/git-config-history` | 基于 Git 的配置历史目录 | | `--no-git-history` | `false` | 跳过基于 Git 的配置快照提交 | | `--git-push` | `false` | 在提交后将 Git 配置历史推送到远程仓库 | #### `history` 子命令 | 选项 | 默认值 | 描述 | |--------|---------|-------------| | `--device` | (全部) | 按名称过滤到单个设备 | | `--last` | `20` | 显示最后 N 次运行 | | `--since` | (无) | 显示过去 N 天/小时的运行记录(例如 `7d`、`24h`、`2w`) | | `--status` | (全部) | 按状态过滤:`pass` 或 `fail` | | `--format` | `table` | 输出格式:`table` 或 `json` | | `--history-dir` | `~/.net-audit` | SQLite 历史数据库目录 | ### 试运行模式 使用 `--dry-run`(或 `-n`)来验证你的清单和基线,并预览将要审计的内容 —— 不会建立任何 SSH 连接: ``` audnet audit --inventory inventories/devices.yaml --dry-run ``` 输出: ``` audnet v0.2.0 — Starting audit... Loaded 2 devices, 11 checks DRY RUN — no device connections will be made Devices that would be audited: • core-router-01 (192.168.1.1) — cisco_ios • dist-switch-02 (192.168.1.2) — cisco_ios Checks that would be run: • ssh_v2_only • no_open_ports • ntp_approved • syslog_approved • aaa_auth • cdp_disabled • login_banner • password_encryption • snmp_v3_only • unused_iface_shutdown • vty_timeout Dry run complete — config and baseline are valid ``` 与 `--device` 和 `--check` 结合使用以过滤预览结果: ``` audnet audit --dry-run --device core-router-01 --check ssh_v2_only ``` ### 输出结果 该工具会生成: - **终端摘要** —— 带有每台设备通过/失败状态的 Rich 表格 - **audit_report.md** —— 包含详细发现表格的 Markdown 报告 - **audit_report.html** —— 可共享的带有样式的 HTML 报告 - **JSON**(使用 --json) —— 用于 CI/CD 的机器可读格式 ## 项目结构 ``` audnet/ ├── pyproject.toml # Build config, dependencies, pytest/ruff settings ├── CHANGELOG.md # Release history (Keep a Changelog format) ├── CONTRIBUTING.md # Development guidelines, testing, PR workflow ├── LICENSE # MIT License ├── README.md # This file ├── SECURITY.md # Security policy, credential handling, disclosure ├── uv.lock # Reproducible dependency lockfile ├── .pre-commit-config.yaml # Pre-commit hooks (ruff, mypy, bandit, etc.) ├── Dockerfile # Multi-stage Docker build (~70MB image) ├── docker-compose.yml # Container orchestration with cron scheduling ├── entrypoint.sh # Container entrypoint (cron/once/shell modes) ├── benchmarks/ │ └── bench_collectors.py # Sync vs async collector performance benchmarks ├── inventories/ │ └── devices.yaml # Sample device inventory ├── baselines/ │ └── security_baseline.yaml # Compliance rules configuration ├── .github/ │ └── workflows/ │ ├── ci.yml # Lint + security + test (3.12/3.13/3.14) │ ├── publish.yml # PyPI publish on v* tags (Trusted Publishing) │ ├── docker.yml # Docker image publish to ghcr.io on v* tags │ ├── release.yml # GitHub Release creation on v* tags │ ├── auto-close-issues.yml # Auto-close linked issues on PR merge │ ├── issue-labeler.yml # Auto-label issues │ └── size-label.yml # Auto-label PR size ├── src/audnet/ │ ├── __init__.py # Package init, version │ ├── cli.py # Typer CLI entry point │ ├── config.py # YAML inventory/baseline loader with env resolution │ ├── models.py # Pydantic data models (incl. SecurityBaseline) │ ├── exceptions.py # Structured exception hierarchy │ ├── vendor_registry.py # Vendor registry for multi-vendor dispatch │ ├── collector.py # Parallel SSH collector (Netmiko + ThreadPool + retries) │ ├── collector_async.py # Asyncio collector (asyncssh + semaphore concurrency) │ ├── scrapli_collector.py # Scrapli async collector (optional, scrapli extra) │ ├── parser.py # TextFSM parser (CLI → structured JSON, vendor-aware) │ ├── compliance.py # Rule engine (11 security checks, vendor-pattern overrides) │ ├── reporter.py # Jinja2 report generator (Markdown + HTML) │ ├── history.py # SQLite audit history store with drift detection │ ├── git_history.py # Git-backed device config history with diff and rollback │ ├── remediate.py # Safe config push with dry-run, diff, rollback │ ├── realtime.py # Real-time listener: syslog/SNMP traps, alerting, polling │ ├── inventory_sources/ │ │ ├── __init__.py │ │ └── netbox.py # NetBox dynamic inventory fetcher │ ├── templates/ │ │ ├── __init__.py │ │ ├── audit_report.md.j2 # Markdown report template │ │ └── audit_report.html.j2 # HTML report template │ └── textfsm_templates/ │ ├── __init__.py │ ├── cisco_ios_show_ip_interface_brief.textfsm │ ├── cisco_ios_show_version.textfsm │ ├── cisco_ios_show_running_config.textfsm │ ├── cisco_ios_show_interface_status.textfsm │ ├── cisco_ios_show_cdp_neighbors_detail.textfsm │ ├── cisco_nxos_show_ip_interface_brief.textfsm │ ├── cisco_nxos_show_version.textfsm │ ├── cisco_nxos_show_running_config.textfsm │ ├── arista_eos_show_ip_interface_brief.textfsm │ ├── arista_eos_show_version.textfsm │ ├── arista_eos_show_running_config.textfsm │ ├── juniper_junos_show_ip_interface_brief.textfsm │ ├── juniper_junos_show_version.textfsm │ ├── juniper_junos_show_running_config.textfsm │ ├── paloalto_panos_show_interface_all.textfsm │ ├── paloalto_panos_show_system_info.textfsm │ └── paloalto_panos_show_config_running.textfsm └── tests/ ├── __init__.py ├── conftest.py # Shared pytest fixtures ├── test_models.py # Device, ComplianceResult, AuditReport ├── test_config.py # Inventory loading, env resolution ├── test_collector.py # SSH collection, error handling, vendor dispatch ├── test_collector_async.py # Async collector: success, auth failure, timeout, mixed ├── test_parser.py # TextFSM parsing, vendor-aware template selection ├── test_compliance.py # All rule types (pass/fail), case-insensitive ├── test_reporter.py # Markdown/HTML rendering ├── test_vendor_registry.py # Vendor profiles, dispatch, registration ├── test_exceptions.py # Exception hierarchy and inheritance ├── test_integration.py # End-to-end: compliant, noncompliant, partial ├── test_logging.py # Structlog configuration and secret redaction ├── test_version.py # Version string format and accessibility ├── test_history.py # SQLite history store operations ├── test_git_history.py # Git-backed config history (sanitize, commit, diff, rollback) ├── test_drift.py # Drift/regression detection ├── test_cli.py # CLI tests including history subcommand ├── test_netbox_inventory.py # NetBox inventory fetcher (mocked API) ├── test_remediate.py # Remediation: dry-run, diff, idempotent, rollback ├── test_realtime.py # Real-time listener: syslog, SNMP, alerting ├── test_scrapli_collector.py # Scrapli collector: driver mapping, collection ├── test_vendors_expanded.py # Fortinet, Aruba, HP vendor command tests ├── test_vendors_juniper_panos.py # Juniper/Palo Alto template tests └── test_history_cli.py # History CLI command tests ``` ## 多供应商支持 audnet 使用供应商注册/调度模式(类似于 NAPALM/Nornir 的驱动程序架构)以实现多供应商支持。设备类型会被自动解析,默认以 Cisco IOS 作为后备选项。 ### 支持的供应商 | 供应商 | device_type | 模板前缀 | |--------|-------------|-----------------| | Cisco IOS/IOS-XE | `cisco_ios` | `cisco_ios` | | Cisco IOS-XE(别名) | `cisco_xe` | `cisco_ios` | | Cisco NX-OS | `cisco_nxos` | `cisco_nxos` | | Cisco ASA | `cisco_asa` | `cisco_asa` | | Arista EOS | `arista_eos` | `arista_eos` | | Juniper JunOS | `juniper_junos` | `juniper_junos` | | Palo Alto PAN-OS | `paloalto_panos` | `paloalto_panos` | | Fortinet FortiOS | `fortinet_fortios` | `fortinet_fortios` | | Aruba OS | `aruba_os` | `aruba_os` | | HP ProCurve | `hp_procurve` | `hp_procurve` | 未知的设备类型会回退到 `cisco_ios` 的命令和模板。 ### 为不同供应商配置设备 在清单 YAML 中针对每台设备设置 `device_type`,或将其设为默认值: ``` defaults: device_type: cisco_ios devices: - name: core-router-01 host: 192.168.1.1 username: admin password: "${AUDNET_PASSWORD}" - name: nexus-switch-01 host: 192.168.1.2 device_type: cisco_nxos username: admin password: "${AUDNET_PASSWORD}" - name: arista-leaf-01 host: 192.168.1.3 device_type: arista_eos username: admin password: "${AUDNET_PASSWORD}" - name: juniper-router-01 host: 192.168.1.4 device_type: juniper_junos username: admin password: "${AUDNET_PASSWORD}" - name: paloalto-fw-01 host: 192.168.1.5 device_type: paloalto_panos username: admin password: "${AUDNET_PASSWORD}" ``` ### 添加新的供应商 添加对新网络 OS 的支持只需三个步骤。无需更改解析器、收集器或合规代码 —— 供应商注册模式会自动处理调度。 #### 步骤 1:添加 TextFSM 模板 在 `textfsm_templates/` 中为每个数据槽创建一个模板。命名规则为 `_.textfsm`,其中后缀必须与内置供应商使用的槽名称相匹配: | 槽 |途 | 示例后缀 | |------|---------|----------------| | `show_ip_interface_brief` | 接口状态 | `show_ip_interface_brief` | | `show_version` | 设备版本/信息 | `show_version` | | `show_running_config` | 完整的运行配置 | `show_running_config` | 例如,要添加 Juniper JunOS: ``` textfsm_templates/ ├── juniper_junos_show_ip_interface_brief.textfsm ├── juniper_junos_show_version.textfsm └── juniper_junos_show_running_config.textfsm ``` 每个模板都应将该供应商等效的 CLI 输出解析为合规引擎所需的相同列名(例如,用于接口的 `INTERFACE`、`IP_ADDRESS`、`STATUS`、`PROTOCOL`)。 **提示:**在提交之前,请使用 [TextFSM CLI 工具](https://github.com/google/textfsm/wiki/TextFSM)针对示例输出交互式地测试模板。 #### 步骤 2:注册供应商 你有两种选择 —— 静态注册(推荐用于内置供应商)或运行时注册(用于插件或动态使用)。 **选项 A:静态注册** —— 添加到 `src/audnet/vendor_registry.py` 中的 `VENDOR_PROFILES`: ``` VENDOR_PROFILES["juniper_junos"] = _profile( commands=[ "show interfaces terse", "show version", "show configuration", ], prefix="juniper_junos", description="Juniper JunOS", ) ``` `commands` 列表必须包含与上述三个槽完全匹配的三个条目(接口简表、版本、运行配置)。`prefix` 必须与 TextFSM 模板的文件名前缀相匹配。 **选项 B:运行时注册** —— 在你的代码或插件中调用 `register_vendor()`: ``` from audnet.vendor_registry import register_vendor register_vendor( device_type="juniper_junos", commands=["show interfaces terse", "show version", "show configuration"], template_prefix="juniper_junos", ) ``` 运行时注册对于插件、测试或在无需修改 audnet 源码的情况下添加供应商非常有用。 #### 步骤 3:(可选)添加特定于供应商的合规模式 如果该供应商对于相同的安全概念使用了不同的 CLI 语法,请在你的基线 YAML 中添加 `vendor_patterns`: ``` checks: ssh_version: severity: critical rule: ssh_v2_only vendor_patterns: juniper_junos: match: "set system ssh" ok_value: "set system ssh protocol-v2" ``` `vendor_patterns` 下的键必须与清单中使用的 `device_type` 相匹配。如果未定义特定于供应商的模式,则使用 `default` 模式。 #### 步骤 4:在清单中配置设备 在你的设备上设置 `device_type`,使其与注册的键相匹配: ``` devices: - name: juniper-router-01 host: 192.168.1.10 device_type: juniper_junos username: admin password: "${AUDNET_PASSWORD}" ``` 就是这样。收集器将自动发送正确的命令,解析器将加载正确的模板,合规引擎将使用正确的模式。 #### 验证你的供应商 运行试运行以确认该供应商已被识别: ``` audnet audit --device juniper-router-01 --dry-run ``` 然后运行全面审计并检查输出: ``` audnet audit --device juniper-router-01 --json ``` ### 工作原理 - `vendor_registry.py` 将 `device_type` 映射到 CLI 命令和 TextFSM 模板前缀 - `collector.py` 调用 `get_commands(device_type)` 而不是硬编码的字典 - `parser.py` 调用 `get_template_name(device_type, slot)` 进行动态模板加载 - `compliance.py` 使用基于模式的匹配,并带有可选的逐供应商覆盖设置 - 所有对于未知设备类型的供应商解析都会回退到 `cisco_ios` ## NetBox 动态清单 audnet 可以直接从 NetBox 实例获取设备清单,从而无需维护单独的 YAML 文件。 ### 使用方法 将清单路径设置为 `netbox://` URL: ``` export NETBOX_TOKEN="your-netbox-api-token" audnet audit --inventory "netbox://netbox.example.com?site=dc1&role=router" ``` 或者在你的清单 YAML 中,直接使用该 URL: ``` audnet audit --inventory "netbox://netbox.example.com" ``` ### URL 格式 ``` netbox:// [?site=&role=&tag=&device_type=] ``` 所有的查询参数都是可选的,用于过滤 NetBox 返回的设备列表。 ### 平台映射 NetBox 设备平台会被自动映射到 audnet 的供应商设备类型: | NetBox 平台 | audnet device_type | |-----------------|-------------------| | `ios` | `cisco_ios` | | `iosxe` | `cisco_ios` | | `nxos` | `cisco_nxos` | | `asa` | `cisco_ios` | | `junos` | `juniper_junos` | | `panos` | `paloalto_panos` | | `arista_eos` | `arista_eos` | ### 凭证覆盖 NetBox `config_context` 可以提供针对特定设备的凭证覆盖。如果设备的配置上下文包含 `audnet` 键,则这些值将覆盖清单中的默认设置: ``` { "audnet": { "username": "netbox_admin", "password": "${NETBOX_AUDNET_PASSWORD}", "port": 2222 } } ``` ### 身份验证 设置带有 NetBox API token 的 `NETBOX_TOKEN` 环境变量。该 token 需要对 `dcim.devices`、`dcim.sites` 和 `dcim.device-roles` 具有读取权限。 ### 环境要求 NetBox 清单模块仅使用 Python 标准库(`urllib.request`、`json`) —— 无需额外的依赖。 ## 性能与可扩展性 ### 当前架构:ThreadPool + Netmiko 默认收集器(`collector.py`)使用 `concurrent.futures.ThreadPoolExecutor` 和 Netmiko 进行 SSH 连接。这对于中小型清单(最多 约 20 台设备)效果很好,但在规模扩展时存在限制: - **线程开销**:每个并发连接都会消耗一个线程(约 8MB 栈空间) - **GIL 争用**:Python 的 GIL 限制了 CPU 密集型解析实现真正的并行 - **内存**:100 台设备 x 4 个线程 = 线程栈会占用大量内存 ### 异步原型:asyncio + asyncssh 一个异步收集器原型可在 `collector_async.py` 中找到。它使用协程 替换了线程,并使用 `asyncssh` 作为 SSH 传输层: | 方面 | 同步 (ThreadPool) | 异步 (asyncio) | |--------|-------------------|-----------------| | 并发模型 | OS 线程 | 协程 | | 单连接内存占用 | ~8MB(线程栈) | ~1KB(协程) | | 默认 `max_workers` | 4 | 50 | | 可扩展至 | ~20-50 台设备 | 100+ 台设备 | | 依赖项 | Netmiko | asyncssh | ### 运行基准测试 ``` uv run python benchmarks/bench_collectors.py ``` 这会在 4/8/16/32 台设备上对比同步与异步收集(使用模拟的 SSH 响应)。结果将写入 `benchmarks/results.json`。 ### 迁移路径 异步收集器是一个**原型** —— 它会生成完全相同的 `DeviceSnapshot` 输出,并共享相同的解析器、合规性和供应商注册代码。 当扩展到超过约 50 台设备时,要切换到异步收集模式: 1. 安装 asyncssh:`uv add asyncssh` 2. 在 `cli.py` 中更改导入: # from audnet.collector import collect_all from audnet.collector_async import collect_all_async as collect_all 3. `--workers` 标志将映射到 `asyncio.Semaphore` 的限制(默认值:50) 4. 将同步收集器保留为后备方案,适用于没有 asyncssh 的环境 ### 未来规划:Scrapli 对于生产环境的异步部署,请考虑从 `asyncssh` 迁移到 [Scrapli](https://github.com/scrapli/scrapli),它提供: - 内置的多供应商支持(替换 Netmiko 的设备类型抽象) - 同时支持同步和异步传输 - 结构化解析(在某些平台上替换 TextFSM) - 活跃的社区和定期更新 `vendor_registry.py` 中的供应商注册模式已经兼容 —— Scrapli 只需要替换收集器中的 SSH 传输层即可。 ## 合规性检查 | 检查项 | 规则 | 严重程度 | 检测内容 | |-------|------|----------|-----------------| | SSH 版本 | `ssh_v2_only` | 严重 | 启用了 SSHv1 或未配置 SSHv2 | | 非活动端口 | `no_open_ports` | 高 | 处于未授权 VLAN 中的交换机端口 | | NTP 配置 | `ntp_approved` | 中 | NTP 服务器不在批准列表中 | | Syslog 配置 | `syslog_approved` | 中 | Syslog 服务器不在批准列表中 | | AAA 认证 | `aaa_auth` | 高 | 缺少 AAA 身份验证 | | 禁用 CDP | `cdp_disabled` | 中 | 在接口上启用了 CDP | | 登录横幅 | `login_banner` | 中 | 缺少登录横幅 | | 密码加密 | `password_encryption` | 高 | 未启用密码加密 | | SNMP v3 | `snmp_v3_only` | 高 | 启用了 SNMPv1/v2 | | 未使用的接口已关闭 | `unused_iface_shutdown` | 中 | 处于活动状态但未使用的接口 | | VTY 超时 | `vty_timeout` | 中 | 缺少 VTY exec-timeout | ### 添加新的合规规则 1. 在 `compliance.py` 中编写一个 `_check_your_rule(snapshot, config) -> ComplianceResult` 函数 2. 将其添加到 `_RULE_DISPATCH` 字典中 3. 将该规则配置添加到 `baselines/security_baseline.yaml` 中 4. 在 `test_compliance.py` 中编写测试 ## 贡献 有关开发环境设置、添加规则、测试和 PR 工作流程,请参阅 [CONTRIBUTING.md](CONTRIBUTING.md)。 ## 测试 ``` # 运行所有测试 pytest tests/ -v # 运行 coverage pytest tests/ --cov=audnet --cov-report=term-missing # 运行特定的测试文件 pytest tests/test_compliance.py -v # Lint ruff check src/ tests/ ``` 所有的测试都使用模拟的设备响应 —— 不需要真实的 SSH 连接或网络硬件。 ## 安全 audnet 非常重视凭证处理。密码以 `SecretStr` (Pydantic) 的形式存储,且绝不会出现在日志或输出中。 ### 快速开始:环境变量 在清单文件中使用 `${ENV_VAR}` 占位符: ``` devices: - name: core-switch-01 host: 10.0.0.1 password: "${AUDNET_PASSWORD}" ``` ``` export AUDNET_PASSWORD="***" audnet audit ``` ### 生产环境:外部密钥存储 对于生产环境,请使用专用的密钥管理器代替环境变量: | 存储库 | 示例 | | ----------------- | ---------------------------------------------------- | | HashiCorp Vault | `export AUDNET_PASSWORD=$(vault kv get ...)` | | AWS Secrets Mgr | `export AUDNET_PASSWORD=$(aws secretsmanager ...)` | | 1Password CLI | `export AUDNET_PASSWORD=$(op read ...)` | | Python keyring | `keyring.set_password("audnet", ...)` | 有关详细的集成示例,请参阅 [SECURITY.md](SECURITY.md)。 ### 严格模式 (CI/CD) 在 CI 流水线中使用 `--strict`,强制要求清单文件中不能存在明文密码: ``` audnet audit --strict ``` 如果任何设备的密码不是 `${ENV_VAR}` 引用,这将引发 `ConfigError` 并失败。如果不使用 `--strict`,系统将仅记录警告。 ### SSH 密钥验证 优先使用 SSH 密钥而不是密码: ``` devices: - name: core-switch-01 host: 10.0.0.1 use_keys: true key_file: ~/.ssh/id_ed25519 ``` ### 检查清单 - **绝不**提交包含明文密码的清单文件 - 将 `inventories/*.yaml` 添加到 `.gitignore` 中(仅提交 `inventories/example.yaml`) - 在本地开发时使用 `.env`(将 `.env` 添加到 `.gitignore`) - 在 CI/CD 中使用 `--strict` - 优先使用 SSH 密钥身份验证 - 定期轮换凭证 有关完整的安全策略、漏洞报告和负责任的披露,请参阅 [SECURITY.md](SECURITY.md)。 ## Docker 部署 audnet 可以作为定时审计容器运行 —— 无需主机级的 cron。每当有新的版本标签发布时, 镜像都会被发布到 `ghcr.io/elshayib/audnet`。 ### 快速开始 ``` # Clone 并启动 git clone https://github.com/Elshayib/Audnet.git && cd Audnet # 将您的 inventory 和 baseline 文件放置在默认路径下: # inventories/devices.yaml # baselines/security_baseline.yaml # 或者通过环境变量设置自定义路径。 docker compose up -d ``` 报告将写入 `./reports/`;历史记录会持久化存储在命名卷中。 ### 配置 所有配置均通过环境变量完成: | 变量 | 默认值 | 描述 | |---|---|---| | `AUDIT_CRON` | `0 * * * *` | Cron 调度(每小时)。每天凌晨 2 点:`"0 2 * * *"` | | `AUDNET_INVENTORY` | `/app/inventory/devices.yaml` | 清单 YAML 路径,或 `netbox://host` URL | | `AUDNET_BASELINE` | `/app/baselines/security_baseline.yaml` | 基线 YAML 路径 | | `AUDNET_REPORTS` | `/app/reports` | 报告输出目录 | | `AUDNET_HISTORY_DIR` | `/app/.net-audit` | 历史数据库目录 | | `NETBOX_TOKEN` | (无) | NetBox API token(用于 `netbox://` 清单时必填) | ### 卷挂载 ``` volumes: - ./inventories:/app/inventory:ro # device inventory (read-only) - ./baselines:/app/baselines:ro # security baseline (read-only) - ./reports:/app/reports # audit reports (writable) - audnet-history:/app/.net-audit # history DB (persistent volume) ``` ### 调度示例 ``` # 每小时(默认) AUDIT_CRON="0 * * * *" docker compose up -d # 每天凌晨 2 点 AUDIT_CRON="0 2 * * *" docker compose up -d # 每周一午夜 AUDIT_CRON="0 0 * * 1" docker compose up -d # 每 6 小时 AUDIT_CRON="0 */6 * * *" docker compose up -d ``` ### 一次性审计 运行单次审计并退出(无 cron): ``` docker compose run --rm audnet once ``` 或者覆盖该命令: ``` docker run --rm \ -v $(pwd)/inventories:/app/inventory:ro \ -v $(pwd)/baselines:/app/baselines:ro \ -v $(pwd)/reports:/app/reports \ ghcr.io/elshayib/audnet:latest once ``` ### NetBox 清单 使用动态 NetBox 清单代替静态 YAML 文件: ``` export NETBOX_TOKEN="your-netbox-token" docker compose run --rm -e AUDNET_INVENTORY="netbox://netbox.example.com?site=dc1&role=router" -e NETBOX_TOKEN audnet once ``` ### 镜像大小 该镜像使用多阶段 Dockerfile 构建,目标大小小于 200MB: ``` docker images ghcr.io/elshayib/audnet:latest # IMAGE SIZE # audnet ~70MB ``` ## 更新日志 有关更改、新功能和错误修复的详细历史记录,请参阅 [CHANGELOG.md](CHANGELOG.md)。
标签:Docker 部署, Python, 无后门, 网络安全研究, 网络自动化, 网络运维, 自动化运维, 请求拦截, 逆向工具