给 QNAP 配置 ACME 那些事
给 QNAP 配置 ACME 那些事
QNAP NAS 使用过程中续签 SSL 证书是一个比较麻烦的事情,因为 QNAP 在国内使用的 ACME 不支持通过 DNS TXT 记录验证,只能通过 HTTP 验证,这就导致了无法使用默认的 Let’s Encrypt 证书续签方式。
因此,基于 QNAP 良好的 Docker 兼容性,我们可以使用 Docker 容器来运行 ACME 客户端,然后通过 DNS 验证来续签证书。
在后续的内容中,<user>
表示你的用户名,<nas_ip>
表示你的 QNAP NAS 的 IP 地址,your.example.com
表示你想要给 NAS 使用的域名。
设置 SSH 登录
在后续的自动化中,acme 容器需要通过 SSH 登录到 QNAP NAS 中,因此需要设置 SSH 登录。
首先,为它创建一个 SSH 密钥对:ssh-keygen -t ed25519 -f ./acme_key -C "acme"
,这句命令会在当前目录下生成两个文件:acme_key
和 acme_key.pub
,分别是私钥和公钥,之后按照以下步骤设置 SSH 登录:
- 在 网路和文件服务 -> Telnet/SSH 中启用 SSH 服务,然后编辑访问权限,启用用户
<user>
。 - 以
<user>
用户登录 QNAP,用户头像 -> 登录与安全性 -> SSH -> SSH 密钥,添加./acme_key.pub
。 - 在本地测试一下是否可以登录:
ssh -i ./acme_key <user>@<nas_ip>
。
选择一个数据目录
在 QNAP 中,我们需要选择一个目录用于存放 ACME 容器的数据,后续会在这个目录下生成证书等文件。
以我自己的配置为例,我具有一个共享文件夹 Config
,用于存放一些配置文件,因此我在这个目录下创建了一个 ACME
目录,用于存放 ACME 容器的数据。
在实际的目录中,它对应了 /share/Config/ACME
,你可以根据自己的情况选择并创建一个目录来代替这一路径进行后续操作:
- 使用默认用户采用 SSH 登录到 QNAP 中。
- 使用
cd /share/Config/ACME
进入到 ACME 目录。 - 使用
vim certbot.key
创建一个空文件,并将上述 SSH 密钥对中的私钥acme_key
的内容复制到这个文件中。 - 使用
ESC
->:wq
保存并退出。 - 使用
sudo chmod 600 certbot.key
修改文件权限。 - 使用
sudo chown admin:administrators certbot.key
修改文件所有者为admin
,也即容器内的root
用户。
配置 ACME 容器
在创建好了上述的准备工作之后,我们可以开始配置 ACME 容器了。
在 Container Station 中,选择 应用程序 -> 创建,名称使用 acmesh
,YAML 配置使用以下内容:
services:
server:
image: neilpang/acme.sh
command: ["daemon"]
container_name: acmesh
restart: always
network_mode: host
volumes:
- "/share/Config/ACME:/acme.sh"
通过采用 network_mode: host
的方式,可以让 ACME 容器直接使用 127.0.0.1
来访问 QNAP 的 SSH 服务,不需要额外的解析。
同时,这里将 /share/Config/ACME
映射到了 /acme.sh
,你应当根据自己的实际情况修改这个路径。
在创建成功后,应该能够在容器列表中看到 acmesh
容器正常运行。
配置 ACME 证书
在容器正常运行之后,可以使用默认用户采用 SSH 登录到 QNAP 中,并使用 docker exec -it acmesh /bin/sh
进入到容器内部。
在容器内部,可以使用 acme.sh --help
查看帮助信息,这里我们需要配置 DNS API 来进行验证,相关的 DNS API 配置可以参考官方文档。
作为参考,我使用的是 Cloudflare 的 DNS API,同时希望使用 ECC 证书,因此我使用了以下命令,其中 your.example.com
表示你的域名:
export CF_Token="XXXXXXXXXXXXXXXX"
export CF_Zone_ID="xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
acme.sh --issue --dns dns_cf -d 'your.example.com' -d '*.your.example.com' --keylength ec-384 --days 85 --server letsencrypt
在预期的情况下,你应当能够正常签发证书,同时在 /acme.sh
目录下看到目录结构:
/acme.sh
├── account.conf
├── ca
├── certbot.key
├── http.header
└── your.example.com_ecc # your domain
├── ca.cer
├── fullchain.cer
├── your.example.com.cer
├── your.example.com.conf
├── your.example.com.csr
├── your.example.com.csr.conf
└── your.example.com.key
创建部署脚本
在签发证书之后,我们需要将证书部署到 QNAP 的 Web 服务中,以便于使用。首先,先测试我们的 SSH 登录是否正常:
ssh -i /acme.sh/certbot.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null <user>@127.0.0.1
如果能够正常登录,那么我们可以继续创建部署脚本。
在 /share/Config/ACME
目录下,创建一个 deploy.sh
文件,用于部署证书。注意,这里的 your.example.com
需要替换为你的域名:
#!/bin/sh
if [ "$1" = "--dry-run" ]; then
DRY_RUN="true"
fi
# check if root and not dry-run
if [ -z "$DRY_RUN" ] && [ "$(id -u)" -ne 0 ]; then
echo "Please run as root"
exit 1
fi
DOMAIN="your.example.com"
CERT_ROOT="/share/Config/ACME/$DOMAIN"
# check is ecc
if [ -d "$CERT_ROOT"_ecc ]; then
CERT_ROOT="$CERT_ROOT"_ecc
fi
KEY_FILE="/etc/stunnel/backup.key.def"
CERT_FILE="/etc/stunnel/backup.cert.def"
CA_FILE="/etc/stunnel/uca.pem"
NEW_KEY_FILE="$CERT_ROOT/$DOMAIN.key"
NEW_CERT_FILE="$CERT_ROOT/$DOMAIN.cer"
NEW_CA_FILE="$CERT_ROOT/ca.cer"
# avoid `jq: command not found`
export PATH="/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/sbin:/usr/local/bin"
run_command() {
local cmd=$1
# check if dry-run
echo "[-] RUN: $cmd"
if [ "$DRY_RUN" != "true" ]; then
eval $cmd
fi
}
echo "[+] Copy new key and cert"
run_command "cp $NEW_KEY_FILE $KEY_FILE"
run_command "cp $NEW_CERT_FILE $CERT_FILE"
run_command "cp $NEW_CA_FILE $CA_FILE"
echo "[+] Restart stunnel"
run_command "/etc/init.d/stunnel.sh generate_cert_key"
run_command "/etc/init.d/stunnel.sh restart"
run_command "/etc/init.d/reverse_proxy.sh reload"
echo "[+] Done"
保存并退出之后,使用 sudo chmod +x deploy.sh
修改文件权限。
在进行下一步之前,请确保你已经将 /etc/stunnel/backup.key.def
和 /etc/stunnel/backup.cert.def
备份好,以防止证书丢失。
配置 sudo 权限
由于 ACME 容器需要执行一些需要 admin 权限的操作,因此需要配置 sudo 权限。
但是默认情况下,QNAP 中的 sudo 需要输入密码,为了自动化操作的便捷,可以配置 sudo 对于特定程序免密码,具体操作如下:
- 使用默认用户采用 SSH 登录到 QNAP 中。
- 使用
sudo mkdir /usr/etc/sudoers.d
创建 sudoers 配置目录。 - 使用
echo "<user> ALL=(ALL) NOPASSWD:/share/Config/ACME/deploy.sh" | sudo tee /usr/etc/sudoers.d/certbot
添加配置。
这句命令会将 <user>
用户对于 /share/Config/ACME/deploy.sh
的特权执行权限添加到 sudoers 中,同时免去密码,你应当根据自己的实际情况修改用户名和脚本路径。
但是这只是一次性方案,重启后会失效,如果需要长期生效,需要编辑 flash 中的 autorun.sh
,并打开 系统设置 -> 硬件 -> 启动时运行用户定义的进程
,下列为具体操作和参考文档:
使用
sudo bash
切换到 root 用户。使用
l
挂载 flash 的配置目录到/tmp/nasconfig_tmp
。使用
vi /tmp/nasconfig_tmp/autorun.sh
编辑autorun.sh
文件,添加以下内容:#!/bin/bash mkdir -p /usr/etc/sudoers.d echo "<user> ALL=(ALL) NOPASSWD:/share/Config/ACME/deploy.sh" > /usr/etc/sudoers.d/certbot
正常保存并退出。
使用
chmod +x /tmp/nasconfig_tmp/autorun.sh
修改文件权限,允许执行。使用
/etc/init.d/init_disk.sh umount_flash_config
卸载 flash。在正确编辑后,你应当能在
系统设置 -> 硬件
下方有关链接中查看到autorun.sh
的内容。
注意事项:
- 挂载时到
6
表示 flash 的第 6 个分区,不要忘记。 - 根据官方文档,一定要在编辑后卸载 flash
参考文档:Running Your Own Application at Startup - QNAP
启用 ACME 部署
再次使用 docker exec -it acmesh /bin/sh
进入到容器内部,运行以下命令:
export DEPLOY_SSH_USER='<user>'
export DEPLOY_SSH_SERVER='127.0.0.1'
export DEPLOY_SSH_CMD='ssh -i /acme.sh/certbot.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'
export DEPLOY_SSH_BACKUP='no'
export DEPLOY_SSH_REMOTE_CMD='sudo /share/Config/ACME/deploy.sh'
并尝试进行部署:
acme.sh --deploy -d your.example.com --deploy-hook ssh
在预期的情况下,你应当能够看到证书部署成功的输出:
[+] Copy new key and cert
[-] RUN: cp /share/Config/ACME/your.example.com_ecc/your.example.com.key /etc/stunnel/backup.key.def
[-] RUN: cp /share/Config/ACME/your.example.com_ecc/fullchain.cer /etc/stunnel/backup.cert.def
[+] Restart stunnel
[-] RUN: /etc/init.d/stunnel.sh generate_cert_key
[-] RUN: /etc/init.d/stunnel.sh restart
Shutting down apache proxy: OK
Start apache proxy: OK
[-] RUN: /etc/init.d/reverse_proxy.sh reload
AH00558: reverseproxy: Could not reliably determine the server's fully qualified domain name, using **. Set the 'ServerName' directive globally to suppress this message
[+] Done
解释与收尾工作
测试是否部署成功
可以通过 curl
来测试证书是否部署成功:
curl -vkI https://your.example.com:5001
检查输出是否包含新的证书信息:
* Server certificate:
* subject: CN=your.example.com
* start date: May 17 21:18:04 2024 GMT
* expire date: Aug 15 21:18:03 2024 GMT
* issuer: C=US; O=Let's Encrypt; CN=R3
* SSL certificate verify ok.
QNAP 使用证书的方式
在 /etc/init.d/stunnel.sh
中,有以下内容:
SSL_KEY_FILE="/etc/stunnel/backup.key"
SSL_KEY_DEF_FILE="/etc/stunnel/backup.key.def"
SSL_CERT_FILE="/etc/stunnel/backup.cert"
SSL_CERT_DEF_FILE="/etc/stunnel/backup.cert.def"
SSL_PEM_FILE="/etc/stunnel/stunnel.pem"
# ...
generate_cert_key()
{
if [ ! -f "/etc/IS_G" ]; then
if [ -f $SSL_KEY_DEF_FILE ] && [ -f $SSL_CERT_DEF_FILE ]; then
/bin/cp $SSL_KEY_DEF_FILE $SSL_KEY_FILE
/bin/cp $SSL_CERT_DEF_FILE $SSL_CERT_FILE
/bin/cat $SSL_KEY_FILE > $SSL_PEM_FILE
/bin/cat $SSL_CERT_FILE >> $SSL_PEM_FILE
/bin/chmod 600 $SSL_CERT_FILE
/bin/chmod 600 $SSL_KEY_FILE
/bin/chmod 600 $SSL_PEM_FILE
return
fi
# ...
}
从而对于 /etc/stunnel
:
backup.key.def
和backup.cert.def
是存放自定义证书的文件backup.key
和backup.cert
是用于存放实际使用证书的文件stunnel.pem
是将backup.key
和backup.cert
合并后的文件
证书定时续签
在默认情况下,ACME 容器中存在 cron 任务,用于自动续签证书。你可以通过 crontab -l
查看当前的 cron 任务:
3 16 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" --config-home "/acme.sh" > /proc/1/fd/1 2>/proc/1/fd/2
如果你需要修改 cron 任务,可以使用 crontab -e
编辑。