以最小系统权限运行证书签发脚本

前言:这个月,我为网站启用了 HSTS 预加载 标志以提高网站的安全评级。因为主站部署在 CDN 上,证书一年更新一次即可,而手动更新众多子域名就麻烦了,所以使用证书自动签发脚本部署免费的 Let's Encrypt 泛域名证书是最方便的选择。

acme.sh 是目前功能最全面、安装最简单的证书签发脚本。纯粹使用 DNS API 模式申请证书无需多余的系统权限,只需要联网、能读写文件就能签发证书。重新将证书载入到 Nginx 和 Apache 等网页服务器的过程往往需要重启该软件,因此使用脚本时一般会赋予 sudo 特权或者直接从 root 用户安装。如果直接按照指南安装 acme.sh,没有任何问题,而你如果想追求更极致的系统安全,那么下面的步骤可能也会对你有帮助。

安装脚本之前

我的目标是尽可能地赋予自动化脚本最小的运行权限。配置起来稍微复杂了一点,但这对于预防中间人攻击导致的恶意代码植入可能会有用,毕竟 acme.sh 需要频繁联网更新。

创建独立的运行用户

现在的云服务器一般会给用户一个 root 用户,或者是可以无密码使用 sudo 的特权用户。为了把脚本程序的执行权完全圈进笼子里,我创建了一个新的用户,取名 acme。该用户无密码,不可登录,只能读写特定的用户目录 /home/acme。

创建用户 acme
1
useradd -m acme -s /usr/sbin/nologin

Debian 系列的非登录用户 shell 一般是指向 /usr/sbin/nologin,可以起到禁止用户登录的作用,其他系统的用户应该参考 /etc/passwd 中描述的 nologin 位置。

为了方便以后维护,你可以在你常用的特权用户下面新建一个快捷指令 alias acme='sudo su - acme -s /bin/bash' 附加到 ~/.bashrc 配置文件,以后只需要输入 acme 就可以快速切换到脚本用户的运行目录。

精确分配 sudo 特权

刚刚创建的 acme 用户还无权重启 Nginx、Apache 等应用软件。这个程序执行权限只是证书更新脚本的可选参数,但不重启网页服务器可能就不会在证书更新时采用新证书,所以按需选择。在特权用户执行 sudo visudo 并编辑配置文件,添加如下内容:

/etc/sudoers
1
2
acme ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx.service
acme ALL=(ALL) NOPASSWD: /usr/sbin/nginx -t

这样一来 acme 用户就可以无需密码地执行 sudo nginx -t && sudo systemctl restart nginx.service,到后面证书更新时,脚本也就可以轻易重启网页服务器来加载新的证书。

安装 acme.sh

这部分就不需要重复叙述了,直接看 GitHub 即可。

关于 DNS API & DNS alias

DNS API 模式是将域名验证消息添加到域名的子域 _acme-challenge 之后向 CA 发起域名验证。这样做的好处是无需服务器就可以签发证书,并且可以签发泛域名证书。由于 CloudFlare 不仅支持对 DNS 区域的 API 操作,还可以详细地控制 API Token 的作用范围,因此使用起来安全又方便。

DNS alias 模式是将 A 域名的子域 _acme-challenge 设置 CNAME 到 B 域名的子域 _acme-challenge 上,来代理证书签发过程中的域名验证。此时,acme.sh 只能读写 B 域名的解析记录,这对保护 A 域名的解析记录安全很有帮助。毕竟我不想让一个陌生的程序完全控制我的域名记录 :)

我的最佳实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 在环境中导入 CloudFalre API Token
export CF_Token="<Your CloudFlare API Token>"
export CF_Account_ID="<Your CloudFlare Account ID>"
export CF_Zone_ID="<Your CloudFlare Zone ID>"

# 使用 DNS API + DNS alias 模式申请泛域名 ECC 证书
acme.sh --issue --dns dns_cf -d uuznn.cn -d '*.uuznn.cn' \
--keylength ec-256 --challenge-alias example.com

mkdir -p /home/acme/cert/uuznn.cn_ecc

# 将证书安装到指定目录,安装完成后不要忘记重启 Web 服务器
acme.sh --install-cert -d uuznn.cn --ecc \
--key-file /home/acme/cert/uuznn.cn_ecc/key.pem \
--fullchain-file /home/acme/cert/uuznn.cn_ecc/fullchain.pem \
--reloadcmd "sudo nginx -t && sudo systemctl restart nginx.service"

总结

通过对 Linux 用户权限的细分、在确保 acme.sh 脚本程序正确运行基础上,我们减少了因为权限泛滥而造成的安全隐患,使得我们以更安全的姿态使用自动化脚本,一方面提高了系统环境的可信度,另一方面,也简化了域名证书签发的流程。

参考阅读

  • An ACME Shell script: acme.sh
  • How to run a specific program as root without a password prompt?