- 后端
- 运维
【免费下载链接】ajenti
Ajenti Core and stock plugins
导读
本文围绕 Ajenti 内置的check_certificates插件展开,讲解如何在一个管理面板中集中监控大量主机、服务端口上的 SSL/TLS 证书是否仍然有效。读完本文,你将掌握该插件的安装配置、Web 界面操作(添加/删除域名、查看到期状态)、默认端口与 STARTTLS 探测逻辑,并能深入到源码层面理解证书抓取、剩余天数分级(danger/warning/info/success)与仪表盘小部件的工作原理,可直接用于部署证书续期监控体系。
插件是什么:一个轻量的证书有效性巡检工具
check_certificates 插件 README 对它的定位只有一句话:"Test if some certificates are still valid. Usefull when you have to monitor a lot of certificates renew."(检测某些证书是否仍然有效,当你需要监控大量证书续期时非常有用)。
这是 Ajenti 面板中的一款实用工具插件,它的核心价值在于:当你的环境中散落着多个域名的 HTTPS 证书、邮件服务器证书(STARTTLS)时,无需逐个使用openssl s_client手工探测,而是把域名统一登记到一个列表里,Ajenti 周期性批量检查,并以红/黄/蓝/绿四级状态直观呈现剩余有效期,从而在证书过期前及时续期,避免服务因证书失效而中断。
从 plugin.yml 可以看到该插件的基本元信息:
| 字段 | 值 | 说明 |
|---|---|---|
name | check_certificates | 插件内部标识名 |
version | 0.9 | 插件版本 |
title | Check certificates | 界面显示标题 |
icon | certificate | 侧边栏图标 |
dependencies | core(!PluginDependency { plugin_name: core }) | 依赖 Ajenti 核心插件 |
resources | 前端模块、路由、控制器、模板等 | resources/js/*与resources/partial/* |
插件依赖列表(requirements.txt)仅有aj与ajenti.plugin.core,说明它是一个纯粹的"面板内工具",自身不依赖额外的系统服务。
注:README 中 "Usefull" 为原文档拼写,引用时保持原文。
安装与启用
check_certificates属于 Ajenti 的官方内置插件,随发行包一起提供。启用方式与其他 Ajenti 插件一致:
- 通过 Web 界面的Plugins(插件管理)页面找到
Check certificates并启用; - 或在配置文件中把插件加入启用列表。
启用后,插件通过 main.py 中的SidebarItemProvider向 Ajenti 侧边栏注册入口:
from jadi import component from aj.plugins.core.api.sidebar import SidebarItemProvider @component(SidebarItemProvider) class ItemProvider(SidebarItemProvider): def __init__(self, context): self.context = context def provide(self): return [ { 'attach': 'category:tools', 'id': 'check_cert', 'name': _('Check certificates'), 'icon': 'fas fa-certificate', 'url': '/view/check_cert/certificates', 'children': [], } ]该代码将菜单项挂载到侧边栏的Tools(工具)分类下,点击后跳转到/view/check_cert/certificates页面。url的路径形如/view/check_cert/certificates,其中check_cert是路由命名空间,certificates是对应的视图名,前端路由由 plugin.yml 中声明的resources/js/routing.es负责解析。
界面操作:从列表总览到添加新主机
官方文档 check_certificates.rst 对界面的描述如下:
- 列表视图:一眼即可看出你的 SSL 证书是否仍然有效;
- 列表展示每个条目的hostname(主机名)、port(端口)、issuer(证书签发者)、证书到期时间(End)、连接状态(Status);
- 添加或移除主机非常容易;
- 默认对443 端口(HTTPS 标准端口)进行测试,你也可以指定其他端口,如 8000 或 587;
- 当指定端口为587时,Ajenti 会尝试建立STARTTLS连接(典型场景:邮件服务器 SMTP 的证书检查)。
证书列表视图
如上图所示,列表表格共 5 列,从左到右依次为:
- Domain(域名/主机名):已登记待检查的域名;
- Port(端口):对应服务的探测端口;
- Issuer(签发者):证书签发机构,例如图中显示的
Let's Encrypt; - End(到期时间):证书的过期时间;若检查异常,该列会显示具体错误信息而非时间,例如
Can not handle SSL on this port !(该端口无法处理 SSL)、Host refuse the connection !(主机拒绝连接); - Status(状态):证书有效性状态,使用徽章样式标识——正常证书显示绿色对勾(✔️),异常证书显示红色骷髅(💀)。
每行最右侧带有删除图标,可一键移除条目;表格底部有Add host(添加主机)按钮,用于登记新的待检查域名。
添加新主机
点击Add host后弹出New hostname(新主机名)模态表单,包含两个字段:
- URL:待检查的域名(可直接带端口,详见下文 API 解析);
- Port (default is 443 if empty):服务端口,留空时默认使用 443;上图中示例填写为
587(邮件服务器 STARTTLS 场景)。
确认后点击ADD提交,CANCEL取消。所有登记的主机 URL 都保存在用户配置中(见 views.py 中handle_api_check_cert的注释:"All urls are stored in the user config")。
端口约定:443 的 HTTPS 探测与 587 的 STARTTLS 探测
这是本插件最值得注意的行为设计:端口不同,探测协议不同。
- 默认端口443:走标准 HTTPS/TLS 直连探测;
- 端口587:走STARTTLS探测,适用于 SMTP 邮件服务器(邮件服务常用 587 端口先明文握手再升级为 TLS);
- 其他任意端口(如 8000):按普通 TLS 直连处理。
该逻辑在 api.py 的checkOnDom中体现:
try: if port == 587: cert = CertLimitSTARTTLS(hostname) else: cert = CertLimitSSL(hostname, port)也就是说,你在添加主机时把端口填成 587,插件就会自动切换为 STARTTLS 握手方式去拉取证书,无需任何额外配置。
源码级实现原理
1. 证书抓取:CertLimitSSL 与 CertLimitSTARTTLS
api.py 提供了两个底层的证书获取函数:
CertLimitSSL(hostname, port)—— 建立 TLSv1.2 直连会话抓取对端证书:
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) s = ctx.wrap_socket(socket.socket(), server_hostname=hostname) s.settimeout(10) s.connect((hostname, port)) cert = crypto.load_certificate(crypto.FILETYPE_ASN1, s.getpeercert(binary_form=True)) s.close() return cert要点:
- 使用
ssl.PROTOCOL_TLSv1_2上下文,并通过server_hostname=hostname启用 SNI(Server Name Indication),确保对端返回与域名匹配的证书; s.settimeout(10)设定 10 秒超时,防止不可达主机长时间阻塞;s.getpeercert(binary_form=True)拿到 DER 二进制证书,再用 OpenSSL 的crypto.load_certificate(crypto.FILETYPE_ASN1, ...)解析为OpenSSL.crypto.X509对象。
CertLimitSTARTTLS(hostname)—— 通过 SMTP 587 端口以 STARTTLS 方式抓取证书:
connection = smtplib.SMTP(hostname, 587) connection.starttls() cert = crypto.load_certificate(crypto.FILETYPE_ASN1, connection.sock.getpeercert(binary_form=True)) connection.quit() return cert这里借助 Python 标准库smtplib.SMTP建立连接后调用starttls()升级为加密通道,再从底层 socket 上取出对端证书,最后quit()优雅关闭。
2. 状态分级:checkOnDom 的剩余天数阈值
checkOnDom(hostname, port='443')是核心入口:它把证书的各类信息收拢进一个字典,并根据"距离到期还剩多少天"附加 Bootstrap 状态类。源码注释与实现明确给出了四级阈值:
| 剩余天数 | status值 | restTime | 含义 |
|---|---|---|---|
<= 7天 | danger(默认值) | < 7 | 即将过期,红色告警 |
<= 14天 | warning | < 14 | 临近过期,黄色预警 |
<= 28天 | info | < 28 | 需要关注,蓝色提示 |
> 28天 | success | (空) | 状态健康,绿色 |
对应实现:
if remainingDays <= 7: certDetails['restTime'] = '< 7' elif remainingDays <= 14: certDetails['status'] = 'warning' certDetails['restTime'] = '< 14' elif remainingDays <= 28: certDetails['status'] = 'info' certDetails['restTime'] = '< 28' else: certDetails['status'] = 'success'注意danger是字典的初始默认值,因此任何证书一旦进入 7 天倒计时(或探测失败),都会呈现红色告警状态。剩余天数通过证书的notAfter字段与当前时间差计算:
remainingDays = (datetime.strptime(cert.get_notAfter().decode(), "%Y%m%d%H%M%SZ") - now).days证书的notAfter是 ASN.1 格式的 UTC 时间字符串(如20260926000000Z),用%Y%m%d%H%M%SZ解析后与模块加载时的now = datetime.now()相减取天数。
返回的certDetails字典包含以下字段:
| 字段 | 类型/说明 |
|---|---|
status | danger/warning/info/success |
hostname | 被检查的主机名 |
port | 探测端口(字符串转 int) |
url | 原始主机名(用于前端展示/回调) |
notAfter | 证书过期时间;探测失败时则为错误提示文本 |
restTime | 剩余天数档位文本(如< 14) |
issuer | 签发者组织名(O 字段) |
subject | 证书主体(Subject 组件字典) |
notBefore | 证书生效时间 |
签发者信息在源码中是从证书组件里按组织名(O)提取的:
issuer = cert.get_issuer().get_components() # Issuer be like [(b'C', b'US'), (b'O', b"Let's Encrypt"), (b'CN', b'R3')] # Extract Organization name certDetails['issuer'] = [e[1] for e in issuer if e[0]==b'O'][0].decode() certDetails['subject'] = dict(cert.get_subject().get_components())这也是界面列表里Issuer列能直接显示Let's Encrypt这类机构名的原因。
3. 异常处理:探测失败的六大场景
探测网络服务时错误在所难免,api.py 为常见失败场景准备了明确的中文友好提示(源码中通过_()国际化标记),并把错误文本填入notAfter字段,前端据此显示红色异常状态:
| 异常类型 | 提示文本 |
|---|---|
TimeoutError/socket.timeout | Timeout from host !(主机超时) |
socket.gaierror | Could not resolve hostname !(无法解析主机名) |
ssl.CertificateError | Certificate is not valid !(证书无效) |
ssl.SSLError | Can not handle SSL on this port !(该端口无法处理 SSL) |
ConnectionRefusedError | Host refuse the connection !(主机拒绝连接) |
从代码结构看,任何异常路径都会立即返回certDetails,此时status保持默认的danger,从而在界面中以红色徽章醒目提示问题主机。
4. HTTP 端点:前端与后端之间的桥梁
views.py 通过 Ajenti 的 HTTP 插件机制暴露了唯一的 API 接口:
@component(HttpPlugin) class Handler(HttpPlugin): def __init__(self, context): self.context = context @post(r'/api/check_cert') @endpoint(api=True) def handle_api_check_cert(self, http_context): url = http_context.json_body()['url'] return json.loads(json.dumps(checkOnDom(*url.split(':'))))调用方式是POST/api/check_cert,请求体 JSON 中包含url字段,格式为www.domain.com:999这种"域名:端口"形式。服务端用url.split(':')拆出主机名与端口,然后作为位置参数传给checkOnDom;若不携带端口,checkOnDom会采用默认值port='443'。返回结果即上文所述的证书详情字典(JSON 序列化后)。
这解释了界面表单里"URL + Port(留空默认 443)"的设计来源:前端把两个字段组合成hostname:port字符串提交,端口留空时后端自动回落 443。
5. 仪表盘小部件:每小时自动刷新
插件还提供了仪表盘(Dashboard)小部件,由 widget.py 实现:
@component(Widget) class CertWidget(Widget): id = 'cert' name = _('Certificates') template = '/check_certificates:resources/partial/widget.html' def __init__(self, context): Widget.__init__(self, context) self.last_update = None def get_value(self, config): now = datetime.now() if self.last_update is None: self.last_update = now return True # One update per hour is sufficient for certificates if (now-self.last_update).seconds > 3600: self.last_update = now return True return False小部件 ID 为cert,名称为 "Certificates",渲染模板指向插件资源目录下的resources/partial/widget.html。get_value采用每小时最多刷新一次的频率控制(注释明确写道 "One update per hour is sufficient for certificates"),因为证书有效期以天计,频繁探测既无必要也会给被检主机带来无谓连接,这是典型的按业务特性设计轮询节奏的做法。
典型使用场景与配置建议
结合上述实现,可以总结出该插件的典型用法:
- Web 服务证书巡检:登记所有对外 HTTPS 服务的域名(默认 443 端口),在面板内集中查看每个域名的签发者与到期时间;
- 邮件服务器证书巡检:对 SMTP 服务器登记端口 587,插件自动以 STARTTLS 方式抓取证书并评估剩余天数;
- 非标端口服务:对运行在 8000、8443 等端口的服务,显式填写端口即可;
- 异常快速定位:当某主机出现
Could not resolve hostname !(DNS 问题)、Host refuse the connection !(服务宕机/防火墙拦截)、Can not handle SSL on this port !(端口被非 TLS 服务占用)等提示时,可第一时间排查; - 面板总览:把 Certificates 小部件添加到仪表盘首页,配合每小时自动刷新,实现证书健康度的一屏监控。
配置层面的注意点:
- 主机条目以
hostname:port形式保存在用户配置中(见 views.py 注释),无需额外修改服务器级配置文件; - 探测超时固定为 10 秒(
s.settimeout(10)),对于延迟较高的主机可能提示Timeout from host !,属预期行为; - 证书解析依赖 PyOpenSSL(
from OpenSSL import crypto),插件在aj与ajenti.plugin.core依赖之外,实际运行环境需要具备pyOpenSSL支持。
小结
check_certificates是 Ajenti 中一个麻雀虽小、五脏俱全的实用插件:Web 层面提供列表总览、一键增删、状态徽章与仪表盘小部件;后端则由 api.py 中的CertLimitSSL/CertLimitSTARTTLS/checkOnDom三个函数完成证书抓取、解析与四级状态分级,配合 views.py 的/api/check_cert端点实现前后端联动。理解其 7/14/28 天三级预警阈值与"587 端口自动切 STARTTLS"的设计,可以帮助你在实际部署中准确解读状态颜色、规划证书续期节奏,构建一套低成本、可视化的证书有效期监控体系。
如需进一步探索,可继续阅读 插件源码目录、插件元信息 与 官方插件文档。
- 后端
- 运维
【免费下载链接】ajenti
Ajenti Core and stock plugins
相关推荐
Pyroscope 配置指南:YAML 配置文件、CLI 参数与多组件部署实践
Pyroscope 配置指南:YAML 配置文件、CLI 参数与多组件部署实践 本文围绕 Grafana Pyroscope 的配置机制展开,系统讲解 YAML
后端运维OneUptime SSL 证书监控完整指南:有效期监测、过期告警与状态判定
OneUptime SSL 证书监控完整指南:有效期监测、过期告警与状态判定 SSL/TLS 证书过期是导致线上服务中断的最常见隐性故障之一:证书在某个深夜悄然
可观测性后端运维前端云原生微服务AI AgentOneUptime SSL 证书监控完整指南:证书有效期、自签名与有效性验证实战
OneUptime SSL 证书监控完整指南:证书有效期、自签名与有效性验证实战 SSL/TLS 证书过期是导致生产服务中断的最常见原因之一。OneUptime
可观测性后端运维前端云原生微服务AI Agent
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考