Zappan-net/cerberus
GitHub: Zappan-net/cerberus
一个专为 Debian 服务器设计的 nginx 虚拟主机安全监控工具,自动检测应用技术栈并运行依赖漏洞审计,仅在发现变化时发出告警。
Stars: 1 | Forks: 0
# Cerberus
[](https://opensource.org/licenses/MIT)
[](https://www.python.org/)
[](https://www.debian.org/)
[](https://github.com/Zappan-net/cerberus/releases)
[](https://github.com/Zappan-net/cerberus/commits/main)
Cerberus 是一个易于维护的 Python 3 监控工具,专为 Debian 服务器设计。它会检查 nginx vhost,检测每个 vhost 背后的应用技术栈,在可能的情况下运行针对特定技术栈的安全审计,将检测到的版本与本地 SQLite 公告缓存进行关联比对,并且仅针对新增或发生实质性变动的发现发送邮件警报。
## 目录
- [功能](#features)
- [架构](#architecture)
- [安装说明](#installation)
- [配置](#configuration)
- [用法](#usage)
- [通知示例](#notification-examples)
- [已知限制](#known-limits)
- [改进计划](#improvement-plan)
## 功能
- 专为具有多个 nginx vhost 和混合技术栈的传统 Debian 主机而构建
- 维护本地 SQLite 公告缓存,并支持在离线扫描时利用缓存数据继续工作
- 发送易读的 HTML 警报,包含按严重程度进行的分组、修复版本以及简明扼要的修复建议

License: MIT。详见 [LICENSE](LICENSE)。
作者:Julien Wehrbach。
详细的内部文档可在 [docs/CODE_BREAKDOWN.md](docs/CODE_BREAKDOWN.md) 中查看。
架构图可在 [docs/DIAGRAMS.md](docs/DIAGRAMS.md) 中查看。
可编辑的 Office 导出源文件可在 [docs/README_EXPORT.md](docs/README_EXPORT.md) 中查看。
## 架构
该项目划分为明确的几个层:
1. `nginx_parser.py`
从 `/etc/nginx/sites-enabled` 读取文件,解析有用的 `include` 指令,并提取 `server_name`、`root`、`proxy_pass`、`fastcgi_pass`、`uwsgi_pass` 以及 upstream/socket 路径。
2. `stack_detection.py`
对文件系统标记和 upstream 名称应用易读的启发式方法。其逻辑是刻意保持明确的,而非晦涩难懂的。
3. `collectors.py`
从常见的清单文件和环境变量中收集依赖版本:
`composer.lock`、`composer.json`、`package-lock.json`、`npm-shrinkwrap.json`、`package.json`、`requirements.txt`、`poetry.lock`、`.venv/bin/python`、`venv/bin/python`、`gitea --version` 和 `VERSION`。
4. `audits.py`
在可用且有超时限制的情况下运行原生的审计工具。对于 npm 项目,Cerberus 会同时运行 `npm audit --omit=dev` 和完整的 `npm audit`,以便将生产/运行时的发现与开发/构建的发现区分开来。Composer 和 Python 项目分别使用 `composer audit` 和 `pip-audit`。如果缺少某个工具或项目不完整,Cerberus 会继续执行并回退到本地公告缓存。
5. `cve_db.py`
维护本地 SQLite 公告缓存。所选的策略非常实用:Cerberus 并不会镜像所有的 CVE,它只针对其实际检测到的包/版本对,对 OSV 的响应进行标准化和缓存。这使得本地数据库保持精简,并且在普通的 Debian 主机上非常实用。在两次刷新之间,扫描可以在离线模式下针对缓存运行。
6. `state_store.py`
存储警报指纹和重复失败的计数器,以避免发送重复的邮件。
7. `notify.py`
通过本地 `sendmail`、普通 SMTP、STARTTLS SMTP 或经过身份验证的 SMTPS/SMTP 发送邮件。
8. `scanner.py`
负责编排一次完整的扫描、缓存刷新、去重通知的生成,以及可选的内部 daemon 循环。
## 技术选择
- 优先使用 Python 3 标准库,并加上用于解析配置的 `PyYAML`。
- 优先选择 `systemd timer` 而不是无限的 daemon 循环。在 Debian 上,它更简单、更易于观察且更具弹性。对于需要的环境,仍然提供了一个内部的 `daemon` 模式。
- 状态数据库和公告缓存均使用 SQLite,以避免引入额外的服务。
- 本地 CVE 策略:定向同步 OSV 并结合 SQLite 标准化。
假设:在定期同步期间有互联网访问权限。如果没有,Cerberus 依然可以利用上次缓存的数据进行工作。
- 外部审计工具是可选的,不是流程继续执行的必要条件。
- 所有的变量名、函数名和注释均使用英语。
## 最终目录树
```
.
├── pyproject.toml
├── README.md
├── packaging
│ ├── examples
│ │ ├── config.yml
│ │ ├── sample-email.txt
│ │ └── sample-log.txt
│ ├── scripts
│ │ ├── install.sh
│ │ └── testmail
│ └── systemd
│ ├── vhost-cve-monitor.service
│ ├── vhost-cve-monitor.timer
│ ├── vhost-cve-monitor-cve-sync.service
│ └── vhost-cve-monitor-cve-sync.timer
├── src
│ └── vhost_cve_monitor
│ ├── __init__.py
│ ├── audits.py
│ ├── cli.py
│ ├── collectors.py
│ ├── config.py
│ ├── cve_db.py
│ ├── logging_utils.py
│ ├── models.py
│ ├── nginx_parser.py
│ ├── notify.py
│ ├── scanner.py
│ ├── stack_detection.py
│ ├── state_store.py
│ └── subprocess_utils.py
└── tests
├── test_cli.py
├── test_collectors.py
├── test_nginx_parser.py
├── test_scanner_digest.py
├── test_scanner_test_mail.py
├── test_stack_detection.py
└── test_state_store.py
```
## 安装说明
### 依赖项
- Python 3.7+
- `python3-venv`
- `python3-yaml`
- 如果您使用默认的示例配置,则需要一个暴露 `/usr/sbin/sendmail` 的本地 MTA
- 可选但推荐:
- `npm`
- `composer`
- `pip-audit`
### 安装
Cerberus 现在会安装到 `/opt/cerberus/.venv` 下的专用虚拟环境中。Debian 将系统 Python 标记为外部管理 (PEP 668),因此 Cerberus 不再依赖 `python3 -m pip install .` 安装到全局解释器中。
面向管理员的封装脚本位于 `/usr/local/bin/`:
- `/usr/local/bin/vhost-cve-monitor`
- `/usr/local/bin/vhost-cve-monitor-testmail`
支持两种安装方式:
- 使用辅助脚本进行直接本地安装
- Debian 软件包构建/安装,该方式在运行时仍使用 `/opt/cerberus/.venv`
```
cd /opt/cerberus
sudo sh packaging/scripts/install.sh
```
该辅助脚本是幂等的:
- 如果需要,它会创建 `/opt/cerberus/.venv`
- 重新运行时,它会更新该 venv 中的软件包
- 重新运行时,它会更新 `/usr/local/bin/` 中的封装脚本
- 仅在不存在配置时,它才会安装默认的 `/etc/vhost-cve-monitor/config.yml`
- 它不会覆盖现有的生效配置
### 构建 Debian 软件包
Cerberus 也可以被打包成一个简单的 `.deb`,同时保留专用的运行时 venv 模型。
```
cd /opt/cerberus
dpkg-buildpackage -us -uc
```
然后从父目录安装生成的软件包:
```
sudo dpkg -i ../cerberus_0.1.1_all.deb
```
该软件包将安装:
- 位于 `/opt/cerberus` 下的项目文件
- 位于 `/lib/systemd/system` 下的 systemd 单元
- 位于 `/usr/local/bin` 下的运行时封装脚本
- 位于 `/usr/share/cerberus/config.yml` 下的打包示例配置
其 `postinst` 脚本随后会:
- 创建或重用 `/opt/cerberus/.venv`
- 使用 `--system-site-packages` 创建该 venv
- 在该 venv 内部安装或更新 Cerberus,而在软件包安装时不下载 Python 依赖项
- 仅在不存在生效配置时,才从 `/usr/share/cerberus/config.yml` 安装 `/etc/vhost-cve-monitor/config.yml`
- 启用计时器
### 升级现有安装
如果 Cerberus 已经安装在机器上,请从代码库根目录更新它:
```
cd /opt/cerberus
sudo sh packaging/scripts/install.sh
```
如果您是从 Debian 软件包安装的 Cerberus,请重新构建并重新安装 `.deb`,而不是调用全局的 `pip`:
```
cd /opt/cerberus
dpkg-buildpackage -us -uc
sudo dpkg -i ../cerberus_0.1.1_all.deb
```
如果您更改了打包文件(例如 systemd 单元),请重新加载 systemd 并确保计时器已启用:
```
sudo systemctl daemon-reload
sudo systemctl enable --now vhost-cve-monitor.timer
sudo systemctl enable --now vhost-cve-monitor-cve-sync.timer
```
如果计时器已经处于活动状态,除非单元文件在结构上发生了更改,否则通常只需执行 `daemon-reload` 即可。如果您想强制立即运行,请重启关联的 `.service` 单元而不是 `.timer`。
如果您更改了邮件身份验证或本地 MTA 集成,也请重新加载相关服务:
```
sudo systemctl restart opendkim
sudo systemctl reload postfix
```
建议的升级后检查:
```
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml --dry-run scan-once
vhost-cve-monitor-testmail HIGH
```
## 配置
示例文件:[packaging/examples/config.yml](packaging/examples/config.yml)
配置拆分:
- 代码库默认示例:[packaging/examples/config.yml](packaging/examples/config.yml)
- 生效的机器配置:`/etc/vhost-cve-monitor/config.yml`
代码库中的文件刻意保持通用且可以安全发布。`/etc` 下的文件是本地部署配置,可能包含真实的收件人、发件人域名以及特定于环境的调整。
默认的示例配置假定了一个简单的本地 Postfix/sendmail 设置,包含:
- `notifications.method: sendmail`
- `notifications.email_to: [root@localhost]`
- `notifications.email_from: cerberus@localhost`
如果您保持该示例不做更改,请确保本地 MTA 提供了 `/usr/sbin/sendmail`。
如果缺少 `sendmail`,Cerberus 现在会返回一个简明的发送错误,而不是完整的 Python traceback。
主要键名:
- `nginx.sites_enabled_dir`:要扫描的 nginx vhost 目录。
- `scanner.default_roots`:当 nginx `root` 缺失或不完整时,作为备选去检查的根目录。
- `scanner.command_timeout_seconds`:`npm audit`、`composer audit`、`pip-audit` 和 `pip freeze` 的超时时间。
- `scanner.repeated_failure_threshold`:在发送警报之前,相同失败的重复次数。
- `notifications.method`:`sendmail` 或 `smtp`。
- `notifications.smtp_host` / `notifications.smtp_port`:当 `method: smtp` 时使用的 SMTP 中继端点。
- `notifications.smtp_ssl`:启用隐式 TLS(`SMTP_SSL`),通常用于端口 465。
- `notifications.smtp_starttls`:使用 STARTTLS 升级普通 SMTP 会话,通常用于端口 587。
- `notifications.smtp_username`:用于经过身份验证的中继的 SMTP 账户名。
- `notifications.smtp_password`:如果您选择将其存储在 YAML 中时的 SMTP 密码。
- `notifications.smtp_password_env`:保存 SMTP 密码的环境变量名;优于直接写在配置中的机密。
- `notifications.max_emails_per_run`:每个扫描周期的硬性上限,超过部分将分组汇总到一封摘要邮件中。
- `notifications.summary_only`:启用后,一次扫描将生成一封包含该次运行所有警报的单一摘要邮件。
- 摘要邮件保留差异化的警报模型,按严重程度对保留的发现进行分组,并在上游数据提供时渲染公告摘要。
- npm 摘要邮件区分生产/运行时的发现与开发/构建的发现。当同时存在这两个范围时,由运行时的发现决定主要摘要的严重程度,而仅在完整审计中出现的发现仍保留在单独的开发/构建部分中可见。
- `filters.*`:用于 vhost 和路径的允许列表/阻止列表。
经过身份验证的 SMTP 示例:
```
notifications:
method: smtp
smtp_host: smtp.example.net
smtp_port: 587
smtp_ssl: false
smtp_starttls: true
smtp_username: cerberus@example.net
smtp_password_env: CERBERUS_SMTP_PASSWORD
```
```
notifications:
method: smtp
smtp_host: smtp.example.net
smtp_port: 465
smtp_ssl: true
smtp_starttls: false
smtp_username: cerberus@example.net
smtp_password_env: CERBERUS_SMTP_PASSWORD
```
不要同时启用 `smtp_ssl` 和 `smtp_starttls`。
## 用法
单次扫描:
```
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml scan-once
```
直接从 venv 运行:
```
/opt/cerberus/.venv/bin/vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml scan-once
```
试运行:
```
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml --dry-run scan-once
```
详细试运行:
```
vhost-cve-monitor --verbose --config /etc/vhost-cve-monitor/config.yml --dry-run scan-once
```
仅针对缓存数据的离线扫描:
```
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml --offline scan-once
```
针对单个 vhost 或模式的聚焦扫描:
```
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml scan-once --only-vhost app.example.net
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml scan-once --only-vhost "admin.*"
```
强制发送一封包含当前所有漏洞发现的邮件,包括已经通知过的发现:
```
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml scan-once --force-notify
```
该选项可以与聚焦扫描结合使用:
```
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml scan-once \
--only-vhost app.example.net \
--force-notify
```
`--force-notify` 仅针对该次调用绕过漏洞指纹过滤。它不会清除、初始化或更新警报去重状态,也不会强制发送重复扫描失败或内部错误的通知。除非有意需要重复发送完整的摘要,否则请避免将其添加到常规的 systemd 计时器中。
验证已加载的配置:
```
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml validate-config
```
运行环境和运行时诊断过程:
```
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml doctor
```
列出带有过滤器和堆栈上下文的已解析 nginx vhost:
```
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml list-vhosts
```
详细解释单个 vhost:
```
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml explain-vhost app.example.net
```
手动刷新 CVE 缓存:
```
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml sync-cve
```
将最新的已固化发现快照导出为 JSON:
```
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml export-findings
```
将该导出直接写入文件以供第三方消费者使用:
```
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml export-findings --output /var/lib/cerberus/findings.json
```
该快照会在每次 `scan-once` 运行结束时自动刷新,因此常规的 systemd 计时器会自动为第三方消费者保持更新,而无需添加本地 Web 服务。如果尚不存在快照,`export-findings` 将执行一次仅收集的过程来初始化它,而不会发送通知。
测试邮件:
```
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml test-mail
```
带有明确严重程度的测试邮件:
```
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml test-mail --severity HIGH
```
对管理员友好的封装脚本:
```
vhost-cve-monitor-testmail HIGH
vhost-cve-monitor-testmail LOW MEDIUM HIGH
```
带有明确严重程度和类别的测试邮件:
```
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml test-mail --severity CRITICAL --category vulnerability
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml test-mail --severity WARNING --category scan-failure
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml test-mail --severity HIGH --category internal-error
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml test-mail --severity MEDIUM --category digest
```
感知技术栈的漏洞模拟:
```
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml test-mail \
--category vulnerability \
--severity HIGH \
--stack nodejs \
--package lodash \
--installed-version 4.17.23 \
--fixed-version ">= 4.17.24" \
--advisory-id GHSA-35jh-r3h4-6jhm \
--vhost app.example.net \
--source-file /srv/app/package-lock.json \
--source-line 3726
```
支持的 `test-mail` 类别:
- `test`
-vulnerability`
- `scan-failure`
- `internal-error`
- `digest`
支持的 `test-mail` 严重程度:
- `CRITICAL`
- `HIGH`
- `MEDIUM`
- `WARNING`
- `LOW`
- `INFO`
- `UNKNOWN`
用于漏洞模拟的额外 `test-mail` 覆盖参数:
- `--stack`
- `--ecosystem`
- `--package`
- `--installed-version`
- `--fixed-version`
- `--advisory-id`
- `--vhost`
- `--source-file`
- `--source-line`
在 `scan-once`、`sync-cve` 和内部 `daemon` 循环期间,未处理的 Cerberus 异常现在会生成专门的高严重性内部错误通知。即使启用了摘要模式,这些邮件也会被直接发送,并且它们会提示操作人员在 GitHub issue 追踪器上报告可重现的错误。
内部 daemon 模式:
```
vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml daemon
```
## systemd
推荐的单元文件:
- [vhost-cve-monitor.service](packaging/systemd/vhost-cve-monitor.service)
- [vhost-cve-monitor.timer](packaging/systemd/vhost-cve-monitor.timer)
- [vhost-cve-monitor-cve-sync.service](packaging/systemd/vhost-cve-monitor-cve-sync.service)
- [vhost-cve-monitor-cve-sync.timer](packaging/systemd/vhost-cve-monitor-cve-sync.timer)
第一个计时器执行扫描。第二个计时器针对已知的包/版本元组刷新本地公告缓存。
由于 `scan-once` 现在会将最新保留的发现固化到 SQLite 中,扫描计时器也会自动保持 `export-findings` 的数据最新。
## 通知示例
Cerberus 仅在以下情况发送邮件:
- 漏洞首次出现
- payload 发生实质性变化,包括严重程度
- 相同的扫描失败重复次数足以超过配置的阈值
邮件正文字段:
- 主机名
- 日期
- vhost
- 技术栈
- 依赖项
- 检测到的版本
- 已知的修复版本
- CVE 或公告 ID
- 严重程度
- 摘要
- 建议
邮件展示形式:
- 紧凑的主题前缀,包含产品名称、最高严重程度、主机范围和警报数量
- 带有颜色编码严重程度横幅的 HTML 版本
- 针对极简邮件客户端的纯文本回退
- 感知严重程度的标头,例如 `X-Cerberus-Severity`、`X-Priority`、`Priority` 和 `Importance`
- 摘要条目将共享相同证据文件、包、已安装版本、公告和审计范围的发现进行分组,同时通过 `Affected targets`(受影响的目标)列表保持基础设施的可见性
- 建议是感知技术栈的,并在公告数据允许的情况下提及修复版本
- npm 建议将即时的运行时修复与计划内的开发/构建依赖项维护区分开来,并且 semver-major 的 `fixAvailable` 建议会被标记为有风险,而不是盲目推荐 `npm audit fix --force`
## 按严重程度分组的 HTML 电子邮件示例
Cerberus 会发送易读的 HTML 警报邮件,包含按严重程度划分的分组、简明的修复指南和纯文本回退。

操作说明:
- Cerberus 成功发送意味着已将邮件移交给本地 `sendmail` 或配置的 SMTP 中继
- 最终送达仍取决于远程接收情况和公共邮件身份验证
- 在目前的线上验证中,在 SPF、DKIM 和 DMARC 对齐之后,邮件送达达到了满分的 `mail-tester` 评分
示例:[packaging/examples/sample-email.txt](packaging/examples/sample-email.txt)
## 日志
默认情况下,日志会输出到 stdout,并且可以由 journald 收集。也可以配置文件路径。
示例:[packaging/examples/sample-log.txt](packaging/examples/sample-log.txt)
## 测试
```
PYTHONPATH=src python3 -m unittest discover -s tests -v
```
已覆盖的关键部分:
- 用于模拟 `test-mail` 严重程度和类别的 CLI 解析
- 用于在 `test-mail` 中进行感知技术栈漏洞模拟的 CLI 解析
- 内部错误通知路由和去重
- 公告严重性优先级和规范公告标识符
- 感知技术栈的建议生成
- 修复版本的提取和渲染
- 在可用的情况下保留依赖项源代码行
- 摘要去重、紧凑的主题渲染和最高严重性渲染
- nginx 配置解析
- 针对仅重定向的 vhost、仅代理的 vhost 以及构建根父目录检测的技术栈检测防护机制
- 跨多个 pipeline 阶段和 vhost 的逻辑发现标准化
- 警报去重和重复失败阈值逻辑
## 执行示例
单次扫描的试运行:
```
$ vhost-cve-monitor --config /etc/vhost-cve-monitor/config.yml --dry-run scan-once
{
"vhosts": 4,
"notifications": 1
}
```
典型流程:
1. 解析 nginx vhost 和包含的文件。
2. 从明确的标记检测技术栈。
3. 收集依赖项版本。
4. 运行可选的生态系统审计工具。
5. 跨审计工具和本地缓存来源标准化公告、严重程度、别名和修复版本。
6. 查询或重用本地 SQLite 公告缓存。
7. 将标准化的发现投射回受影响的 vhost,并发出去重后的通知。
## 已知限制
- nginx 解析刻意保持保守。它处理常见的指令布局,但不是一个完整的 nginx 解释器。
- 当固定了依赖项版本或存在本地 virtualenv 时,Python 依赖项发现能力最强。
- Composer、npm 和 pip 工具可以报告比仅解析清单文件更精确的运行时发现。
- OSV 并未对每个生态系统提供同等深度的覆盖。缓存完整性仅取决于检测到的包对应的上游数据。
- 除非 `gitea` 二进制文件可用或存在 `VERSION` 文件,否则 Gitea 版本检测是启发式的。
- 位于 `proxy_pass` 之后且没有可读的本地文件系统树的项目可能只会产生服务级别的检测,而不是完整的依赖项提取。
- 修复版本的准确性仅取决于上游公告元数据。当 Cerberus 必须从范围表达式中推断第一个安全版本时,它会保持措辞明确且保守。
## 改进计划
1. 添加对 nginx `upstream` 块的支持,并更精确地将命名的 upstream 映射到服务 socket。
2. 添加针对通过 `apt` 安装的代理服务的 Debian 软件包关联。
3. 改进对 `pyproject.toml` 以及来自 Poetry、Pipenv 和 PDM 的 lockfile 的解析。
4. 为 Ruby、Java 或通用容器等新技术栈添加插件接口。
5. 为 npm、Packagist、PyPI 和 Go 之外的生态系统添加更丰富的修复指南。
6. 添加可选的报告级别的控制功能,例如计划的定期摘要、警报抑制窗口和更丰富的通知路由。
标签:Debian, Nginx, Python, SQLite, 恶意代码分类, 无后门, 监控告警