По большей части мы используем сертификаты Let’s Encrypt. Их преимущества в том, что они бесплатные, а получение полностью автоматизировано. С другой стороны, у них есть особенность: короткий — всего три месяца — срок действия. Соответственно, их приходится часто обновлять. Мы пытались это как-то автоматизировать, но всё равно оставалась масса ручной работы, и постоянно что-то ломалось. Год назад мы придумали простой и надёжный метод обновления этой кипы сертификатов и с тех пор забыли о такой проблеме.
От одного сертификата на одном сервере к сотням в нескольких дата-центрах
Когда-то давным-давно был всего один сервер. И на нём жил certbot, который работал из-под крона. Потом один сервер перестал справляться с нагрузкой, так что появился ещё один сервер. А потом ещё и ещё. На каждом из них были свои сертификаты со своим уникальным набором имён, и везде надо было настроить их обновление. Где-то при расширении копировали существующие сертификаты, а про обновление забывали.
Чтобы получить сертификат Let’s Encrypt, нужно подтвердить владение доменным именем, указанным в сертификате. Обычно это делается обратным HTTP запросом.
Вот пара стандартных трудностей, с которыми мы столкнулись по мере роста:
- Не все новые сервера были доступны снаружи: некоторые убраны за балансировщик входящего трафика и не доступны более из интернета. На них сертификаты приходилось копировать вручную.
- Также появились сервера вообще без HTTP. Скажем, с почтой. Или с базами данных. Или с каким-нибудь LDAP. Или ещё с чем-нибудь странным. Туда также приходилось копировать сертификаты вручную.
Кое-где довольно долго использовали самоподписанные сертификаты, и это казалось хорошим решением в тех местах, где не нужна проверка подлинности — например, для внутреннего тестирования. Чтобы браузер не сообщал постоянно о “подозрительном сайте”, достаточно лишь добавить наш корневой сертификат в список доверенных, и дело в шляпе. Но позже и здесь возникли трудности.
Беда в том, что в BrowserStack, которым пользуются тестировщики, невозможно добавить сертификат в список доверенных как минимум для iPad, Mac, iPhone. Так что тестировщикам приходилось мириться с постоянно выскакивающими предупреждениями об опасных сайтах.
Поиски решения
Конечно же прежде всего надо сделать мониторинг, чтобы узнавать о заканчивающихся сертификатах не тогда, когда они уже закончились, а немного раньше. Ну, хорошо. Мониторинг есть, мы теперь знаем, что скоро закончатся сертификаты там и тут. И что теперь делать?
Большое Ухо — старый бот, который сертификат не испортит.
А давайте использовать wildcard сертификаты? Давайте! Let’s Encrypt их уже выдаёт. Правда, придётся настроить подтверждение владения доменом через DNS. А DNS у нас живёт в AWS Route53. И придётся разложить реквизиты доступа в AWS по всем серверам. А при появлении новых серверов копировать всё это хозяйство туда тоже.
Хорошо, имена 3-го уровня покрываются wildcard-ом. А что делать с именами 4-го уровня и выше? У нас много команд, которые занимаются разработкой различных сервисов. Сейчас принято делить фронтенд и бэкенд. И если фронтенд получает имя 3-го уровня вроде service.skyeng.ru, то бэкенду норовят дать имя api.service.skyeng.ru. Хм, может быть запретить им впредь так делать? Отличная идея! А что делать с десятками уже существующих? Может быть железной рукой согнать их все на одно доменное имя? Заменим все эти имена различных уровней на URL-ы типа skyeng.ru/service. Технически это вариант, но сколько времени это займёт? И как для бизнеса обосновать необходимость таких действий? У нас 30+ команд разработки, каждую пойди уговори — это займёт как минимум полгода. А ещё мы создаём единую точку отказа. Как ни крути, это спорное решение.
Какие ещё есть идеи?.. Может быть сделать один сертификат, куда включим всё-всё-всё? И будем его устанавливать на все сервера. Это могло бы быть решением наших проблем, но Let’s Encrypt позволяет иметь только 100 имён в сертификате, а у нас различных микросервисов уже больше.
А что делать с тестировщиками? Так и не придумали ничего, а они постоянно жалуются. Всё фигня кроме пчёл. Пчёлы тоже фигня, но их много. Каждому разработчику или тестировщику выдаётся тестовый сервер — мы их называем тестингами. Тестинги не пчёлы, но их уже далеко за сотню. И на каждый деплоятся все проекты. Вообще все. И если для прода нужно N сертификатов, то здесь столько же на каждый тестинг. Пока что они самоподписанные. Было бы здорово заменить их на настоящие…
Два плейбука и один источник правды
Лебедь, рак и щука телегу никуда не привезут. Нужен единый центр управления
Реквизиты доступа в AWS также присутствуют только в этом одном месте. Соответственно, у нас исчезают вопросы вроде настроить ещё и AWS CLI на новом сервере, кто имеет доступ в Route53 и тому подобное.
Все требуемые сертификаты описываются в одном файле в Ансибле в формате YAML:
certificates:
- common_name: skyeng.ru
alt_names:
- *.skyeng.ru
- common_name: olympiad.skyeng.ru
alt_names:
- *.olympiad.skyeng.ru
- api.content.olympiad.skyeng.ru
- games.skyeng.ru
- common_name: skyeng.tech
alt_names:
- *.skyeng.tech
. . .
Периодически запускается один плейбук, который проходит по этому списку и делает свою тяжёлую работу — в сущности всё то же самое, что делает certbot:
- создаёт аккаунт в Let’s Encrypt Certificate Authority
- генерирует приватный ключ
- генерирует (не подписанный ещё) сертификат — так называемый certificate signing request
- отправляет запрос на подписание
- получает DNS challenge
- помещает полученные записи в DNS
- отправляет запрос на подписание ещё раз
- и, получив наконец подписанный сертификат, помещает его в хранилище.
Плейбук выполняется раз в сутки. Если он не смог обновить какие-то сертификаты по какой-либо причине — будь то сетевые проблемы или какие-то ошибки на стороне Let’s Encrypt — это не проблема. Обновится в следующий раз.
Теперь, когда на каком-нибудь хосте service нужен SSL, можно прийти в это хранилище и взять оттуда несколько файлов — простейшая операция, которую выполняет второй плейбук… Какие сертификаты нужны на данном хосте, описывается в параметрах этого хоста, в inventories/host_vars/server.yml:
certificates:
- common_name: skyeng.ru
handler: reload nginx
- common_name: crm.skyeng.ru
. . .
Если файлы изменились, то Ансибл дёргает хук — типично это перезагрузить Nginx (в нашем случае это действие по умолчанию). И точно так же можно получать сертификаты от других CA, работающих по протоколу ACME.
Итого
- У нас было множество разных конфигураций. Постоянно что-то ломалось. Частенько приходилось лазить по серверам и разбираться, что там опять отвалилось.
- Теперь у нас два плейбука и всё записано в одном месте. Всё работает как часы. Жизнь стала скучнее.
Тестинги
Да, а что же тестировщики с их тестингами? Каждому разработчику или тестировщику выдаётся личный тестовый сервер — тестинг. Их в настоящее время около 200. Они имеют имена вида test-y123.skyeng.link, где 123 — это номер тестинга. Создание и удаление тестинга автоматизировано. Одно из составляющих действий — это установка на него SSL-сертификата. SSL-сертификат генерируется заранее, с именами по шаблону:
ssl_cert_pattern:
- *
- *.auth
- *.bill
. . .
Всего около 30 имён. Так что в готовый сертификат входят имена
test-y123.skyeng.link
*.test-y123.skyeng.link
*.auth.test-y123.skyeng.link
*.bill.test-y123.skyeng.link
и так далее.
После увольнения разработчика или тестировщика его тестинг удаляется. Сертификат остаётся, готовый к использованию. Хранится это всё сами знаете где и раскладывается на хосты сами знаете как.
P.S.
Ещё по этой теме может быть интересно почитать, как Stack Overflow переходил на HTTPS:
- Сотни доменов разного уровня
- Websockets
- Множество HTTP API (вопросы с прокси)
- Всё сделать и не уронить производительность
Если остались вопросы, пишите в комментариях, буду рад ответить.
ZloyKishechnik
Это всё классно и прикольно, но почему бы не выложить вашу роль/плейбук и показать не части кода, а полноценный и то, как это работает?
Думаю не только мне будет интересно посмотреть…
igmp Автор
Да, будет время — выложу куда-нибудь.