☰
Ajenti check_certificates 插件实战:批量监控 SSL/TLS 证书有效期与到期预警
2026/9/27 23:38:06 网站建设 项目流程
  • 后端
  • 运维

【免费下载链接】ajenti

Ajenti Core and stock plugins

项目地址:https://gitcode.com/gh_mirrors/aj/ajenti
点击查看免费下载

导读

本文围绕 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 可以看到该插件的基本元信息:

字段值说明
namecheck_certificates插件内部标识名
version0.9插件版本
titleCheck certificates界面显示标题
iconcertificate侧边栏图标
dependenciescore(!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 列,从左到右依次为:

  1. Domain(域名/主机名):已登记待检查的域名;
  2. Port(端口):对应服务的探测端口;
  3. Issuer(签发者):证书签发机构,例如图中显示的Let's Encrypt;
  4. End(到期时间):证书的过期时间;若检查异常,该列会显示具体错误信息而非时间,例如Can not handle SSL on this port !(该端口无法处理 SSL)、Host refuse the connection !(主机拒绝连接);
  5. 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字典包含以下字段:

字段类型/说明
statusdanger/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.timeoutTimeout from host !(主机超时)
socket.gaierrorCould not resolve hostname !(无法解析主机名)
ssl.CertificateErrorCertificate is not valid !(证书无效)
ssl.SSLErrorCan not handle SSL on this port !(该端口无法处理 SSL)
ConnectionRefusedErrorHost 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"),因为证书有效期以天计,频繁探测既无必要也会给被检主机带来无谓连接,这是典型的按业务特性设计轮询节奏的做法。

典型使用场景与配置建议

结合上述实现,可以总结出该插件的典型用法:

  1. Web 服务证书巡检:登记所有对外 HTTPS 服务的域名(默认 443 端口),在面板内集中查看每个域名的签发者与到期时间;
  2. 邮件服务器证书巡检:对 SMTP 服务器登记端口 587,插件自动以 STARTTLS 方式抓取证书并评估剩余天数;
  3. 非标端口服务:对运行在 8000、8443 等端口的服务,显式填写端口即可;
  4. 异常快速定位:当某主机出现Could not resolve hostname !(DNS 问题)、Host refuse the connection !(服务宕机/防火墙拦截)、Can not handle SSL on this port !(端口被非 TLS 服务占用)等提示时,可第一时间排查;
  5. 面板总览:把 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

项目地址:https://gitcode.com/gh_mirrors/aj/ajenti
点击查看免费下载

相关推荐

上一篇:Formbricks性能优化指南:PostgreSQL数据库架构与调优策略
下一篇:智能网页自动化革命:5步打造你的AI数字员工

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询