ADG Оставить заявку
Блог Информационная безопасность 5 мин чтения

certbot как официальный клиент Let's Encrypt: systemd-timer вместо cron, 60+ доменов за месяц

EFF выпускает certbot и официально убирает letsencrypt-auto. Мигрируем на certbot, переписываем пролонгацию на systemd-timer и закрываем 60+ клиентских доменов без единого ручного действия.

Контекст момента

EFF выпускает certbot как официальный клиент Let's Encrypt, заменяя letsencrypt-auto; старое имя объявлено deprecated

EFF на прошлой неделе официально переименовала letsencrypt-auto в certbot и объявила его основным клиентом Let's Encrypt. Новый пакет - в EPEL, в backports Debian, в официальной документации. Старое имя помечено deprecated. Для нас это не просто rename: хороший повод пересмотреть то, что мы уже раскатали в апреле, и заодно закрыть оставшуюся очередь клиентских доменов.

К этому моменту мы прошли через GA в апреле и в первый же день закрыли 15 доменов. За апрель добавили ещё, но список ещё не исчерпан - примерно 30-40 позиций висели «на потом» из-за нестандартных конфигураций или просто потому что очередь. Официальный релиз certbot - хороший стимул дожать.

Что изменилось с новым именем

Формально - только имя пакета и бинаря. Но попутно EFF подтянул пакетную инфраструктуру: теперь certbot устанавливается из EPEL на CentOS 7 одной командой без pip, без virtualenv, без лишних зависимостей. Раньше letsencrypt-auto при первом запуске на несколько минут уходил в self-bootstrap - тащил Python-зависимости через pip. Это было неудобно при первоначальной установке через Ansible: задача с непредсказуемым временем выполнения, зависящая от pip-зеркала. Теперь просто yum install certbot.

Наша Ansible-роль из марта использовала вызов через /usr/local/bin/letsencrypt с путём из virtualenv. Пришлось поправить переменную с путём к бинарю - ничего архитектурно сложного, но всё равно изменение, которое нужно раскатать.

systemd-timer вместо cron

Пока обновляли роль, решили заодно закрыть то, что давно чесалось: cron-задача для certbot renew - это не ужасно, но systemd-timer на CentOS 7 и современных Ubuntu даёт несколько реальных преимуществ, которые в нашем сценарии имеют значение.

Во-первых, логи. Cron пишет вывод в почту или в /var/log/cron в зависимости от дистрибутива и настройки. С systemd-timer весь вывод идёт в journald - journalctl -u certbot-renew.service показывает всё: когда запускался, что вернул, ошибки. Для 60+ доменов это важнее, чем для одного.

Во-вторых, статус. systemctl status certbot-renew.timer показывает когда запускался последний раз и когда следующий запуск. Не нужно лезть в crontab и считать следующее срабатывание вручную.

В-третьих, зависимости. Timer может быть привязан к network.target - не запустится, если сеть ещё не поднялась при старте системы. Cron в этом месте ни о чём не знает.

Структура получилась такая. Два файла в /etc/systemd/system/:

certbot-renew.service - unit самой задачи:

[Unit]
Description=Certbot renewal
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx"

certbot-renew.timer - расписание:

[Unit]
Description=Certbot renewal timer

[Timer]
OnCalendar=Wed,Sat 04:30
Persistent=true

[Install]
WantedBy=timers.target

Persistent=true - важная деталь: если система была выключена в момент планового запуска, timer запустится при следующем старте системы. Cron это делать не умеет.

--post-hook вместо отдельной cron-строчки - certbot сам вызывает systemctl reload nginx только если хотя бы один сертификат был реально обновлён. Не каждый запуск, а только при обновлении. Это чище, чем безусловный reload после каждого certbot renew.

Включить и запустить:

systemctl daemon-reload
systemctl enable --now certbot-renew.timer
systemctl list-timers certbot-renew.timer

Последняя команда - для проверки: покажет next и last trigger.

Как дожали оставшийся список

За апрель-май мы закрыли оставшиеся домены. Часть из них - те самые нестандартные случаи.

HAProxy перед nginx. В апрельском посте мы описали временное решение через отдельный backend в HAProxy для challenge-пути. Теперь оформили это нормально: в Ansible-роль добавился вариант с haproxy_acl - если переменная letsencrypt_proxy: haproxy, роль прописывает в haproxy.cfg ACL и backend для .well-known/acme-challenge/ и делает reload HAProxy перед запросом сертификата. Не временный патч, а часть роли.

Много поддоменов на одном домене. Certbot принимает несколько -d в одной команде - один сертификат на весь список. Ограничение: не более 100 доменных имён в одном сертификате. До этого потолка мы нигде не дошли, но держим в голове.

DNS-валидация для одного закрытого стенда. Один клиент держит сервис без публичного HTTP - только VPN-доступ. Webroot там не работает. Использовали --preferred-challenges dns с ручным добавлением TXT-записи при первоначальном выпуске. Автопродление для такого стенда не автоматизировано - требует либо API к DNS-провайдеру, либо ручных действий раз в 90 дней. Оставили на ручном для этого одного случая, остальное - автомат.

Что получилось за месяц

За апрель-май прошли весь накопленный список. Больше 60 доменов на managed-сопровождении с автоматическим продлением через systemd-timer. Один ручной выпуск - тот самый закрытый стенд без публичного HTTP.

Несколько вещей, которые стали очевидны при таком масштабе.

Rate limits Let's Encrypt реальны. 20 сертификатов в неделю на один зарегистрированный домен - это не на домен вида example.ru, а на зарегистрированный домен (eTLD+1). Несколько клиентов с многодоменными конфигурациями на общих поддоменах потребовали планирования выдачи по дням, чтобы не упереться.

--quiet не значит «нет вывода при ошибке`. Certbot с --quiet молчит при успехе, но пишет ошибку при падении. Именно так и должно работать - но проверили на практике.

Мониторинг нужен отдельный. Systemd-timer фиксирует факт запуска и результат, но не предупредит, если сертификат не обновился и до истечения осталось 10 дней. Добавили в Zabbix проверку даты истечения через openssl s_client - алерт за 21 день. Это второй уровень страховки поверх автопродления.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.