Автоматизация DNS-аутентификации с помощью современного CLI-инструмента lego для обновления сертификатов
Здравствуйте, это Munou.
В последнее время я пытаюсь отказаться от файловой аутентификации — точнее, мне не нравится, что обновление сертификатов через certbot происходит именно этим способом, так как для этого приходится держать домен mail.example.com в режиме прослушивания на стороне Nginx в качестве заглушки.
Это работает, так как процесс автоматизирован, но certbot временно размещает файлы по пути для проверки, чтобы выполнить обновление. Однако почтовому серверу не нужен http, и на самом деле я бы не хотел его принимать.
Кроме того, если в A-записи указано несколько серверов Origin, такая схема работы ломается.
Потому что:
$dig soulminingrig.com +short
91.98.169.80
163.44.113.145
Если запросы принимаются на два таких IP-адреса, и certbot запущен на одном сервере, то на другом сервере файла для проверки не будет. Вероятно, это срабатывает благодаря тому, что HTTP- или DNS-клиент отправляет запрос на ближайший сервер, но полагаться на такую «магию» в эксплуатации — путь к провалу.
Сложности внедрения DNS-аутентификации из-за особенностей acme.sh и certbot
В обоих этих инструментах сама по себе DNS-аутентификация возможна.
Однако в случае с certbot нет готовых пресетов для автоматического вызова API обновления DNS. Поэтому регистрацию записей и прочее приходится делать полностью вручную, и автоматическое обновление при DNS-аутентификации фактически становится невозможным.
В случае с acme.sh поддержка ConoHa DNS API по умолчанию отсутствует. Попытка использовать его через дополнительные опции превращает конфигурацию в хаос. В такой ситуации смена DNS-сервера становится очень сложной задачей.
lego
В поисках альтернатив я внезапно обнаружил, что инструмент на Go поддерживает ConoHa DNS API.
ConoHa v3 :: Let’s Encrypt client and ACME library written in Go.
GitHub - go-acme/lego: Let's Encrypt/ACME client and library written in Go · GitHub
Круто!
Установка
Приятно, что он есть и в pkg...
# pkg search lego
lego-4.33.0 Let's Encrypt client and ACME library written in Go
pkg install lego-4.33.0
Скрипт выполнения
В моем случае почти все домены управляются на стороне Nginx, за исключением некоторых, поэтому я попросил Chappy (ChatGPT) написать скрипт обновления.
В моем случае я хочу выпускать SAN-сертификаты для www.example.com и example.com, но не хочу использовать wildcard-сертификаты, поэтому получается так:
#!/bin/sh
set -eu
SITES_DIR="/usr/local/etc/nginx/sites-enabled"
export CONOHAV3_TENANT_ID=""
export CONOHAV3_API_USER_ID=""
export CONOHAV3_API_PASSWORD=""
export CONOHAV3_PROPAGATION_TIMEOUT="600"
export CONOHAV3_POLLING_INTERVAL="300"
LEGO="/usr/local/bin/lego"
SSLDIR="/usr/local/etc/ssl/lego"
EMAIL="taro@example.com"
DNS_PROVIDER="conohav3"
# nginx管理外でも更新したいドメインをここに追加
# スペース区切りで複数記述
EXTRA_DOMAINS="
mail.example.com
"
tmp_all="$(mktemp)"
tmp_done="$(mktemp)"
trap 'rm -f "$tmp_all" "$tmp_done"' EXIT INT TERM
# nginx の server_name から抽出
grep -RhoE 'server_name[[:space:]]+[^;]+' "$SITES_DIR" \
| sed -E 's/^server_name[[:space:]]+//' \
| tr ' ' '\n' \
| sed 's/;$//' \
| sed '/^$/d' \
| sed '/^\*\./d' \
| sed '/^_/d' \
>> "$tmp_all"
printf '%s\n' "$EXTRA_DOMAINS" \
| tr ' ' '\n' \
| sed '/^$/d' \
>> "$tmp_all"
sort -u -o "$tmp_all" "$tmp_all"
: > "$tmp_done"
issue_cert() {
echo "==> issuing certificate for: $*"
"$LEGO" \
--accept-tos \
--path "${SSLDIR}" \
--email "$EMAIL" \
--dns "$DNS_PROVIDER" \
--dns.resolvers 1.1.1.1 \
"$@" \
run
}
already_done() {
grep -Fxq "$1" "$tmp_done"
}
mark_done() {
printf '%s\n' "$1" >> "$tmp_done"
}
while IFS= read -r host; do
[ -n "$host" ] || continue
if already_done "$host"; then
continue
fi
case "$host" in
www.*)
apex="${host#www.}"
if grep -Fxq "$apex" "$tmp_all"; then
issue_cert -d "$apex" -d "$host"
mark_done "$apex"
mark_done "$host"
else
issue_cert -d "$host"
mark_done "$host"
fi
;;
*.*.*)
issue_cert -d "$host"
mark_done "$host"
;;
*.*)
if grep -Fxq "www.$host" "$tmp_all"; then
issue_cert -d "$host" -d "www.$host"
mark_done "$host"
mark_done "www.$host"
else
issue_cert -d "$host"
mark_done "$host"
fi
;;
*)
echo "skip invalid host: $host" >&2
;;
esac
done < "$tmp_all"
Для обновления достаточно заменить опцию run на renew.
Автоматическое обновление
В моем случае я использую FreeBSD, поэтому добавляю запись в /etc/periodic.conf
weekly_lego_enable="YES"
weekly_lego_renewscript="/usr/local/etc/lego/dns.sh"
weekly_lego_deployscript="/usr/local/etc/lego/deploy.sh"
Поскольку существуют стандартные /usr/local/etc/lego/deploy.sh и /usr/local/etc/lego/lego.sh, я считаю, что в целом лучше придерживаться этой структуры. Однако, судя по всему, по умолчанию используется проверка подлинности через файлы, поэтому, если вы хотите использовать DNS-проверку, как в данном случае, потребуются изменения.