モダンな証明書更新CLIツールlegoでDNS認証を自動化する

5 min

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

こんにんちは、無能です。

最近ファイル認証脱却、というか気持ち悪いのが certbot で証明書更新を走らせている訳ですがその場合ファイル認証で行っているので mail.example.com ドメインをダミーでNginx側でListenしている状態です。

自動で走らせているから良いんですが certbot は一時的にファイル認証用のパスに対してファイル配置して更新してくれるような感じになっています。でも、メールサーバは別に http はいらないし本当は受け付けたくありません。
また、Aレコードに複数Origin先を指定している場合この運用が破綻します。
なぜならば

$dig soulminingrig.com +short
91.98.169.80
163.44.113.145

このように2つのIPで受け付けている場合、片方のサーバで certbot を実行している場合もう片方のサーバーにはそのファイル認証用のファイルは存在しません。HTTPなりDNSのクライアント側の最寄りのサーバーにリクエストを送る仕様のおかげで恐らく成功しているのでしょうがそんな魔法にあやかるのは運用として破綻します。

acme.sh及びcertbotの仕様上のDNS認証導入できつい部分

この2つでもDNS認証自体は可能です。
ただしcertbotの場合自動でDNS更新のAPIを叩くプリセットは行われてません。なのでレコード登録などはこっちが全てやる必要あり自動更新は実質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 側で管理しているドメインとそれ以外は一部なので更新スクリプトはちゃっぴーに作ってもらいました。
私の場合、www.example.comexample.com はSAN証明書発行したいかつワイルドカード証明書は発行したくないのでこうなります。

#!/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