Содержание

  • Общая информация

  • Установка

  • Подготовка к работе

  • API

  • Пример использования модуля

  • Возможные проблемы

Общая информация

Ссылка на GitHub. Подробнее о работе алгоритма и модуля можно посмотреть здесь.

Клиент ACME-протокола используется для автоматического получения сертификата безопасности для вашего сайта. Для бесплатного получения сертификата и автоматического его продления в основном все используют Let's Encrypt. Но и есть другие сервисы, например Zero SSL. Он тоже поддерживает ACME-протокол.

Я опирался на две статьи с Хабра (эту и эту), а также RFC8555. Но информации в них оказалось недостаточно, для того, чтобы реализовать собственный вариант модуля. Примерно половину нужной информации потребовалось дополнительно извлечь из нескольких реализаций данного модуля на других языках. Тесты проводил на живом сервисе, поэтому автотестов пока нет. Можете написать и сделать пулл реквест.

Модуль написан под Linux. В статье подробно разобран алгоритм работы - при необходимости Вы можете дописать его под другую ОС. Рассматривается только вторая версия протокола.

Установка

Доступны следующие способы:

  • клонировать репозиторий:

git clone https://github.com/a1div0/acme-client.git
  • установить через tarantoolctl:

tarantoolctl rocks install acme-client

Подготовка к работе

CSR

Предварительно необходимо сформировать запрос на подпись сертификата (Certificate Signing Request) - CSR. Этот файл (назовём его csr.pem) содержит информацию о вашем домене и организации. Необходимо заполнить следующие поля:

  1. Доменное имя (CN) - на которое выпускается сертификат;

  2. Организация (O) - полное имя организации, которой принадлежит сайт;

  3. Отдел (OU) - подразделение организации, которое занимается выпуском сертификата;

  4. Страна (C) - код из двух символов, соответствующий стране организации (список);

  5. Штат/Область (ST) и город (L) - местонахождение организации;

  6. email (EMAIL) - почта для связи с организацией.

Сгенерировать такой файл можно с помощью онлайн-генераторов, например вот и вот. Можно с помощью OpenSSL. Для этого необходимо ввести команды вида:

openssl genrsa -out private.key 4096
openssl req -new -key private.key -out domain_name.csr -sha256

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

-----BEGIN CERTIFICATE REQUEST-----
MIICyDCCAbACAQAwgYIxCzAJBgNVBAYTAlJVMSQwIgYDVQQIDBvQkNC70YLQsNC5
. . .
Mf5rbR8Ok/PfHohVHsOp85mAyTInt7a5H4PHVHb7U8j5aPhc4HarH+LcJhM=
-----END CERTIFICATE REQUEST-----

-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCttTORMQRaZYq2
. . .
QARm4Qu60qmM30MrhtCYOBk=
-----END PRIVATE KEY-----

В планах есть автоматизировать процесс создания CSR, так как практически можно продлять сертификат им же, но лучше создавать каждый раз новый.

Добавить и настроить модуль

См. API.

API

  • local acmeClient = require('acme-client') - получить дескриптор библиотеки

  • acmeClient.getCert(settings, proc) - процедура запускает механизм автоматического получения SSL-сертификата

getCert

getCert(settings, yourChallengeSetupProc)

Эта процедура запускает процесс автоматического получения сертификата. Содержит параметр settings, представляющий собой таблицу с полями:

  • dnsName - обязательное поле, доменное имя с сертификатом

  • certPath - обязательное поле, полный путь к папке с сертификатами

  • certName - необязательный, по умолчанию = cert.pem, это имя файла, с которым будет создан сертификат

  • csrName - обязательное поле, имя файла запроса на подпись сертификата, созданного ранее и помещенного в папку certPath

  • challengeType - необязательный, по умолчанию = http-01, этот параметр указывает, какой тип подтверждения владения доменом будет использован. Доступно два варианта: http01 и dns01. Первый тип проверки подтверждает право собственности, посылая GET-запрос на конкретный адрес сайта. Второй тип проверки делает DNS-запрос. Второй тип проверки требуется, если сертификат получаем сразу на все поддомены: *.domain.name (wildcard-сертификаты). Более подробную информацию можно найти ниже в статье и здесь.

  • acmeDirectoryUrl - необязательный, по умолчанию = "https://acme-v02.api.letsencrypt.org/directory", это путь к точке входа ACME-сервера.

Второй параметр - proc - это ваш обработчик установки проверки ACME. Реализация зависит от типа проверки:

Если http-01

function yourProc(url, body)
    -- your code --
end

Процедура будет вызываться, когда необходимо установить ответ сервера. Сервер должен прослушивать порт 80, если мы получаем сертификат SSL в первый раз. Или 443, если у вас есть действующий сертификат SSL. В момент вызова модуль будет передавать в качестве параметров:

  • url - адрес, на который должен быть установлен ответ. Это будет строка типа /.well-known/acme-challenge/<token>

  • body - текст, который будет возвращен при поступлении GET-запроса на указанный адрес.

Процедура вызывается дважды - один раз для установки ответа, второй раз для отмены установки. Если тело содержит текст, код ответа должен быть = 200. Если body == nil, то код ответа должен быть 404.

Если тип проверки dns-01

function yourProc(key, value)
    -- your code --
end

Процедура будет вызываться, когда необходимо установить запись DNS типа TXT. В момент вызова модуль будет передавать имя ключа key и его значение value, которые должны быть записаны в DNS.

Процедура вызывается дважды - один раз для установки записи, второй раз для отмены установки (в параметре value будет передано nil).

Пример реализации этого типа проверки выходит за рамки этой статьи.

Пример использования модуля

В примере используется внешний модуль - http.server.

    local server = require("http.server").new(nil, 80)
    local acmeClient = require("acme-client")
    
    local acmeSettings = {
        acmeDirectoryUrl = 'https://acme-v02.api.letsencrypt.org/directory' -- ACME-service
        ,dnsName = 'mysite.com'
        ,certPath = '/home/my/projects/project123/cert/'
        ,certName = 'certificate.pem'
        ,csrName = 'csr.pem'
        ,challengeType = 'http-01'
    }
    
    local function myChallengeSetup(url, body)
        local proc = nil
        if body ~= nil then
            proc = function (request)
                return request:render{status = 200, text = body}
            end
        else
            proc = function (request)
                return request:render{status = 404}
            end
        end
        server:route({ path = url }, proc)
    end

    acmeClient.getCert(settings, myChallengeSetup)

Возможные проблемы

Если есть проблема - обратите внимание на лимиты сервиса. Например, Let's Encrypt выдаёт не более 5 бесплатных сертификатов на домен в неделю. Есть ограничения по количеству запросов - во время отладки на живом сервисе их легко превысить.

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


  1. alexandrim
    21.01.2022 09:47
    +1

    А хорошо ли терминировать tls на однопоточном сервере приложений?

    В каком случае это допустимо, на ваш взгляд?


    1. 1div0 Автор
      21.01.2022 09:51

      Я не понял, что Вы спросили ). Можете спросить то же самое другими словами пожалуйста?


      1. alexandrim
        21.01.2022 16:26

        Почему на стоит предпочесть классический вариант с proxy описанному выше?


        1. 1div0 Автор
          21.01.2022 18:27

          На мой взгляд это зависит от задачи. Если у меня небольшой проект - всё, что я хочу сделать - это запустить ровно одну команду на сервере. И получать команды снаружи я хочу ровно в том виде, как мне удобно. Я не хочу настраивать nginx и всё остальное прочее.

          Надеюсь я правильно понял вопрос.


          1. alexandrim
            21.01.2022 18:42

            Да, вопрос об этом и ответ понятен, спасибо.
            Но возникает другой вопрос.
            На каком небольшом проекте может понадобиться Tarantool?
            Или это не вопрос надобности,а вопрос предпочтений?


            1. 1div0 Автор
              22.01.2022 04:28

              Нельзя сказать, что Tarantool прям необходим на небольшом проекте - серверную часть можно написать на чём угодно.

              Вот Вы говорите "классический вариант", а я не знаю точно, что Вы имеете ввиду. Есть где-то руководство, где написано, как делать классический вариант? Чем классический вариант лучше или предпочтительнее остальных?

              Тем не менее, Tarantool удобен - что бы реализовать какой-то концепт не нужно много кода. Он даёт большой потенциал роста - если проект вырастет, будет несложно его масштабировать. Впоследствии, можно будет докрутить и тот же nginx (если он понадобится) и всё что нужно ещё.

              Отвечая на вопрос - выбор инструмента это всегда вопрос предпочтений, но если небольшой проект может вырасти, то имеет смысл написать его, используя Tarantool.


              1. alexandrim
                22.01.2022 04:35

                Спору нет. Хотелось понять мотивацию. Ответ понятен. Благодарю!