Управление SSL-сертификатами: от хаоса на сотнях серверов к централизованному решению

Что может стоять за словами «крупнейшая онлайн-школа Европы»?  С одной стороны, это 1 тысяча уроков в час, 10 тысяч преподавателей, 100 тысяч учащихся. А для меня, инженера инфраструктуры, это еще и 200+ серверов, сотни сервисов (микро- и не очень), доменные имена от 2-го до 6-го уровня. Везде нужен SSL и, соответственно, сертификат к нему.

snhaddan7851uhnwtm7fjn8zste.jpeg

По большей части мы используем сертификаты Let«s Encrypt. Их преимущества в том, что они бесплатные, а получение полностью автоматизировано. С другой стороны, у них есть особенность: короткий — всего три месяца — срок действия. Соответственно, их приходится часто обновлять. Мы пытались это как-то автоматизировать, но всё равно оставалась масса ручной работы, и постоянно что-то ломалось. Год назад мы придумали простой и надёжный метод обновления этой кипы сертификатов и с тех пор забыли о такой проблеме.

От одного сертификата на одном сервере к сотням в нескольких дата-центрах


Когда-то давным-давно был всего один сервер. И на нём жил certbot, который работал из-под крона. Потом один сервер перестал справляться с нагрузкой, так что появился ещё один сервер. А потом ещё и ещё. На каждом из них были свои сертификаты со своим уникальным набором имён, и везде надо было настроить их обновление. Где-то при расширении копировали существующие сертификаты, а про обновление забывали.

Чтобы получить сертификат Let«s Encrypt, нужно подтвердить владение доменным именем, указанным в сертификате. Обычно это делается обратным HTTP запросом.

p8vyz3i9qzoxvbfmxkev6rev8t8.png

Вот пара стандартных трудностей, с которыми мы столкнулись по мере роста:

  • Не все новые сервера были доступны снаружи: некоторые убраны за балансировщик входящего трафика и не доступны более из интернета. На них сертификаты приходилось копировать вручную.
  • Также появились сервера вообще без HTTP. Скажем, с почтой. Или с базами данных. Или с каким-нибудь LDAP. Или ещё с чем-нибудь странным. Туда также приходилось копировать сертификаты вручную.

Кое-где довольно долго использовали самоподписанные сертификаты, и это казалось хорошим решением в тех местах, где не нужна проверка подлинности — например, для внутреннего тестирования. Чтобы браузер не сообщал постоянно о «подозрительном сайте», достаточно лишь добавить наш корневой сертификат в список доверенных, и дело в шляпе. Но позже и здесь возникли трудности.

frmbpydq1rypmgjuwpz54phjjaa.png

Беда в том, что в BrowserStack, которым пользуются тестировщики, невозможно добавить сертификат в список доверенных как минимум для iPad, Mac, iPhone. Так что тестировщикам приходилось мириться с постоянно выскакивающими предупреждениями об опасных сайтах.

Поиски решения


Конечно же прежде всего надо сделать мониторинг, чтобы узнавать о заканчивающихся сертификатах не тогда, когда они уже закончились, а немного раньше. Ну, хорошо. Мониторинг есть, мы теперь знаем, что скоро закончатся сертификаты там и тут. И что теперь делать?

asiwtuhzz_az9aqy_lfwc2vohpq.png
Большое Ухо — старый бот, который сертификат не испортит.

А давайте использовать 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 сертификатов, то здесь столько же на каждый тестинг. Пока что они самоподписанные. Было бы здорово заменить их на настоящие…

Два плейбука и один источник правды


Лебедь, рак и щука телегу никуда не привезут. Нужен единый центр управления полётами серверами. В нашем случае это Ansible. Certbot на каждом сервере — это зло. Пусть все сертификаты хранятся в одном месте. Если где-то кому-то нужен сертификат, то приходите в это место и берите с полки свежую версию. А мы позаботимся о том, чтобы в этом хранилище сертификаты были всегда актуальные.

Реквизиты доступа в 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 (вопросы с прокси)
  • Всё сделать и не уронить производительность

Если остались вопросы, пишите в комментариях, буду рад ответить.

© Habrahabr.ru