Letsencrypt и автопродление на основе dns

Желаемый результат: автоматическое продление сертификатов, а если точнее, то только wildcard-сертификата.

Подготовка DNS-сервера

Wildcard-сертификаты у Letsencrypt работают только на основе подтверждения через DNS. При подтверждении скрипт должен будет автоматически вносить TXT-запись в зону. Т.к. динамически дёргать основную доменную зону страшно, заведём специальную зону под эту задачу, и вся жизнь будет проходить в ней.

# Генерим ключ
cd /chroot/dns/etc/bind/keys/
dnssec-keygen -a HMAC-MD5 -b 512 -n USER certbot
 
# Визуально проверяем, что получились нормальные ключи
cat Kcertbot.*.key
cat Kcertbot.*.private
 
# Прописываем НОВУЮ зону, указываем, что с помощью выше созданного ключа можно эту зону править.
cd ..
mcedit ddns.key
mcedit named.conf
 
# Создаём новый зонный файл
cd dyn
cp ../pri/sabitov.ru ./certbot.sabitov.ru
mcedit certbot.sabitov.ru
chmod 664 certbot.sabitov.ru
chown root:named certbot.sabitov.ru
 
# Правим родительскую зону, прописываем glue-запись
cd ../pri/
mcedit sabitov.ru
 
# Рестарт, проверяем, что получилось
/etc/init.d/named restart
host -t any test.certbot.sabitov.ru ns2
host -t any test.certbot.sabitov.ru ns1

В файле ddns.key прописываем такое:

ddns.key
key "certbot" {
        algorithm HMAC-MD5;
        secret "... очень много букв ...";
};

В algorithm прописываем значение ключа «-a» команды dnssec-keygen, в secret прописываем значение строки «Key:» из файла Kcertbot.*.private

В файле named.conf должны появиться следующие строки:

named.conf
include "/etc/bind/ddns.key";
 
zone "certbot.sabitov.ru" {
        type master;
        file "dyn/certbot.sabitov.ru";
        allow-query { any; };
        allow-transfer { xfer; };
        allow-update { key certbot; };
};

Где xfer – имя ACL, который разрешает трансфер зоны.

Зонный файл для certbot.sabitov.ru самый обычный. На момент написания:

certbot.sabitov.ru
$ORIGIN .
$TTL 900        ; 15 minutes
certbot.sabitov.ru      IN SOA  ns1.sabitov.ru. root.sabitov.ru. (
                                1812000011 ; serial
                                400        ; refresh (6 minutes 40 seconds)
                                400        ; retry (6 minutes 40 seconds)
                                800        ; expire (13 minutes 20 seconds)
                                400        ; minimum (6 minutes 40 seconds)
                                )
                        NS      ns1.sabitov.ru.
                        NS      ns2.sabitov.ru.
$ORIGIN certbot.sabitov.ru.

Зонный файл для sabitov.ru должен содержать две принципиально важные записи:

sabitov.ru
; glue-запись, особенно важно, если основная зона и certbot.... будут расположены на разных NS'ах
certbot IN      NS      ns1.sabitov.ru.
        IN      NS      ns2.sabitov.ru.
 
; собственно эта запись и нужна для работы.
_acme-challenge CNAME   _acme-challenge.certbot.sabitov.ru.

Логика работы такая: скриптам обновления сертификатов будет указано, что запись _acme-challenge надо вносить в зону certbot.sabitov.ru. При обновлении сертификата для всего домена sabitov.ru. Letsencrypt проверит запись _acme-challenge.sabitov.ru, увидит, что это CNAME, запросит _acme-challenge.certbot.sabitov.ru и получит правильные данные, после чего проверка успешно завершится.

Подготовка головной машины.

Одна из машин должна стать головной и следить за обновлением сертификата, при успешном обновлении новый сертификат тарболится, выкладывается под http-сервер, и скачивается остальными машинами, которые используют этот сертификат.

Прежде всего проверяем, что с головной машины можно выполнить апдейт зоны. Для этого готовим отдельный файлик, условно, nsupd:

server 1.2.3.4
key certbot ...те самые много букв...
zone certbot.sabitov.ru
update add t.certbot.sabitov.ru. 86400 IN TXT  t
;update del t.certbot.sabitov.ru.
send

Сервером указываем мастера зоны. Выполняем команду nsupdate nsupd, после чего проверяем, что в зоне появилась новая запись: host t.certbot.sabitov.ru.

Если всё хорошо:

  1. ставим app-crypt/acme-sh
  2. создаём директорию /etc/letsencrypt
  3. регистрируем эккаунт в Letsencrypt
  4. для каждого домена, для которого будем генерить ключи создаём свою поддиректорию (в данном примере только одну sabitov.ru)
  5. копируем _оба_ файла Kcertbot.*.{key,private} в /etc/letsencrypt с DNS-сервера
  6. генерим сертификаты

Теперь тоже самое, только понятным языком:

mkdir -p /etc/letsencrypt/sabitov.ru  # объединяем пп. 1 и 3
acme.sh --register-account --accountemail letsencrypt@sabitov.ru --config-home /etc/letsencrypt/ --cert-home /etc/letsencrypt
scp master.sabitov.ru:/etc/bind/keys/Kcertbot.\* /etc/letsencrypt/
 
export NSUPDATE_SERVER=1.2.3.4 # IP мастера зоны certbot.sabitov.ru.
export NSUPDATE_KEY=/etc/letsencrypt/Kcertbot._какая-то_фигня_.private
 
acme.sh --config-home /etc/letsencrypt/ --cert-home /etc/letsencrypt/  --issue -d sabitov.ru -d '*.sabitov.ru' --challenge-alias certbot.sabitov.ru --dns dns_nsupdate

Если и тут всё хорошо, лабаем скриптец, который будет дёргаться cron'ом ежесуточно:

/usr/local/sbin/renew_certs.sh
#!/bin/bash -xe
 
CERT_HOME="/etc/letsencrypt"
EXGANGE_POOL="/var/www/site_name/htdocs/exchange_pool"
DOMAIN_LIST=( sabitov.ru )
 
 notAfter=`openssl x509 -text -noout -in "${CERT_HOME}/${DOMAIN_LIST[0]}/${DOMAIN_LIST[0]}.cer" | sed -ne 's/.*Not After : //p'`
notBefore=`openssl x509 -text -noout -in "${CERT_HOME}/${DOMAIN_LIST[0]}/${DOMAIN_LIST[0]}.cer" | sed -ne 's/.*Not Before: //p'`
 
delta=$(expr -- `date +%s --date="${notAfter}"`  - `date +%s ` )
 
if [ $delta -ge 604800 ] ; then # one week
        # До конца сертификата осталось больше недели.
        # Проверяем, как давно обновились сертификаты,
        # если прошло больше 2х суток удаляем тарболы.
 
        delta=$(expr -- `date +%s --date="${notBefore}"`  - `date +%s ` )
        if [ $delta -ge 172800 ] ; then # two days
                rm -f "${EXGANGE_POOL}"/*.tar
        fi
 
        exit;
fi
 
# До конца сертификата осталось меньше недели. Обновляемся.
 
export NSUPDATE_SERVER=1.2.3.4
export NSUPDATE_KEY=/etc/letsencrypt/Kcertbot.......private
 
/usr/bin/acme.sh --log /tmp/acme.sh.log --log-level 2 --config-home "${CERT_HOME}" --cert-home "${CERT_HOME}" --renew-all
 
cd "${CERT_HOME}"
for d in ${DOMAIN_LIST[@]} ; do
        tar  cvf    "${EXGANGE_POOL}/${d}.tar"    "${d}/"*.{cer,csr,key}
done

Как финальный штрих, накручиваем в http-сервере доступ к «$EXGANGE_POOL».

Настройка остальных машин.

/usr/local/sbin/renew_certs_at_clients.sh
#!/bin/bash -xe
 
CERT_HOME="/etc/letsencrypt"
EXGANGE_URL="http://site_name/exchange_pool/"
DOMAIN_LIST=( sabitov.ru )
 
notAfter=`openssl x509 -text -noout -in "${CERT_HOME}/${DOMAIN_LIST[0]}/${DOMAIN_LIST[0]}.cer" | sed -ne 's/.*Not After : //p'`
 
delta=$(expr -- `date +%s --date="${notAfter}"`  - `date +%s ` )
if [ $delta -ge 604800 ] ; then # one week
        exit;
fi
 
# До конца сертификата осталось меньше недели. Обновляемся.
 
mkdir -p "${CERT_HOME}"
cd "${CERT_HOME}"
for d in ${DOMAIN_LIST[@]} ; do
        /usr/bin/wget -t0 -q "${EXGANGE_URL}/${d}.tar"
        tar  xvf  "${d}.tar"
        rm -f     "${d}.tar"
done
 
for s in nginx dovecot postfix ; do
        /etc/init.d/$s restart
done

sys/letsencrypt_и_автопродление_на_основе_dns.txt · Последние изменения: 2019-04-10 14:31 — Andrew A. Sabitov