Elshayib/Audnet
GitHub: Elshayib/Audnet
Audnet 是一款基于 SSH 的自动化网络设备安全合规审计工具,用于检测多供应商网络环境中的配置漂移问题。
Stars: 0 | Forks: 2
# 网络安全与合规审计工具
[](https://opensource.org/licenses/MIT)
[](https://www.python.org/downloads/)
[](https://github.com/Elshayib/Audnet/actions/workflows/ci.yml)
[](https://github.com/Elshayib/Audnet/releases/latest)
[](https://pypi.org/project/audnet/)
[](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, 无后门, 网络安全研究, 网络自动化, 网络运维, 自动化运维, 请求拦截, 逆向工具