Написал программу для автоматического выписывания ssl-сертификатов налету (при первом запросе к домену). Для начала работы программы её достаточно просто запустить, настраивать не надо совсем (даже домены для сертификатов указывать не надо).

Подробности внутри.

Предыстория
Я работаю с let's encrypt примерно с апреля этого года, выписывая сертификаты в больших количествах (много тысяч, постоянно добавляются новые).
Изначально это выглядело примерно так: одна программа составляет список доменов для которых нужен сертификат, вторая программа идёт и эти сертификаты выписывает, третья проверяет выписанные сертификаты и составляет их список для haproxy/nginx (потому что с неправильными сертификатами они не перезапустятся при обновлении списка сертификатов и все https-сайты лягут). Работает это вполне надёжно, но получается много компонентов.

Кроме того запуск этого чуда на windows + iis оказался нетривиальным и там коллега придумывал дополнительные костыли.

Требования:

1. Со стороны клиента поддержка SNI (основными браузерами давно поддерживается).
2. Со стороны сервера: чтобы туда можно было из golang бинарник скомпилировать (windows,linux,freebsd,mac). Проверяется на windows и linux.
3. Домен соответствует правилам Lets encrypt, на данный момент это: длина домена 64 символа или короче, без punycode (т.е. что-то.рф сертификата не получит).
4. Сервер с сайтом доступен из публичной сети.

Принцип работы:

Программа работает как реверс-прокси, выписывая правильные сертификаты на ходу по мере необходимости.

При получении запроса через расширение SNI программа узнаёт домен для которого нужен сертификат. Если сертификат уже есть — дальше идёт обработка с уже существующим сертификатом.

Если сертификата нет — отправляется запрос в lets encrypt на получение сертификата из SNI-заголовка и потом обработка идёт с только что полученным сертификатом (сертификат сохраняется в кеш).

В lets encrypt домен проверяется методом tls-sni-01 — путём выдачи сертификата на специально сформированный https-запрос.

Время на все проверки и получение сертификата — около 3 секунд. Это задержка для первого https-запроса к домену.

Дальше запрос уходит на тот же IP, на котором был принят https-запрос, но уже на порт 80 и без шифрования — т.е. обычный http.

» github.com/rekby/lets-proxy/releases/latest

Несмотря на простоту варианта по умолчанию у программы много ключей запуска для определения нюансов работы. Для полного перечня можно воспользоваться ключем --help.
Поделиться с друзьями
-->

Комментарии (29)


  1. andreymal
    20.11.2016 15:34

    Я так понимаю, эта штука один сертификат на сразу несколько доменов сделать не позволит?


    1. rekby
      20.11.2016 16:12

      Да, в сертификате будет указан только один домен.

      Однако самих сертификатов может быть много, например на сервере где сейчас программа в пробном режиме работает за сутки выписалось около 500 сертификатов.

      т.е. если у вас к одному серверу привязано несколько доменов для каждого из них будет выписан свой сертификат и все они могут работать одновременно.


    1. rekby
      28.11.2016 23:38

      добавка — теперь в один сертификат можно записать домен и его поддомены. По умолчанию сертификат выписывается сразу для поддомена www, т.е. для domain.com и www.domain.com в одном сертификате.


  1. BVadim
    20.11.2016 17:00
    +1

    Интересная реализация. А что на счёт производительности? Интересно было бы увидеть замеры: с этим прокси против стандартной реализации ssl в nginx, на различном количестве соединений (в большом диапазоне), с различным размером тела запроса (от маленького до большого), как при этом ведёт себя сервис, сколько потребляет памяти. Так же было бы здорово, если бы сразу был представлен результат проверки такого сайта популярным ssl-чекером. Поскольку это приложение претендует на точку входа — оно может стать неожиданной причиной падения сервиса, поэтому такие подробности были бы очень кстати.


    1. rekby
      21.11.2016 10:49

      отчет чекера можно посмотреть тут https://www.ssllabs.com/ssltest/analyze.html?d=www.f1f2.ru

      тесты производительности занимают много времени, пока провести их не готов. Но могу для теста выделить пару виртуальных машин на несколько дней человеку, который знает как это делается и готов провести и опубликовать результаты сравнений.


  1. masterspline2
    20.11.2016 17:31

    Думаю, надежнее и производительнее было бы сделать так: вначале стоит nginx, у которого сконфигурированы все домены, для которых уже есть сертификаты, а сайт по умолчанию — тот, что с прокси. Прокси, получив запрос, создает сертификат и генерирует конфигурацию для этого домена, добавляет ее nginx'у и делает ему reload. После чего проксирует его на обновленный nginx. При такой конфигурации запросы к уже существующим доменам до прокси не дойдут (точнее их будет проксировать nginx, который протестирован гораздо лучше), а прокся будет заниматься только одной своей задачей.


    1. rekby
      20.11.2016 17:42

      Думаю такое возможно, если заставить для сайта по умолчанию nginx проксировать запросы тоже по https.
      Однако тут потребуются дополнительные инструменты:
      1. для формирования/обновления конфига nginx, причем при обновлении надо заново проверять все сертификаты которые туда включаются (вдруг файл повредился и nginx из-за битого сертификата перестанет работать).
      2. Для перезапуска nginx.
      3. Мне кажется что в случаях
      4. Потребуется отдельная простукивался, которая будет бегать по выписанным сертификатам, проверять срок их действия и заранее что-то делать (обращаться к выписывалке, переделывать временно конфиг nginx и т.п.) чтобы своевременно обновлять сертификат.

      В такой схеме думаю проще будет выписывать сертификаты без такой прокси, просто по списку. Хотя мысль интересная, попробую может ли это в принципе работать.


    1. rekby
      21.11.2016 11:43

      Посмотрел, nginx вроде нельзя настроить таким образом чтобы при несовпадении заголовков запрос начал бы проксироваться в tcp-режиме на отдельный сервер с сохранением начального запроса, содержащего запрашиваемый домен.

      Т.е. сам домен передать не проблема, заголовок передать тоже относительно не проблема — можно просто сделать новый https-запрос к бэкэнду. (вопрос как быть с фейковыми запросами чтобы как проксёй для внешних сервисов не служить — отдельный). Проблема потом передать обратно ответ сервера в неизменном виде, как при обычном tcp-проксировании.

      Если есть желание проксировать именно через nginx — можно или отдельно сертификаты выписывать, по спискам или воспользоваться https://github.com/GUI/lua-resty-auto-ssl как Erelecano рекомендовал


  1. Erelecano
    20.11.2016 18:39

    А зачем нам велосипед с квадратными колесами, если уже есть готовое решение для OpenResty?
    https://github.com/GUI/lua-resty-auto-ssl
    И даже пакеты для trusty openresty и бэкпорт openssl'а нужного для его работы вон в ppa https://launchpad.net/~ernillew/+archive/ubuntu/operesty-for-le есть(не обновлялся правда, ибо перестало быть нужным, но желающие могут взять все в свои руки и форкнуться)


    1. rekby
      20.11.2016 19:27

      1. К сожалению я не нашел его, когда искал.
      2. Система нужна универсальная для windows и linux, lua-resty-auto-ssl на работу в windows явно не рассчитан и собрать его если и можно то думаю что нетривиально.

      за наводку спасибо, если бы нашел раньше — попробовал бы под винду собрть.

      Ну и в любом случае для настройки мой вариант явно проще, так что это скорее альтернатива чем велосипед.


      1. Erelecano
        20.11.2016 19:35
        +2

        Как-то странно искали, ибо ссылка на него тут https://letsencrypt.org/docs/client-options/
        Я его оттуда и нашел, в свое время.
        Посмотрите, может вам чем-то будет полезен, в любом случае.

        Я его у себя ставил, что бы при запросе на *.domain.tld всегда был ответ с валидным сертификатом, потом нужда отпала(потому OpenResty в ppa и не обновляю)


        1. rekby
          20.11.2016 19:44

          Да, согласен, странно искал — самый простой вариант (эту страницу) почему-то не посмотрел.


          1. Erelecano
            20.11.2016 19:49

            А я вот рассматривал там внимательно что есть, на основных серверах выбрал для себя https://github.com/hlandau/acme, а под кейс описанный выше вот нашел вариант для OpenResty.
            Там, вообще, много интересного.
            Раз пишите свой вариант, то вам могут быть интересны ссылки оттуда, что бы посмотреть кто как реализововал это дело.


            1. rekby
              20.11.2016 19:50

              https://github.com/hlandau/acme — я как раз внутри использую, как api к lets encrypt.

              Другие варианты тоже еще посмотрю — что у себя мог упустить.


              1. Erelecano
                20.11.2016 19:54

                https://github.com/hlandau/acme чертовски удобен и как основной клиент. Настроил его один раз левой пяткой и можно забыть о всех проблемах, он обновляет себе сертификаты и есть не просит.


  1. IGHOR
    20.11.2016 20:03

    А как с проверкой/защитой от флуда?
    Допустим начнут приходить запросы на ssl подключения с рандомными сабдоменами.
    Вы для всех сертификат будете генерировать?


    1. rekby
      20.11.2016 20:15
      +2

      нет, перед выпиской сертификата производится проверка через локальный dns, dns yandex, dnsgoogle для A и AAAA записей. Если найдётся хоть одна, которая не соответствует одному из IP-адресов сервера — сертификат выписываться не будет.

      собственно они и без проверок бы не выписались, но можно было бы упереться в лимиты на висящие авторизации.


    1. foxmuldercp
      20.11.2016 21:42

      тот же вопрос. у меня на каждом шаред хостинг сервере не одна тысяча доменов с поддоменами, и кое где домены протухают, например, но боты ещё месяц и больше могут ломиться...


      1. rekby
        21.11.2016 06:31

        Собственно ответ чуть выше — перед попыткой выписать сертификат делаются проверки о том что домен действительно работает на этом сервере. Рассчет изнакально как раз для использования на хостинге.


  1. kekekeks
    20.11.2016 22:36

    А в качестве модуля к nginx это оформить как-то можно?


    1. Erelecano
      20.11.2016 23:37

      Выше есть ссылка на https://github.com/GUI/lua-resty-auto-ssl работающий с openresty/nginx. К сожалению в чистом nginx так и нет ssl_certificate_by_lua, который он использует, так что только в openresty/nginx, а не в чистом, но если вам оно нужно посмотрите в сторону openresty.


  1. utilmind
    21.11.2016 06:09

    Так же вроде бы CloudFlare работает как прокси. Всё что надо для перехода — указать DNS cloudflare + настроить редирект в .htaccess.


    1. rekby
      21.11.2016 06:29
      +2

      да, cloudflare умеет раздавать сайт по https, но это решение другого плана — это внешний сервис, а внешний сервис это сразу ряд рисков:
      1. Домен нужно переделегировать и дальше его работа целиком зависит от cloudflare — как в плане работоспособности, так и в плане ценовой/тарифной политики. Кроме того это может быть сложно (как технически так и административно).
      2. http/https трафик будет проходить через чужие серверы, при этом если у вас на конечном сервере https не насртроен то от cloudflare до вас трафик пойдет в открытом виде (будет ли собираться и использоваться зависит уже от везения).
      3. Дополнительная точка отказа.
      Сама инфраструктура по опыту вполне надёжна, но бывают недоразумения — например с блокировкой IP-адреса из-за нехорошего соседа. CF — иностранная компания и на предупреждения роскомнадзора может внимания не обратить даже если его получит и не выключить провинившегося.
      Лично сталкивался с тем что у некоторых клиентов сайт просто не работал при пропускании трафика через CF (еще когда блокировок даже небыло). Разбираться с этим в теории можно через поддержку, но это время (часто — потеря денег) + нужен английский.
      4. Это может стать проблемой когда доменов много и/или когда они чужите и свободного управления доменами нет (хостинг, веб-студии).


      1. Erelecano
        21.11.2016 09:27

        > на предупреждения роскомнадзора может внимания не обратить

        Не совсем верно. Не может не обратить внимания, а не обращает и это — официальная позиция. Они выпускали заявление, что им писульки Росцензуры до лампочки и они не выполняют и не будут выполнять приказы незаконного цензурного органа из РФ.


    1. Erelecano
      21.11.2016 10:02

      Что-то я сомневаюсь, что CF сделает wildcard(не пробовал прописывать домены которые у меня работают через них по *). А тут предлагается решение фактически заменяющее wildcard, просто строчащее сертификаты по мере обращения.


  1. hostmaster
    22.11.2016 11:58
    +1

    Похожий проект https://github.com/artyom/leproxy — используется в production уже около года, отлично себя зарекомендовал. Простой, компактный, мало зависимостей. Написал мой коллега, за что ему огромное спасибо. Экономит кучу времени и нервов.


    1. rekby
      22.11.2016 13:48
      +1

      Посмотрел, отличное решение, особенно в плане краткости. Мне понравилось. https://github.com/artyom/leproxy реализован заметно проще, чем у меня как за счет более активного использования сторонних компонентов, так и за счет меньшего количества проверок.

      Увидел реальное использование golang.org/x/crypto/acme/autocert — как раз интересно было насколько реально работает этот инструмент или он еще совсем в разработке. Смотрел его, когда свой проект писать начинал.
      Собственно и сейчас на него посматриваю, чтобы выкинуть значительную часть своего кода и проект упростить. Пока удерживают некоторые нюансы в реализации и сложность работы с гуглом как с автором библиотеки, т.е. несмотря на то что это BSD-лицензия внести туда изменения может быть проблемой. Например они pull-request'ы не принимают и надо отдельно изучать как у них это устроено. Чуть позже буду выделять часть управления сертификатами в отдельный пакет — чтобы его можно было переиспользовать отдельно от прокси, как раз буду ориентироваться на интерфейс autocert.

      Сам менеджер еще не очень готов, например у них только запланирована, но еще не сделана поддержка таймаутов при выписке сертификата + еще какие-то мелочи нашел, которые меня в менеджере сертификатов не устроили. Т.е. я бы и поправил с удовольствием, но потом непонятно как свои изменения обратно влить.

      По итогам сравнения:
      https://github.com/artyom/leproxy
      Очень простой для понимания и дополнения кода. Собственно самого кода минимум, все основные компоненты переиспользуются из сторонних библиотек (проксирование, выписка и хранение сертификатов).
      Для проксирования используется обработчик, встроенный в язык. Он точно лучше протестирован, чем у меня в lets-proxy.

      Работает с заранее заданным списком доменов.
      Может проксировать запросы к разным доменам на разные бэкенды, причем не только по tcp, но и через сокеты.

      https://github.com/rekby/lets-proxy
      Работает с произвольным, заранее неизвестным, изменяющимся во времени набором доменов. Например на сервере хостинга (для этого и делается) — постоянно появляются новые домены, удаляются старые.

      В простейшем случае совсем не требует настройки, в т.ч. задания списка серверов.

      По команде умеет устанавливаться/работать как служба в windows и демон в linux.

      Поддержка не только http, но и tcp-проксирования.
      Собственная реализация прокси-сервера более топорная и экономичная по ресурсам, чем встроенный вариант (намного меньше манипуляций, выделений памяти и т.п.). Кроме того встроенный вариант режет поддержку websocket (вырезает заголовок upgrade из запроса).


  1. saipr
    22.11.2016 15:43

    Здорово! Надо будет проверить на сайтах с поддержкой https на базе российской криптографии!


    1. rekby
      23.11.2016 09:04

      Шифрование по ГОСТ сейчас не поддерживается (встроенная библиотека не поддерживает)