# certlens **Repository Path**: tomlen/certlens ## Basic Information - **Project Name**: certlens - **Description**: 证书体检镜:目录盘点 到期分层 端点实测,13项检查,零依赖单文件CLI(PEM/DER通吃,退出码可接cron告警) - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-23 - **Last Updated**: 2026-09-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # certlens [简体中文](README.md) | [English](README_EN.md) > **证书体检镜** — 目录盘点 + 到期分层 + 域名端点实测,13 项检查,零依赖单文件。 > 你的证书不是突然过期的,只是没人看它。 [![release](https://img.shields.io/badge/release-v1.0.0-blue)]() [![license](https://img.shields.io/badge/license-MIT-green)]() [![python](https://img.shields.io/badge/python-3.8%2B-informational)]() ## 为什么有这个工具 每张证书都写了 `Not After`。但没人写「谁在盯着这个日子」。 浏览器记住例外、`curl -k` 成了习惯、内网服务的报错被当成玄学——直到某天早上 nginx 起不来,大家才知道证书昨晚死了。 `certlens` 把「看一眼」变成一个可以进 cron 的动作: ```bash python3 certlens.py watch ~/certs; echo exit=$? ``` 退出码 `2` = 有 P0/P1。剩下的交给 crontab。 ## 快速开始 ```bash # 直接跑(零依赖,Python 3.8+ 标准库,需要系统有 openssl) python3 certlens.py scan ./certs # 或一键安装到 /usr/local/bin curl -fsSL https://cdn.jsdelivr.net/gh/tomlen/certlens@main/install.sh | bash ``` ## 用法 ```bash # 盘点目录/单文件(PEM/DER、fullchain 通吃),到期分层 + 弱配置 certlens.py scan ~/certs [--json] [--strict] [--as-of ISO] # 实测远程端点:到期/域名匹配/链完整度(浏览器不报错≠没问题) certlens.py probe 内网服务:8443 # 默认档案位(./certs ~/certs + 配置文件),只报 P0/P1 certlens.py doctor # cron/CI 友好,--warn 自定义警报线,退出码可接告警 certlens.py watch ~/certs --warn 30 # 按签发方输出续期建议(Let's Encrypt/ZeroSSL/自签 各有各的动作) certlens.py renew-hints ~/certs ``` 输出长这样: ``` certlens v1.0.0 — 证书体检镜 扫描 1 份档案(3 张证书)· scan …/nginx -------------------------------------------------------------- P2 CL-04 …/nginx/example.com.pem 剩余 42 天(2026-11-04 到期) -------------------------------------------------------------- 合计 P0=0 P1=0 P2=1 ``` ## 13 项检查 | 检查项 | 级别 | 一句话 | |---|---|---| | CL-01 已过期 | P0 | notAfter 已过 | | CL-02 临终 | P0 | 剩余 ≤ 7 天 | | CL-03 到期警报 | P1 | 剩余 ≤ 30 天(`--warn` 可调) | | CL-04 到期提醒 | P2 | 剩余 ≤ 90 天 | | CL-05 私钥不配对 | P1 | 同目录候选私钥与证书公钥不一致 | | CL-06 弱密钥 | P1 | RSA < 2048 / EC < 256 | | CL-07 弱签名 | P1 | SHA1 / MD5 签名 | | CL-08 域名不匹配 | P1 | 实测端点证书不含该域名/IP | | CL-09 链不完整 | P2 | 服务器只回一张证书且非自签 | | CL-10 超长有效期 | P2 | 叶子证书 > 398 天(CA/B Forum 上限) | | CL-11 无 SAN 仅 CN | P2 | 2017 后签发不被现代客户端接受 | | CL-12 端点不可达 | P1 | 连接/握手失败(含超时) | | CL-13 自签名 | P2 | issuer == subject(部署意图人工判断) | ## 四条不变量 | 不变量 | 承诺 | |---|---| | INV1 事实性 | 每条 finding 必带证据(notAfter/serial/公钥对照/服务器返回),不做无证据断言 | | INV2 幂等 | 同一输入同一 `--as-of` 连续两次 scan,除 `generated_at` 外输出逐字节一致 | | INV3 只读 | 不改写任何被扫描文件,不做任何修复动作 | | INV4 退出码 | 0=干净(`--strict` 时 P2 也计) 1=内部错误 2=有发现 | ## 接入 cron ```cron # 每天早上 8 点体检,异常时退出码 2 → 告警链自己接 0 8 * * * python3 ~/certlens/certlens.py watch ~/certs >> ~/certlens.log 2>&1 ``` 配置默认扫描位和端点(可选,`~/.config/certlens.conf`): ```ini [scan] dirs = ~/certs, /usr/local/etc/nginx/certs [endpoints] targets = vpn.example.internal:8443, gateway.example.internal:443 ``` 之后 `certlens.py doctor` 一条命令全查。 ## 自测 ```bash python3 selftest.py # 22 项,fixture 全走临时目录 + 本地 TLS 服务器,不碰真实配置 ``` ## 一句大实话 certlens 看的是「文件里的证书」和「端点此刻回的证书」。它不验证吊销状态(OCSP/CRL),不做扫描器(不是 nmap),也不替你续期——`renew-hints` 只告诉你该跑哪条命令。 ## 为什么不是另一个工具 - `openssl x509 -enddate`:一次看一张,没有分层,没有档案 - `check_ssl_cert`(nagios 插件):单端点为主,不管本地档案盘点和私钥配对 - `certbot`:只管自己签发的,不管你内网那一堆自签和采购证书 certlens 把「本地档案」和「线上端点」放进同一份体检报告。 ## License MIT