Watch & Learn

Debugwar Blog

Step in or Step over, this is a problem ...

让群晖用上Let‘s Encrypt证书并设置自动更新

2022-09-05 @ UTC+0

群晖的DSM默认带了一个Synology自签名证书,使用web访问时浏览器会提示网站是不受信任的网站。作为一个强迫症,这个可不能忍!于是花了点时间研究了一下怎么通过DDNS申请Let's Encrypt的证书。

本次计划有这么几个原则:

  1. 尽量使用虚拟化方案,方便后续迁移(虽然后续更换NAS的可能性微乎其微)
  2. 对整个系统的侵入性尽量的低,能少做配置类工作就少做

幸好目前DSM已经支持了Docker虚拟化,可以基本满足上面两个需求。既然需求和目标已经明确了,接下来开工!

acme.sh申请证书

首先介绍一下笔者环境的基本信息:笔者用duckdns动态域名将DSM映射到了公网以方便访问,所以笔者倾向于给duckdns的域名申请Let's Encrypt的证书。同时笔者比较喜欢用acme.sh,而该程序内置了对duckdns的支持。

问题是,虽然有公网ip,但是由于国内运营商封禁了80和443端口,因此并不能使用http的方式申请ddns域名的证书,经过调研,发现duckdns可以设置txt记录,因此决定使用dns的txt记录方式申请证书。

登录DSM,使用如下命令创建acme.sh镜像:

  1. docker run -itd -v <保存证书的绝对路径>:/acme.sh --env DuckDNS_Token="xxxxxxxx-oooo-xxxx-zzzz-aaaaaaaaaaa" --net=host --name=acme.sh --restart=always neilpang/acme.sh daemon 

neilpang/acme.sh为网上其他人做的镜像,经过调研完全满足本次的需求。注意将上述命令 <保存证书的绝对路径> 部分替换为你自己存储证书的路径, 同时如果你用的也是duckdns,需要设置一个环境变量DuckDNS_Token,acme.sh对其他ddns的支持请参考文末的参考1

如果上述容器创建成功,便可以在web页面看到刚才创建的镜像:


然后执行如下命令正式开始申请证书,其中xxxx.duckdns.org为笔者的ddns域名:

  1. docker exec acme.sh --set-default-ca --server letsencrypt
  2. docker exec acme.sh --issue --dns dns_duckdns -d <你的域名> --insecure --debug  

网络没有问题的情况下,会在 <保存证书的绝对路径> 位置,以<你的域名> 为文件夹名,存放申请到的证书:

acme.sh更新证书

由于Let's Encrypt申请的证书有效期为3个月,因此需要考虑定期更新证书的问题。

好消息是这个问题neilpang/acme.sh容器已经为我们考虑到了,只要你是按照上文的daemon模式启动的这个容器,容器会自动创建crontab任务,每天检查证书是否需要更新:


因此我们只需要保证acme.sh容器一直运行,即可自动更新申请到的证书。

让DSM帮我们初始化证书

OK,现在证书有了,接下来需要配置DSM使用申请的证书。这里笔者偷个懒,先让DSM帮我们做一遍初始化,登录DSM依次选择“控制面板”、“安全性”、“证书”,选择“添加新证书”并点“下一步”:



第二步选择导入证书,名字随意起,建议使用你的域名, 这一步注意一定要勾选“设为默认证书”,因为后文中我们的更新脚本依赖默认证书这一特性:


第三步选择上面使用acme.sh申请的证书:


点击确定之后,就可以在DSM的证书管理中看到刚才申请的证书了:


此时选中刚才导入的证书, 点击上方的设置按钮, 将所有系统的证书设置为刚才导入的证书:


点击确定后会提示你要重启Web服务,重启完成之后,退出到登录页面,发现使用ddns域名访问DSM就会被标记为安全链接了:

配置证书自动更新

通过研究发现,DSM将所有证书信息存放在了/usr/syno/etc/certificate/_archive目录下,且/usr/syno/etc/certificate/_archive/DEFAULT文件中记录了当前的默认证书(此处关联上文中提到的勾选默认证书动作)。

因此,思路是先从DEFAULT文件中读出当前证书的文件夹名, 然后把默认证书替换掉系统服务下的证书文件,最后重启系统服务即可。形成的更新脚本如下(假设你将如下内容保存为update-certificate.sh文件),使用方法:

./update-certificate.sh “<保存证书的绝对路径>/<你的域名>”

脚本内容如下:

  1. #!/bin/sh  
  2.   
  3. ACME_CERTIFICATE_PATH=${@:1:1}  
  4. ACME_DOMAIN_NAME=$(ls ${ACME_CERTIFICATE_PATH}/*.csr | tr -d "\n")  
  5. ACME_DOMAIN_NAME=$(basename ${ACME_DOMAIN_NAME})  
  6. ACME_DOMAIN_NAME=${ACME_DOMAIN_NAME//.csr/}  
  7.   
  8. SYNOLOGY_CERTIFICATE_PATH="/usr/syno/etc/certificate"  
  9. SYNOLOGY_DEFAULT_CERTIFICATE_NAME=$(cat "${SYNOLOGY_CERTIFICATE_PATH}/_archive/DEFAULT" | tr -d "\n")  
  10. SYNOLOGY_DEFAULT_CERTIFICATE_PATH="${SYNOLOGY_CERTIFICATE_PATH}/_archive/${SYNOLOGY_DEFAULT_CERTIFICATE_NAME}"  
  11.   
  12. /bin/sh -c "cp '${ACME_CERTIFICATE_PATH}/fullchain.cer' ${SYNOLOGY_DEFAULT_CERTIFICATE_PATH}/fullchain.pem"  
  13. /bin/sh -c "cp '${ACME_CERTIFICATE_PATH}/${ACME_DOMAIN_NAME}.cer' ${SYNOLOGY_DEFAULT_CERTIFICATE_PATH}/cert.pem"  
  14. /bin/sh -c "cp '${ACME_CERTIFICATE_PATH}/${ACME_DOMAIN_NAME}.key' ${SYNOLOGY_DEFAULT_CERTIFICATE_PATH}/privkey.pem"  
  15.   
  16. for each_file in `find ${SYNOLOGY_CERTIFICATE_PATH} -name info -not -path "${SYNOLOGY_CERTIFICATE_PATH}/_archive/*"`;do  
  17.     each_directory=$(dirname ${each_file})  
  18.     /bin/sh -c "cp '${ACME_CERTIFICATE_PATH}/fullchain.cer' ${each_directory}/fullchain.pem"  
  19.     /bin/sh -c "cp '${ACME_CERTIFICATE_PATH}/${ACME_DOMAIN_NAME}.cer' ${each_directory}/cert.pem"  
  20.     /bin/sh -c "cp '${ACME_CERTIFICATE_PATH}/${ACME_DOMAIN_NAME}.key' ${each_directory}/privkey.pem"  
  21. done  

最后,在DSM的“控制面板”、“任务计划”中新建一个任务, 每个页面的设置如下,这样每个月的1号会将容器中申请(更新)的证书拷贝到DSM系统的证书目录下:


注意: 由于笔者是个NAS关机党,NAS每天晚上12点会关机,第二天早上9点会启动,因此上面的脚本中没有加入重启服务的功能,你们可以根据自己的需求加入需要重启的服务!

参考

  1. acme.sh: How to use DNS API
目录
acme.sh申请证书
acme.sh更新证书
让DSM帮我们初始化证书
配置证书自动更新
参考

版权所有 (c) 2020 - 2025 Debugwar.com

由 Hacksign 设计