
Статья будет понятна и полезна только тем, кто уже знаком с процессом выписывания сертификатов через Let’s Encrypt, если это не про вас сначала ознакомьтесь с подробной статьей HTTPS в Nginx с сертификатом от Let’s Encrypt
Обзор
Предисловие
Самый распространенный способ подтверждения владения доменом при выписывании сертификата в Let’s Encrypt является http01, при котором резолвится ваше доменное имя и на полученный адрес отправляется http запрос файла с хешем и ваш веб-сервер должен ответить правильным файлом, иначе валидация не пройдет. Но что делать если у вас несколько веб-серверов и ответить может любой из них, при этом только на одном будет нужный файл с хешем? За ответами предлагаю нырнуть в основной текст статьи.
Почему dns01 метод?
Чтобы ответить на этот вопрос, в начале, нужно понять какие вообще варианты существуют. Официальные ответы на форуме Let’s Encrypt предлагают следующие:
- Проксировать запросы в папку /.well-known всегда на один и тот же сервер и на нем же выполнять обновление сертификатов.
- Сделать папку /.well-known общей для всех веб-серверов и тогда неважно на каком сервере запускать обновление сертификатов, все веб-серверы смогут ответить правильным файлом.
Мы в OneTwoTrip столкнулись с описанной выше проблемой и почитав предложенные варианты, решили что нам они не подходят и пошли искать дальше. Нашли малопопулярный вариант с подтверждением через DNS. Предположительно, люди им не пользуются потому что операции с DNS записями очень медленные, некоторые регистраторы и сервисы обновляют записи сутки (как они вообще существуют?). Однако в наше время есть достаточно прогрессивные, которые даже предоставляют API для внесения изменений. Мы пользуемся одним из таких это AWS Route53, он позволяет создавать, обновлять, редактировать и удалять любые DNS записи через aws-sdk и времени на это уходит не больше пары минут.
Amazon Route53 я также использую на данном сайте, о чем писал в статье Под капотом у блога
Свой велосипед
“Я буду долго гнать велосипед…”
Помимо основного инструмента для выписывания сертификатов от Let’s Encrypt certbot существуют уже много реализаций, на разных языках программирования. Но автоматических аналогов работающих с подтверждением через DNS мы не нашли (если плохо искал, жду ваших комментариев). Поэтому было принято решение запилить свой велосипед, удовлетворяющий нашим потребностям и вписывающийся в текущую инфраструктуру. Так как разработка велась быстро и код там пах не очень приятно, приводить его здесь нет смысла, скажу лишь, что первая реализация была самописным демоном, который засыпал на 12 часов с помощью простого sleep
, в дальнейшем демон был переписан на rufus-scheduler
.
Процесс выписывания точно такой же как и при методе http01, отличается только валидацией, в случае c dns01, вам возвращается хеш, который нужно записать в TXT запись _acme-challenge.exmaple.com.
, где exmaple.com доменное имя для которого запрошена валидация, сервера Let’s Encrypt проверяют наличие данной записи и если она присутствует и содержит правильный хеш, можно продолжить процесс получения сертификата.
В рамках рефакторинга было решено переписать приложение и заодно добавить возможность использовать любой DNS сервис, который предоставляет программное API.


Всего в процессе выписывания участвуют следующие компоненты:
- Cron - внутренняя разработка OneTwoTrip, которой скармливается урл и расписание по которому его нужно дергать
- Legdns App - приложение на Sinatra, в котором, на данный момент, всего один endpoint
- Sidekiq - фоновый обработчик задач, использующий redis как очередь
- File System - Файловая система, на которую записываются выписанные сертификаты

Логику сервиса можно описать приведенной выше диаграммой. В Sinatra приложение прилетает POST запрос у которого в теле JSON с массивом доменов, для которых нужно выписать сертификаты:
{
"domains":[
[
"example.com",
"a.example.com",
"b.example.com"
],
[
"example.net"
]
]
}
Этот json прокидывается в Sidekiq Worker, который проводит проверку, есть ли необходимость обновления сертификата. Делает он это по двум параметрам:
- Время до истечения сертификата (меньше 30 дней)
- Совпадают ли запрошенные домены и поддомены с теми что в выписанном сертификате
Далее воркер, используя предоставленный провайдер добавляет необходимую DNS запись, после чего получает сертификат и записывает его в файлы. В конце запускается очистка DNS записей. Провайдер должен соответствовать некоторым требованиям - имя класса должно быть LegdnsProvider
и он должен предоставлять два метода upsert
и cleanup
.
Чтобы сервис заработал нужно установить все зависимости через
bundle install
а также запустить Redis сервер. После чего запустить Sidekiq, с помощью команды:
sidekiq -r app.rb
и собственно Sinatra приложение:
bundle exec rackup -p 3000
Запустить выписывание можно например так:
curl -X POST -is 'http://127.0.0.1:3000/cert' -d '{"domains":[["example.com", "a.example.com", "b.example.com"], ["example.net"]]}'
Если запустить приложение с установленной переменной окружения RACK_ENV=production
все не https запросы будут отброшены. Приложение не предусматривает никакой авторизации, самым простым способом ограничить доступ будет установка впереди Nginx, также через него можно будет отдавать файлы с сертификатами.
А теперь рисуем сову
Наша доблестная команда DevOps любит все автоматизировать, а для управления конфигурацией использует Chef, поэтому мало просто сохранить сертификат в файл. Нужно еще загрузить его на Сhef сервер, откуда в свою очередь сертифкаты разливаются во все места где необходимы, а также нужно отправлять уведомления в слак, чтобы знать если выписывание сертификатов сломается.
Включая загрузку в Chef, диаграмма приложения будет выглядеть следующим образом:

Таким образом, если полностью заменить чтение и запись сертификатов на работу с Сhef сервером, приложение можно легко запустить в Docker контейнере, но это уже тема другой статьи.
Open Source версия приложения доступна на GitHub
Мы хантим! Список открытых вакансий и информацию о компании можно посмотреть на нашем специальном сайте