ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

libwebsockets lws-acme-client 插件实战:在 lwsws 中自动签发与续期 Let‘s Encrypt TLS 证书

libwebsockets lws-acme-client 插件实战:在 lwsws 中自动签发与续期 Let‘s Encrypt TLS 证书 人工智能AI Agent多模态语音AI 应用【免费下载链接】ten-frameworkOpen-source framework for conversational voice AI agents项目地址https://gitcode.com/TEN-framework/ten-framework点击查看免费下载本篇文章以 third_party/libwebsockets 仓库中的 lws-acme-client 协议插件为核心系统讲解如何让基于 libwebsockets 的服务如 lwsws在启动阶段无中生有地自动向 Lets Encrypt 等 ACME 证书服务商申请、安装并续期浏览器可信任的 TLS 证书。读完本文你将掌握 lwsws 配置文件中的完整 PVOper-vhost option参数含义、tls-sni-01质询的自动化流程、root 权限与低权限运行模式下的证书存储设计以及多 vhost 共享证书的更新机制可直接上手部署一个免人工干预的 HTTPS 服务。引言什么是 lws-acme-clientlws-acme-client是 libwebsockets 的一个协议插件protocol plugin它实现了一个完整的 ACMEAutomatic Certificate Management Environment客户端可以与 Lets Encrypt 以及其它遵循 ACME 协议的证书签发服务商通信自动获取 TLS 证书。它实现了tls-sni-01质询challenge能够凭空from thin air配置出被所有主流浏览器接受的 TLS 证书——也就是说你不需要预先购买或手工放置证书文件只要服务器满足基本条件插件会在运行时自动完成证书的申请与安装。同时它还会在证书剩余有效期不足两周时自动重新申请证书自动续期无需人工干预。该插件同时支持 OpenSSL 与 mbedTLS 两种 TLS 后端因此无论构建时选择了哪种后端都可以正常使用。从源码看插件的实现位于 plugins/acme-client/protocol_lws_acme_client.c文件头注释明确说明This implementation follows draft 7 of the IETF standard, and falls back to whatever differences exist for Boulders tls-sni-01 challenge. tls-sni-02 is also supported.即遵循 IETF ACME 标准草案第 7 版并兼容 BoulderLets Encrypt 的服务端实现的tls-sni-01差异同时支持tls-sni-02。使用前准备三个前提条件要让 lws-acme-client 正常工作你需要满足以下三个前提域名解析为你的服务器 IP 配置域名解析。例如myserver.com必须能够解析到承载你的服务器的那个 IP 地址——因为 ACME 服务器需要根据证书里的域名找到你的服务器来完成 SNI 质询。网络可达开启端口转发或外部防火墙放行规则通常放行443端口使外部网络能够访问到你的服务。启用插件在希望由该插件管理证书的 vhost 上启用lws-acme-client插件。配置 PVO为每个 vhost 添加描述证书中应该包含什么内容的每 vhost 选项per-vhost optionsPVO。完成以上配置后其余工作申请、签发、安装、续期全部由插件自动完成。完整 lwsws 配置示例下面是在 lwswslibwebsockets 的 WebSocket HTTP 服务器守护进程配置文件中启用该插件的完整示例。该配置声明了一个名为home.warmcat.com的 vhost监听443端口并把lws-acme-client协议插件挂到该 vhost 上vhosts: [ { name: home.warmcat.com, port: 443, host-ssl-cert: /etc/lwsws/acme/home.warmcat.com.crt.pem, host-ssl-key: /etc/lwsws/acme/home.warmcat.com.key.pem, ignore-missing-cert: 1, access-log: /var/log/lwsws/test-access-log, ws-protocols: [{ lws-acme-client: { auth-path: /etc/lwsws/acme/auth.jwk, cert-path: /etc/lwsws/acme/home.warmcat.com.crt.pem, key-path: /etc/lwsws/acme/home.warmcat.com.key.pem, directory-url: https://acme-staging.api.letsencrypt.org/directory, country: TW, state: Taipei, locality: Xiaobitan, organization: Crash Barrier Ltd, common-name: home.warmcat.com, email: andywarmcat.com }, ...配置要点说明vhost 级别的host-ssl-cert与host-ssl-key含义与平时完全一致分别指向证书与私钥文件但因为 ACME 插件可以自动生成这些文件所以必须同时给 vhost 打上ignore-missing-cert : 1标记。ws-protocols下的lws-acme-client对象其内部字段就是插件的 PVOper-vhost options用于描述证书内容与 ACME 服务端地址等信息。需要特别强调的是配置里提到的所有目录都必须预先手工创建——lws 不会替你创建目录。这些目录建议设置为0700且属主为root:root即使之后 lws 以降权后的身份运行也没有关系原因见下文安全与密钥存储设计一节。必需 PVORequired PVOs关于ignore-missing-cert与host-ssl-cert/host-ssl-key在 lwsws 配置中host-ssl-cert和host-ssl-key的含义与常规配置完全一致它们指向 vhost 实际使用的证书与私钥文件。区别在于这些文件最初并不存在需要 ACME 插件在运行期把它们生成出来。因此必须给 vhost 设置ignore-missing-cert : 1这样 lwsws 在启动时对缺失的证书/密钥不会报错退出而是会启动 ACME 流程去创建所需证书与密钥。如果是在代码层面实现不使用 lwsws 配置等价的做法是创建 vhost 时确保info.options设置了LWS_SERVER_OPTION_IGNORE_MISSING_CERT位。也就是说ignore-missing-cert : 1在底层对应的就是 vhost options 中的这个标志位。同样在代码中上面展示的每个 per-vhost 选项都可以在创建 vhost 时通过一个struct lws_protocol_vhost_options链表提供。原文档建议参考./test-apps/test-server-v2.0.c注该文件在当前仓库快照中未收录相关 PVO 链表的查询与绑定实现可参见 lib/core-net/vhost.c 中的lws_vhost_protocol_options()与 lib/core-net/wsi.c 中的lws_pvo_search()。auth-pathauth-path是插件存放**自己生成的认证密钥auth keys**的位置。插件启动时会检查该路径下是否已有 JWK 格式的注册密钥如果没有就生成新的 RSA 密钥对并保存。源码 protocol_lws_acme_client.c 中的lws_acme_load_create_auth_keys()函数L663-L686 附近展示了这一逻辑先用lws_jwk_load()尝试加载已有密钥若加载失败即密钥尚不存在则通过lws_genrsa_new_keypair()生成新的 RSA 密钥对再用lws_jwk_save()保存到auth-path。在非 ESP32 平台上默认使用4096 位RSA 密钥见lws_acme_load_create_auth_keys(vhd, 4096)的调用处L852。cert-pathcert-path是插件存放证书文件的位置。它应当与 vhost 使用的host-ssl-cert指向同一个文件这样证书签发完成后 vhost 才能直接使用。路径中至少要包含一个0700 root:root权限的目录原因同样是root-only 存储设计见下。key-pathkey-path是插件存放证书私钥的位置同样应当与 vhost 使用的host-ssl-key一致。路径中同样至少要包含一个0700 root:root权限的目录。directory-urldirectory-url定义你要从中获取证书的ACME 服务端目录 URL。以 Lets Encrypt 为例它有两个练习staging地址https://acme-staging.api.letsencrypt.org/directory正式real地址https://acme-v01.api.letsencrypt.org/directory两者的主要区别在于正式地址的 CA 证书已经预置在绝大多数浏览器中而 staging 地址的 CA 证书不在浏览器内置列表中。同时 staging 服务器对反复测试的限制更宽松让你更随意地滥用它做重复测试。官方强烈建议先用 staging 的 directory-url 确认整个流程按预期工作然后再切换到正式 URL。这样可以避免测试过程中反复触发 Lets Encrypt 的签发频率限制。common-namecommon-name是你的服务器 DNS 名称例如libwebsockets.org。远程 ACME 服务器会用这个名称去找到你的服务器然后执行 SNI 质询——这就是整个自动签发的关键环节ACME 服务器必须能通过该域名访问到你的 443 端口。源码在lws_acme_start_acquisition()L689-L697 附近中会首先检查是否配置了common-nameLWS_TLS_REQ_ELEMENT_COMMON_NAME如果没有则直接返回失败——它是证书申请的必要信息之一。emailemail是证书的联系邮箱地址。源码在 ACME 注册new account阶段使用它对应枚举ACME_STATE_NEW_ACCOUNT见 protocol_lws_acme_client.c 中ACME_STATE_*状态机定义L45-L57即注册一个新的 RSA 密钥 email 组合。可选 PVOOptional PVOs以下 PVO 是证书主体subject中可选的填充项。文档特别注明These are not included in the cert by letsencrypt这些字段 Lets Encrypt 不会包含进证书里即它们会参与证书请求的构造但 Lets Encrypt 签发的证书不会携带这些可辨识身份信息。它们分别是country证书的两字母国家代码Two-letter country code示例中为TW。state证书的州/省State or province示例中为Taipei。locality证书的所在地Locality示例中为Xiaobitan。organization你的公司名称Your company name示例中为Crash Barrier Ltd。从源码角度这些字段对应的 PVO 名称全部定义在pvo_names[]数组中protocol_lws_acme_client.c L643-L655country、state、locality、organization、common-name、subject-alt-name、email、directory-url、auth-path、cert-path、key-path共 11 个。插件在 vhost 初始化LWS_CALLBACK_PROTOCOL_INIT时会遍历 PVO 链表并逐一比对名字完成绑定其中common-name及之后的必填项除subject-alt-name外缺失时初始化会直接失败L826-L845 附近的校验逻辑。安全与密钥存储设计root-only 存储 动态热更新lws-acme-client插件最精巧的设计在于即使 lws 进程以非 root 的 uid/gid 运行、且对存储目录没有任何访问权限它也能在一个完全仅 root 可访问root-only的环境中完成证书和密钥的签发与更新。其实现机制如下源码证据见 protocol_lws_acme_client.c启动阶段以 root 权限打开更新文件描述符在 vhost 初始化LWS_CALLBACK_PROTOCOL_INIT期间插件还拥有 root 权限它会为每个证书和私钥在更新路径上打开并持有两个只写WRONLY文件描述符。这些更新路径就是正常的 cert/key 路径加上.upd后缀即cert-path.upd与key-path.upd源码 L859-L885对%s.upd以LWS_O_WRONLY | LWS_O_CREAT | LWS_O_TRUNC模式打开权限 0600。文件描述符保存在vhd-fd_updated_cert与vhd-fd_updated_key结构体定义见 L128-L129注释写明 these are opened while we have root...。运行期低权限写入之后 lws 即便降权运行这两个 fd 依然有效插件可以随时向其中写入新证书/新私钥。到期前两周自动续期当证书剩余有效期进入两周内时插件会走完整的 ACME 协商流程申请新证书并通过这两个 fd 写入。对应的回调是LWS_CALLBACK_VHOST_CERT_AGINGL898-L929源码首先通过(int)(ssize_t)len 14判断证书是否已接近到期剩余天数不超过 14 天然后确认该 vhost 是否是自己被配置的 vhost接着从caa-element_overrides合并证书元素覆盖项最后调用lws_acme_start_acquisition()开始申请。新证书下载与写入在ACME_STATE_DOWNLOAD_CERT状态L1507-L1581中插件校验响应码为 200 后把证书与私钥分别写入两个.updfdlws_plat_write_cert()且故意dont close it... we may update the certs again随后调用lws_tls_cert_updated()通知 libwebsockets 发生了证书更新。下次启动时落盘下一次服务器启动时如果发现.upd证书与密钥存在它会在降权之前把旧文件备份、将.upd内容拷贝到位作为新证书。这样持久化的证书/密钥始终只存在于 root-only 目录里。长时间运行场景的热更新为了应对服务器长时间不重启的情况lws 在更新证书后还会用证书和密钥的内存临时副本即时更新 vhost 正在使用的 TLS 证书——vhost 无需重启即可使用新证书。通过这套root-only 落盘 内存热更新的双轨机制证书与私钥始终被保护在仅 root 可读的目录中同时 vhost 能动态跟上证书的任何变化。这也解释了为什么前文要求存储目录必须手工创建为0700 root:root——插件在降权前就已打开 fd运行期不再需要目录写权限。多个 vhost 共享同一证书在多个 vhost 使用同一份证书的场景下只需把lws-acme-client插件挂载到其中一个 vhost 实例上即可不要重复挂载。当证书更新时所有使用该证书的 vhost 都会被通知而那些通过相同文件路径访问证书的 vhost 也能同步更新自己的证书。这依赖于前文提到的lws_tls_cert_updated()通知机制以及 vhost 的证书老化cert aging回调。实现注意事项切换 TLS 后端时清除认证密钥一个需要特别注意的实现细节当从 OpenSSL 后端切换到 mbedTLS 后端或反之时必须删除auth-path指向的认证密钥文件示例路径为/etc/lwsws/acme/auth.jwk。原因是认证密钥JWK 注册密钥由旧的加密后端生成切换后端后格式可能不兼容。删除后插件会在下次运行时自动重新生成见lws_acme_load_create_auth_keys()中加载失败则重新生成的逻辑无需手工干预。底层 ACME 状态机一览从源码可以清晰地看到插件实现的 ACME 客户端完整状态机protocol_lws_acme_client.c L45-L57ACME_STATE_DIRECTORY /* GET 目录 JSON 并解析 */ ACME_STATE_NEW_NONCE /* 获取 replay nonce */ ACME_STATE_NEW_ACCOUNT /* 注册新的 RSA 密钥 email 组合 */ ACME_STATE_NEW_ORDER /* 开始请求证书的流程 */ ACME_STATE_AUTHZ /* 授权 */ ACME_STATE_START_CHALL /* 通知服务器准备接收一个质询 */ ACME_STATE_POLLING /* 服务器应正在验证我们的质询 */ ACME_STATE_POLLING_CSR /* 已发送 CSR检查结果 */ ACME_STATE_DOWNLOAD_CERT /* 下载签发的证书 */ ACME_STATE_FINISHED整个流程的起点是lws_acme_start_acquisition()L689若尚未取得目录信息则先从directory-urlGET 目录 JSONACME_STATE_DIRECTORY否则直接进入新账户注册ACME_STATE_NEW_ACCOUNT。即使是非首次运行重复注册也只是收到一个合法的、非致命的 409 JSON 响应源码注释中有明确示例Registration key is already in use并不会导致失败。此外源码中ACME_STATE_NEW_NONCE对应获取 replay nonce这是 ACME 协议防重放anti-replay的要求在证书下载阶段L1518-L1534插件还会处理 ACME 2.0 可能返回的证书链最多 3 张证书通过查找END CERTIFICATE-----标记只保存第一张叶子证书。部署检查清单最后整理一份可操作的部署清单确保域名如home.warmcat.com已解析到服务器 IP且 443 端口对外可达端口转发 / 防火墙放行。手工创建证书存储目录如/etc/lwsws/acme/权限设为0700 root:root。在 lwsws 配置中为 vhost 声明host-ssl-cert/host-ssl-key并设置ignore-missing-cert: 1。在ws-protocols中挂载lws-acme-client至少配置 5 个必需 PVOauth-path、cert-path、key-path、directory-url、common-name、email。先用directory-url指向 Lets Encryptstaging地址验证完整流程确认无误后再切换到正式地址。若从 OpenSSL 后端切换到 mbedTLS 后端记得删除auth-path下的 JWK 文件让其重新生成。多 vhost 共享同一证书时只在其中一个 vhost 上挂载插件。完成上述步骤后libwebsockets 的 lwsws 即具备证书自动申请、自动安装、到期前两周自动续期的全生命周期管理能力HTTPS 服务从此无需人工维护证书。赞分享人工智能AI Agent多模态语音AI 应用【免费下载链接】ten-frameworkOpen-source framework for conversational voice AI agents项目地址https://gitcode.com/TEN-framework/ten-framework点击查看免费下载相关推荐EMQX ACME 插件实战为 MQTT TLS 与 Dashboard HTTPS 自动签发和续期证书EMQX ACME 插件实战为 MQTT TLS 与 Dashboard HTTPS 自动签发和续期证书 EMQX 从 6.1.x 起通过 emqx_acme后端物联网消息队列通信CAS 与 ACME 集成指南基于 Lets Encrypt 的自动化证书签发与续期CAS 与 ACME 集成指南基于 Lets Encrypt 的自动化证书签发与续期 CAS 服务器内置了对 ACMEAutomatic Certific后端认证鉴权单点登录acme-companion 的 Lets Encrypt / ACME 证书自动化指南ACME_HOST 驱动签发、DNS-01 挑战与智能续期全解acme companion 的 Lets Encrypt / ACME 证书自动化指南ACME_HOST 驱动签发、DNS 01 挑战与智能续期全解 本指云原生运维创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表