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

Накануне GA Let's Encrypt: пишем Ansible-роль для certbot и готовим 40+ клиентских сервисов

Let's Encrypt объявил о переходе в General Availability в апреле. Пишем Ansible-роль для certbot через cron, тестируем на staging, планируем покрыть клиентские сервисы к маю.

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

Let's Encrypt готовится к General Availability в апреле 2016, публикует финальные детали ACME-протокола и снимает ограничения закрытой беты

Let's Encrypt на прошлой неделе опубликовал официальное объявление: General Availability запланирован на апрель. Закрытая бета заканчивается, инвайты больше не нужны, любой домен - просто берёшь и получаешь сертификат. Для нас это не абстрактная хорошая новость, а конкретная точка, к которой нужно подготовиться.

У нас на managed-сопровождении сейчас больше сорока внешних клиентских сервисов. У большинства сертификаты - это либо платный Comodo куплен вручную и однажды просроченный, либо вообще голый HTTP. Мы хотим закрыть всё к маю. Значит, к апрелю нужна рабочая автоматизация, проверенная на staging, а не поделка из bash, которую «как-нибудь допишем».

Что уже есть

В январе мы перевели несколько доменов на Let's Encrypt из закрытой беты - вручную, с прямыми вызовами letsencrypt-auto из ssh. Cron добавили тоже руками на каждом хосте. Это работает, но не масштабируется. Когда нужно покрыть один-два домена и забыть - нормально. Когда нужно раскатать на несколько десятков хостов с разными конфигурациями nginx и при этом иметь возможность что-то поменять централизованно - нужна роль.

Параллельно в марте мы разобрались с Ansible Vault для секретов в CI/CD. Тот же подход - роли, переменные, vault для чувствительного - применим и здесь.

Что делает роль

Роль letsencrypt закрывает три вещи:

  • Установка letsencrypt-auto. Берём из официального репозитория EPEL на CentOS или из backports на Debian/Ubuntu - зависит от дистрибутива хоста. Версию фиксируем переменной, чтобы не получать сюрпризов при обновлении пакетов.
  • Выпуск сертификата. Certonly в режиме webroot - nginx не останавливается, .well-known/acme-challenge/ отдаётся из выделенной директории. Список доменов передаётся как переменная роли, один хост может иметь несколько записей.
  • Настройка cron и reload. Задача на обновление запускается дважды в неделю через letsencrypt renew - letsencrypt-auto сам разберётся, нужно ли обновлять (обновляет при остатке менее 30 дней). После renew - reload nginx.

Параметры, которые переопределяются на уровне хоста или группы:

letsencrypt_email: "ops@adg.ru"
letsencrypt_webroot: "/var/www/letsencrypt-challenge"
letsencrypt_domains: []
letsencrypt_cron_hour: "4"
letsencrypt_cron_minute: "30"
letsencrypt_cron_weekday: "2,5"

Никаких хардкодов в задачах - только переменные. Это важно потому, что часть хостов мы не контролируем полностью и там cron или время запуска могут быть жёстко оговорены.

Nginx-конфиг под webroot

Один момент, на который потеряли время на staging: на части хостов nginx уже настроен с редиректом 80 -> 443. В таком случае .well-known/acme-challenge/ никогда не доходит до certbot - nginx редиректит запрос Let's Encrypt на HTTPS раньше, чем тот успевает ответить.

Решение - явное исключение в конфиге виртуального хоста, которое роль тоже умеет прописывать через шаблон:

server {
    listen 80;
    server_name example.ru www.example.ru;

    location ^~ /.well-known/acme-challenge/ {
        alias /var/www/letsencrypt-challenge/.well-known/acme-challenge/;
        default_type text/plain;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

На хостах без редиректа роль ставит alias в тот же блок server без дополнительной логики. Шаблон параметризуется переменной letsencrypt_has_https_redirect: true/false.

Тестирование на staging

Let's Encrypt предоставляет отдельный staging-endpoint: https://acme-staging.api.letsencrypt.org. Сертификаты оттуда не валидируются браузерами - они подписаны тестовым CA - но всё остальное работает как в production. Rate limits там значительно мягче.

Включается одним параметром:

letsencrypt_staging: true

Роль в этом случае добавляет --staging флаг к вызовам letsencrypt-auto. Это спасает от ситуации, когда ты прогоняешь роль в третий раз на дне разработки и упираешься в rate limit на production-эндпоинте - что у нас и случилось при первом подходе к написанию роли.

Что пошло не так при разработке

Несколько вещей, которые мы не предусмотрели с первого раза.

Порядок задач имеет значение. Если роль сначала пытается получить сертификат, а webroot-директория ещё не создана или nginx не перечитал конфиг с alias-ом - letsencrypt-auto падает с ошибкой валидации домена. Пришлось добавить явную зависимость: создать директорию, reload nginx, потом letsencrypt-auto.

Идемпотентность letsencrypt renew. letsencrypt renew не выдаёт ошибку, если обновление не нужно - просто пишет «not yet due for renewal» и выходит с кодом 0. Это хорошо. Но letsencrypt certonly при повторном запуске на уже существующий сертификат ведёт себя иначе в зависимости от версии. Добавили условие: выпускать сертификат только если его нет, или явно через --force-renew при нужде.

Кто делает reload. На хостах с несколькими виртуальными хостами reload nginx после renew должен происходить один раз, а не по одному разу на каждый домен. В итоге cron-запись одна на хост, а не на домен.

Состояние на сейчас

Роль написана, прогоняется на staging без ошибок на трёх тестовых хостах с разными конфигурациями: CentOS 7 с одним доменом, Ubuntu 14.04 с тремя доменами и редиректом, Debian 8 с поддоменами. Staging-сертификаты Let's Encrypt получены, cron настроен, nginx перезагружается после renew.

Один незакрытый случай - хосты, где перед nginx стоит HAProxy или другой балансировщик: там webroot-схема потребует отдельного рассмотрения. Несколько таких хостов в списке есть, разберёмся отдельно.

К апрелю планируем прогнать роль по production-хостам в ручном режиме (с --check сначала), получить реальные сертификаты и начать систематически закрывать весь список клиентских сервисов. Сорок с лишним позиций - это несколько дней работы при нормальной автоматизации, не несколько недель.

Контакт

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

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