Настройка ACME#
Модуль ACME в Angie
обеспечивает автоматическое получение сертификатов с использованием протокола
ACME.
Протокол ACME предусматривает несколько способов проверки доменов
(также используется термин верификация);
модуль реализует
HTTP-проверку, DNS-проверку,
ALPN-проверку, а также
проверку с помощью хуков
через самостоятельно реализуемый внешний сервис. Общие шаги для включения запроса сертификатов в конфигурации: Настройте ACME-клиент в блоке Укажите домены, для которых запрашиваются сертификаты:
для доменных имен, перечисленных во всех директивах server_name
всех блоков IP-адреса (IPv4 или IPv6), указанные в server_name,
также являются допустимыми идентификаторами сертификата,
за исключением DNS-проверки. Обратите внимание: публичные центры сертификации могут выдавать
сертификаты для IP-адресов только в рамках определенных профилей
сертификатов; например, Let's Encrypt в настоящее время требует
профиль, допускающий IP-идентификаторы
(см. документацию по профилям).
Чтобы запросить такой профиль, задайте параметр Настройте обработку запросов и вызовов ACME:
это нужно для проверки владения доменом.
Способ настройки зависит от способа проверки доменных имен: Способ Требования к пользователю Мультидомены Домены со звездочкой Открыть порт 80 (или указанный в acme_http_port)
для входящих соединений на сервере Angie. ✔ Открыть порт 53 (или указанный в acme_dns_port)
для входящих соединений на сервере Angie. Настроить NS-запись для поддомена ✔ ✔ Открыть порт 443 (или TLS-порт, используемый сервером Angie)
для входящих соединений. ✔ Реализовать внешний сервис (скрипт или приложение),
который по команде Angie сможет внести изменения в DNS-зону
или разместить специальный ответ на веб-сервере. ✔ ✔ Настройте SSL с использованием полученного сертификата и ключа:
Модуль делает сертификаты и ключи доступными в виде
встроенных переменных,
которые можно использовать в конфигурации
для заполнения ssl_certificate и ssl_certificate_key. Инструкции по настройке SSL см. в разделе Настройка SSL. Совет Процесс получения и обновления сертификатов зависит от работы многих служб
и может занимать какое-то время. Запаситесь терпением,
а в случае возникновения проблем или сомнений обратитесь к
отладочному логу. Здесь ключи и сертификаты клиентов хранятся
в кодировке PEM
в соответствующих подкаталогах каталога,
заданного с помощью параметра сборки
Примечание Эти файлы сохраняются на диске между перезапусками. При запуске клиент
повторно использует сохраненный сертификат, если тот еще действителен,
вместо запроса нового, что устраняет задержку на выпуск и лишние обращения
к серверу CA, на которые могут действовать ограничения частоты запросов. В контейнере размещайте
каталог хранения (по умолчанию Примечание Перезагрузка конфигурации ( Клиенту ACME требуется учетная запись на сервере CA.
Для ее создания и управления ею клиент использует закрытый ключ
( Примечание Если у вас уже есть ключ учетной записи,
поместите его в подкаталог клиента перед запуском
для повторного использования учетной записи.
Файл ключа также можно указать с помощью параметра Некоторые CA требуют привязать учетную запись ACME к уже существующей внешней
учетной записи с помощью External Account Binding (EAB), прежде
чем выпускать сертификаты. Для этого задайте параметр Клиент ACME также использует отдельный ключ
( При запуске клиент запрашивает сертификат, если его еще нет,
подписывая и отправляя CSR для всех доменов, которыми он управляет, серверу CA.
Сервер проверяет владение доменом
путем HTTP- или DNS-проверки
и выдает сертификат,
который клиент сохраняет локально ( Как сказано выше, сертификат будет единым для всех доменных имен,
для которых используется один и тот же ACME-клиент,
то есть потенциально может быть мультидоменным.
Список всех имен, для которых выдан сертификат,
см. в разделе Subject Alternative Name (SAN) полученного сертификата.
Проверить его можно в командной строке, например: Когда приближается завершение срока действия сертификата
или изменяется список доменов,
клиент подписывает и отправляет еще один CSR на сервер CA.
Сервер снова проверяет владение и выдает новый сертификат,
который клиент устанавливает локально, заменяя предыдущий. В конфигурации полученный сертификат и соответствующий ключ
доступны через префиксные переменные
$acme_cert_<имя> и $acme_cert_key_<имя>.
Их значения — содержимое соответствующих файлов,
которое следует использовать
с директивами ssl_certificate и ssl_certificate_key, например: Примечание Клиент ACME определяет адрес сервера CA по его имени через resolver
того же контекста, который по умолчанию обращается к DNS-серверам из
Директива acme служит только для сбора доменных имен
для запросов сертификатов.
Она не определяет, где можно использовать сертификат:
любой блок Например, если у вас есть блок Чтобы точно контролировать набор доменных имен в сертификате,
не полагаясь на автоматический сбор из всех блоков Другие блоки Каждый acme_client управляет одним сертификатом. Чтобы получить
несколько независимых сертификатов (например, для несвязанных доменов, которым
не следует использовать общий сертификат), настройте отдельный
acme_client для каждого из них в блоке Проверка выполняется автоматически.
Когда Angie заказывает сертификат, ACME-сервер
запрашивает по HTTP
файл-токен по адресу Здесь ACME-клиент с именем Как уже отмечалось,
порт 80 должен быть открыт для приема вызовов ACME по HTTP.
Если ни один сервер не слушает порт HTTP-проверки,
модуль создает отдельный слушающий сокет на порту 80
(или указанном в acme_http_port). Отдельный блок
server не требуется. Проверка выполняется автоматически, но требует настройки DNS с вашей стороны.
Когда Angie заказывает сертификат, ACME-сервер отправляет DNS-запрос
TXT-записи в поддомене Чтобы запрос дошел до Angie, DNS вашего домена должен делегировать поддомен
DNS-проверка подтверждает контроль над доменным именем,
поэтому не может проверять идентификаторы в виде IP-адресов:
для клиента с Примечание Сервер Angie должен быть доступен из интернета
на UDP-порту 53 (или указанном в acme_dns_port).
Если сервер находится за межсетевым экраном,
убедитесь, что этот порт открыт для входящих соединений. Например, чтобы подтвердить домен Эта конфигурация делегирует разрешение DNS для
Предупреждение Распространение изменений NS-записей может занимать от нескольких минут
до 48 часов в зависимости от TTL и DNS-провайдера.
Рекомендуется проверить корректность настройки перед запросом сертификата. Для проверки корректности настройки DNS можно использовать следующие команды: Этот способ позволяет запрашивать wildcard-сертификаты, например сертификат,
включающий запись Предупреждение Применимость данного сценария во многом зависит от возможностей,
предоставляемых вашим DNS-провайдером;
некоторые провайдеры не позволяют выполнять такие настройки. В целом, конфигурация схожа с примером из предыдущего раздела.
Нет необходимости в настройках, специфичных для HTTP;
вместо этого достаточно установить Здесь ACME-клиент с именем Проверка работает автоматически. ACME‑сервер устанавливает TLS‑соединение и
запрашивает через ALPN протокол Чтобы использовать этот способ, задайте Конфигурация аналогична предыдущим разделам; достаточно задать
Здесь ACME-клиент с именем В отличие от предыдущих способов,
эта проверка требует дополнительных усилий.
Здесь ACME-сервер производит обычную HTTP-проверку
или DNS-проверку,
но обращается не к самому серверу Angie, а к внешнему сервису,
которым сервер Angie управляет с помощью вызовов-хуков (acme_hook).
В свою очередь, этот сервис настраивает отдельный DNS- или HTTP-сервер,
куда и направляются запросы ACME-сервера. Получив ожидаемый ответ от настроенного таким образом DNS- или HTTP-сервера,
ACME-сервер подтверждает, что домен принадлежит клиенту. Когда для выпуска или обновления сертификата требуется проверка домена,
Angie формирует внутренний запрос
к именованному Общая схема такова: Создайте именованный Настройте обработчик запроса в том же Передайте обработчику переменные ACME
с помощью поддерживаемого им механизма,
например, Обработчик должен возвращать код состояния Независимо от используемого обработчика,
При DNS-проверке обработчик должен использовать Здесь настраивается ACME-клиент Блок Именованный блок Пример соответствующего внешнего FastCGI-сервиса на Perl: Здесь Еще один пример, с использованием PHP-FPM: Переданные параметры доступны в PHP через Здесь хук — это скрипт, запускаемый модулем CGI;
скрипт добавляет и удаляет TXT-запись Модуль подключается в контексте octoDNS собирает зону из источников по порядку и объединяет их, поэтому хук
хранит проверочную запись в отдельном каталоге-источнике
В файле зоны находятся записи, которые вы ведете сами: Чтобы проверить конфигурацию octoDNS вручную, запустите octodns-sync
без Скрипт читает переменные ACME из своего окружения
и записывает проверочную запись в источник Как только хук Если значение не появится вовремя, скрипт отвечает Предел ожидания задается переменной окружения Значение Переменная $acme_hook_domain приходит без префикса Скрипт выполняется от имени пользователя рабочих процессов Angie —
Стандартный поток ошибок скрипта попадает в журнал ошибок
Angie на уровне Чтобы все это настроить в системе на основе Debian: Установите модуль CGI: Установите octoDNS и провайдер Cloudflare
в отдельное виртуальное окружение: Создайте каталоги; пакет модуля
не создает Создайте Сохраните показанный выше скрипт под именем Добавьте показанную выше конфигурацию, проверьте ее и
перезагрузите Angie: Сразу после перезагрузки клиент запрашивает сертификат; его состояние
и статус сертификата видны в разделе API /status/http/acme_clients/, а полученный сертификат сохраняется
в каталоге хранения клиента. Потоковый модуль ACME позволяет автоматизировать
выпуск и использование сертификатов для TCP-трафика.
Для его корректной работы необходимо сначала настроить HTTP-аналог:
ACME-клиент должен быть объявлен в контексте По умолчанию для получения сертификатов используется режим HTTP-проверки.
Для него, как упоминалось в разделе HTTP-проверка,
нужен HTTP-сервер, слушающий на порту 80: Также можно использовать DNS-проверку,
настроив Если до перехода с nginx на Angie
вы использовали certbot
для получения и продления SSL-сертификатов от центра сертификации Let's Encrypt,
выполните следующие шаги, чтобы перейти к использованию нашего модуля ACME. Предположим, вы настроили сертификаты следующим образом: Автоматически созданная при этом конфигурация
обычно находится в файле В примере выше выделены строки, которые потребуется изменить.
В зависимости от ваших обстоятельств и предпочтений настройте
HTTP-проверку
или DNS-проверку с помощью модуля ACME. Итоговая конфигурация Angie
может выглядеть приблизительно так: Не забудьте перезагрузить конфигурацию
после изменения: Убедившись, что эта конфигурация работает,
вы можете удалить сертификаты certbot,
а также отключить или целиком удалить его с сервера,
если он больше нигде не используется,
например:Шаги настройки#
http
с помощью директивы acme_client,
задающей уникальное имя клиента и другие параметры;
можно настроить несколько клиентов ACME.server с директивами acme,
указывающими на один и тот же ACME-клиент,
будет выдан единый сертификат.profile=
директивы acme_client._acme-challenge.,
направив ее на свой сервер Angie.Подробности реализации#
--http-acme-client-path:$ ls /var/lib/angie/acme/example/
account.key certificate.pem private.key
/var/lib/angie/acme/) на постоянном
томе, чтобы выданные сертификаты сохранялись при пересоздании контейнера;
см. запуск Angie в контейнере.angie -s reload) заново сверяет каждый
клиент ACME с сертификатами, сохраненными на диске. Клиент, сертификат
которого в данный момент недействителен (в том числе если его предыдущий
запрос завершился ошибкой), запрашивается заново немедленно, независимо от
retry_after_error. Задержка перед повторной попыткой
(как и значение retry_after_error=off) действует, только пока Angie
работает, и не сохраняется после перезагрузки. Поэтому повторные перезагрузки
при постоянно неудачных запросах могут приводить к частым обращениям
к серверу CA, на которые действуют упомянутые выше ограничения частоты
запросов.account.key);
если ключа у него еще нет, ключ создается при запуске.
Затем клиент использует его для регистрации учетной записи на сервере.account_key
в acme_client.eab
директивы acme_client, указав идентификатор ключа и MAC-ключ,
предоставленные CA; точный синтаксис приведен в описании директивы.private.key) для запросов на подпись сертификата (CSR);
если нужно, этот ключ сертификата также создается автоматически при запуске.certificate.pem).$ openssl x509 -in certificate.pem -noout -text | grep -A5 "Subject Alternative Name"
server {
listen 443 ssl;
server_name example.com www.example.com;
acme example;
ssl_certificate $acme_cert_example;
ssl_certificate_key $acme_cert_key_example;
}
/etc/resolv.conf; задавайте директиву явно, только если нужны
другие серверы или параметры. На узле без поддержки IPv6 resolver может
все равно отправлять AAAA-запросы (IPv6) для сервера CA и не суметь
подключиться; отключите их параметром ipv6=off, например
resolver conf ipv6=off;.Сбор доменов и использование сертификата#
server может ссылаться на полученный сертификат
через переменную $acme_cert_<имя>,
независимо от того, содержит ли блок директиву acme.server с маской,
который уже охватывает все поддомены,
дополнительные блоки server для конкретных поддоменов
не нуждаются в директиве acme:http {
acme_client example https://acme-v02.api.letsencrypt.org/directory
challenge=dns;
# В этом блоке перечислены домены для запроса сертификата
server {
listen 443 ssl;
server_name example.com *.example.com;
acme example;
ssl_certificate $acme_cert_example;
ssl_certificate_key $acme_cert_key_example;
}
# Этот блок использует тот же сертификат, но не добавляет
# свой server_name в запрос на сертификат
server {
listen 443 ssl;
server_name app.example.com;
ssl_certificate $acme_cert_example;
ssl_certificate_key $acme_cert_key_example;
}
}
Явный список доменов#
server,
создайте отдельный блок server,
содержащий только директивы server_name и acme.
Чтобы этот блок не обрабатывал реальный трафик,
привяжите его к Unix-сокету:# Отдельный блок, определяющий список доменов сертификата
server {
listen unix:/tmp/acme_example.sock;
server_name example.com www.example.com;
acme example;
}
server могут затем использовать сертификат
через переменную $acme_cert_<имя>,
не влияя на то, какие домены запрашиваются.Отдельные сертификаты для разных доменов#
http и укажите в директиве
acme каждого блока server соответствующий клиент по имени:http {
# Два независимых клиента, каждый управляет своим сертификатом
acme_client shop https://acme-v02.api.letsencrypt.org/directory
challenge=http;
acme_client blog https://acme-v02.api.letsencrypt.org/directory
challenge=http;
server {
listen 443 ssl;
server_name shop.example.com www.shop.example.com;
acme shop;
ssl_certificate $acme_cert_shop;
ssl_certificate_key $acme_cert_key_shop;
}
server {
listen 443 ssl;
server_name blog.example.com;
acme blog;
ssl_certificate $acme_cert_blog;
ssl_certificate_key $acme_cert_key_blog;
}
}
HTTP-проверка#
/.well-known/acme-challenge/<TOKEN>.
Angie отвечает на такие запросы сам, поэтому отдавать этот путь
вручную не требуется. Получив файл, ACME-сервер сверяет токен
с выданным значением и, если они совпадают, засчитывает проверку домена.Пример конфигурации#
example управляет сертификатами для
example.com и www.example.com
(учтите, что wildcard-сертификаты не поддерживаются при HTTP-проверке):http {
acme_client example https://acme-v02.api.letsencrypt.org/directory;
server {
listen 80; # Не обязательно: если порт HTTP-проверки
# никто не слушает, модуль откроет его сам
# (см. директиву 'acme_http_port')
listen 443 ssl;
server_name example.com www.example.com;
acme example;
ssl_certificate $acme_cert_example;
ssl_certificate_key $acme_cert_key_example;
}
}
DNS-проверка#
_acme-challenge. проверяемого домена.
Angie отвечает на такие запросы сам: он выступает авторитетным сервером имен
для этого поддомена и возвращает TXT-запись с тем значением, которого ждет
ACME-сервер. ACME-сервер сверяет полученную запись с выданным значением и,
если они совпадают, засчитывает проверку домена._acme-challenge. серверу Angie; нужные записи приведены ниже.
По умолчанию Angie отвечает с очень небольшим TTL, а некоторые DNS-провайдеры
отфильтровывают ответы с таким низким TTL — если проверка не завершается,
увеличьте его директивой acme_dns_ttl.challenge=dns Angie игнорирует IP-адрес
в server_name и выводит предупреждение при запуске.
Блок server, в котором указан IP-адрес, должен по-прежнему содержать
хотя бы одно доменное имя, которое клиент может проверить.example.com,
используя сервер Angie с IP-адресом 203.0.113.10,
DNS-конфигурация вашего домена должна включать следующие записи:_acme-challenge.example.com. 60 IN NS ns.example.com.
ns.example.com. 60 IN A 203.0.113.10
_acme-challenge.example.com на ns.example.com,
обеспечивая доступность ns.example.com
путем сопоставления с IP-адресом (203.0.113.10).$ dig NS _acme-challenge.example.com +short # Проверка NS-записи для поддомена _acme-challenge
ns.example.com.
$ dig A ns.example.com +short # Проверка A-записи для сервера имен
203.0.113.10
$ nc -zv 203.0.113.10 53 # Проверка доступности DNS-сервера на порту 53
*.example.com в разделе Subject Alternative Name
(SAN). Чтобы в явной форме запросить сертификат для поддомена, например
www.example.com, следует отдельно подтвердить этот поддомен описанным
выше способом.Пример конфигурации#
challenge=dns
для директивы acme_client.example управляет сертификатами для
example.com и *.example.com:http {
acme_client example https://acme-v02.api.letsencrypt.org/directory
challenge=dns;
server {
server_name example.com *.example.com;
acme example;
ssl_certificate $acme_cert_example;
ssl_certificate_key $acme_cert_key_example;
}
}
ALPN-проверка#
acme-tls/1. Модуль отдает временный
сертификат для валидационного запроса.challenge=alpn в директиве
acme_client и убедитесь, что ваш TLS‑слушатель доступен на порту 443
(или на используемом TLS‑порту).Пример конфигурации#
challenge=alpn для директивы acme_client и убедиться, что
TLS-сервер доступен на порту 443.example управляет сертификатом для
example.com и www.example.com:http {
acme_client example https://acme-v02.api.letsencrypt.org/directory
challenge=alpn;
server {
listen 443 ssl;
server_name example.com www.example.com;
acme example;
ssl_certificate $acme_cert_example;
ssl_certificate_key $acme_cert_key_example;
}
}
Проверка с помощью хуков#
location, содержащему директиву acme_hook.
Способ обработки этого запроса полностью зависит
от других директив, заданных в том же location.location с директивой acme_hook.location
с помощью модуля, подходящего для вашей конфигурации:
fastcgi_pass для FastCGI,
proxy_pass для HTTP,
cgi_pass для CGI-скриптов и т. д.fastcgi_param для FastCGI
или cgi_set_var для CGI.2xx,
который можно передать через заголовок Status.
Любой другой код от хука add прерывает попытку обновления сертификата;
от хука remove тот же код только записывается в журнал
как предупреждение, и обновление продолжается.
Вывод обработчика игнорируется.Минимальная конфигурация#
location для хука имеет следующую структуру:location @acme_hook_location {
acme_hook example;
# Директива обработчика (fastcgi_pass, proxy_pass, cgi_pass, ...)
# Передайте переменные ACME с помощью механизма обработчика:
# ACME_HOOK — $acme_hook_name ("add" или "remove")
# ACME_CHALLENGE — $acme_hook_challenge ("dns" или "http")
# ACME_DOMAIN — $acme_hook_domain
# ACME_TOKEN — $acme_hook_token
# ACME_KEYAUTH — $acme_hook_keyauth
}
ACME_HOOK
для определения действия:
если значение add,
создать TXT-запись для _acme-challenge.ACME_DOMAIN
со значением из ACME_KEYAUTH;
если значение remove, удалить эту запись.Пример с FastCGI#
example
для подтверждения домена при помощи DNS-вызова,
на что указывает параметр challenge=dns директивы acme_client.server применяется ко всем поддоменам example.com
(например, *.example.com)
и использует ACME-клиент example для управления сертификатами,
что указано в директиве acme.location обрабатывает вызовы хуков.
Директива acme_hook связывает его
с ACME-клиентом example.
Запросы хуков отправляются на локальный FastCGI-сервер
на порту 9000 с помощью fastcgi_pass.
Директивы fastcgi_param передают
переменные ACME во внешний сервис.acme_client example https://acme-v02.api.letsencrypt.org/directory
challenge=dns;
server {
listen 80;
server_name *.example.com;
acme example;
ssl_certificate $acme_cert_example;
ssl_certificate_key $acme_cert_key_example;
location @acme_hook_location {
acme_hook example;
fastcgi_pass localhost:9000;
fastcgi_param ACME_CLIENT $acme_hook_client;
fastcgi_param ACME_HOOK $acme_hook_name;
fastcgi_param ACME_CHALLENGE $acme_hook_challenge;
fastcgi_param ACME_DOMAIN $acme_hook_domain;
fastcgi_param ACME_TOKEN $acme_hook_token;
fastcgi_param ACME_KEYAUTH $acme_hook_keyauth;
include fastcgi.conf;
}
}
#!/usr/bin/perl
use strict; use warnings;
use FCGI;
my $socket = FCGI::OpenSocket(":9000", 5);
my $request = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV, $socket);
while ($request->Accept() >= 0) {
print "\r\n";
my $client = $ENV{ACME_CLIENT};
my $hook = $ENV{ACME_HOOK};
my $challenge = $ENV{ACME_CHALLENGE};
my $domain = $ENV{ACME_DOMAIN};
my $token = $ENV{ACME_TOKEN};
my $keyauth = $ENV{ACME_KEYAUTH};
if ($hook eq 'add') {
DNS_set_TXT_record("_acme-challenge.$domain.", $keyauth);
} elsif ($hook eq 'remove') {
DNS_clear_TXT_record("_acme-challenge.$domain.");
}
};
FCGI::CloseSocket($socket);
DNS_set_TXT_record() и DNS_clear_TXT_record() —
функции, предположительно добавляющие и удаляющие TXT-записи
в конфигурации некого внешнего DNS-сервера, к которому и обратится ACME-сервер.
В этих записях должны содержаться переданные сервером Angie данные,
что позволит внешнему DNS-серверу успешно пройти проверку,
аналогичную описанной в разделе DNS-проверка.
Подробности реализации таких функций выходят за рамки этого руководства;
так, например, передавать параметры можно и через URI запроса:# ...
location @acme_hook_location {
acme_hook example uri=/acme_hook/$acme_hook_name?domain=$acme_hook_domain&key=$acme_hook_keyauth;
fastcgi_pass localhost:9000;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param ACME_CLIENT $acme_hook_client;
fastcgi_param ACME_CHALLENGE $acme_hook_challenge;
fastcgi_param ACME_TOKEN $acme_hook_token;
include fastcgi.conf;
}
Пример с PHP-FPM#
location @acme_hook_location {
acme_hook example;
root /var/www/dns;
fastcgi_pass unix:/run/php-fpm/php-dns.sock;
fastcgi_index hook.php;
fastcgi_param SCRIPT_FILENAME /var/www/dns/hook.php;
include fastcgi_params;
fastcgi_param ACME_CLIENT $acme_hook_client;
fastcgi_param ACME_HOOK $acme_hook_name;
fastcgi_param ACME_CHALLENGE $acme_hook_challenge;
fastcgi_param ACME_DOMAIN $acme_hook_domain;
fastcgi_param ACME_TOKEN $acme_hook_token;
fastcgi_param ACME_KEYAUTH $acme_hook_keyauth;
}
[dns]
listen = /run/php-fpm/php-dns.sock
listen.mode = 0666
user = angie
group = angie
chdir = /var/www/dns
# ...
$_SERVER['...'].Пример с CGI и octoDNS#
_acme-challenge через octoDNS, инструмент для описания DNS как кода:
зона задается в YAML и синхронизируется с DNS-провайдером. В примере
используется Cloudflare, но точно так же подойдет любой другой провайдер
octoDNS, если объявить его в providers и указать в targets.
У API-токена должны быть права Zone:Read, DNS:Read
и DNS:Edit для этой зоны.main. ACME-клиент example
заказывает один сертификат для example.com и *.example.com,
подтверждая домен при помощи DNS-вызовов, на что указывает параметр
challenge=dns директивы acme_client. Вызовы хуков обрабатывает
именованный блок location: директива acme_hook связывает его
с клиентом, директивы cgi_set_var передают
переменные ACME под стандартными именами,
а cgi_pass запускает скрипт при каждом вызове. Из этих переменных
скрипт использует ACME_CHALLENGE, ACME_HOOK,
ACME_DOMAIN и ACME_KEYAUTH.# в контексте main
load_module modules/ngx_http_cgi_module.so;
acme_client example https://acme-v02.api.letsencrypt.org/directory
challenge=dns;
server {
listen 443 ssl;
server_name example.com *.example.com;
acme example;
ssl_certificate $acme_cert_example;
ssl_certificate_key $acme_cert_key_example;
location @acme_hook_location {
acme_hook example;
cgi_set_var ACME_CLIENT $acme_hook_client;
cgi_set_var ACME_HOOK $acme_hook_name;
cgi_set_var ACME_CHALLENGE $acme_hook_challenge;
cgi_set_var ACME_DOMAIN $acme_hook_domain;
cgi_set_var ACME_TOKEN $acme_hook_token;
cgi_set_var ACME_KEYAUTH $acme_hook_keyauth;
cgi_pass /usr/share/angie/cgi-bin/acme-octodns;
}
}
/etc/octodns/acme, и обновление сертификата никогда не переписывает
файл зоны в /etc/octodns/zones. Параметр ignore_missing_zones
(octoDNS 1.16 и новее) разрешает каталогу /etc/octodns/acme оставаться
пустым между обновлениями. Сам файл зоны не должен содержать записей
_acme-challenge.providers:
config:
class: octodns.provider.yaml.YamlProvider
directory: /etc/octodns/zones
acme:
class: octodns.provider.yaml.YamlProvider
directory: /etc/octodns/acme
ignore_missing_zones: true
cloudflare:
class: octodns_cloudflare.CloudflareProvider
token: YOUR_API_TOKEN
zones:
example.com.:
sources:
- config
- acme
targets:
- cloudflare
'':
type: A
value: 203.0.113.10
www:
type: A
value: 203.0.113.10
--doit.acme:Скрипт acme-octodns
#!/opt/octodns/bin/python3
# Хук DNS-01 для ACME-клиента Angie: хранит TXT-запись _acme-challenge
# в отдельном каталоге-источнике octodns и публикует ее через octodns-sync.
import fcntl
import os
import subprocess
import sys
import time
import dns.message
import dns.query
import dns.resolver
import yaml
CONFIG = "/etc/octodns/octodns.yaml"
ACME_DIR = "/etc/octodns/acme"
SYNC = "/opt/octodns/bin/octodns-sync"
TTL = 120
WAIT = int(os.environ.get("ACME_WAIT", "300"))
def respond(code, message=""):
if message:
print(message, file=sys.stderr)
sys.stdout.write(f"Status: {code}\r\n\r\n")
sys.exit(0)
def find_zone(domain):
with open(CONFIG) as f:
zones = [z.rstrip(".") for z in yaml.safe_load(f)["zones"]]
match = [z for z in zones if domain == z or domain.endswith("." + z)]
if not match:
respond(500, f"no zone for {domain} in {CONFIG}")
return max(match, key=len)
def update(zone, name, value, add):
# В одной записи может быть несколько значений: сертификат для example.com
# и *.example.com проверяется двумя TXT-значениями с одним именем.
path = f"{ACME_DIR}/{zone}.yaml"
with open(f"{ACME_DIR}/.lock", "w") as lock:
fcntl.flock(lock, fcntl.LOCK_EX)
data = {}
if os.path.exists(path):
with open(path) as f:
data = yaml.safe_load(f) or {}
values = set(data.get(name, {}).get("values", []))
(values.add if add else values.discard)(value)
if values:
data[name] = {"type": "TXT", "ttl": TTL, "values": sorted(values)}
else:
data.pop(name, None)
if data:
with open(path, "w") as f:
yaml.safe_dump(data, f)
elif os.path.exists(path):
os.remove(path)
cmd = [SYNC, f"--config-file={CONFIG}", "--doit", zone + "."]
run = subprocess.run(cmd, capture_output=True, text=True)
if run.returncode != 0:
respond(500, "octodns-sync failed:\n" + run.stdout + run.stderr)
def has_txt(server, fqdn, value):
try:
reply = dns.query.udp(dns.message.make_query(fqdn, "TXT"), server, timeout=3)
except Exception:
return False
return any(value == b"".join(getattr(r, "strings", ())).decode()
for rrset in reply.answer for r in rrset)
def wait_visible(zone, fqdn, value):
# ACME-сервер обращается к авторитетным серверам зоны, поэтому опрашиваем
# их напрямую: резолвер мог закэшировать отсутствие записи.
deadline = time.monotonic() + WAIT
while time.monotonic() < deadline:
try:
servers = [str(a) for ns in dns.resolver.resolve(zone, "NS")
for a in dns.resolver.resolve(str(ns.target), "A")]
if servers and all(has_txt(s, fqdn, value) for s in servers):
return True
except Exception:
pass
time.sleep(5)
return False
def main():
hook = os.environ.get("ACME_HOOK")
domain = os.environ.get("ACME_DOMAIN", "")
value = os.environ.get("ACME_KEYAUTH", "")
if os.environ.get("ACME_CHALLENGE") != "dns" or hook not in ("add", "remove"):
respond(500, "the hook supports only DNS validation")
zone = find_zone(domain)
rel = domain[: -len(zone) - 1]
name = "_acme-challenge" + (f".{rel}" if rel else "")
update(zone, name, value, hook == "add")
if hook == "add" and WAIT and not wait_visible(zone, f"{name}.{zone}.", value):
update(zone, name, value, False)
respond(500, f"{name}.{zone} is not visible on the name servers after {WAIT}s")
respond(200)
try:
main()
except Exception as e:
respond(500, f"{type(e).__name__}: {e}")
add вернет код 2xx, Angie попросит ACME-сервер
выполнить проверку и будет опрашивать результат в течение 60 секунд.
Распространения записи в DNS Angie не дожидается, поэтому ждать приходится
самому скрипту. Он опрашивает авторитетные серверы имен зоны напрямую,
поскольку резолвер мог закэшировать отсутствие записи, и завершается, как
только значение отдадут все серверы; по умолчанию он ждет не дольше
300 секунд.Status: 500, Angie
прерывает попытку и повторяет ее через интервал, заданный параметром
retry_after_error. После неудачного add Angie не
вызывает хук remove, поэтому скрипт сам удаляет свое значение, прежде
чем ответить.ACME_WAIT. В показанном
выше location для хука она не задается; чтобы изменить предел, добавьте
ее туда, передав значение через переменную, как и остальные:set $acme_wait 600;
cgi_set_var ACME_WAIT $acme_wait;
0 отключает ожидание — это подходит для собственных
авторитетных серверов, на которых изменение через API видно сразу.*., поэтому
сертификат для example.com и *.example.com порождает два вызова
add для одного и того же имени _acme-challenge.example.com.
Скрипт хранит список значений в одной записи, а при remove удаляет
только значение из этого вызова; когда значений не остается, он удаляет и саму
запись, и хранивший ее файл в /etc/octodns/acme.angie. Ему нужен доступ на чтение к конфигурации octoDNS, где хранится
токен, и на запись в /etc/octodns/acme; больше ничего. CGI-процесс не
наследует окружение Angie, поэтому токен хранится в файле конфигурации,
а скрипт использует абсолютные пути.warn — именно там разбирают сбои хуков; эти сообщения
видны только при уровне warn или менее серьезном.$ sudo apt-get install angie-module-cgi
$ sudo apt-get install python3-venv
$ sudo python3 -m venv /opt/octodns
$ sudo /opt/octodns/bin/pip install 'octodns>=1.16' octodns-cloudflare
/usr/share/angie/cgi-bin:$ sudo install -d /etc/octodns/zones /usr/share/angie/cgi-bin
$ sudo install -d -o angie -g angie -m 750 /etc/octodns/acme
/etc/octodns/octodns.yaml и
/etc/octodns/zones/example.com.yaml, как показано выше, и ограничьте
доступ к конфигурации группой angie, поскольку в ней хранится
API-токен:$ sudo chgrp angie /etc/octodns/octodns.yaml
$ sudo chmod 640 /etc/octodns/octodns.yaml
acme-octodns
и установите его:$ sudo install -m 755 acme-octodns /usr/share/angie/cgi-bin/
$ sudo angie -t
$ sudo kill -HUP $(cat /run/angie.pid)
ACME в потоковом модуле#
http,
а сам блок stream должен располагаться после блока http
в конфигурации.Пример конфигурации#
# HTTP-часть
http {
# ACME-клиент для потоковой части
acme_client example https://acme-v02.api.letsencrypt.org/directory;
# Сервер для HTTP-проверки
server {
listen 80;
return 444;
}
}
# Потоковая часть
stream {
server {
listen 12345 ssl;
proxy_pass backend_upstream;
ssl_certificate $acme_cert_example;
ssl_certificate_key $acme_cert_key_example;
server_name example.com www.example.com;
acme example; # ссылка на ACME-клиент, определенный в HTTP-части
}
upstream backend_upstream {
server 127.0.0.1:54321;
}
}
challenge=dns в директиве acme_client;
тогда сервер будет не нужен.Миграция с certbot#
$ sudo certbot --nginx -d example.com -d www.example.com
/etc/nginx/sites-available/example.conf
и выглядит приблизительно так:server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name example.com www.example.com;
root /var/www/example;
index index.html;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
http {
acme_client example https://acme-v02.api.letsencrypt.org/directory;
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name example.com www.example.com;
root /var/www/example;
index index.html;
acme example;
ssl_certificate $acme_cert_example;
ssl_certificate_key $acme_cert_key_example;
}
}
$ sudo kill -HUP $(cat /run/angie.pid)
$ sudo rm -rf /etc/letsencrypt
$ sudo systemctl stop certbot.timer
$ sudo systemctl disable certbot.timer
$ # -- или --
$ sudo rm /etc/cron.d/certbot
$ sudo apt remove certbot
$ # -- или --
$ sudo dnf remove certbot