REG.RU и Let's Encrypt
Множество людей покупают доменные имена у регистраторов и создают собственные сайты. Разумеется, им необходимы SSL‑сертификаты. Однако покупать доверенные сертификаты могут не все и, зачастую, используют бесплатные Let’s Encrypt SSL‑сертификаты, время жизни которых составляет 90 суток.
Для автоматического обновления SSL‑сертификатов Let’s Encrypt обычно используется API управления DNS‑зоной, который предоставляет регистратор доменных имен.
Рассмотрим один из возможных способов автоматического обновления сертификатов на примере регистратора REG.RU и его API управления DNS‑зонами.
Авторизация
Для того чтобы не указывать в скриптах пароли администратора DNS‑зоны в открытом виде, прежде всего, необходимо создать ключ. Сделать это можно с помощью openssl:
openssl req -new -x509 -nodes -sha1 -days 365 -newkey rsa:2048 -keyout my.key -out my.crt
Ключ будет действителен в течение года.
Для того чтобы не вводить при создании нового ключа все данные вручную, можно добавить еще несколько параметров, например, имя организации и e‑mail:
openssl req -new -x509 -nodes -sha1 -days 365 -newkey rsa:2048 -keyout my.key -out my.crt \
-subj "/C=RU/ST=St.-Petersburg/L=St.-Petersburg/O=my org, Inc/OU=RcL/CN=kx-home.su/emailAddress=myname@mail.ru"
Здесь необходимо заменить значения «my org» и «myname@mail.ru» на ваши собственные.
Для того чтобы загрузить полученный ключ в личном кабинете REG.RU, необходимо выбрать пункт меню 'Настройки':
Рис. 1. Настройки.
далее пункт 'Настройки API':
Рис. 2. Настройки API.
затем пункт 'Добавить SSL':
Рис. 3. Добавить SSL.
и наконец скопировать текст файла my.crt в поле над кнопкой «Добавить»:
Рис. 4. Добавить сертификат.
Теперь, для аутентификации администратора DNS‑зоны, в качестве имени пользователя вы можете использовать e‑mail (который указали при создании личного кабинета на сайте REG.RU), а вместо пароля в своих скриптах, — файлы my.key и my.crt.
Автоматизация
Допустим вы имеете домен example.org и поддомен cloud.example.org. Создадим рабочий каталог следующего содержания:
/root/cron.letsencrypt
├── renew-certificates
└── scripts
├── _dns.example.org-auth.php
├── _dns.example.org-cleanup.php
├── cert
│ ├── my.crt
│ └── my.key
├── cloud.example.org-renew
└── example.org-renew
В каталоге /root/cron.letsencrypt/scripts/cert/ находятся, созданные нами ранее, сертификат и ключ. Скрипты _dns.example.org‑auth.php и _dns.example.org‑cleanup.php осуществляют создание TXT записи _acme‑challenge.XXXXXXXXXX в DNS‑зоне и ее удаление после обновления SSL-сертификата Let’s Encrypt. Основной скрипт renew‑certificates вызывает поочередно скрипты example.org‑renew и cloud.example.org‑renew, которые, в свою очередь, вызывают утилиту certbot.
Утилита certbot
Скрипт example.org‑renew, для вызова утилиты certbot, выглядит следующим образом:
#!/bin/sh
cwd() {
pushd `dirname $0` > /dev/nill
CWD=`pwd -P`
popd > /dev/null
echo "$CWD"
}
CWD=`cwd`
certbot certonly -n \
--manual \
--preferred-challenges=dns \
--manual-auth-hook $CWD/_dns.example.org-auth.php \
--manual-cleanup-hook $CWD/_dns.example.org-cleanup.php -d example.org
Здесь параметр -d задает имя домена, для которого надо получить SSL‑сертификат. Разумеется, в файле cloud.example.org‑renew будет указан поддомен cloud.example.org.
Параметры ‑‑manual‑auth‑hook и ‑‑manual‑cleanup‑hook указывают на способ создания DNS‑записи _acme‑challenge.XXXXXXXXXX и ее последующего удаления.
Создание TXT записи
Рассмотрим процесс создания TXT записи (_dns.example.org‑auth.php):
#!/usr/bin/php
$login,
'domain_name' => $zone, /* main domain */
'subdomain' => $record_name, /* Name of TXT record */
'text' => $certbot_validation,
'input_format' => 'plain',
];
$sig_params = $params;
sort( $sig_params );
$pkeyid = openssl_pkey_get_private( "file://" . getcwd() . "/cert/kx.key" );
openssl_sign( implode(';', $sig_params), $sig, $pkeyid );
$params['sig'] = base64_encode( $sig );
$url = 'https://api.reg.ru/api/regru2/zone/add_txt';
for( $i = 0; $i < 7; $i++ ) {
$curl = curl_init();
curl_setopt( $curl, CURLOPT_URL, $url );
curl_setopt( $curl, CURLOPT_POST, true );
curl_setopt( $curl, CURLOPT_SSLCERT, getcwd() . "/cert/kx.crt" );
curl_setopt( $curl, CURLOPT_SSLKEY, getcwd() . "/cert/kx.key" );
curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER, 1 );
curl_setopt( $curl, CURLOPT_POSTFIELDS, $params );
$result = curl_exec( $curl );
curl_close( $curl );
$result = json_decode( urldecode($result), true );
$status = $result["result"];
if( strcmp($status, 'success') == 0 ) {
break;
}
sleep( 180 ); /* 3 minute */
}
if( strcmp($status, 'success') !== 0 ) {
exit( 1 );
}
sleep( 1500 ); /* 25 minutes seems to enough */
exit( 0 );
?>
Утилита certbot передает параметры посредством переменных окружения CERTBOT_DOMAIN и CERTBOT_VALIDATION. Первая из них задает имя домена, а вторая, — уникальный ключ.
Поскольку данный скрипт должен работать одинаково для всех наших доменов и поддоменов, мы должны разобрать доменное имя, задаваемое с помощью опции -d утилиты certbot, на составляющие. Далее сформировать параметры вызова curl чтобы воспользоваться API REG.RU, сделать POST‑запрос и получить вразумительный ответ.
Здесь, по сути, все ясно. Более подробно все наши действия можно разобрать по документации REG.RU.
Основным моментом, на который следует обратить внимание, является цикл наших попыток получить правильный ответ от сервера REG.RU и задержка, которая необходима для того, чтобы утилита certbot успела прочитать DNS‑запись и, тем самым, идентифицировать домен как вашу собственность.
Итак, мы делаем 7 попыток с интервалом 3 минуты на случай, если сайт REG.RU будет тормозить. В случае успешного создания _acme‑challenge.XXXXXXXXXX записи, мы ждем еще 25 минут, так как выгрузка зоны и ее распространение требует времени.
Цифру 25 минут мы подобрали с запасом, проведя несколько экспериментов. Вы можете скорректировать время ожидания так, как посчитаете оптимальным для себя.
Удаление TXT записи
После обновления вашего Let’s Encrypt сертификата, TXT запись _acme‑challenge.XXXXXXXXXX надо удалить и снова выгрузить зону на всеобщее обозрение. Этим занимается скрипт _dns.example.org‑cleanup.php:
#!/usr/bin/php
$login,
'domain_name' => $zone, /* main domain */
'subdomain' => $record_name, /* Name of TXT record */
'record_type' => 'TXT',
'input_format' => 'plain',
];
$sig_params = $params;
sort( $sig_params );
$pkeyid = openssl_pkey_get_private( "file://" . getcwd() . "/cert/kx.key" );
openssl_sign( implode(';', $sig_params), $sig, $pkeyid );
$params['sig'] = base64_encode( $sig );
$url = 'https://api.reg.ru/api/regru2/zone/remove_record';
for( $i = 0; $i < 3; $i++ ) {
$curl = curl_init();
curl_setopt( $curl, CURLOPT_URL, $url );
curl_setopt( $curl, CURLOPT_POST, true );
curl_setopt( $curl, CURLOPT_SSLCERT, getcwd() . "/cert/kx.crt");
curl_setopt( $curl, CURLOPT_SSLKEY, getcwd() . "/cert/kx.key");
curl_setopt( $curl, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $curl, CURLOPT_SSL_VERIFYPEER, 1 );
curl_setopt( $curl, CURLOPT_POSTFIELDS, $params );
$result = curl_exec( $curl );
curl_close( $curl );
$result = json_decode( urldecode($result), true );
$status = $result["result"];
if( strcmp($status, 'success') == 0 ) {
break;
}
sleep( 180 ); /* 3 minute */
}
if( strcmp($status, 'success') !== 0 ) {
exit( 1 );
}
exit( 0 );
?>
Вот, собственно, и всё.
Обновление SSL‑сертификатов
Основной скрипт renew‑certificates может выглядеть следующим образом:
#!/bin/sh
cwd() {
pushd `dirname $0` > /dev/null
CWD=`pwd -P`
popd > /dev/null
echo "$CWD"
}
CWD=`cwd`
export PYTHONWARNINGS=ignore
LOGFILE=/var/log/renew-example.org-certificates.log
#
# Renew for DNS challenge domains:
#
( cd $CWD/scripts/ ; ./example.org-renew 1> /dev/null 2> /dev/null )
( cd $CWD/scripts/ ; ./cloud.example.org-renew 1> /dev/null 2> /dev/null )
/etc/rc.d/rc.nginx reload 1> /dev/null
if [ "$?" == "0" ]; then
echo "[`date +'%Y-%m-%d %H:%M:%S'`] Let's Encrypt certificates: RENEWED" >> ${LOGFILE}
else
echo "[`date +'%Y-%m-%d %H:%M:%S'`] Let's Encrypt certificates: renew FAILED" >> ${LOGFILE}
fi
Его задача состоит в том, чтобы последовательно обновить Let’s Encrypt сертификаты, заставить NGINX, не останавливая свою работу, перечитать конфигурацию и оставить следы наших действий в LOG‑файле.
Периодическое выполнение процедуры обновления сертификатов /root/cron.letsencrypt/renew‑certificates можно поручить cron‑демону или написать systemd‑таймер.
Enjoy.