new post: a-helper-to-place-certs-from-certbot-to-proper-location
This commit is contained in:
parent
7623898960
commit
1063cd5c24
|
@ -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 分钟的时限之类的,总之就先作罢了。
|
Loading…
Reference in a new issue