Automatizar la autenticación DNS con la moderna herramienta CLI de renovación de certificados lego

8 min

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

Hola, soy Munou.

Recientemente he estado tratando de alejarme de la autenticación por archivos, o mejor dicho, me resulta molesto estar ejecutando la renovación de certificados con certbot; en ese caso, como se realiza mediante autenticación por archivos, tengo el dominio mail.example.com configurado como un dummy escuchando en el lado de Nginx.

Está bien porque se ejecuta automáticamente, pero certbot funciona colocando temporalmente archivos en la ruta de autenticación para realizar la renovación. Sin embargo, el servidor de correo no necesita http y, en realidad, no quiero aceptarlo.
Además, si se especifican múltiples orígenes en el registro A, esta operación falla.
Esto se debe a que:

$dig soulminingrig.com +short
91.98.169.80
163.44.113.145

Cuando se aceptan peticiones en dos IPs de esta manera, si ejecutas certbot en un servidor, el archivo para la autenticación no existirá en el otro servidor. Probablemente tenga éxito gracias a la especificación de enviar la solicitud al servidor más cercano del lado del cliente (ya sea HTTP o DNS), pero depender de tal "magia" es un fracaso operativo.

Partes difíciles de implementar la autenticación DNS debido a las especificaciones de acme.sh y certbot

La autenticación DNS en sí es posible con ambos.
Sin embargo, en el caso de certbot, no hay ajustes preestablecidos para llamar automáticamente a la API de actualización de DNS. Por lo tanto, nosotros tendríamos que hacer todo el registro de los récords, lo que hace que la renovación automática sea prácticamente inutilizable con la autenticación DNS.
En el caso de acme.sh, no es compatible con la API de ConoHa DNS por defecto. Intentar usarlo especificando opciones hace que estas se vuelvan un caos. En este caso, si el servidor DNS cambia, se vuelve bastante difícil.

lego

Buscando si había algo más, me encontré con que, sorprendentemente, una herramienta hecha en Go soporta la API de ConoHa DNS.

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

¡Increíble!

Instalación

Afortunadamente también está disponible en pkg...

# pkg search lego
lego-4.33.0                    Let's Encrypt client and ACME library written in Go
pkg install lego-4.33.0

Script de ejecución

En mi caso, casi todos los dominios están gestionados en el lado de Nginx y solo unos pocos están fuera, así que le pedí a Chappy que me hiciera el script de renovación.
En mi situación, quiero emitir certificados SAN para www.example.com y example.com, pero no quiero emitir certificados comodín (wildcard), así que queda de la siguiente manera:

#!/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"
# Agregue aquí los dominios que desea actualizar incluso si no están bajo la gestión de nginx
# Describa varios separados por espacios
EXTRA_DOMAINS="
mail.example.com
"
tmp_all="$(mktemp)"
tmp_done="$(mktemp)"
trap 'rm -f "$tmp_all" "$tmp_done"' EXIT INT TERM
# Extraer de server_name de nginx
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"

Para la actualización, simplemente cambie la opción run a renew.

Actualización automática

En mi caso, como uso FreeBSD, lo escribo en /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"

Dado que existen los archivos predeterminados /usr/local/etc/lego/deploy.sh y /usr/local/etc/lego/lego.sh, generalmente es mejor seguir este diseño. Sin embargo, por lo que se puede ver, el valor predeterminado parece realizarse mediante autenticación de archivos, por lo que es necesario realizar cambios si desea utilizar la autenticación DNS como en este caso.

Related Posts