summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorleafee98 <leafee98@hotmail.com>2022-08-12 19:26:35 +0800
committerleafee98 <leafee98@hotmail.com>2022-08-12 19:26:35 +0800
commit1063cd5c245edf58757244e4c430ba35b95a8256 (patch)
tree8e918880475a9be12b943b9037c40b91769b7713
parent7623898960ba77e97b1771d565e36aa2a603d4c5 (diff)
new post: a-helper-to-place-certs-from-certbot-to-proper-location
-rw-r--r--content/posts/a-helper-to-place-certs-from-certbot-to-proper-location.md90
1 files changed, 90 insertions, 0 deletions
diff --git a/content/posts/a-helper-to-place-certs-from-certbot-to-proper-location.md b/content/posts/a-helper-to-place-certs-from-certbot-to-proper-location.md
new file mode 100644
index 0000000..b7d0b5c
--- /dev/null
+++ b/content/posts/a-helper-to-place-certs-from-certbot-to-proper-location.md
@@ -0,0 +1,90 @@
+---
+title: "将 certbot 的证书放到合适位置的简单脚本工具"
+date: 2022-08-12T17:56:46+08:00
+tags: [ linux, bash, certbot ]
+categories: [ tech ]
+weight: 50
+show_comments: true
+draft: false
+---
+
+> ⚠️文章中所有的命令都一定在 Bash 下运行,即便是 Zsh 也不能使用文章中描述的某些语法⚠️
+
+众所周知,certbot 会将获取到的证书放到 `/etc/letsencrypt/live/<DOMAIN>/` 之下,并且申请之后常用的文件为 `privkey.pem` 和 `fullchain.pem` 。而我们常使用的如 Apache httpd 等以 root 身份启动的服务具有最高的权限,自然可以从 certbot 的证书目录下读取证书内容,但是对于其他一些通过 systemd 来限制运行时用户的服务,则没有从该目录下读取证书内容的权限,为此我们需要使用脚本,在每次 certbot 获取证书以后,将这些证书放到对应的位置并修改属主权限,必要时要重新启动服务。
+
+<!--more-->
+
+依然是众所周知,certbot 在每次申请或更新到证书时,会去运行特定目录下的所有可执行文件,这种行为一般称之为“钩子”,如同 [文档](https://eff-certbot.readthedocs.io/en/stable/using.html#renewing-certificates) 所说,三种钩子的行为如下:
+
++ `renewal-hooks/pre` 会在尝试更新证书之前运行
++ `renewal-hooks/post` 会在尝试证书更新之后运行
++ `renewal-hooks/deploy` 只会在成功获取到证书以后运行
+
+有了以上前置知识,我们就可以了解到 `renewal-hooks/deploy` 就是我们所需要的钩子。我们需要为每一个需要使用证书却无访问 certbot 证书目录权限的服务都要写一个脚本,才能使需要的服务拿到证书更新后的证书。
+
+为了可维护性我的建议是提高复用性,所以应当使某一个脚本能够提供核心功能,并使用另外多个脚本去对应到每一个服务去调用前一个脚本,由于前者不能独立完成任务,只为了简化后者的编写逻辑,我称前者为 “helper”。
+
+写完之后的 helper 意外的简单,所需要的逻辑只有复制证书、修改属主信息、必要时重启服务:
+
+```bash
+#!/bin/bash
+# I placed this script at /etc/letsencrypt/renewal-hooks/copy-cert
+
+DOMAIN="${DOMAIN:?not initialized}"
+DEST_DIR="${DEST_DIR:?not initialized}"
+CHOWN_PARAM="${CHOWN_PARAM:?not initialized}"
+SERVICE_NAME="${SERVICE_NAME}"
+
+cp --force --target-directory ${DEST_DIR} /etc/letsencrypt/live/${DOMAIN}/{fullchain,privkey}.pem
+
+chown $CHOWN_PARAM ${DEST_DIR}/{fullchain,privkey}.pem
+
+[ -n "$SERVICE_NAME" ] && systemctl reload $SERVICE_NAME || systemctl restart $SERVICE_NAME
+```
+
+而针对某一个具体的服务所使用的脚本如下,无非是设置环境变量和调用 helper:
+
+```bash
+#!/usr/bin/bash
+# I place this script as /etc/letsencrypt/renewal-hooks/deploy/exim4
+
+export DOMAIN=mail.leafee98.com
+export DEST_DIR=/etc/exim4/cert/
+export CHOWN_PARAM=Debian-exim:Debian-exim
+export SERVICE_NAME=exim4.service
+
+bash /etc/letsencrypt/renewal-hooks/copy-cert
+```
+
+## 脚本中用到的知识
+
+新学到的知识主要有两个,一个在 helper 的赋值语句中参数展开分部分,一个就是复制时的花括号展开。
+
+赋值语句中的参数展开语法为 `${parameter:?word}`,它的作用是如果参数的前者没有被设定或是 null,那么将问号之后的内容输出到错误输出流,如果是非交互终端的话,还会结束程序。它的 [文档](https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html) 中是这么描述的:
+
+> If *parameter* is null or unset, the expansion of *word* (or a message to that effect if *word* is not present) is written to the standard error and the shell, if it is not interactive, exits. Otherwise, the value of parameter is substituted.
+
+花括号展开在本脚本中只使用了基础语法,即通过花括号将一个字符串展开为多个部分不相同的字符串,注意它不能放在双引号中,基础用法如下:
+
+```
+bash$ echo a{d,c,b}e
+ade ace abe
+```
+
+花括号展开支持嵌套,并且可以提供范围展开,如下,更详细的讲解可以看它的 [文档](https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html#Brace-Expansion) 。
+
+```
+bash$ echo s-{a{1,2,3},b{1,3,5}}
+s-a1 s-a2 s-a3 s-b1 s-b3 s-b5
+
+bash$ echo s-{a{1..3},b{1..5..2}}
+s-a1 s-a2 s-a3 s-b1 s-b3 s-b5
+```
+
+## 遗留问题
+
+或许你也注意到了,这些脚本会在任何一个证书更新后运行,比如 exim4 的证书更新了,那么 coturn 的 hook 也会运行一遍,理论上来说,一个证书的更新触发其他证书的 hook 是不必要的,但是考虑到多运行一次也只会有服务多重启一次的损失罢了,并且暂时没有发现 certbot 对 hook 提供所更新的证书的信息,所以就暂时作罢了。
+
+目前想到的解决办法就是检查本域名对应的证书的修改时间,如果距离现在时间小于一个阈值(比如 3 分钟),那么就执行此 hook,否则跳过。
+
+不过这种方法毕竟不是很优雅,检查 3 分钟是因为 ext4 文件系统的时间的最小单位是分钟,而不能是秒,此外服务器极高负载的时候,有可能触发其他 hook 运行时间过长导致超过 3 分钟的时限之类的,总之就先作罢了。