Автоматизация DNS-аутентификации с помощью современного CLI-инструмента lego для обновления сертификатов

7 min

language: ja bn en es hi pt ru zh-cn zh-tw

Здравствуйте, это 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-проверку, как в данном случае, потребуются изменения.

Related Posts