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

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



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

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


    Лебедь, рак и щука телегу никуда не привезут. Нужен единый центр управления полётами серверами. В нашем случае это 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 (вопросы с прокси)
    • Всё сделать и не уронить производительность

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

    Комментарии 24

      +2
      Это всё классно и прикольно, но почему бы не выложить вашу роль/плейбук и показать не части кода, а полноценный и то, как это работает?
      Думаю не только мне будет интересно посмотреть…
        +2
        Да, будет время — выложу куда-нибудь.
          0
          Предположу, потому что это решение привязано к их DNS провайдеру.
            0
            Мы юзаем AWS Route53. Подойдёт любой провайдер с API.
              0
              Уточните пожалуйста, как?
              Например у меня Google DNS или PowerDNS, как ваша роль взлетит с ними?
                +1
                В нашем случае DNS живёт в Route53, и для управления им используется эта штука:
                docs.ansible.com/ansible/latest/modules/route53_module.html

                Если бы DNS жил в Гугле, то использовалась бы эта штука:
                docs.ansible.com/ansible/latest/modules/gcdns_record_module.html

                А для PowerDNS есть масса вариантов.
                  0
                  Где дока живет я знаю :) Но я сказал, что ваше решение привязано к вашему провайдеру. Ссылки выше означают, что ваше решение нужно переписывать. Имено переписывать, т.к. у меня есть решение для нескольких облаков,(AWS,GCP,Aliyun) которое родилось из чужой роли для Route53. Взять что-то готовое и просто указать другого провайдера — не получится.
          0
          Что только не придумаешь лишь бы не поднять Caddy
            +1
            И traefik вместе с ним.
              0
              Это самом собой)
                0

                Что люди только не делают чтобы не использовать Nginx/HAproxy.

                0

                а чем бы тут помог caddy?

                  0
                  Генерацией сертификатов без костылей?
                    +2

                    из статьи:


                    А давайте использовать wildcard сертификаты? Давайте! Let’s Encrypt их уже выдаёт. Правда, придётся настроить подтверждение владения доменом через DNS. А DNS у нас живёт в AWS Route53. И придётся разложить реквизиты доступа в AWS по всем серверам.

                    и


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

                    caddy точно покрыл бы эти случаи?


                    P.S. на мой взгляд получение сертификатов LE в веб-сервере — весьма сомнительное решение с точки зрения изящности архитектуры.
                    P.P.S. странно, что ещё не догадался на веб-сервере держать и зоны dns.

                  +1
                  edo1h точно отметил. У нас сертификаты используются в разных местах. В том числе таких, где нет никакого HTTP.
                  0
                  А почему когда вам не понравился api.service.skyeng.ru, вы решили сразу переносить всё в папки, а не добавили тире? Например вот так: service-api.skyeng.ru? Красиво и поменять быстро.
                    0
                    Да, для новых сервисов так и делаем. А старые остались с именами разных уровней.
                    0

                    А вы как-нибудь проверяете полученные сертификаты на валидность? Что бы вдруг не распространить пустой файл или просроченный сертификат на серверы?

                      –1
                      Интересная идея, не думал об этом.

                      С одной стороны, это просто сделать. С другой стороны, за год эксплуатации у нас не было ничего подобного. Тогда зачем исправлять проблему, которая никогда не возникнет?
                      0

                      Когда у себя решали этот же вопрос, накидали отдельный сервис с API поверх certboot и все складываем в Hashicorp Vault, откуда уже каждая команда забирает сертификат для своих сервисов.


                      У вас в качестве хранилища что используется?

                        +1
                        У нас всё просто на файловой системе. Так же, как делает certbot.

                        Vault тоже юзаем. Можно было бы и там хранить.
                          0
                          Опенсурсное? Расшарите решение?
                            +1
                            Там ничего секретного нет. Будет время — выложу куда-нибудь.
                              +1

                              Добавил в P.S. ссылку на репу с кодом: https://github.com/igmp/lets-use-ssl.

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                          Самое читаемое