自建私有 ACME 证书服务:Windows AD CS + acme2certifier + 1Panel 自动签发与续签实战

yaoyue 最后更新于 8 小时前 11 次阅读 3582 字


实战记录日期:2026 年 10 月 软件版本:acme2certifier 0.46.2(nginx-wsgi)/ Debian 13 / 1Panel + OpenResty / Windows AD CS。

本文目标:将现有 Windows AD CS 私有证书颁发机构转化为可供 1Panel 调用的 ACME 服务,实现内部 HTTPS 证书的自动申请、部署与续签。本文根据实际部署对话整理,区分“已实测通过”“示例配置”和“尚待验证”;账户密码、EAB HMAC 等敏感信息均不收录。

脱敏说明:本文涉及的 example.com、ad.example.net、192.168.50.0/24、ca01、Example Issuing CA、svc_certbot 和 example-1panel 均为演示值,不对应真实部署。不要将示例地址直接用于生产环境。

部署结果:已经通过 acme.sh 验证 AD CS 签发、通过 1Panel 自定义 ACME 账户完成 HTTP-01 证书申请并成功绑定站点,随后人工触发续签成功;ACME Directory 返回 externalAccountRequired: true。DNS-01 与泛域名证书方案最终决定暂缓。

一、整体架构与环境

1Panel / OpenResty (192.168.50.10)
   ├── HTTPS: https://acme.example.com/directory
   │        ↓ 反向代理 HTTP
   └────── acme2certifier (Debian 13, 192.168.50.20:8080)
                  ↓ MS-ICPR / SMB 445, 使用 svc_certbot 认证
            Windows AD CS
            ca01.ad.example.net (192.168.50.30)
                  ↓
            Example Issuing CA
                  ↓
            Example Root CA(离线根 CA)
组件 本次部署使用的信息
Active Directory 域 ad.example.net
网站/证书域名 example.com;ACME 服务为 acme.example.com
Windows 证书服务器 ca01.ad.example.net(192.168.50.30)
AD CS CA 名称 Example Issuing CA(由离线 Root CA 签发)
ACME 虚拟机 Debian 13,192.168.50.20
反向代理 1Panel/OpenResty,192.168.50.10
证书吊销/颁发者信息 http://pki.example.com/pki/
ACME 软件 grindsa/acme2certifier:0.46.2-nginx-wsgi
证书模板 ExampleACMEWebServer,RSA 2048,90 天

本环境采用内部拆分 DNS:example.com 公网记录由腾讯云 DNS 管理,内部同名区域由 AD DNS 管理;ad.example.net 仅使用 AD DNS。HTTP-01 测试时,ACME 服务器必须能正确解析目标域名并访问目标 Web 服务的 80 端口。

二、Windows AD CS:准备证书模板和签发账户

1. 确认 CA 服务

在 Windows CA 服务器上确认 CA 名称是 ca01.ad.example.net\Example Issuing CA;确认根证书与 Issuing CA 证书链有效,CRL/AIA 发布正常。

certutil -getconfig
certutil -CATemplates

2. 创建并发布专用证书模板

  • 从 Web Server 模板复制出 ExampleACMEWebServer。
  • 用途保持 Server Authentication;申请者使用 RSA 2048 私钥。
  • 主题名称采用 Supply in the request,让 ACME CSR 提供 CN/SAN。
  • 模板有效期设置为 90 天;不要求人工批准。
  • 在模板安全页,为专用域账户 svc_certbot@ad.example.net 授予 Read 与 Enroll,不要为了签发证书而赋予域管理员权限。
  • 在 CA 控制台的 Certificate Templates 节点发布该模板,并使用 certutil -CATemplates 检查已发布。

如出现模板“拒绝访问”,重点检查模板 ACL、CA 是否发布模板、申请账号是否具有 Enroll。模板自动注册(Autoenroll)与本次通过 MS-ICPR 申请证书不是一回事,不应把自动注册失败直接当成 ACME 签发失败。

3. CA 服务器和网络前提

acme2certifier 必须能解析 ca01.ad.example.net 并使用 MS-ICPR 连接 CA,部署中采用 NTLM 认证(use_kerberos = False);长期使用建议评估 Kerberos。检查 Debian 到 CA 的 TCP 445 连接,并确保服务账号及密码正确。

getent ahostsv4 ca01.ad.example.net
nc -zvw3 192.168.50.30 445

三、Debian 13:部署 acme2certifier

1. 安装 Docker 并准备目录

本次部署使用 Docker/Compose,相关工作目录为 /opt/acme2certifier。以下命令用于检查运行环境;Docker 的具体安装可按照当前 Debian 官方/Docker 官方步骤完成。

docker --version
docker compose version
mkdir -p /opt/acme2certifier/data
cd /opt/acme2certifier

2. Compose 参考文件

下面是与本次部署架构一致的示例;以实际运行的 compose.yaml 为准,尤其是数据卷和环境变量挂载。部署日志曾显示容器端口为 0.0.0.0:8080->80/tcp;在完全内网条件下可以使用,但不宜将该端口直接开放到公网。

services:
  acme-srv:
    image: grindsa/acme2certifier:0.46.2-nginx-wsgi
    container_name: acme2certifier
    restart: unless-stopped
    ports:
      - "8080:80"
    env_file:
      - ./adcs.env
    volumes:
      - ./data:/var/www/acme2certifier/volume

在 /opt/acme2certifier/adcs.env 中保存 AD CS 服务账号口令,真实口令不要写到博客、Git 仓库、Compose 文件正文或故障排查截图中。

A2C_ADCS_PASSWORD=<请填写服务账号真实口令>
chmod 600 /opt/acme2certifier/adcs.env
docker compose up -d
docker compose ps
docker compose logs --tail=80

3. 配置 CA Handler

本次运行中确认使用了 MS-ICPR CA Handler,示例中的关键参数如下。注意:请将下面的内容与现有配置合并,而不是覆盖完整的 acme_srv.cfg;原文件中还可能有数据库、目录、验证及 EAB 相关设置。

[DEFAULT]
server_name = acme.example.com

[CAhandler]
handler_module = acme2certifier.cahandlers.msicpr_ca_handler
host = ca01.ad.example.net
target_domain = ad.example.net
ca_name = Example Issuing CA
user = svc_certbot
password_variable = A2C_ADCS_PASSWORD
use_kerberos = False
template = ExampleACMEWebServer
allowed_domainlist = ["acme-test.example.com", "acme.example.com"]

[Challenge]
challenge_validation_disable = False
http01_block_private_ips = False

http01_block_private_ips = False 是为了允许本次 ACME 服务验证内网目标 IP;只适用于可信任的内部部署场景。务必保留真实的挑战验证,不要以 challenge_validation_disable = True 来绕过验证。配置修改后重启容器。

cd /opt/acme2certifier
docker compose restart
curl -fsS http://192.168.50.20:8080/directory | python3 -m json.tool

四、使用 acme.sh 验证 AD CS 真实签发

正式接入 1Panel 之前,曾先通过 acme.sh 使用 HTTP-01 standalone 模式申请测试证书。测试域名 acme-test.example.com 指向 ACME 测试服务器 192.168.50.20;客户端在验证期间需能监听 TCP 80,且 ACME 服务端能够访问它。

cd /opt/acme-sh
./acme.sh --issue --standalone \
  --server http://192.168.50.20:8080/directory \
  -d acme-test.example.com \
  --keylength 2048

这一步实际成功拿到了由 Example Issuing CA 签发的 RSA 2048、90 天证书,AD CS 曾记录到对应签发请求(可在 Windows AD CS 中查看对应签发请求)。随后对 acme.example.com 也完成过签发;最初因为内部 DNS 返回 NXDOMAIN 失败,添加正确记录后成功。

注意:上述命令对应早期的基础联通性测试。后续开启 EAB 严格模式后,新 ACME 账户注册需要有效的 KID/HMAC,不能直接假设未经 EAB 注册的 acme.sh 客户端还能正常使用。

五、1Panel/OpenResty:配置正式 HTTPS ACME 入口

1. 配置 DNS 与反向代理

  • 内部 DNS 将 acme.example.com 解析到 1Panel/OpenResty 的 192.168.50.10,而不是直接指向后台 192.168.50.20。
  • 在 1Panel 建立 acme.example.com HTTPS 网站,反向代理到 http://192.168.50.20:8080。
  • 保留 Host 及客户端信息,确保 X-Forwarded-Proto 为 https,并检查 ACME Directory 输出的 URL 均为 https://acme.example.com/...。
  • HTTP-01 使用的 http://<目标域名>/.well-known/acme-challenge/... 必须由受验证网站正确响应;不能把所有目标域名的挑战路径误转发到 ACME API。

正式的 ACME Directory 地址:https://acme.example.com/directory。在证书受信任的终端运行:

curl -fsS https://acme.example.com/directory | python3 -m json.tool

2. 处理自签私有根 CA 信任问题

最初在 1Panel 创建自定义 ACME 账户时出现 x509: certificate signed by unknown authority。原因是请求 ACME Directory 的 1Panel 宿主环境尚未信任 Example 根 CA 链。解决方式是将正确的私有 Root CA 导入发起请求的 1Panel 后端运行环境的受信任根证书存储,并检查证书链;不是简单关闭 TLS 校验。修复后用户已确认自定义 ACME 账户创建成功。

六、EAB:保护 ACME 账户注册

本次为 ACME 服务启用了 External Account Binding (EAB)。早期测试 KID 为 acme-lab-test,后续计划规范化为 example-1panel。不要直接将已注册并被 1Panel 续签使用的 KID 改名或删除,应新建 KID、在 1Panel 注册新账户、签发并绑定新证书,验证完成后再退出旧账户。

EAB Handler 的实际路径、JSON Profile 权限格式应以运行实例与对应版本文档核对。此次对话已确认开启严格模式后的 Directory 响应如下:

{
  "meta": {
    "name": "acme2certifier",
    "version": "0.46.2",
    "externalAccountRequired": true
  }
}

这里的 externalAccountRequired: true 说明服务要求新 ACME 账户进行外部账户绑定,但不能单凭这项信息证明每个 KID 的 CA 模板、域名白名单已按预期生效。关于 kid_profile_handler 的细粒度权限,本次曾给出 example-1panel 覆盖 example.com 与 ad.example.net 的配置方案,尚没有贴出完整的正向与越权拒绝实测结果,因此本稿标记为“待补验证”。

七、1Panel 自定义 ACME 账户、申请与续签

1. 创建自定义账户

1Panel → 网站 → 证书 → ACME 账户 → 创建,类型选择“自定义 ACME 服务”,URL 使用 https://acme.example.com/directory;密钥算法选 RSA 2048,按 EAB 策略填写 KID 与 HMAC。已确认账户创建成功。EAB HMAC 不应进入 WordPress 正文或截图。

2. 申请证书

1Panel → 网站 → 证书 → 申请证书,选择新建的自定义 ACME 账户,目标域名 acme.example.com,验证方式 HTTP,勾选自动续签。实际申请日志关键部分如下:

YYYY/MM/DD 16:26:44 开始申请证书,域名 [acme.example.com],方式 [HTTP]
YYYY/MM/DD 16:26:44 [INFO] acme: Obtaining bundled SAN certificate
YYYY/MM/DD 16:26:44 [INFO] acme: use http-01 solver
YYYY/MM/DD 16:26:45 [INFO] The server validated our request
YYYY/MM/DD 16:26:45 [INFO] acme: Validations succeeded; requesting certificates
YYYY/MM/DD 16:26:46 [INFO] Server responded with a certificate.
YYYY/MM/DD 16:26:46 申请 [acme.example.com] 证书成功

随后在 1Panel 的 网站 → HTTPS 将新申请的证书绑定到 acme.example.com,验证服务仍正常访问。后来又由用户手动触发 1Panel 续签并确认成功,因此“签发、部署、手动续签”三项均已实测。自动续期调度本身需继续随到期时间观察,不应将单次手动续签视为未来定时任务的永久保证。

3. 在服务器上核实正在使用的证书

echo | openssl s_client \
  -connect 192.168.50.10:443 \
  -servername acme.example.com \
  2>/dev/null | openssl x509 \
  -noout -subject -issuer -serial -dates

预期签发者 CN 为 Example Issuing CA。需要注意:系统能读取证书不等于信任链自动可信;使用方仍需要信任 Example Root CA,并能按需访问 CRL/AIA。

八、常见问题与排查记录

现象 原因/处理
1Panel 创建 ACME 账户报 x509: certificate signed by unknown authority 导入 Example 私有根 CA 至发起 ACME 请求的 1Panel 运行环境,检查签发链和服务端证书。
首次为 acme.example.com 申请时报 NXDOMAIN 检查内部 AD DNS 正向区域与实际 A 记录;ACME 服务必须能正确解析验证目标。
HTTP-01 验证失败 检查目标主机的 80 端口、DNS 解析、/.well-known/acme-challenge/ 响应是否由 1Panel 正确提供。
EAB 严格模式生效但 Profile 权限不确定 先检查加载的 Handler 和非敏感策略,再分别测试授权域名签发成功与未授权域名被拒绝。
1Panel 证书列表“颁发组织”为空 本次签发 CA 只有 CN=Example Issuing CA,没有组织 O 字段;证书本身与续签均正常,属界面元数据呈现问题,不需要重新签发 CA。
修改配置后 Directory 正常,但不清楚后端是否真正生效 /directory 只验证入口可用;结合容器日志、CA 请求记录及真实签发/拒绝测试判断。

九、运维与安全注意事项

  • 备份 /opt/acme2certifier/ 中的 Compose 文件、配置、持久化数据库和 EAB Profile,并限制备份访问权限;本环境可使用 PVE/PBS 做虚拟机级定期备份。
  • 严禁在 WordPress、公开仓库或日志分享中公开 adcs.env、服务账号密码、EAB HMAC、账户私钥等秘密。
  • 服务当前以内网使用为主,Docker 8080 端口曾监听所有接口;如果未来增加公网路由、端口映射或不可信客户端,应再收紧访问范围。
  • 不要取消 HTTP-01 验证,也不要向 ACME 服务授予不必要的 AD 管理权限。
  • 现有 example.com 使用腾讯云公网 DNS + AD 内网 DNS,ad.example.net 仅使用 AD DNS。DNS-01、通配符证书和 AD DNS 动态 TXT 自动化均已决定暂缓;当前以 HTTP-01 申请普通主机证书为主。
  • 关于 example-1panel 允许两个域名及子域名的独立授权,需补做边界测试后再标记为完成。

十、验收清单与最终状态

项目 本次对话状态
AD CS 专用模板与服务账户 已配置并完成真实签发测试
acme2certifier 0.46.2 容器 已运行并返回正常 ACME Directory
1Panel HTTPS 反向代理 已验证
私有根 CA 信任及 1Panel 自定义 ACME 账户 已解决并成功创建
HTTP-01 申请 acme.example.com 成功
1Panel 网站绑定新证书 成功
1Panel 手动续签 成功
EAB 严格模式 Directory 显示 externalAccountRequired: true
按 KID 隔离 example.com / ad.example.net 权限 尚待完整正反例测试
DNS-01 / 泛域名证书 暂缓
长期定时自动续签可靠性 已开启自动续签设置;持续观察

参考资料

本篇已对真实域名、AD 域、服务器名称、内网 IP、CA 名称、证书模板、服务账号及 EAB KID 进行示例化处理。示例域名 example.com 与 ad.example.net、示例 IP 192.168.50.0/24 不代表实际环境;实际操作须替换为自己的有效配置,并重新核对当前版本文档。