Automatize a autenticação DNS com a ferramenta CLI moderna de renovação de certificados lego

7 min

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

Olá, aqui é o Munou.

Recentemente, tenho tentado me livrar da autenticação por arquivo — ou melhor, o que me incomoda é que estou rodando a atualização de certificados com o certbot e, como ele usa autenticação por arquivo, acabo tendo que deixar o Nginx ouvindo o domínio mail.example.com como um dummy.

Como está rodando automaticamente, tudo bem, mas o certbot funciona colocando temporariamente um arquivo no caminho de autenticação para realizar a atualização. No entanto, um servidor de e-mail não precisa de http e, na verdade, eu não gostaria de aceitar essas requisições.
Além disso, se você tiver vários destinos de origem especificados no registro A, essa operação torna-se inviável.
Isso porque:

$dig soulminingrig.com +short
91.98.169.80
163.44.113.145

Quando você aceita conexões em dois IPs como este, se estiver executando o certbot em um servidor, o arquivo de autenticação não existirá no outro servidor. Provavelmente funciona devido à especificação de enviar a requisição ao servidor mais próximo do lado do cliente (seja HTTP ou DNS), mas confiar em tal "mágica" torna a operação inviável.

Partes difíceis de implementar a autenticação DNS devido às especificações do acme.sh e certbot

A autenticação DNS em si é possível com ambos.
No entanto, no caso do certbot, não há presets para chamar automaticamente a API de atualização de DNS. Portanto, precisamos fazer todo o registro de recordes manualmente, o que torna a atualização automática praticamente inutilizável para autenticação DNS.
acme.sh, por padrão, não suporta a API do ConoHa DNS. Tentar usar isso via opções torna as configurações um tanto caóticas. Se o servidor DNS mudar, fica bem difícil.

lego

Procurando por outras alternativas, para minha surpresa, encontrei uma ferramenta escrita em Go que suporta a API do 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

Incrível!

Instalação

Fico feliz que também esteja disponível no 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 execução

No meu caso, quase todos os domínios são gerenciados pelo Nginx, com apenas algumas exceções, então pedi ao Chappy para criar o script de atualização.
No meu caso, quero emitir certificados SAN para www.example.com e example.com, mas não quero emitir certificados wildcard, então ficou assim:

#!/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"
# Adicione aqui domínios que deseja atualizar fora do gerenciamento do nginx
# Múltiplas entradas separadas por espaço
EXTRA_DOMAINS="
mail.example.com
"
tmp_all="$(mktemp)"
tmp_done="$(mktemp)"
trap 'rm -f "$tmp_all" "$tmp_done"' EXIT INT TERM
# Extrair do server_name do 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 atualizar, basta mudar a opção run para renew.

Atualização automática

No meu caso, como uso FreeBSD, escrevo no /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"

Como existem os padrões /usr/local/etc/lego/deploy.sh e /usr/local/etc/lego/lego.sh, acho que é melhor seguir basicamente este design. No entanto, pelo que vi, o padrão parece utilizar autenticação por arquivo, então, se você quiser realizar a autenticação DNS como neste caso, serão necessárias alterações.

Related Posts