Guangyang Li

群晖 NAS 上快速申请与自动部署 SSL 证书

我在 NAS 上绑定了自定义域名,之前没有为此域名申请 SSL 证书,浏览器在访问 NAS 时会返回“不安全”警告。虽然我的 NAS 只能内网访问,并没有太多安全上的风险,但我也厌倦了每次点击忽视警告,因此决定用在 NAS 上申请并部署 SSL 证书。我也强烈建议你检查自己的外网使用需求,如果只是为了绑定域名和申请 SSL 证书,那么外网访问并不是必需的,具体操作可见我的另一篇文章:通过自建 DNS 给仅内网访问的群晖 NAS 做域名解析

翻看了很多篇介绍在群晖 DSM 内申请证书的各种方法的文章,感觉很多方案已经过时,或者不够便捷。目前通过 Docker + acme.sh + DNS 验证来申请和部署是我找到的最简便的方案

先罗列我认为较麻烦的做法:

  • 使用针对群晖的 acme 脚本,如 andyzhshg/syno-acme
    基本上是 acme.sh 的 wrapper,无法减少因为 acme.sh 等依赖升级带来的变动,反而增加了额外的依赖。
  • 通过其他云服务商的面版申请证书,通过群晖面板导入证书
    此方案需要较多手动操作,能够自动化的部分也因各 SSL 服务商提供的 API 不同而有不同。
  • 使用群晖面板内的 Let’s Encrypt 证书申请功能
    此方案对仅本地访问的 NAS 不友好。该功能仅支持 HTTP-01 验证方式,因此必须开放 80 或 443 端口。Let’s Encrypt 不公布验证服务器的地址,因此也难以设立针对 Let’s Encrypt 的防火墙白名单。
  • 通过 SSH 手动安装 acme.sh
    由于 DSM 默认没有安装 git,需要使用 wget 和 tar 下载解压后安装。如果要让 Web 服务器加载新证书,acme.sh 还必须安装在 root 用户下。

如果你还在使用以上这些方案,那么换用本文的方法会让你的任务流程简化很多。

Acme.sh 的官网 Wiki 页面提供了在群晖 NAS 上使用的例子:Synology NAS Guide 讲解了如何在群晖 NAS 上申请和部署证书,Run acme.sh in docker 介绍了如何通过 Docker 以 executable 模式或 daemon 模式运行 acme.sh。

本文就详细介绍如何根据上面的例子,在群晖 NAS 中使用 Docker + acme.sh,以最简便的方法申请和部署 SSL 证书。

1. 准备 DNS 服务的 Token

以 Cloudflare 为例,我们需要创建一个有编辑 DNS 记录权限的 Token。

进入 Cloudflare 页面,点击 My Profile > API Tokens > 选择 “Edit zone DNS” > Use template 来到如下页面:

Cloudflare Token 申请

这个模板自动添加了 Zone - DNS 的编辑权限。在 Zone Resources 中选择绑定的域名,然后 Continue to summary -> Create Token。将其返回的 Token 保存下来。

如果不确定 Token 是否有效,可以通过如下命令确认。将 “<YOUR_TOKEN>” 替换为你自己的 Token。

curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
     -H "Authorization: Bearer <YOUR_TOKEN>" \
     -H "Content-Type:application/json"

2. 通过容器化的 acme.sh 注册 ACME 账号

尽管可以通过群晖面板中的 Docker GUI 下载和启动镜像,但通过 CLI 仅需一个命令即可完成下载和运行。

SSH 进入 NAS 后,执行 sudo -i 命令进入 root 用户,运行以下命令:

docker run --rm \
  -v "/volume1/docker/acme":/acme.sh \
  --net=host \
  neilpang/acme.sh \
  --register-account -m <YOUR_EMAIL> --server letsencrypt

其中,<YOUR_EMAIL> 应替换为你的邮箱;
/volume1/docker/acme 可以替换为你想要加载 acme 输出的本地目录,并确保文件夹已存在;
--server letsencrypt 可以替换为你想用的 SSL 服务商,Acme.sh 目前的默认选项是 zerossl,本文仅以 letsencrypt 为例。

成功的话会显示 Registered 和 ACCOUNT_THUNMPRINT 值。但后文并不需要用到这个值。

3. 签发证书

这里我们以 executable 模式运行 acme.sh Docker。此模式下只有在运行时才会启动容器,命令完成后容器即关闭。如果使用 daemon 模式,则 acme.sh 会每 60 天更新证书,容器不会被关闭。

仍在 root 用户下,运行:

export DOMAIN=example.com

docker run --rm \
  -v "/volume1/docker/acme":/acme.sh \
  -e CF_Token="<YOUR_CF_TOKEN>" \
  --net=host \
  neilpang/acme.sh \
  --issue --dns dns_cf --ocsp --server letsencrypt \
  -d "${DOMAIN}" -d "*.${DOMAIN}"

其中,DOMAIN 变量的值应替换为你的域名;
<YOUR_CF_TOKEN> 应替换为你申请到的 Cloudflare Token;
--dns dns_cf 参数指定了我们要使用 DNS 验证方式,并使用 Cloudflare 的 Token 让 acme.sh 自动添加验证条目;
--server letsencrypt 参数与上一步骤注册时的一致。

命令执行完后,证书会输出在 /volume1/docker/acme/<DOMAIN> 目录下。

4. 部署证书

证书生成后,不建议手动复制和导入生成的证书。Acme.sh 的 deploy 命令提供了群晖 NAS 的部署方式。具体命令如下:

docker run --rm \
  -v "/volume1/docker/acme":/acme.sh \
  -e SYNO_Username="<NAS_USERNAME>" \
  -e SYNO_Password="<NAS_PASSWORD>" \
  -e SYNO_Scheme="https" \
  -e SYNO_Port="5001" \
  -e SYNO_Certificate="acme.sh certificate" \
  -e SYNO_Create="1"
  --net=host \
  neilpang/acme.sh \
  --deploy --insecure --deploy-hook synology_dsm \
  -d "${DOMAIN}" -d "*.${DOMAIN}"

其中,SYNO_Certificate 不能为空;
如果是首次创建该证书,就需要加上 SYNO_Create="1",否则可以去掉;
如果选择以 HTTP 方式创建链接,则可不指定 SYNO_SchemeSYNO_Port--insecure 也可删除。

如果启用了二次验证,则还需要指定 SYNO_DID 值,具体解释可参见 https://github.com/acmesh-official/acme.sh/wiki/Synology-NAS-Guide#deploy-the-default-certificate

5. 更新证书

Let’s Encrypt 证书有效期有三个月,因此我们还需要定期更新它。使用群晖自带的定时任务功能即可。

在群晖的控制面板中新建定时任务,将如下命令复制进任务设置中:

export DOMAIN=example.com

docker run --rm \
  -v "/volume1/docker/acme":/acme.sh \
  -e CF_Token="<YOUR_CF_TOKEN>" \
  --net=host \
  neilpang/acme.sh \
  --renew --force --dns dns_cf --ocsp --server letsencrypt \
  -d "${DOMAIN}" -d "*.${DOMAIN}"

docker run --rm \
  -v "/volume1/docker/acme":/acme.sh \
  -e SYNO_Username="<NAS_USERNAME>" \
  -e SYNO_Password="<NAS_PASSWORD>" \
  -e SYNO_Scheme="https" \
  -e SYNO_Port="5001" \
  -e SYNO_Certificate="acme.sh certificate" \
  --net=host \
  neilpang/acme.sh \
  --deploy --insecure --deploy-hook synology_dsm \
  -d "${DOMAIN}" -d "*.${DOMAIN}"

以上命令大致是步骤 3 与 4 的合并,但步骤 3 中的 --issue 替换为了 --renew --force,因为我们是要对证书进行更新。部署命令中也不再需要 SYNO_Create 参数,因为证书已经创建了。其他参数皆一致。

将此任务的运行用户设置为 root

群晖定时任务,运行用户为 root

频率设置为每个月一次:

群晖定时任务,频率为每个月一次

还可按需求设置保存任务输出,和任务状态通知。任务保存后可以手动运行一次,检查输出无误。

如此设置后,自动化的 SSL 证书更新和部署就大功告成了。